#include <limits.h>
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_nfc.h>
#include <furi_hal_spi.h>
#include <furi_hal_gpio.h>
#include <furi_hal_cortex.h>
#include <furi_hal_resources.h>
#include <st25r3916.h>
#include <st25r3916_irq.h>

#include "nfcv.h"
#include "nfc_util.h"
#include "slix.h"

#define TAG "NfcV"

/* macros to map "modulate field" flag to GPIO level */
#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY
#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED)

/* timing macros */
#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f)
#define DIGITAL_SIGNAL_UNIT_US (100000.0f)

ReturnCode nfcv_inventory(uint8_t* uid) {
    uint16_t received = 0;
    rfalNfcvInventoryRes res;
    ReturnCode ret = ERR_NONE;

    for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
        /* TODO: needs proper abstraction via furi_hal(_ll)_* */
        ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received);

        if(ret == ERR_NONE) {
            break;
        }
    }

    if(ret == ERR_NONE) {
        if(uid != NULL) {
            memcpy(uid, res.UID, NFCV_UID_LENGTH);
        }
    }

    return ret;
}

ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) {
    UNUSED(reader);

    uint16_t received = 0;
    for(size_t block = 0; block < nfcv_data->block_num; block++) {
        uint8_t rxBuf[32];
        FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1));

        ReturnCode ret = ERR_NONE;
        for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
            ret = rfalNfcvPollerReadSingleBlock(
                RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received);

            if(ret == ERR_NONE) {
                break;
            }
        }
        if(ret != ERR_NONE) {
            FURI_LOG_D(TAG, "failed to read: %d", ret);
            return ret;
        }
        memcpy(
            &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size);
        FURI_LOG_D(
            TAG,
            "  %02X %02X %02X %02X",
            nfcv_data->data[block * nfcv_data->block_size + 0],
            nfcv_data->data[block * nfcv_data->block_size + 1],
            nfcv_data->data[block * nfcv_data->block_size + 2],
            nfcv_data->data[block * nfcv_data->block_size + 3]);
    }

    return ERR_NONE;
}

ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
    uint8_t rxBuf[32];
    uint16_t received = 0;
    ReturnCode ret = ERR_NONE;

    FURI_LOG_D(TAG, "Read SYSTEM INFORMATION...");

    for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) {
        /* TODO: needs proper abstraction via furi_hal(_ll)_* */
        ret = rfalNfcvPollerGetSystemInformation(
            RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received);

        if(ret == ERR_NONE) {
            break;
        }
    }

    if(ret == ERR_NONE) {
        nfc_data->type = FuriHalNfcTypeV;
        nfc_data->uid_len = NFCV_UID_LENGTH;
        /* UID is stored reversed in this response */
        for(int pos = 0; pos < nfc_data->uid_len; pos++) {
            nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)];
        }
        nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2];
        nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3];
        nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1;
        nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1;
        nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6];
        FURI_LOG_D(
            TAG,
            "  UID:          %02X %02X %02X %02X %02X %02X %02X %02X",
            nfc_data->uid[0],
            nfc_data->uid[1],
            nfc_data->uid[2],
            nfc_data->uid[3],
            nfc_data->uid[4],
            nfc_data->uid[5],
            nfc_data->uid[6],
            nfc_data->uid[7]);
        FURI_LOG_D(
            TAG,
            "  DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d",
            nfcv_data->dsfid,
            nfcv_data->afi,
            nfcv_data->block_num,
            nfcv_data->block_size,
            nfcv_data->ic_ref);
        return ret;
    }
    FURI_LOG_D(TAG, "Failed: %d", ret);

    return ret;
}

bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
    furi_assert(reader);
    furi_assert(nfc_data);
    furi_assert(nfcv_data);

    if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) {
        return false;
    }

    if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) {
        return false;
    }

    /* clear all know sub type data before reading them */
    memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data));

    if(slix_check_card_type(nfc_data)) {
        FURI_LOG_I(TAG, "NXP SLIX detected");
        nfcv_data->sub_type = NfcVTypeSlix;
    } else if(slix2_check_card_type(nfc_data)) {
        FURI_LOG_I(TAG, "NXP SLIX2 detected");
        nfcv_data->sub_type = NfcVTypeSlix2;
        if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) {
            return false;
        }
    } else if(slix_s_check_card_type(nfc_data)) {
        FURI_LOG_I(TAG, "NXP SLIX-S detected");
        nfcv_data->sub_type = NfcVTypeSlixS;
    } else if(slix_l_check_card_type(nfc_data)) {
        FURI_LOG_I(TAG, "NXP SLIX-L detected");
        nfcv_data->sub_type = NfcVTypeSlixL;
    } else {
        nfcv_data->sub_type = NfcVTypePlain;
    }

    return true;
}

