/*
 * This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
 * Copyright (c) 2022 Pol Henarejos.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
#include "version.h"
#include "asn1.h"
#include "hid/ctap_hid.h"
#include "usb.h"
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ENABLE_EMULATION
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
#else
#include "tusb.h"
#endif
#include "mbedtls/aes.h"
#include "management.h"

#define FIXED_SIZE          16
#define KEY_SIZE            16
#define UID_SIZE            6
#define KEY_SIZE_OATH       20
#define ACC_CODE_SIZE       6

#define CONFIG1_VALID       0x01
#define CONFIG2_VALID       0x02
#define CONFIG1_TOUCH       0x04
#define CONFIG2_TOUCH       0x08
#define CONFIG_LED_INV      0x10
#define CONFIG_STATUS_MASK  0x1f

/* EXT Flags */
#define SERIAL_BTN_VISIBLE  0x01    // Serial number visible at startup (button press)
#define SERIAL_USB_VISIBLE  0x02    // Serial number visible in USB iSerial field
#define SERIAL_API_VISIBLE  0x04    // Serial number visible via API call
#define USE_NUMERIC_KEYPAD  0x08    // Use numeric keypad for digits
#define FAST_TRIG           0x10    // Use fast trig if only cfg1 set
#define ALLOW_UPDATE        0x20    // Allow update of existing configuration (selected flags + access code)
#define DORMANT             0x40    // Dormant config (woken up, flag removed, requires update flag)
#define LED_INV             0x80    // LED idle state is off rather than on
#define EXTFLAG_UPDATE_MASK (SERIAL_BTN_VISIBLE | SERIAL_USB_VISIBLE | SERIAL_API_VISIBLE | \
                             USE_NUMERIC_KEYPAD | FAST_TRIG | ALLOW_UPDATE | DORMANT | LED_INV)

/* TKT Flags */
#define TAB_FIRST       0x01    // Send TAB before first part
#define APPEND_TAB1     0x02    // Send TAB after first part
#define APPEND_TAB2     0x04    // Send TAB after second part
#define APPEND_DELAY1   0x08    // Add 0.5s delay after first part
#define APPEND_DELAY2   0x10    // Add 0.5s delay after second part
#define APPEND_CR       0x20    // Append CR as final character
#define OATH_HOTP       0x40    // OATH HOTP mode
#define CHAL_RESP       0x40    // Challenge-response enabled (both must be set)
#define PROTECT_CFG2    0x80    // Block update of config 2 unless config 2 is configured and has this bit set
#define TKTFLAG_UPDATE_MASK (TAB_FIRST | APPEND_TAB1 | APPEND_TAB2 | APPEND_DELAY1 | APPEND_DELAY2 | \
                             APPEND_CR)

/* CFG Flags */
#define SEND_REF            0x01    // Send reference string (0..F) before data
#define PACING_10MS         0x04    // Add 10ms intra-key pacing
#define PACING_20MS         0x08    // Add 20ms intra-key pacing
#define STATIC_TICKET       0x20    // Static ticket generation
// Static
#define SHORT_TICKET        0x02    // Send truncated ticket (half length)
#define STRONG_PW1          0x10    // Strong password policy flag #1 (mixed case)
#define STRONG_PW2          0x40    // Strong password policy flag #2 (subtitute 0..7 to digits)
#define MAN_UPDATE          0x80    // Allow manual (local) update of static OTP
// Challenge (no keyboard)
#define HMAC_LT64           0x04    // Set when HMAC message is less than 64 bytes
#define CHAL_BTN_TRIG       0x08    // Challenge-response operation requires button press
#define CHAL_YUBICO         0x20    // Challenge-response enabled - Yubico OTP mode
#define CHAL_HMAC           0x22    // Challenge-response enabled - HMAC-SHA1
// OATH
#define OATH_HOTP8          0x02    // Generate 8 digits HOTP rather than 6 digits
#define OATH_FIXED_MODHEX1  0x10    // First byte in fixed part sent as modhex
#define OATH_FIXED_MODHEX2  0x40    // First two bytes in fixed part sent as modhex
#define OATH_FIXED_MODHEX   0x50    // Fixed part sent as modhex
#define OATH_FIXED_MASK     0x50    // Mask to get out fixed flags
#define CFGFLAG_UPDATE_MASK (PACING_10MS | PACING_20MS)

static uint8_t config_seq = { 1 };

