#include "fuzz.h"

#define MODULE_NAME "Type1 Read/Write"

enum {
  SUB_TYPE_PRESENCE_CHECK,
  SUB_TYPE_RID,
  SUB_TYPE_READ_ALL,
  SUB_TYPE_READ,
  SUB_TYPE_WRITE_ERASE,
  SUB_TYPE_WRITE_NO_ERASE,
  SUB_TYPE_READ_SEG,
  SUB_TYPE_READ_8,
  SUB_TYPE_WRITE_ERASE_8,
  SUB_TYPE_WRITE_NO_ERASE_8,
  SUB_TYPE_FORMAT_NDEF,
  SUB_TYPE_LOCATE_TLV,
  SUB_TYPE_READ_NDEF,
  SUB_TYPE_WRITE_NDEF,
  SUB_TYPE_SET_READONLY,

  SUB_TYPE_MAX
};

static void rw_cback(tRW_EVENT event, tRW_DATA* p_rw_data) {
  FUZZLOG(MODULE_NAME ": rw_cback: event=0x%02x, p_rw_data=%p", event,
          p_rw_data);

  if (event == RW_T1T_RAW_FRAME_EVT || event == RW_T1T_RID_EVT ||
      event == RW_T1T_RALL_CPLT_EVT || event == RW_T1T_READ_CPLT_EVT ||
      event == RW_T1T_RSEG_CPLT_EVT || event == RW_T1T_READ8_CPLT_EVT) {
    if (p_rw_data->data.p_data) {
      GKI_freebuf(p_rw_data->data.p_data);
      p_rw_data->data.p_data = nullptr;
    }
  }
}

#define TEST_NFCID_VALUE \
  { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 }

static bool Init(Fuzz_Context& /*ctx*/) {
  tNFC_ACTIVATE_DEVT activate_params = {
      .protocol = NFC_PROTOCOL_T1T,
      .rf_tech_param = {.mode = NFC_DISCOVERY_TYPE_POLL_A,
                        .param = {.pa = {
                                      .hr = {T1T_NDEF_SUPPORTED, 0x01},
                                      .nfcid1 = TEST_NFCID_VALUE,
                                  }}}};

  rw_init();
  if (NFC_STATUS_OK != RW_SetActivatedTagType(&activate_params, rw_cback)) {
    FUZZLOG(MODULE_NAME ": RW_SetActivatedTagType failed");
    return false;
  }

  return true;
}

static bool Init_PresenceCheck(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tPresenceCheck();
}

static bool Init_Rid(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tRid();
}

static bool Init_ReadAll(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tReadAll();
}

static bool Init_Read(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tRead(0, 0x10);
}

static bool Init_WriteErase(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tWriteErase(0, 0x10, 0x20);
}

static bool Init_WriteNoErase(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tWriteNoErase(0, 0x10, 0x20);
}

static bool Init_ReadSeg(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tReadSeg(0);
}

static bool Init_Read8(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tRead8(0);
}

static bool Init_WriteErase8(Fuzz_Context& ctx) {
  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
                          0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04};

  auto scratch = ctx.GetBuffer(sizeof(data), data);
  return NFC_STATUS_OK == RW_T1tWriteErase8(0, scratch);
}

static bool Init_WriteNoErase8(Fuzz_Context& ctx) {
  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
                          0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04};

  auto scratch = ctx.GetBuffer(sizeof(data), data);
  return NFC_STATUS_OK == RW_T1tWriteNoErase8(0, scratch);
}

static bool Init_FormatNdef(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tFormatNDef();
}
static bool Init_LocateTlv(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tLocateTlv(TAG_NDEF_TLV);
}
static bool Init_ReadNdef(Fuzz_Context& ctx) {
  tRW_T1T_CB* p_t1t = &rw_cb.tcb.t1t;
  p_t1t->tag_attribute = RW_T1_TAG_ATTRB_READ_WRITE;
  p_t1t->ndef_msg_len = 256;

  auto scratch = ctx.GetBuffer(4096);
  return NFC_STATUS_OK == RW_T1tReadNDef(scratch, 4096);
}
static bool Init_WriteNdef(Fuzz_Context& ctx) {
  const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04,
                          0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04};

  auto scratch = ctx.GetBuffer(sizeof(data), data);
  return NFC_STATUS_OK == RW_T1tWriteNDef(sizeof(data), scratch);
}
static bool Init_SetReadOnly(Fuzz_Context& /*ctx*/) {
  return NFC_STATUS_OK == RW_T1tSetTagReadOnly(true);
}