void nfcv_crc(uint8_t* data, uint32_t length) {
    uint32_t reg = 0xFFFF;

    for(size_t i = 0; i < length; i++) {
        reg = reg ^ ((uint32_t)data[i]);
        for(size_t j = 0; j < 8; j++) {
            if(reg & 0x0001) {
                reg = (reg >> 1) ^ 0x8408;
            } else {
                reg = (reg >> 1);
            }
        }
    }

    uint16_t crc = ~(uint16_t)(reg & 0xffff);

    data[length + 0] = crc & 0xFF;
    data[length + 1] = crc >> 8;
}

void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) {
    furi_assert(signals);

    if(signals->nfcv_resp_one) {
        digital_signal_free(signals->nfcv_resp_one);
    }
    if(signals->nfcv_resp_zero) {
        digital_signal_free(signals->nfcv_resp_zero);
    }
    if(signals->nfcv_resp_sof) {
        digital_signal_free(signals->nfcv_resp_sof);
    }
    if(signals->nfcv_resp_eof) {
        digital_signal_free(signals->nfcv_resp_eof);
    }
    signals->nfcv_resp_one = NULL;
    signals->nfcv_resp_zero = NULL;
    signals->nfcv_resp_sof = NULL;
    signals->nfcv_resp_eof = NULL;
}

bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) {
    furi_assert(air);
    furi_assert(signals);

    bool success = true;

    if(!signals->nfcv_resp_one) {
        /* logical one: unmodulated then 8 pulses */
        signals->nfcv_resp_one = digital_signal_alloc(
            slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt));
        if(!signals->nfcv_resp_one) {
            return false;
        }
        for(size_t i = 0; i < slowdown; i++) {
            success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod);
        }
        for(size_t i = 0; i < slowdown * 8; i++) {
            success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse);
        }
        if(!success) {
            return false;
        }
    }
    if(!signals->nfcv_resp_zero) {
        /* logical zero: 8 pulses then unmodulated */
        signals->nfcv_resp_zero = digital_signal_alloc(
            slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt));
        if(!signals->nfcv_resp_zero) {
            return false;
        }
        for(size_t i = 0; i < slowdown * 8; i++) {
            success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse);
        }
        for(size_t i = 0; i < slowdown; i++) {
            success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod);
        }
        if(!success) {
            return false;
        }
    }
    if(!signals->nfcv_resp_sof) {
        /* SOF: unmodulated, 24 pulses, logic 1 */
        signals->nfcv_resp_sof = digital_signal_alloc(
            slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) +
            signals->nfcv_resp_one->edge_cnt);
        if(!signals->nfcv_resp_sof) {
            return false;
        }
        for(size_t i = 0; i < slowdown * 3; i++) {
            success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod);
        }
        for(size_t i = 0; i < slowdown * 24; i++) {
            success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse);
        }
        success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one);
        if(!success) {
            return false;
        }
    }
    if(!signals->nfcv_resp_eof) {
        /* EOF: logic 0, 24 pulses, unmodulated */
        signals->nfcv_resp_eof = digital_signal_alloc(
            signals->nfcv_resp_zero->edge_cnt +
            slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) +
            air->nfcv_resp_unmod->edge_cnt);
        if(!signals->nfcv_resp_eof) {
            return false;
        }
        success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero);
        for(size_t i = 0; i < slowdown * 23; i++) {
            success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse);
        }
        /* we don't want to add the last level as we just want a transition to "unmodulated" again */
        for(size_t i = 0; i < slowdown; i++) {
            success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse);
        }
    }
    return success;
}

