//-----------------------------------------------------------------------------
// Copyright by Łukasz Jurczyk, 2021-2022
//
// This code is licensed to you under the terms of the GNU GPL, version 3.
// See the LICENSE.txt file for the text of the license.
//-----------------------------------------------------------------------------
// LF rsww   -  This mode can read EM4100 tag, save it to flash (RDV4 only), emulate it, clone it to T55xx tag, validate the write and wipe T55xx tag.
//
//              To recall stored ID from flash execute:
//                  mem spiffs dump -s lf
//              then from shell:
//                  hexdump lf.bin -e '5/1 "%02X" /0 "\n"'
//
//              To recall only LAST stored ID from flash use lf-last instead of lf file.
//
//-----------------------------------------------------------------------------
// Modes of operation:
//
// --- Read ---
// Proxmark reads an EM4100 tag. LED A is turned on. When the tag is detected, it is saved to flash (RDV4 only) and proxmark enters the emulation mode.
// It's the default mode for non-RDV4 devices, and if no previous read is present in the flash it's the default mode for RDV4 devices.
// Pressing the button exists reading mode and enters emulation mode (only if any read is present in the memory).
// Double pressing the button enters wiping mode.
//
// --- Emulate ---
// Proxmark emulates last read tag. LED B is turned on.
// It's the default mode for RDV4 if lf-last file is present on the flash.
// Pressing the button enters writing mode and clones the emulated tag.
// Double pressing the button enters the validation mode.
// Holding the button enters the reading mode.
//
// --- Write ---
// Proxmarks writes the last read tag. LEDs A and B are turned on.
// When writing is complete LEDs A and B blink three times and proxmark enters the emulation mode.
//
// --- Validate ---
// Proxmark reads an EM4100 tag. LED C is turned on.
// If tag matches the last saved tag, LED C blinks three times. If it doesn't all LEDs blink three times. Proxmark enters the emulation mode afterwards.
// The result of the read is DISCARDED.
// Pressing the button enters the emulation mode.
//
// --- Wipe ---
// Proxmark continously wipes all approached T55xx tags. LED D is turned on, LEDs A-C are blinking.
// Pressing the button enters the default mode (reading or emulation).
//-----------------------------------------------------------------------------

#include "standalone.h"
#include "proxmark3_arm.h"
#include "appmain.h"
#include "fpgaloader.h"
#include "util.h"
#include "dbprint.h"
#include "ticks.h"
#include "string.h"
#include "BigBuf.h"
#include "spiffs.h"
#include "inttypes.h"
#include "parity.h"
#include "lfops.h"

#ifdef WITH_FLASH
#include "flashmem.h"
char *filename = "lf";
char *filenameLast = "lf-last";
#endif

#define LF_CLOCK 64 // for 125kHz
#define LF_RWSB_T55XX_TYPE 1 // Tag type: 0 - T5555, 1-T55x7

static uint64_t low = 0;
static uint64_t low2 = 0;
static uint32_t high = 0;
static uint32_t high2 = 0;
static unsigned char mode = 0;
static int buflen;

void ModInfo(void) {
    DbpString("=== LF EM4100 read/sim/write/wipe/validate ===");
}

static uint64_t rev_quads(uint64_t bits) {
    uint64_t result = 0;
    for (int i = 0; i < 16; i++) {
        result += ((bits >> (60 - 4 * i)) & 0xf) << (4 * i);
    }
    return result >> 24;
}

static void fill_buff(uint8_t bit) {
    uint8_t *bba = BigBuf_get_addr();
    memset(bba + buflen, bit, LF_CLOCK / 2);
    buflen += (LF_CLOCK / 2);
    memset(bba + buflen, bit ^ 1, LF_CLOCK / 2);
    buflen += (LF_CLOCK / 2);
}

static void construct_EM410x_emul(uint64_t id) {
    int i, j;
    int binary[4] = {0, 0, 0, 0};
    int parity[4] = {0, 0, 0, 0};
    buflen = 0;

    for (i = 0; i < 9; i++)
        fill_buff(1);

    for (i = 0; i < 10; i++) {
        for (j = 3; j >= 0; j--, id /= 2)
            binary[j] = id % 2;

        for (j = 0; j < 4; j++)
            fill_buff(binary[j]);

        fill_buff(binary[0] ^ binary[1] ^ binary[2] ^ binary[3]);
        for (j = 0; j < 4; j++)
            parity[j] ^= binary[j];
    }

    for (j = 0; j < 4; j++)
        fill_buff(parity[j]);

    fill_buff(0);
}

#ifdef WITH_FLASH
static void SaveIDtoFlash(uint64_t id) {
    uint8_t bt[5];
    rdv40_spiffs_mount();
    for (int i = 0; i < 5; i++) {
        bt[4 - i] = (uint8_t)(id >> 8 * i & 0xff);
    }
    if (exists_in_spiffs(filename))
        rdv40_spiffs_append(filename, &bt[0], 5, RDV40_SPIFFS_SAFETY_NORMAL);
    else
        rdv40_spiffs_write(filename, &bt[0], 5, RDV40_SPIFFS_SAFETY_NORMAL);

    if (exists_in_spiffs(filenameLast))
        rdv40_spiffs_remove(filenameLast, RDV40_SPIFFS_SAFETY_NORMAL);

    rdv40_spiffs_write(filenameLast, &bt[0], 5, RDV40_SPIFFS_SAFETY_NORMAL);
}

