#!/bin/bash

# Due to the complexity to android buile environment this script is created for
# development purpose to build, run and debug the fuzzers. It's not needed or
# required for official build and fuzzing.

function init() {
  if [ -z "$ANDROID_BUILD_TOP" ];
  then
    echo "Did you forget lunch?"
    exit 1
  fi
  source $ANDROID_BUILD_TOP/build/envsetup.sh

  PROJ=$(basename $PWD)
  FUZZER_NAME=nfc_${PROJ}_fuzzer
  FUZZ_DIR=data/fuzz/$(get_build_var TARGET_ARCH)/$FUZZER_NAME
  FUZZ_OPTIONS="$FUZZ_OPTIONS -close_fd_mask=3 -max_len=512 -artifact_prefix=/$FUZZ_DIR/crashes/"
}

function run_once() {
  if [ "$1" == "-c" ];
  then
    adb shell rm -rf /$FUZZ_DIR/corpus /$FUZZ_DIR/crashes /$FUZZ_DIR/gcov
    adb shell mkdir -p /$FUZZ_DIR/corpus /$FUZZ_DIR/crashes /$FUZZ_DIR/gcov
    adb push ./corpus/* /$FUZZ_DIR/corpus/  >/dev/null 2>&1
    rm -rf ./logs ./coverage

    shift
  fi

  adb logcat -c
  if [ -z "$1" ];
  then
    PAYLOAD=/$FUZZ_DIR/corpus
    echo "Fuzzing with corpus from $PAYLOAD..."
  else
    PAYLOAD=$1
    echo "Verifying payload $PAYLOAD..."
  fi

  adb shell mkdir -p /$FUZZ_DIR/corpus /$FUZZ_DIR/crashes /$FUZZ_DIR/gcov
  adb shell LD_LIBRARY_PATH=/system/lib64/vndk-29 GCOV_PREFIX=/$FUZZ_DIR/gcov GCOV_PREFIX_STRIP=3 /$FUZZ_DIR/$FUZZER_NAME $FUZZ_OPTIONS $PAYLOAD

  echo "==========================================================================================="
  adb logcat -d| $ANDROID_BUILD_TOP/external/compiler-rt/lib/asan/scripts/symbolize.py
}

function run_fuzz() {
  if [ "$1" == "-c" ];
  then
    adb shell rm -rf /$FUZZ_DIR/corpus /$FUZZ_DIR/crashes /$FUZZ_DIR/gcov
    adb shell mkdir -p /$FUZZ_DIR/corpus /$FUZZ_DIR/crashes /$FUZZ_DIR/gcov
    adb push ./corpus/* /$FUZZ_DIR/corpus/  >/dev/null 2>&1
    rm -rf ./logs ./coverage
  fi

  mkdir -p ./logs/ERROR ./logs/UNKNOWN ./coverage
  while true
  do
    echo "Running ..."
    TS=`date +"%m-%d-%Y-%H-%M-%S"`
    run_once >./logs/fuzz.log 2>&1

    echo "Fuzzer crashed, looking for crash input ..."
    CRASH=$(grep -aoP "Test unit written to \K\S+" ./logs/fuzz.log)
    if [ -z "$CRASH" ];
    then
      echo "Error, crash not found!"
      mv ./logs/fuzz.log ./logs/ERROR/run_$TS.log
      continue
    fi

    echo "Verifying crash ..."
    run_once $CRASH >./logs/verify.log 2>&1
    SIG=$(grep -m 1 -aoP "#?? \S+ in \K\S+ system/nfc/src\S+:\S+" ./logs/verify.log)
    if [ -z "$SIG" ];
    then
      SIG='UNKNOWN'
      cat ./logs/verify.log>>./logs/fuzz.log
    else
      cp ./logs/verify.log ./logs/fuzz.log
    fi

    SIG_DIR=$(echo $SIG | tr " /:" '#@#')
    if [ ! -d "./logs/$SIG_DIR" ];
    then
      echo "New crash category found: $SIG"
      mkdir -p ./logs/$SIG_DIR
    else
      echo "Known crash: $SIG"
    fi

    mv ./logs/fuzz.log ./logs/$SIG_DIR/run_$TS.log
    adb pull $CRASH ./logs/$SIG_DIR/crash_$TS.bin >/dev/null 2>&1
    adb rm $CRASH >/dev/null 2>&1
  done
}

function build() {
  pushd $ANDROID_BUILD_TOP
  SANITIZE_HOST="address" \
    SANITIZE_TARGET="hwaddress fuzzer" \
    NATIVE_COVERAGE="true" \
    NATIVE_COVERAGE_PATHS="system/nfc/src" \
    make -j $FUZZER_NAME
  popd
  adb shell mkdir -p /$FUZZ_DIR
  adb push $OUT/symbols/$FUZZ_DIR/$FUZZER_NAME /$FUZZ_DIR/
}

function run() {
  if [ "$1" == "--once" ];
  then
    shift
    run_once $@
  else
    echo "fuzzing..."
    run_fuzz $@
  fi
}

function debug() {
  if [ -z "$1" ];
  then
    echo "Which payload?"
    exit
  fi

  FUZZ_PAYLOAD=$1

  adb forward tcp:5039 tcp:5039
  adb shell LD_LIBRARY_PATH=/system/lib64/vndk-29 gdbserver64 remote:5039 /$FUZZ_DIR/$FUZZER_NAME $FUZZ_OPTIONS $FUZZ_PAYLOAD 2>&1 >/dev/null&
  sleep 5
  $ANDROID_BUILD_TOP/prebuilts/gdb/linux-x86/bin/gdb --directory=$ANDROID_BUILD_TOP -ex "target remote:5039"
}

function get_cov() {
  mkdir -p ./coverage && adb pull /$FUZZ_DIR/gcov/0/out/soong ./coverage
  unzip -o $OUT/coverage/$FUZZ_DIR/$FUZZER_NAME.zip -d ./coverage
  lcov --directory ./coverage --base-directory $ANDROID_BUILD_TOP --gcov-tool $(pwd)/../llvm-gcov --capture -o ./coverage/cov.info
  TS=`date +"%m-%d-%Y-%H-%M-%S"`
  genhtml ./coverage/cov.info -o ./coverage/report_$TS
  xdg-open ./coverage/report_$TS/index.html
}

function fuzz() {
  init
  action=$1
  shift

  case "$action" in
    run)
      run $@
      ;;
    build)
      build $@
      ;;
    debug)
      debug $@
      ;;
    gcov)
      get_cov $@
      ;;
    *)
      echo "Usage: $0 {run|build|debug|gcov}"
      exit 1
  esac
}

if [ "$0" == "${BASH_SOURCE[0]}" ];
then
  fuzz $@
fi