bool nfcv_emu_alloc(NfcVData* nfcv_data) {
    furi_assert(nfcv_data);

    if(!nfcv_data->frame) {
        nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX);
        if(!nfcv_data->frame) {
            return false;
        }
    }

    if(!nfcv_data->emu_air.nfcv_signal) {
        /* assuming max frame length is 255 bytes */
        nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi);
        if(!nfcv_data->emu_air.nfcv_signal) {
            return false;
        }
    }
    if(!nfcv_data->emu_air.nfcv_resp_unmod) {
        /* unmodulated 256/fc or 1024/fc signal as building block */
        nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4);
        if(!nfcv_data->emu_air.nfcv_resp_unmod) {
            return false;
        }
        nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED;
        nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] =
            (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S);
        nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1;
    }
    if(!nfcv_data->emu_air.nfcv_resp_pulse) {
        /* modulated fc/32 or fc/8 pulse as building block */
        nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4);
        if(!nfcv_data->emu_air.nfcv_resp_pulse) {
            return false;
        }
        nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED;
        nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] =
            (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
        nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] =
            (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
        nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2;
    }

    if(!nfcv_data->emu_air.nfcv_resp_half_pulse) {
        /* modulated fc/32 or fc/8 pulse as building block */
        nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4);
        if(!nfcv_data->emu_air.nfcv_resp_half_pulse) {
            return false;
        }
        nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED;
        nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] =
            (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
        nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1;
    }

    bool success = true;
    success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1);
    success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4);

    if(!success) {
        FURI_LOG_E(TAG, "Failed to allocate signals");
        return false;
    }

    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_SOF,
        nfcv_data->emu_air.signals_high.nfcv_resp_sof);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_BIT0,
        nfcv_data->emu_air.signals_high.nfcv_resp_zero);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_BIT1,
        nfcv_data->emu_air.signals_high.nfcv_resp_one);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_EOF,
        nfcv_data->emu_air.signals_high.nfcv_resp_eof);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_LOW_SOF,
        nfcv_data->emu_air.signals_low.nfcv_resp_sof);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_LOW_BIT0,
        nfcv_data->emu_air.signals_low.nfcv_resp_zero);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_LOW_BIT1,
        nfcv_data->emu_air.signals_low.nfcv_resp_one);
    digital_sequence_set_signal(
        nfcv_data->emu_air.nfcv_signal,
        NFCV_SIG_LOW_EOF,
        nfcv_data->emu_air.signals_low.nfcv_resp_eof);

    return true;
}

void nfcv_emu_free(NfcVData* nfcv_data) {
    furi_assert(nfcv_data);

    if(nfcv_data->frame) {
        free(nfcv_data->frame);
    }
    if(nfcv_data->emu_protocol_ctx) {
        free(nfcv_data->emu_protocol_ctx);
    }
    if(nfcv_data->emu_air.nfcv_resp_unmod) {
        digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod);
    }
    if(nfcv_data->emu_air.nfcv_resp_pulse) {
        digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse);
    }
    if(nfcv_data->emu_air.nfcv_resp_half_pulse) {
        digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse);
    }
    if(nfcv_data->emu_air.nfcv_signal) {
        digital_sequence_free(nfcv_data->emu_air.nfcv_signal);
    }
    if(nfcv_data->emu_air.reader_signal) {
        // Stop pulse reader and disable bus before free
        pulse_reader_stop(nfcv_data->emu_air.reader_signal);
        // Free pulse reader
        pulse_reader_free(nfcv_data->emu_air.reader_signal);
    }

    nfcv_data->frame = NULL;
    nfcv_data->emu_air.nfcv_resp_unmod = NULL;
    nfcv_data->emu_air.nfcv_resp_pulse = NULL;
    nfcv_data->emu_air.nfcv_resp_half_pulse = NULL;
    nfcv_data->emu_air.nfcv_signal = NULL;
    nfcv_data->emu_air.reader_signal = NULL;

    nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high);
    nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low);
}

void nfcv_emu_send(
    FuriHalNfcTxRxContext* tx_rx,
    NfcVData* nfcv,
    uint8_t* data,
    uint8_t length,
    NfcVSendFlags flags,
    uint32_t send_time) {
    furi_assert(tx_rx);
    furi_assert(nfcv);

    /* picked default value (0) to match the most common format */
    if(flags == NfcVSendFlagsNormal) {
        flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof |
                NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate;
    }

    if(flags & NfcVSendFlagsCrc) {
        nfcv_crc(data, length);
        length += 2;
    }

    /* depending on the request flags, send with high or low rate */
    uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0;
    uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1;
    uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF;
    uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF;

    digital_sequence_clear(nfcv->emu_air.nfcv_signal);

    if(flags & NfcVSendFlagsSof) {
        digital_sequence_add(nfcv->emu_air.nfcv_signal, sof);
    }

    for(int bit_total = 0; bit_total < length * 8; bit_total++) {
        uint32_t byte_pos = bit_total / 8;
        uint32_t bit_pos = bit_total % 8;
        uint8_t bit_val = 0x01 << bit_pos;

        digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0);
    }

    if(flags & NfcVSendFlagsEof) {
        digital_sequence_add(nfcv->emu_air.nfcv_signal, eof);
    }

    furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED);
    digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time);
    digital_sequence_send(nfcv->emu_air.nfcv_signal);
    furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED);

    if(tx_rx->sniff_tx) {
        tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context);
    }
}

static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) {
    for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) {
        dst[pos] = src[NFCV_UID_LENGTH - 1 - pos];
    }
}

static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) {
    for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) {
        if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) {
            return 1;
        }
    }
    return 0;
}