PACK(
typedef struct otp_config {
    uint8_t fixed_data[FIXED_SIZE];
    uint8_t uid[UID_SIZE];
    uint8_t aes_key[KEY_SIZE];
    uint8_t acc_code[ACC_CODE_SIZE];
    uint8_t fixed_size;
    uint8_t ext_flags;
    uint8_t tkt_flags;
    uint8_t cfg_flags;
    uint8_t rfu[2];
    uint16_t crc;
}) otp_config_t;

#define otp_config_size sizeof(otp_config_t)
uint16_t otp_status(bool is_otp);

int otp_process_apdu();
int otp_unload();

extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);

const uint8_t otp_aid[] = {
    7,
    0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
};

int otp_select(app_t *a, uint8_t force) {
    (void) force;
    if (cap_supported(CAP_OTP)) {
        a->process_apdu = otp_process_apdu;
        a->unload = otp_unload;
        if (file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ||
            file_has_data(search_dynamic_file(EF_OTP_SLOT2))) {
            config_seq = 1;
        }
        else {
            config_seq = 0;
        }
        otp_status(false);
        return PICOKEY_OK;
    }
    return PICOKEY_ERR_FILE_NOT_FOUND;
}

uint8_t modhex_tab[] =
{ 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' };
int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
    for (size_t l = 0; l < len; l++) {
        *out++ = modhex_tab[in[l] >> 4];
        *out++ = modhex_tab[in[l] & 0xf];
    }
    return 0;
}
static bool scanned = false;
extern void scan_all();
void init_otp() {
    if (scanned == false) {
        scan_all();
        for (uint8_t i = 0; i < 2; i++) {
            file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
            uint8_t *data = file_get_data(ef);
            otp_config_t *otp_config = (otp_config_t *) data;
            if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) &&
                !(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
                uint16_t counter = get_uint16_t_be(data + otp_config_size);
                if (++counter <= 0x7fff) {
                    uint8_t new_data[otp_config_size + 8];
                    memcpy(new_data, data, sizeof(new_data));
                    put_uint16_t_be(counter, new_data + otp_config_size);
                    file_put_data(ef, new_data, sizeof(new_data));
                }
            }
        }
        scanned = true;
        low_flash_available();
    }
}
extern int calculate_oath(uint8_t truncate,
                          const uint8_t *key,
                          size_t key_len,
                          const uint8_t *chal,
                          size_t chal_len);

uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
    uint16_t crc = 0xFFFF;
    for (size_t idx = 0; idx < data_len; idx++) {
        crc ^= data[idx];
        for (uint8_t i = 0; i < 8; i++) {
            uint16_t j = crc & 0x1;
            crc >>= 1;
            if (j == 1) {
                crc ^= 0x8408;
            }
        }
    }
    return crc & 0xFFFF;
}

static uint8_t session_counter[2] = { 0 };
int otp_button_pressed(uint8_t slot) {
    init_otp();
    if (!cap_supported(CAP_OTP)) {
        return 3;
    }
    file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
    const uint8_t *data = file_get_data(ef);
    otp_config_t *otp_config = (otp_config_t *) data;
    if (file_has_data(ef) == false) {
        return 1;
    }
    if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
        return 2;
    }
#ifdef ENABLE_OATH_APP
    if (otp_config->tkt_flags & OATH_HOTP) {
        uint8_t tmp_key[KEY_SIZE + 2];
        tmp_key[0] = 0x01;
        memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
        uint64_t imf = 0;
        const uint8_t *p = data + otp_config_size;
        imf = get_uint64_t_be(p);
        p += 8;
        if (imf == 0) {
            imf = get_uint16_t_be(otp_config->uid + 4);
        }
        uint8_t chal[8];
        put_uint64_t_be(imf, chal);
        res_APDU_size = 0;
        int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
        if (ret == PICOKEY_OK) {
            uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
            uint32_t number = get_uint16_t_be(res_APDU + 2);
            number %= base;
            char number_str[9];
            if (otp_config->cfg_flags & OATH_HOTP8) {
                sprintf(number_str, "%08lu", (long unsigned int) number);
                add_keyboard_buffer((const uint8_t *) number_str, 8, true);
            }
            else {
                sprintf(number_str, "%06lu", (long unsigned int) number);
                add_keyboard_buffer((const uint8_t *) number_str, 6, true);
            }
            imf++;
            uint8_t new_chal[8];
            put_uint64_t_be(imf, new_chal);
            uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
            memcpy(new_otp_config, otp_config, otp_config_size);
            memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
            file_put_data(ef, new_otp_config, sizeof(new_otp_config));
            low_flash_available();
        }
        if (otp_config->tkt_flags & APPEND_CR) {
            append_keyboard_buffer((const uint8_t *) "\r", 1);
        }
    }