static bool Fuzz_Init(Fuzz_Context& ctx) {
  if (!Init(ctx)) {
    FUZZLOG(MODULE_NAME ": initialization failed");
    return false;
  }

  bool result = false;
  switch (ctx.SubType) {
    case SUB_TYPE_PRESENCE_CHECK:
      result = Init_PresenceCheck(ctx);
      break;
    case SUB_TYPE_RID:
      result = Init_Rid(ctx);
      break;
    case SUB_TYPE_READ_ALL:
      result = Init_ReadAll(ctx);
      break;
    case SUB_TYPE_READ:
      result = Init_Read(ctx);
      break;
    case SUB_TYPE_WRITE_ERASE:
      result = Init_WriteErase(ctx);
      break;
    case SUB_TYPE_WRITE_NO_ERASE:
      result = Init_WriteNoErase(ctx);
      break;
    case SUB_TYPE_READ_SEG:
      result = Init_ReadSeg(ctx);
      break;
    case SUB_TYPE_READ_8:
      result = Init_Read8(ctx);
      break;
    case SUB_TYPE_WRITE_ERASE_8:
      result = Init_WriteErase8(ctx);
      break;
    case SUB_TYPE_WRITE_NO_ERASE_8:
      result = Init_WriteNoErase8(ctx);
      break;
    case SUB_TYPE_FORMAT_NDEF:
      result = Init_FormatNdef(ctx);
      break;
    case SUB_TYPE_LOCATE_TLV:
      result = Init_LocateTlv(ctx);
      break;
    case SUB_TYPE_READ_NDEF:
      result = Init_ReadNdef(ctx);
      break;
    case SUB_TYPE_WRITE_NDEF:
      result = Init_WriteNdef(ctx);
      break;
    case SUB_TYPE_SET_READONLY:
      result = Init_SetReadOnly(ctx);
      break;

    default:
      FUZZLOG(MODULE_NAME ": Unknown command %d", ctx.SubType);
      result = false;
      break;
  }

  if (!result) {
    FUZZLOG(MODULE_NAME ": Initializing command %02X failed", ctx.SubType);
  }

  return result;
}

static void Fuzz_Deinit(Fuzz_Context& /*ctx*/) {
  if (rf_cback) {
    tNFC_CONN conn = {
        .deactivate = {.status = NFC_STATUS_OK,
                       .type = NFC_DEACTIVATE_TYPE_IDLE,
                       .is_ntf = true,
                       .reason = NFC_DEACTIVATE_REASON_DH_REQ_FAILED}};

    rf_cback(NFC_RF_CONN_ID, NFC_DEACTIVATE_CEVT, &conn);
  }
}

static void Fuzz_Run(Fuzz_Context& ctx) {
  for (auto it = ctx.Data.cbegin() + 1; it != ctx.Data.cend(); ++it) {
    NFC_HDR* p_msg;
    p_msg = (NFC_HDR*)GKI_getbuf(sizeof(NFC_HDR) + it->size());
    if (p_msg == nullptr || it->size() < 1) {
      FUZZLOG(MODULE_NAME ": GKI_getbuf returns null, size=%zu", it->size());
      return;
    }

    /* Initialize NFC_HDR */
    p_msg->len = it->size() - 1;
    p_msg->offset = 0;

    uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset;
    memcpy(p, it->data(), it->size());

    tNFC_CONN conn = {.data = {
                          .status = NFC_STATUS_OK,
                          .p_data = p_msg,
                      }};

    FUZZLOG(MODULE_NAME ": SubType=%02X, Response[%zd/%zd]=%s", ctx.SubType,
            it - ctx.Data.cbegin(), ctx.Data.size() - 1,
            BytesToHex(*it).c_str());

    rf_cback(NFC_RF_CONN_ID, NFC_DATA_CEVT, &conn);
  }
}

void Type1_FixPackets(uint8_t /*SubType*/, std::vector<bytes_t>& /*Data*/) {}

void Type1_Fuzz(uint8_t SubType, const std::vector<bytes_t>& Data) {
  Fuzz_Context ctx(SubType % SUB_TYPE_MAX, Data);
  if (Fuzz_Init(ctx)) {
    Fuzz_Run(ctx);
  }
  Fuzz_Deinit(ctx);
}