void nfcv_emu_handle_packet(
    FuriHalNfcTxRxContext* tx_rx,
    FuriHalNfcDevData* nfc_data,
    void* nfcv_data_in) {
    furi_assert(tx_rx);
    furi_assert(nfc_data);
    furi_assert(nfcv_data_in);

    NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
    NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;

    if(nfcv_data->frame_length < 2) {
        return;
    }

    if(nfcv_data->echo_mode) {
        nfcv_emu_send(
            tx_rx,
            nfcv_data,
            nfcv_data->frame,
            nfcv_data->frame_length,
            NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof,
            ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data");
        return;
    }

    /* parse the frame data for the upcoming part 3 handling */
    ctx->flags = nfcv_data->frame[0];
    ctx->command = nfcv_data->frame[1];
    ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT);
    ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) &&
                     (ctx->flags & NFCV_REQ_FLAG_ADDRESS);
    ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED);
    ctx->address_offset = 2 + (ctx->advanced ? 1 : 0);
    ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0);
    ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof;
    ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380);

    if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) {
        ctx->response_flags |= NfcVSendFlagsHighRate;
    }
    if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) {
        ctx->response_flags |= NfcVSendFlagsTwoSubcarrier;
    }

    if(ctx->payload_offset + 2 > nfcv_data->frame_length) {
#ifdef NFCV_VERBOSE
        FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command);