#endif
    else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
        uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
        if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
            //fixed_size /= 2;
        }
        add_keyboard_buffer(otp_config->fixed_data, fixed_size, false);
        if (otp_config->tkt_flags & APPEND_CR) {
            append_keyboard_buffer((const uint8_t *) "\x28", 1);
        }
    }
    else {
        uint8_t otpk[22], *po = otpk;
        bool update_counter = false;
        uint16_t counter = get_uint16_t_be(data + otp_config_size), crc = 0;
        uint32_t ts = board_millis() / 1000;
        if (counter == 0) {
            update_counter = true;
            counter = 1;
        }
        memcpy(po, otp_config->fixed_data, 6);
        po += 6;
        memcpy(po, otp_config->uid, UID_SIZE);
        po += UID_SIZE;
        po += put_uint16_t_le(counter, po);
        ts >>= 1;
        *po++ = ts & 0xff;
        *po++ = ts >> 8;
        *po++ = ts >> 16;
        *po++ = session_counter[slot - 1];
        random_gen(NULL, po, 2);
        po += 2;
        crc = calculate_crc(otpk + 6, 14);
        po += put_uint16_t_le(~crc, po);
        mbedtls_aes_context ctx;
        mbedtls_aes_init(&ctx);
        mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
        mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, otpk + 6, otpk + 6);
        mbedtls_aes_free(&ctx);
        uint8_t otp_out[44];
        encode_modhex(otpk, sizeof(otpk), otp_out);
        add_keyboard_buffer((const uint8_t *) otp_out, sizeof(otp_out), true);
        if (otp_config->tkt_flags & APPEND_CR) {
            append_keyboard_buffer((const uint8_t *) "\r", 1);
        }

        if (++session_counter[slot - 1] == 0) {
            if (++counter <= 0x7fff) {
                update_counter = true;
            }
        }
        if (update_counter == true) {
            uint8_t new_data[otp_config_size + 8];
            memcpy(new_data, data, sizeof(new_data));
            put_uint16_t_be(counter, new_data + otp_config_size);
            file_put_data(ef, new_data, sizeof(new_data));
            low_flash_available();
        }
    }

    return 0;
}

INITIALIZER( otp_ctor ) {
    register_app(otp_select, otp_aid);
    button_pressed_cb = otp_button_pressed;
    hid_set_report_cb = otp_hid_set_report_cb;
    hid_get_report_cb = otp_hid_get_report_cb;
}

int otp_unload() {
    return PICOKEY_OK;
}

uint8_t status_byte = 0x0;
uint16_t otp_status(bool is_otp) {
    if (scanned == false) {
        scan_all();
        scanned = true;
    }
    res_APDU_size = 0;
    if (is_otp) {
        res_APDU_size++;
    }
    res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
    res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
    res_APDU[res_APDU_size++] = 0;
    res_APDU[res_APDU_size++] = config_seq;
    uint8_t opts = 0;
    file_t *ef = search_dynamic_file(EF_OTP_SLOT1);
    if (file_has_data(ef)) {
        opts |= CONFIG1_VALID;
        otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
        if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
            opts |= CONFIG1_TOUCH;
        }
    }
    ef = search_dynamic_file(EF_OTP_SLOT2);
    if (file_has_data(ef)) {
        opts |= CONFIG2_VALID;
        otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
        if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
            opts |= CONFIG2_TOUCH;
        }
    }
    res_APDU[res_APDU_size++] = opts;
    res_APDU[res_APDU_size++] = 0;
    res_APDU[res_APDU_size++] = status_byte;
    if (is_otp) {
        res_APDU_size = 0;
    }
    else {
        apdu.ne = res_APDU_size;
    }

    return SW_OK();
}

bool check_crc(const otp_config_t *data) {
    uint16_t crc = calculate_crc((const uint8_t *) data, otp_config_size);
    return crc == 0xF0B8;
}