static bool ReadFlash(void) {
    if (exists_in_spiffs(filenameLast) == false)
        return false;

    uint8_t bt[5];
    if (rdv40_spiffs_read(filenameLast, (uint8_t *) &bt, 5, RDV40_SPIFFS_SAFETY_NORMAL) < 0)
        return false;

    low = bt[0];
    low <<= 32;
    low |= (bt[1] << 24) | (bt[2] << 16) | (bt[3] << 8) | bt[4];
    low2 = low;
    high = 0;
    high2 = 0;
    return true;
}
#endif

static void Wipe(void) {
    DbpString("Wipe mode");
    LEDsoff();

    for (;;) {
        LED_A_ON();
        LED_B_ON();
        LED_C_ON();
        LED_D_ON();
        copy_em410x_to_t55xx(LF_RWSB_T55XX_TYPE
                             , LF_CLOCK
                             , (uint32_t) 0
                             , (uint32_t) 0
                             , false
                             , false
                            );
        SpinDelay(60);
        LEDsoff();
        LED_D_ON();

        int b = BUTTON_HELD(100);
        if (b != BUTTON_NO_CLICK || data_available())
            return;

        SpinDelay(100);

        b = BUTTON_HELD(100);
        if (b != BUTTON_NO_CLICK || data_available())
            return;
    }
}

static void Read(void) {
    mode = 0;

    while (low2 == 0 || mode == 0) {
        DbpString("Read");
        LEDsoff();
        LED_A_ON();

        low2 = 0;
        high2 = 0;
        lf_em410x_watch(1, &high2, &low2, false);

        if (low2 != 0) {
            LED_B_ON();
            low = low2;
            high = high2;
            mode = 1;

#ifdef WITH_FLASH
            SaveIDtoFlash(low2);
#endif

            SpinDelay(50);
            LED_C_ON();
            SpinDelay(50);
            LED_D_ON();
            SpinDelay(50);
            LEDsoff();
            return;
        }

        if (data_available())
            return;

        int b = BUTTON_CLICKED(1000);

        if ((b == BUTTON_SINGLE_CLICK || b == BUTTON_HOLD) && low != 0) {
            mode = 1;
            return;
        }

        if (b == BUTTON_DOUBLE_CLICK) {
            Wipe();

            if (low != 0) {
                mode = 1;
                return;
            }
        }
    }
}

static void Validate(void) {
    DbpString("Validate");
    LEDsoff();
    LED_C_ON();

    for (;;) {
        low2 = 0;
        high2 = 0;

        lf_em410x_watch(1, &high2, &low2, false);

        if (low == low2 && high == high2) {
            LED_C_OFF();
            SpinDelay(150);
            for (int i = 0; i < 3; i++) {
                LED_C_ON();
                SpinDelay(150);
                LED_C_OFF();
                SpinDelay(150);
            }

            return;
        } else if (low2 != 0 || high2 != 0) {
            LEDsoff();
            for (int i = 0; i < 3; i++) {
                LED_A_ON();
                LED_B_ON();
                LED_C_ON();
                LED_D_ON();
                SpinDelay(250);
                LEDsoff();
                SpinDelay(150);
            }

            return;
        } else
            SpinDelay(200);

        int b = BUTTON_HELD(200);
        if (b != BUTTON_NO_CLICK || data_available())
            return;
    }
}

static void Write(void) {
    DbpString("Write");
    LED_A_ON();
    LED_B_ON();
    copy_em410x_to_t55xx(LF_RWSB_T55XX_TYPE
                         , LF_CLOCK
                         , (uint32_t)(low >> 32)
                         , (uint32_t)(low & 0xffffffff)
                         , false
                         , false
                        );
    SpinDelay(75);
    LEDsoff();

    for (int i = 0; i < 3; i++) {
        LED_A_ON();
        LED_B_ON();
        SpinDelay(75);
        LED_A_OFF();
        LED_B_OFF();
        SpinDelay(75);
    }
}

static void Emulate(void) {
    DbpString("Emulate");
    LEDsoff();

    for (;;) {
        int bx = BUTTON_HELD(50);
        if (bx == BUTTON_NO_CLICK)
            break;
        SpinDelay(50);
    }

    LED_B_ON();
    construct_EM410x_emul(rev_quads(low));
    SimulateTagLowFrequencyEx(buflen, 0, false, -1);

    int b = BUTTON_CLICKED(800);

    if (b == BUTTON_NO_CLICK)
        return;

    for (;;) {
        int bx = BUTTON_HELD(50);
        if (bx == BUTTON_NO_CLICK)
            break;
        SpinDelay(50);
    }

    if (b == BUTTON_SINGLE_CLICK)
        Write();
    else if (b == BUTTON_HOLD)
        mode = 0;
    else if (b == BUTTON_DOUBLE_CLICK)
        Validate();
}

void RunMod() {
    StandAloneMode();
    LEDsoff();
    LED_D_ON();
    FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
    WDT_HIT();

#ifdef WITH_FLASH
    if (ReadFlash())
        mode = 1;
    else Read();
#else
    Read();
#endif

    for (;;) {
        WDT_HIT();
        LEDsoff();

        if (data_available()) return;

        if (mode == 0)
            Read();
        else Emulate();
    }
}