#endif
        return;
    }

    /* standard behavior is implemented */
    if(ctx->addressed) {
        uint8_t* address = &nfcv_data->frame[ctx->address_offset];
        if(nfcv_revuidcmp(address, nfc_data->uid)) {
#ifdef NFCV_VERBOSE
            FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command);
            FURI_LOG_D(
                TAG,
                "  dest:     %02X%02X%02X%02X%02X%02X%02X%02X",
                address[7],
                address[6],
                address[5],
                address[4],
                address[3],
                address[2],
                address[1],
                address[0]);
            FURI_LOG_D(
                TAG,
                "  our UID:  %02X%02X%02X%02X%02X%02X%02X%02X",
                nfc_data->uid[0],
                nfc_data->uid[1],
                nfc_data->uid[2],
                nfc_data->uid[3],
                nfc_data->uid[4],
                nfc_data->uid[5],
                nfc_data->uid[6],
                nfc_data->uid[7]);
#endif
            return;
        }
    }

    if(ctx->selected && !nfcv_data->selected) {
#ifdef NFCV_VERBOSE
        FURI_LOG_D(
            TAG,
            "selected card shall execute command 0x%02X, but we were not selected",
            ctx->command);
#endif
        return;
    }

    /* then give control to the card subtype specific protocol filter */
    if(ctx->emu_protocol_filter != NULL) {
        if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) {
            if(strlen(nfcv_data->last_command) > 0) {
#ifdef NFCV_VERBOSE
                FURI_LOG_D(
                    TAG, "Received command %s (handled by filter)", nfcv_data->last_command);
#endif
            }
            return;
        }
    }

    switch(ctx->command) {
    case NFCV_CMD_INVENTORY: {
        bool respond = false;

        if(ctx->flags & NFCV_REQ_FLAG_AFI) {
            uint8_t afi = nfcv_data->frame[ctx->payload_offset];

            uint8_t family = (afi & 0xF0);
            uint8_t subfamily = (afi & 0x0F);

            if(family) {
                if(subfamily) {
                    /* selected family and subfamily only */
                    if(afi == nfcv_data->afi) {
                        respond = true;
                    }
                } else {
                    /* selected family, any subfamily */
                    if(family == (nfcv_data->afi & 0xf0)) {
                        respond = true;
                    }
                }
            } else {
                if(subfamily) {
                    /* proprietary subfamily only */
                    if(afi == nfcv_data->afi) {
                        respond = true;
                    }
                } else {
                    /* all families and subfamilies */
                    respond = true;
                }
            }

        } else {
            respond = true;
        }

        if(!nfcv_data->quiet && respond) {
            int buffer_pos = 0;
            ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;
            ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid;
            nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid);
            buffer_pos += NFCV_UID_LENGTH;

            nfcv_emu_send(
                tx_rx,
                nfcv_data,
                ctx->response_buffer,
                buffer_pos,
                ctx->response_flags,
                ctx->send_time);
            snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY");
        } else {
            snprintf(
                nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)");
        }
        break;
    }

    case NFCV_CMD_STAY_QUIET: {
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET");
        nfcv_data->quiet = true;
        break;
    }

    case NFCV_CMD_LOCK_BLOCK: {
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        nfcv_data->security_status[block] |= 0x01;
        nfcv_data->modified = true;

        ctx->response_buffer[0] = NFCV_NOERROR;
        nfcv_emu_send(
            tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);

        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block);
        break;
    }

    case NFCV_CMD_WRITE_DSFID: {
        uint8_t id = nfcv_data->frame[ctx->payload_offset];

        if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) {
            nfcv_data->dsfid = id;
            nfcv_data->modified = true;
            ctx->response_buffer[0] = NFCV_NOERROR;
            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        }

        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id);
        break;
    }

    case NFCV_CMD_WRITE_AFI: {
        uint8_t id = nfcv_data->frame[ctx->payload_offset];

        if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) {
            nfcv_data->afi = id;
            nfcv_data->modified = true;
            ctx->response_buffer[0] = NFCV_NOERROR;
            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        }

        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id);
        break;
    }

    case NFCV_CMD_LOCK_DSFID: {
        if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) {
            nfcv_data->security_status[0] |= NfcVLockBitDsfid;
            nfcv_data->modified = true;

            ctx->response_buffer[0] = NFCV_NOERROR;
            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        }

        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID");
        break;
    }

    case NFCV_CMD_LOCK_AFI: {
        if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) {
            nfcv_data->security_status[0] |= NfcVLockBitAfi;
            nfcv_data->modified = true;

            ctx->response_buffer[0] = NFCV_NOERROR;
            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        }

        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI");
        break;
    }

    case NFCV_CMD_SELECT: {
        ctx->response_buffer[0] = NFCV_NOERROR;
        nfcv_data->selected = true;
        nfcv_data->quiet = false;
        nfcv_emu_send(
            tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT");
        break;
    }

    case NFCV_CMD_RESET_TO_READY: {
        ctx->response_buffer[0] = NFCV_NOERROR;
        nfcv_data->quiet = false;
        nfcv_emu_send(
            tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY");
        break;
    }

    case NFCV_CMD_READ_MULTI_BLOCK:
    case NFCV_CMD_READ_BLOCK: {
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        int blocks = 1;

        if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) {
            blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
        }

        /* limit the maximum block count, underflow accepted */
        if(block + blocks > nfcv_data->block_num) {
            blocks = nfcv_data->block_num - block;
        }

        /* only respond with the valid blocks, if there are any */
        if(blocks > 0) {
            uint8_t buffer_pos = 0;

            ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;

            for(int block_index = 0; block_index < blocks; block_index++) {
                int block_current = block + block_index;
                /* prepend security status */
                if(ctx->flags & NFCV_REQ_FLAG_OPTION) {
                    ctx->response_buffer[buffer_pos++] =
                        nfcv_data->security_status[1 + block_current];
                }
                /* then the data block */
                memcpy(
                    &ctx->response_buffer[buffer_pos],
                    &nfcv_data->data[nfcv_data->block_size * block_current],
                    nfcv_data->block_size);
                buffer_pos += nfcv_data->block_size;
            }
            nfcv_emu_send(
                tx_rx,
                nfcv_data,
                ctx->response_buffer,
                buffer_pos,
                ctx->response_flags,
                ctx->send_time);
        } else {
            /* reply with an error only in addressed or selected mode */
            if(ctx->addressed || ctx->selected) {
                ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
                ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
                nfcv_emu_send(
                    tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
            }
        }
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);

        break;
    }

    case NFCV_CMD_WRITE_MULTI_BLOCK:
    case NFCV_CMD_WRITE_BLOCK: {
        uint8_t blocks = 1;
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        uint8_t data_pos = ctx->payload_offset + 1;

        if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
            blocks = nfcv_data->frame[data_pos] + 1;
            data_pos++;
        }

        uint8_t* data = &nfcv_data->frame[data_pos];
        uint32_t data_len = nfcv_data->block_size * blocks;

        if((block + blocks) <= nfcv_data->block_num &&
           (data_pos + data_len + 2) == nfcv_data->frame_length) {
            ctx->response_buffer[0] = NFCV_NOERROR;
            memcpy(
                &nfcv_data->data[nfcv_data->block_size * block],
                &nfcv_data->frame[data_pos],
                data_len);
            nfcv_data->modified = true;

            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        } else {
            ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR;
            ctx->response_buffer[1] = NFCV_ERROR_GENERIC;
            nfcv_emu_send(
                tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time);
        }

        if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
            snprintf(
                nfcv_data->last_command,
                sizeof(nfcv_data->last_command),
                "WRITE MULTI BLOCK %d, %d blocks",
                block,
                blocks);
        } else {
            snprintf(
                nfcv_data->last_command,
                sizeof(nfcv_data->last_command),
                "WRITE BLOCK %d <- %02X %02X %02X %02X",
                block,
                data[0],
                data[1],
                data[2],
                data[3]);
        }
        break;
    }

    case NFCV_CMD_GET_SYSTEM_INFO: {
        int buffer_pos = 0;
        ctx->response_buffer[buffer_pos++] = NFCV_NOERROR;
        ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI |
                                             NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF;
        nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid);
        buffer_pos += NFCV_UID_LENGTH;
        ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */
        ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */
        ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */
        ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */
        ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */

        nfcv_emu_send(
            tx_rx,
            nfcv_data,
            ctx->response_buffer,
            buffer_pos,
            ctx->response_flags,
            ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO");
        break;
    }

    case NFCV_CMD_CUST_ECHO_MODE: {
        ctx->response_buffer[0] = NFCV_NOERROR;
        nfcv_data->echo_mode = true;
        nfcv_emu_send(
            tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode");
        break;
    }

    case NFCV_CMD_CUST_ECHO_DATA: {
        nfcv_emu_send(
            tx_rx,
            nfcv_data,
            &nfcv_data->frame[ctx->payload_offset],
            nfcv_data->frame_length - ctx->payload_offset - 2,
            NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof,
            ctx->send_time);
        snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data");
        break;
    }

    default:
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "unsupported: %02X",
            ctx->command);
        break;
    }

    if(strlen(nfcv_data->last_command) > 0) {
#ifdef NFCV_VERBOSE
        FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command);