bool _is_otp = false;
int cmd_otp() {
    uint8_t p1 = P1(apdu), p2 = P2(apdu);
    if (p2 != 0x00) {
        return SW_INCORRECT_P1P2();
    }
    if (p1 == 0x01 || p1 == 0x03) { // Configure slot
        otp_config_t *odata = (otp_config_t *) apdu.data;
        file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
        if (file_has_data(ef)) {
            otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
            if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
                return SW_SECURITY_STATUS_NOT_SATISFIED();
            }
        }
        for (int c = 0; c < otp_config_size; c++) {
            if (apdu.data[c] != 0) {
                if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
                    return SW_WRONG_DATA();
                }
                memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
                file_put_data(ef, apdu.data, otp_config_size + 8);
                low_flash_available();
                config_seq++;
                return otp_status(_is_otp);
            }
        }
        // Delete slot
        delete_file(ef);
        config_seq++;
        return otp_status(_is_otp);
    }
    else if (p1 == 0x04 || p1 == 0x05) { // Update slot
        otp_config_t *odata = (otp_config_t *) apdu.data;
        if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
            return SW_WRONG_DATA();
        }
        file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
        if (file_has_data(ef)) {
            otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
            if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
                return SW_SECURITY_STATUS_NOT_SATISFIED();
            }
            memcpy(apdu.data, file_get_data(ef), FIXED_SIZE + UID_SIZE + KEY_SIZE);
            odata->fixed_size = otpc->fixed_size;
            odata->ext_flags = (otpc->ext_flags & ~EXTFLAG_UPDATE_MASK) |
                               (odata->ext_flags & EXTFLAG_UPDATE_MASK);
            odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) |
                               (odata->tkt_flags & TKTFLAG_UPDATE_MASK);
            if (!(otpc->tkt_flags & CHAL_RESP)) {
                odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) |
                                   (odata->cfg_flags & CFGFLAG_UPDATE_MASK);
            }
            else {
                odata->cfg_flags = otpc->cfg_flags;
            }
            file_put_data(ef, apdu.data, otp_config_size);
            low_flash_available();
            config_seq++;
        }
        return otp_status(_is_otp);
    }
    else if (p1 == 0x06) { // Swap slots
        uint8_t tmp[otp_config_size + 8];
        bool ef1_data = false;
        file_t *ef1 = file_new(EF_OTP_SLOT1);
        file_t *ef2 = file_new(EF_OTP_SLOT2);
        if (file_has_data(ef1)) {
            memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
            ef1_data = true;
        }
        if (file_has_data(ef2)) {
            file_put_data(ef1, file_get_data(ef2), file_get_size(ef2));
        }
        else {
            delete_file(ef1);
            // When a dynamic file is deleted, existing referenes are invalidated
            ef2 = file_new(EF_OTP_SLOT2);
        }
        if (ef1_data) {
            file_put_data(ef2, tmp, sizeof(tmp));
        }
        else {
            delete_file(ef2);
        }
        low_flash_available();
        config_seq++;
        return otp_status(_is_otp);
    }
    else if (p1 == 0x10) {
        memcpy(res_APDU, pico_serial.id, 4);
        res_APDU[0] &= ~0xFC; // Force 8-digit serial number
        res_APDU_size = 4;
    }
    else if (p1 == 0x13) { // Get config
        man_get_config();
    }
    else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP
        file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
        if (file_has_data(ef)) {
            otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
            if (!(otp_config->tkt_flags & CHAL_RESP)) {
                return SW_WRONG_DATA();
            }
            int ret = 0;
            uint8_t *rdata_bk = apdu.rdata;
            if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
                status_byte = 0x20;
                otp_status(_is_otp);
#ifndef ENABLE_EMULATION
                if (wait_button() == true) {
                    status_byte = 0x00;
                    otp_status(_is_otp);
                    return SW_CONDITIONS_NOT_SATISFIED();
                }
#endif
                status_byte = 0x10;
                apdu.rdata = rdata_bk;
            }
            if (p1 == 0x30 || p1 == 0x38) {
                if (!(otp_config->cfg_flags & CHAL_HMAC)) {
                    return SW_WRONG_DATA();
                }
                uint8_t aes_key[KEY_SIZE + UID_SIZE];
                memcpy(aes_key, otp_config->aes_key, KEY_SIZE);
                memcpy(aes_key + KEY_SIZE, otp_config->uid, UID_SIZE);
                uint8_t chal_len = 64;
                if (otp_config->cfg_flags & HMAC_LT64) {
                    while (chal_len > 0 && apdu.data[63] == apdu.data[chal_len - 1]) {
                        chal_len--;
                    }
                }
                mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), aes_key, sizeof(aes_key), apdu.data, chal_len, res_APDU);
                if (ret == 0) {
                    res_APDU_size = 20;
                }
            }
            else if (p1 == 0x20 || p1 == 0x28) {
                if (!(otp_config->cfg_flags & CHAL_YUBICO)) {
                    return SW_WRONG_DATA();
                }
                uint8_t challenge[16];
                memcpy(challenge, apdu.data, 6);
                memcpy(challenge + 6, pico_serial_str, 10);
                mbedtls_aes_context ctx;
                mbedtls_aes_init(&ctx);
                mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
                ret = mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, challenge, res_APDU);
                mbedtls_aes_free(&ctx);
                if (ret == 0) {
                    res_APDU_size = 16;
                }
            }
            if (ret == 0) {
                status_byte = 0x00;
            }
        }
    }
    return SW_OK();
}