#endif
    }
}

void nfcv_emu_sniff_packet(
    FuriHalNfcTxRxContext* tx_rx,
    FuriHalNfcDevData* nfc_data,
    void* nfcv_data_in) {
    furi_assert(tx_rx);
    furi_assert(nfc_data);
    furi_assert(nfcv_data_in);

    NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
    NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;

    if(nfcv_data->frame_length < 2) {
        return;
    }

    /* parse the frame data for the upcoming part 3 handling */
    ctx->flags = nfcv_data->frame[0];
    ctx->command = nfcv_data->frame[1];
    ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT);
    ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) &&
                     (ctx->flags & NFCV_REQ_FLAG_ADDRESS);
    ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED);
    ctx->address_offset = 2 + (ctx->advanced ? 1 : 0);
    ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0);

    char flags_string[5];

    snprintf(
        flags_string,
        5,
        "%c%c%c%d",
        (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ?
            'I' :
            (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')),
        ctx->advanced ? 'X' : ' ',
        (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l',
        (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1);

    switch(ctx->command) {
    case NFCV_CMD_INVENTORY: {
        snprintf(
            nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string);
        break;
    }

    case NFCV_CMD_STAY_QUIET: {
        snprintf(
            nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string);
        nfcv_data->quiet = true;
        break;
    }

    case NFCV_CMD_LOCK_BLOCK: {
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s LOCK %d",
            flags_string,
            block);
        break;
    }

    case NFCV_CMD_WRITE_DSFID: {
        uint8_t id = nfcv_data->frame[ctx->payload_offset];
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s WR DSFID %d",
            flags_string,
            id);
        break;
    }

    case NFCV_CMD_WRITE_AFI: {
        uint8_t id = nfcv_data->frame[ctx->payload_offset];
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s WR AFI %d",
            flags_string,
            id);
        break;
    }

    case NFCV_CMD_LOCK_DSFID: {
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s LOCK DSFID",
            flags_string);
        break;
    }

    case NFCV_CMD_LOCK_AFI: {
        snprintf(
            nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string);
        break;
    }

    case NFCV_CMD_SELECT: {
        snprintf(
            nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string);
        break;
    }

    case NFCV_CMD_RESET_TO_READY: {
        snprintf(
            nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string);
        break;
    }

    case NFCV_CMD_READ_MULTI_BLOCK:
    case NFCV_CMD_READ_BLOCK: {
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        uint8_t blocks = 1;

        if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) {
            blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
        }

        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s READ %d cnt: %d",
            flags_string,
            block,
            blocks);

        break;
    }

    case NFCV_CMD_WRITE_MULTI_BLOCK:
    case NFCV_CMD_WRITE_BLOCK: {
        uint8_t block = nfcv_data->frame[ctx->payload_offset];
        uint8_t blocks = 1;
        uint8_t data_pos = 1;

        if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
            blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
            data_pos++;
        }

        uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos];

        if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) {
            snprintf(
                nfcv_data->last_command,
                sizeof(nfcv_data->last_command),
                "%s WRITE %d, cnd %d",
                flags_string,
                block,
                blocks);
        } else {
            snprintf(
                nfcv_data->last_command,
                sizeof(nfcv_data->last_command),
                "%s WRITE %d %02X %02X %02X %02X",
                flags_string,
                block,
                data[0],
                data[1],
                data[2],
                data[3]);
        }
        break;
    }

    case NFCV_CMD_GET_SYSTEM_INFO: {
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s SYSTEMINFO",
            flags_string);
        break;
    }

    default:
        snprintf(
            nfcv_data->last_command,
            sizeof(nfcv_data->last_command),
            "%s unsupported: %02X",
            flags_string,
            ctx->command);
        break;
    }

    if(strlen(nfcv_data->last_command) > 0) {
        FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command);
    }
}

void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
    furi_assert(nfc_data);
    furi_assert(nfcv_data);

    if(!nfcv_emu_alloc(nfcv_data)) {
        FURI_LOG_E(TAG, "Failed to allocate structures");
        nfcv_data->ready = false;
        return;
    }

    strcpy(nfcv_data->last_command, "");
    nfcv_data->quiet = false;
    nfcv_data->selected = false;
    nfcv_data->modified = false;

    /* everything is initialized */
    nfcv_data->ready = true;

    /* ensure the GPIO is already in unmodulated state */
    furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
    furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED);

    rfal_platform_spi_acquire();
    /* stop operation to configure for transparent and passive mode */
    st25r3916ExecuteCommand(ST25R3916_CMD_STOP);
    /* set enable, rx_enable and field detector enable */
    st25r3916WriteRegister(
        ST25R3916_REG_OP_CONTROL,
        ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en |
            ST25R3916_REG_OP_CONTROL_en_fd_auto_efd);
    /* explicitely set the modulation resistor in case system config changes for some reason */
    st25r3916WriteRegister(
        ST25R3916_REG_PT_MOD,
        (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift));
    /* target mode: target, other fields do not have any effect as we use transparent mode */
    st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ);
    /* let us modulate the field using MOSI, read ASK modulation using IRQ */
    st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);

    furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);

    /* if not set already, initialize the default protocol handler */
    if(!nfcv_data->emu_protocol_ctx) {
        nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx));
        if(nfcv_data->sub_type == NfcVTypeSniff) {
            nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet;
        } else {
            nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet;
        }
    }

    FURI_LOG_D(TAG, "Starting NfcV emulation");
    FURI_LOG_D(
        TAG,
        "  UID:          %02X %02X %02X %02X %02X %02X %02X %02X",
        nfc_data->uid[0],
        nfc_data->uid[1],
        nfc_data->uid[2],
        nfc_data->uid[3],
        nfc_data->uid[4],
        nfc_data->uid[5],
        nfc_data->uid[6],
        nfc_data->uid[7]);

    switch(nfcv_data->sub_type) {
    case NfcVTypeSlixL:
        FURI_LOG_D(TAG, "  Card type:    SLIX-L");
        slix_l_prepare(nfcv_data);
        break;
    case NfcVTypeSlixS:
        FURI_LOG_D(TAG, "  Card type:    SLIX-S");
        slix_s_prepare(nfcv_data);
        break;
    case NfcVTypeSlix2:
        FURI_LOG_D(TAG, "  Card type:    SLIX2");
        slix2_prepare(nfcv_data);
        break;
    case NfcVTypeSlix:
        FURI_LOG_D(TAG, "  Card type:    SLIX");
        slix_prepare(nfcv_data);
        break;
    case NfcVTypePlain:
        FURI_LOG_D(TAG, "  Card type:    Plain");
        break;
    case NfcVTypeSniff:
        FURI_LOG_D(TAG, "  Card type:    Sniffing");
        break;
    }

    /* allocate a 512 edge buffer, more than enough */
    nfcv_data->emu_air.reader_signal =
        pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER);
    /* timebase shall be 1 ns */
    pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond);
    /* and configure to already calculate the number of bits */
    pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS);
    /* this IO is fed into the µC via a diode, so we need a pulldown */
    pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown);

    /* start sampling */
    pulse_reader_start(nfcv_data->emu_air.reader_signal);
}

void nfcv_emu_deinit(NfcVData* nfcv_data) {
    furi_assert(nfcv_data);

    furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
    nfcv_emu_free(nfcv_data);

    if(nfcv_data->emu_protocol_ctx) {
        free(nfcv_data->emu_protocol_ctx);
        nfcv_data->emu_protocol_ctx = NULL;
    }

    /* set registers back to how we found them */
    st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00);
    st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08);
    rfal_platform_spi_release();
}

bool nfcv_emu_loop(
    FuriHalNfcTxRxContext* tx_rx,
    FuriHalNfcDevData* nfc_data,
    NfcVData* nfcv_data,
    uint32_t timeout_ms) {
    furi_assert(tx_rx);
    furi_assert(nfc_data);
    furi_assert(nfcv_data);

    bool ret = false;
    uint32_t frame_state = NFCV_FRAME_STATE_SOF1;
    uint32_t periods_previous = 0;
    uint32_t frame_pos = 0;
    uint32_t byte_value = 0;
    uint32_t bits_received = 0;
    uint32_t timeout = timeout_ms * 1000;
    bool wait_for_pulse = false;

    if(!nfcv_data->ready) {
        return false;
    }

#ifdef NFCV_DIAGNOSTIC_DUMPS
    uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE];
    uint32_t period_buffer_pos = 0;