#define INS_OTP             0x01

static const cmd_t cmds[] = {
    { INS_OTP, cmd_otp },
    { 0x00, 0x0 }
};

int otp_process_apdu() {
    if (CLA(apdu) != 0x00) {
        return SW_CLA_NOT_SUPPORTED();
    }
    if (cap_supported(CAP_OTP)) {
        for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
            if (cmd->ins == INS(apdu)) {
                int r = cmd->cmd_handler();
                return r;
            }
        }
    }
    return SW_INS_NOT_SUPPORTED();
}

uint8_t otp_frame_rx[70] = {0};
uint8_t otp_frame_tx[70] = {0};
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
uint8_t otp_header[4] = {0};

extern uint16_t *get_send_buffer_size(uint8_t itf);

int otp_send_frame(uint8_t *frame, size_t frame_len) {
    uint16_t crc = calculate_crc(frame, frame_len);
    frame_len += put_uint16_t_le(~crc, frame + frame_len);
    *get_send_buffer_size(ITF_KEYBOARD) = frame_len;
    otp_exp_seq = (frame_len / 7);
    if (frame_len % 7) {
        otp_exp_seq++;
    }
    otp_curr_seq = 0;
    return 0;
}

int otp_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
    if (report_type == 3) {
        DEBUG_PAYLOAD(buffer, bufsize);
        if (buffer[7] == 0xFF) { // reset
            *get_send_buffer_size(ITF_KEYBOARD) = 0;
            otp_curr_seq = otp_exp_seq = 0;
            memset(otp_frame_tx, 0, sizeof(otp_frame_tx));
        }
        else if (buffer[7] & 0x80) { // a frame
            uint8_t rseq = buffer[7] & 0x1F;
            if (rseq < 10) {
                if (rseq == 0) {
                    memset(otp_frame_rx, 0, sizeof(otp_frame_rx));
                }
                memcpy(otp_frame_rx + rseq * 7, buffer, 7);
                if (rseq == 9) {
                    DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
                    DEBUG_PAYLOAD(otp_frame_rx, sizeof(otp_frame_rx));
                    uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = get_uint16_t_le(otp_frame_rx + 65);
                    uint8_t slot_id = otp_frame_rx[64];
                    if (residual_crc == rcrc) {
                        uint8_t hdr[5];
                        apdu.header = hdr;
                        apdu.data = otp_frame_rx;
                        apdu.nc = 64;
                        apdu.rdata = otp_frame_tx;
                        apdu.header[0] = 0;
                        apdu.header[1] = 0x01;
                        apdu.header[2] = slot_id;
                        apdu.header[3] = 0;
                        _is_otp = true;
                        int ret = otp_process_apdu();
                        if (ret == 0x9000 && res_APDU_size > 0) {
                            otp_send_frame(apdu.rdata, apdu.rlen);
                        }
                        _is_otp = false;
                    }
                    else {
                        printf("[OTP] Bad CRC!\n");
                    }
                }
            }
        }
        return 1;
    }
    return 0;
}

uint16_t otp_hid_get_report_cb(uint8_t itf,
                               uint8_t report_id,
                               hid_report_type_t report_type,
                               uint8_t *buffer,
                               uint16_t reqlen) {
    // TODO not Implemented
    (void) itf;
    (void) report_id;
    (void) report_type;
    (void) buffer;
    (void) reqlen;
    uint16_t send_buffer_size = *get_send_buffer_size(ITF_KEYBOARD);
    if (send_buffer_size > 0) {
        uint8_t seq = otp_curr_seq++;
        memset(buffer, 0, 8);
        memcpy(buffer, otp_frame_tx + 7 * seq, MIN(7, send_buffer_size));
        buffer[7] = 0x40 | seq;
        DEBUG_DATA(buffer, 8);
        *get_send_buffer_size(ITF_KEYBOARD) -= MIN(7, send_buffer_size);
    }
    else if (otp_curr_seq == otp_exp_seq && otp_exp_seq > 0) {
        memset(buffer, 0, 7);
        buffer[7] = 0x40;
        DEBUG_DATA(buffer,8);
        otp_curr_seq = otp_exp_seq = 0;
    }
    else {
        res_APDU = buffer;
        otp_status(true);
        DEBUG_DATA(buffer, 8);
    }

    return reqlen;
}