#endif

    while(true) {
        uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout);
        uint32_t timestamp = DWT->CYCCNT;

        /* when timed out, reset to SOF state */
        if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) {
            break;
        }

#ifdef NFCV_DIAGNOSTIC_DUMPS
        if(period_buffer_pos < sizeof(period_buffer)) {
            period_buffer[period_buffer_pos++] = periods;
        }
#endif

        /* short helper for detecting a pulse position */
        if(wait_for_pulse) {
            wait_for_pulse = false;
            if(periods != 1) {
                frame_state = NFCV_FRAME_STATE_RESET;
            }
            continue;
        }

        switch(frame_state) {
        case NFCV_FRAME_STATE_SOF1:
            if(periods == 1) {
                frame_state = NFCV_FRAME_STATE_SOF2;
            } else {
                frame_state = NFCV_FRAME_STATE_SOF1;
                break;
            }
            break;

        case NFCV_FRAME_STATE_SOF2:
            /* waiting for the second low period, telling us about coding */
            if(periods == 6) {
                frame_state = NFCV_FRAME_STATE_CODING_256;
                periods_previous = 0;
                wait_for_pulse = true;
            } else if(periods == 4) {
                frame_state = NFCV_FRAME_STATE_CODING_4;
                periods_previous = 2;
                wait_for_pulse = true;
            } else {
                frame_state = NFCV_FRAME_STATE_RESET;
            }
            break;

        case NFCV_FRAME_STATE_CODING_256:
            if(periods_previous > periods) {
                frame_state = NFCV_FRAME_STATE_RESET;
                break;
            }

            /* previous symbol left us with some pulse periods */
            periods -= periods_previous;

            if(periods > 512) {
                frame_state = NFCV_FRAME_STATE_RESET;
                break;
            } else if(periods == 2) {
                frame_state = NFCV_FRAME_STATE_EOF;
                break;
            }

            periods_previous = 512 - (periods + 1);
            byte_value = (periods - 1) / 2;
            if(frame_pos < NFCV_FRAMESIZE_MAX) {
                nfcv_data->frame[frame_pos++] = (uint8_t)byte_value;
            }

            wait_for_pulse = true;

            break;

        case NFCV_FRAME_STATE_CODING_4:
            if(periods_previous > periods) {
                frame_state = NFCV_FRAME_STATE_RESET;
                break;
            }

            /* previous symbol left us with some pulse periods */
            periods -= periods_previous;
            periods_previous = 0;

            byte_value >>= 2;
            bits_received += 2;

            if(periods == 1) {
                byte_value |= 0x00 << 6; // -V684
                periods_previous = 6;
            } else if(periods == 3) {
                byte_value |= 0x01 << 6;
                periods_previous = 4;
            } else if(periods == 5) {
                byte_value |= 0x02 << 6;
                periods_previous = 2;
            } else if(periods == 7) {
                byte_value |= 0x03 << 6;
                periods_previous = 0;
            } else if(periods == 2) {
                frame_state = NFCV_FRAME_STATE_EOF;
                break;
            } else {
                frame_state = NFCV_FRAME_STATE_RESET;
                break;
            }

            if(bits_received >= 8) {
                if(frame_pos < NFCV_FRAMESIZE_MAX) {
                    nfcv_data->frame[frame_pos++] = (uint8_t)byte_value;
                }
                bits_received = 0;
            }
            wait_for_pulse = true;
            break;
        }

        /* post-state-machine cleanup and reset */
        if(frame_state == NFCV_FRAME_STATE_RESET) {
            frame_state = NFCV_FRAME_STATE_SOF1;
        } else if(frame_state == NFCV_FRAME_STATE_EOF) {
            nfcv_data->frame_length = frame_pos;
            nfcv_data->eof_timestamp = timestamp;
            break;
        }
    }

    if(frame_state == NFCV_FRAME_STATE_EOF) {
        /* we know that this code uses TIM2, so stop pulse reader */
        pulse_reader_stop(nfcv_data->emu_air.reader_signal);
        if(tx_rx->sniff_rx) {
            tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context);
        }
        nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data);

        pulse_reader_start(nfcv_data->emu_air.reader_signal);
        ret = true;

    }
#ifdef NFCV_VERBOSE
    else {
        if(frame_state != NFCV_FRAME_STATE_SOF1) {
            FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state);
        }
    }
#endif

#ifdef NFCV_DIAGNOSTIC_DUMPS
    if(period_buffer_pos) {
        FURI_LOG_T(TAG, "pulses:");
        for(uint32_t pos = 0; pos < period_buffer_pos; pos++) {
            FURI_LOG_T(TAG, "     #%lu: %u", pos, period_buffer[pos]);
        }
    }
#endif

    return ret;
}
