소스 검색

Merge seader from https://github.com/bettse/seader

Willy-JL 1 년 전
부모
커밋
e41dcd8e0e

+ 144 - 0
seader/apdu_log.c

@@ -0,0 +1,144 @@
+#include "apdu_log.h"
+
+#include <storage/storage.h>
+#include <flipper_format/flipper_format.h>
+#include <toolbox/stream/file_stream.h>
+#include <toolbox/stream/buffered_file_stream.h>
+#include <toolbox/args.h>
+
+#define TAG "APDULog"
+
+struct APDULog {
+    Stream* stream;
+    size_t total_lines;
+};
+
+static inline void apdu_log_add_ending_new_line(APDULog* instance) {
+    if(stream_seek(instance->stream, -1, StreamOffsetFromEnd)) {
+        uint8_t last_char = 0;
+
+        // Check if the last char is new line or add a new line
+        if(stream_read(instance->stream, &last_char, 1) == 1 && last_char != '\n') {
+            FURI_LOG_D(TAG, "Adding new line ending");
+            stream_write_char(instance->stream, '\n');
+        }
+
+        stream_rewind(instance->stream);
+    }
+}
+
+static bool apdu_log_read_log_line(APDULog* instance, FuriString* line, bool* is_endfile) {
+    if(stream_read_line(instance->stream, line) == false) {
+        *is_endfile = true;
+    }
+
+    else {
+        size_t newline_index = furi_string_search_char(line, '\n', 0);
+
+        if(newline_index != FURI_STRING_FAILURE) {
+            furi_string_left(line, newline_index);
+        }
+
+        FURI_LOG_T(
+            TAG, "Read line: %s, len: %zu", furi_string_get_cstr(line), furi_string_size(line));
+
+        return true;
+    }
+
+    return false;
+}
+
+bool apdu_log_check_presence(const char* path) {
+    furi_check(path);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    bool log_present = storage_common_stat(storage, path, NULL) == FSE_OK;
+
+    furi_record_close(RECORD_STORAGE);
+
+    return log_present;
+}
+
+APDULog* apdu_log_alloc(const char* path, APDULogMode mode) {
+    furi_check(path);
+
+    APDULog* instance = malloc(sizeof(APDULog));
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = buffered_file_stream_alloc(storage);
+
+    FS_OpenMode open_mode = (mode == APDULogModeOpenAlways) ? FSOM_OPEN_ALWAYS :
+                                                              FSOM_OPEN_EXISTING;
+
+    instance->total_lines = 0;
+
+    bool file_exists =
+        buffered_file_stream_open(instance->stream, path, FSAM_READ_WRITE, open_mode);
+
+    if(!file_exists) {
+        buffered_file_stream_close(instance->stream);
+    } else {
+        // Eventually add new line character in the last line to avoid skipping lines
+        apdu_log_add_ending_new_line(instance);
+    }
+
+    FuriString* line = furi_string_alloc();
+
+    bool is_endfile = false;
+
+    // In this loop we only count the entries in the file
+    // We prefer not to load the whole file in memory for space reasons
+    while(file_exists && !is_endfile) {
+        bool read_log = apdu_log_read_log_line(instance, line, &is_endfile);
+        if(read_log) {
+            instance->total_lines++;
+        }
+    }
+    stream_rewind(instance->stream);
+    FURI_LOG_I(TAG, "Loaded log with %zu lines", instance->total_lines);
+
+    furi_string_free(line);
+
+    return instance;
+}
+
+void apdu_log_free(APDULog* instance) {
+    furi_check(instance);
+    furi_check(instance->stream);
+
+    buffered_file_stream_close(instance->stream);
+    stream_free(instance->stream);
+    free(instance);
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+size_t apdu_log_get_total_lines(APDULog* instance) {
+    furi_check(instance);
+
+    return instance->total_lines;
+}
+
+bool apdu_log_rewind(APDULog* instance) {
+    furi_check(instance);
+    furi_check(instance->stream);
+
+    return stream_rewind(instance->stream);
+}
+
+bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log) {
+    furi_assert(instance);
+    furi_assert(instance->stream);
+    furi_assert(log);
+
+    bool log_read = false;
+    bool is_endfile = false;
+
+    furi_string_reset(log);
+
+    while(!log_read && !is_endfile)
+        log_read = apdu_log_read_log_line(instance, log, &is_endfile);
+
+    return log_read;
+}

+ 63 - 0
seader/apdu_log.h

@@ -0,0 +1,63 @@
+#pragma once
+
+#include <flipper_format/flipper_format.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    APDULogModeOpenExisting,
+    APDULogModeOpenAlways,
+} APDULogMode;
+
+typedef struct APDULog APDULog;
+
+/** Check if the file list exists
+ *
+ * @param path      - list path
+ *
+ * @return true if list exists, false otherwise
+*/
+bool apdu_log_check_presence(const char* path);
+
+/** Open or create list
+ * Depending on mode, list will be opened or created.
+ *
+ * @param path      - Path of the file that contain the list
+ * @param mode      - ListKeysMode value
+ *
+ * @return Returns APDULog list instance
+*/
+APDULog* apdu_log_alloc(const char* path, APDULogMode mode);
+
+/** Close list
+ *
+ * @param instance  - APDULog list instance
+*/
+void apdu_log_free(APDULog* instance);
+
+/** Get total number of lines in list
+ *
+ * @param instance  - APDULog list instance
+ *
+ * @return Returns total number of lines in list
+*/
+size_t apdu_log_get_total_lines(APDULog* instance);
+
+/** Rewind list
+ *
+ * @param instance  - APDULog list instance
+ *
+ * @return Returns true if rewind was successful, false otherwise
+*/
+bool apdu_log_rewind(APDULog* instance);
+
+bool apdu_log_get_next_log_str(APDULog* instance, FuriString* log);
+
+#ifdef __cplusplus
+}
+#endif

+ 178 - 0
seader/apdu_runner.c

@@ -0,0 +1,178 @@
+#include "apdu_runner.h"
+
+#define TAG "APDU_Runner"
+
+// Max length of firmware upgrade: 731 bytes
+#define SEADER_APDU_MAX_LEN 732
+
+void seader_apdu_runner_cleanup(Seader* seader, SeaderWorkerEvent event) {
+    SeaderWorker* seader_worker = seader->worker;
+    seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
+    apdu_log_free(seader->apdu_log);
+    seader->apdu_log = NULL;
+    if(seader_worker->callback) {
+        seader_worker->callback(event, seader_worker->context);
+    }
+}
+
+bool seader_apdu_runner_send_next_line(Seader* seader) {
+    SeaderWorker* seader_worker = seader->worker;
+    SeaderUartBridge* seader_uart = seader_worker->uart;
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+
+    FuriString* line = furi_string_alloc();
+    apdu_log_get_next_log_str(seader->apdu_log, line);
+
+    size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
+    uint8_t* apdu = malloc(len);
+    if(apdu == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    if(len > SEADER_UART_RX_BUF_SIZE) {
+        FURI_LOG_E(TAG, "APDU length is too long");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+
+    if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
+        FURI_LOG_E(TAG, "Failed to convert line to number");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+    FURI_LOG_I(
+        TAG,
+        "APDU Runner => (%d/%d): %s",
+        apdu_runner_ctx->current_line,
+        apdu_runner_ctx->total_lines,
+        furi_string_get_cstr(line));
+
+    if(seader_worker->callback) {
+        seader_worker->callback(SeaderWorkerEventAPDURunnerUpdate, seader_worker->context);
+    }
+
+    apdu_runner_ctx->current_line++;
+    if(seader_uart->T == 1) {
+        seader_send_t1(seader_uart, apdu, len);
+    } else {
+        seader_ccid_XfrBlock(seader_uart, apdu, len);
+    }
+    furi_string_free(line);
+    free(apdu);
+
+    return true;
+}
+
+void seader_apdu_runner_init(Seader* seader) {
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+
+    if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) {
+        FURI_LOG_I(TAG, "APDU log file exists");
+    } else {
+        FURI_LOG_W(TAG, "APDU log file does not exist");
+        return;
+    }
+
+    seader->apdu_log = apdu_log_alloc(SEADER_APDU_RUNNER_FILE_NAME, APDULogModeOpenExisting);
+    apdu_runner_ctx->current_line = 0;
+    apdu_runner_ctx->total_lines = apdu_log_get_total_lines(seader->apdu_log);
+    FURI_LOG_I(TAG, "APDU log lines: %d", apdu_runner_ctx->total_lines);
+
+    seader_apdu_runner_send_next_line(seader);
+}
+
+bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len) {
+    SeaderUartBridge* seader_uart = seader->worker->uart;
+    SeaderAPDURunnerContext* apdu_runner_ctx = &(seader->apdu_runner_ctx);
+    uint8_t GET_RESPONSE[] = {0x00, 0xc0, 0x00, 0x00, 0xff};
+
+    uint8_t SW1 = r_apdu[r_len - 2];
+    uint8_t SW2 = r_apdu[r_len - 1];
+
+    switch(SW1) {
+    case 0x61:
+        //FURI_LOG_D(TAG, "Request %d bytes", SW2);
+        GET_RESPONSE[4] = SW2;
+        seader_ccid_XfrBlock(seader_uart, GET_RESPONSE, sizeof(GET_RESPONSE));
+        return true;
+    }
+
+    if(r_len < SEADER_UART_RX_BUF_SIZE) {
+        char* display = malloc(r_len * 2 + 1);
+        if(display == NULL) {
+            FURI_LOG_E(TAG, "Failed to allocate memory for display");
+            seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+            return false;
+        }
+        memset(display, 0, r_len * 2 + 1);
+        for(uint8_t i = 0; i < r_len; i++) {
+            snprintf(display + (i * 2), sizeof(display), "%02x", r_apdu[i]);
+        }
+        FURI_LOG_I(
+            TAG,
+            "APDU Runner <=: (%d/%d): %s",
+            apdu_runner_ctx->current_line,
+            apdu_runner_ctx->total_lines,
+            display);
+        free(display);
+    } else {
+        FURI_LOG_I(TAG, "APDU Runner <=: Response too long to display");
+    }
+
+    /** Compare last two bytes to expected line **/
+
+    FuriString* line = furi_string_alloc();
+    apdu_log_get_next_log_str(seader->apdu_log, line);
+    if(furi_string_size(line) % 2 == 1) {
+        FURI_LOG_E(TAG, "APDU log file has odd number of characters");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    size_t len = furi_string_size(line) / 2; // String is in HEX, divide by 2 for bytes
+    uint8_t* apdu = malloc(len);
+    if(apdu == NULL) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for APDU");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        return false;
+    }
+
+    if(!hex_chars_to_uint8(furi_string_get_cstr(line), apdu)) {
+        FURI_LOG_E(TAG, "Failed to convert line to byte array");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        // TODO: Send failed event
+        return false;
+    }
+
+    apdu_runner_ctx->current_line++;
+    furi_string_free(line);
+
+    if(memcmp(r_apdu + r_len - 2, apdu + len - 2, 2) != 0) {
+        FURI_LOG_W(
+            TAG,
+            "APDU runner response does not match.  Response %02x%02x != expected %02x%02x",
+            r_apdu[r_len - 2],
+            r_apdu[r_len - 1],
+            apdu[len - 2],
+            apdu[len - 1]);
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerError);
+        free(apdu);
+        return false;
+    }
+    free(apdu);
+
+    // Check if we are at the end of the log
+    if(apdu_runner_ctx->current_line >= apdu_runner_ctx->total_lines) {
+        FURI_LOG_I(TAG, "APDU runner finished");
+        seader_apdu_runner_cleanup(seader, SeaderWorkerEventAPDURunnerSuccess);
+        return false;
+    }
+
+    // Send next line
+    return seader_apdu_runner_send_next_line(seader);
+}

+ 10 - 0
seader/apdu_runner.h

@@ -0,0 +1,10 @@
+
+#pragma once
+
+#include <lib/toolbox/hex.h>
+#include "seader_i.h"
+
+#define SEADER_APDU_RUNNER_FILE_NAME APP_DATA_PATH("script.apdu")
+
+void seader_apdu_runner_init(Seader* seader);
+bool seader_apdu_runner_response(Seader* seader, uint8_t* r_apdu, size_t r_len);

+ 63 - 51
seader/ccid.c

@@ -63,23 +63,44 @@ void seader_ccid_GetSlotStatus(SeaderUartBridge* seader_uart, uint8_t slot) {
 void seader_ccid_SetParameters(Seader* seader, uint8_t slot, uint8_t* atr, size_t atr_len) {
     SeaderWorker* seader_worker = seader->worker;
     SeaderUartBridge* seader_uart = seader_worker->uart;
-    UNUSED(slot);
-    UNUSED(atr);
     UNUSED(atr_len);
-    uint8_t T1 = 1;
+    FURI_LOG_D(TAG, "seader_ccid_SetParameters(%d)", slot);
+
+    uint8_t payloadLen = 0;
+    if(seader_uart->T == 0) {
+        payloadLen = 5;
+    } else if(atr[4] == 0xB1 && seader_uart->T == 1) {
+        payloadLen = 7;
+    }
     memset(seader_uart->tx_buf, 0, SEADER_UART_RX_BUF_SIZE);
     seader_uart->tx_buf[0] = SYNC;
     seader_uart->tx_buf[1] = CTRL;
     seader_uart->tx_buf[2 + 0] = CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters;
-    seader_uart->tx_buf[2 + 1] = 0;
-    seader_uart->tx_buf[2 + 5] = sam_slot;
-    seader_uart->tx_buf[2 + 6] = getSequence(sam_slot);
-    seader_uart->tx_buf[2 + 7] = T1;
+    seader_uart->tx_buf[2 + 1] = payloadLen;
+    seader_uart->tx_buf[2 + 5] = slot;
+    seader_uart->tx_buf[2 + 6] = getSequence(slot);
+    seader_uart->tx_buf[2 + 7] = seader_uart->T;
     seader_uart->tx_buf[2 + 8] = 0;
     seader_uart->tx_buf[2 + 9] = 0;
 
-    seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10);
+    if(seader_uart->T == 0) {
+        // I'm leaving this here for completeness, but it was actually causing ICC_MUTE on the first apdu.
+        seader_uart->tx_buf[2 + 10] = 0x96; //atr[2]; //bmFindexDindex
+        seader_uart->tx_buf[2 + 11] = 0x00; //bmTCCKST1
+        seader_uart->tx_buf[2 + 12] = 0x00; //bGuardTimeT0
+        seader_uart->tx_buf[2 + 13] = 0x0a; //bWaitingIntegerT0
+        seader_uart->tx_buf[2 + 14] = 0x00; //bClockStop
+    } else if(seader_uart->T == 1) {
+        seader_uart->tx_buf[2 + 10] = atr[2]; //bmFindexDindex
+        seader_uart->tx_buf[2 + 11] = 0x10; //bmTCCKST1
+        seader_uart->tx_buf[2 + 12] = 0xfe; //bGuardTimeT1
+        seader_uart->tx_buf[2 + 13] = atr[6]; //bWaitingIntegerT1
+        seader_uart->tx_buf[2 + 14] = atr[8]; //bClockStop
+        seader_uart->tx_buf[2 + 15] = atr[5]; //bIFSC
+        seader_uart->tx_buf[2 + 16] = 0x00; //bNadValue
+    }
 
+    seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10 + payloadLen);
     furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx);
 }
 
@@ -121,14 +142,17 @@ void seader_ccid_XfrBlockToSlot(
     seader_uart->tx_buf[2 + 8] = 0;
     seader_uart->tx_buf[2 + 9] = 0;
 
-    memcpy(seader_uart->tx_buf + 2 + 10, data, len);
-    seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, 2 + 10 + len);
+    uint8_t header_len = 2 + 10;
+    memcpy(seader_uart->tx_buf + header_len, data, len);
+    seader_uart->tx_len = header_len + len;
+    seader_uart->tx_len = seader_add_lrc(seader_uart->tx_buf, seader_uart->tx_len);
 
-    char display[SEADER_UART_RX_BUF_SIZE * 2 + 1] = {0};
+    char* display = malloc(seader_uart->tx_len * 2 + 1);
     for(uint8_t i = 0; i < seader_uart->tx_len; i++) {
         snprintf(display + (i * 2), sizeof(display), "%02x", seader_uart->tx_buf[i]);
     }
-    FURI_LOG_D(TAG, "seader_ccid_XfrBlock %d bytes: %s", seader_uart->tx_len, display);
+    FURI_LOG_D(TAG, "seader_ccid_XfrBlockToSlot(%d) %d: %s", slot, seader_uart->tx_len, display);
+    free(display);
 
     furi_thread_flags_set(furi_thread_get_id(seader_uart->tx_thread), WorkerEvtSamRx);
 }
@@ -139,11 +163,12 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
     CCID_Message message;
     message.consumed = 0;
 
-    char display[SEADER_UART_RX_BUF_SIZE * 2 + 1] = {0};
+    char* display = malloc(cmd_len * 2 + 1);
     for(uint8_t i = 0; i < cmd_len; i++) {
         snprintf(display + (i * 2), sizeof(display), "%02x", cmd[i]);
     }
     FURI_LOG_D(TAG, "seader_ccid_process %d: %s", cmd_len, display);
+    free(display);
 
     if(cmd_len == 2) {
         if(cmd[0] == CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange) {
@@ -230,11 +255,6 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
         message.bError = ccid[8];
         message.payload = ccid + 10;
 
-        memset(display, 0, sizeof(display));
-        for(uint8_t i = 0; i < message.dwLength; i++) {
-            snprintf(display + (i * 2), sizeof(display), "%02x", message.payload[i]);
-        }
-
         if(cmd_len < 2 + 10 + message.dwLength + 1) {
             // Incomplete
             return message.consumed;
@@ -251,27 +271,6 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
             return message.consumed;
         }
 
-        /*
-        if(message.dwLength == 0) {
-            FURI_LOG_D(
-                TAG,
-                "CCID [%d|%d] type: %02x, status: %02x, error: %02x",
-                message.bSlot,
-                message.bSeq,
-                message.bMessageType,
-                message.bStatus,
-                message.bError);
-        } else {
-            FURI_LOG_D(
-                TAG,
-                "CCID [%d|%d] %ld: %s",
-                message.bSlot,
-                message.bSeq,
-                message.dwLength,
-                display);
-        }
-        */
-
         //0306 81 00000000 0000 0200 01 87
         //0306 81 00000000 0000 0100 01 84
         if(message.bMessageType == CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus) {
@@ -319,10 +318,25 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
             return message.consumed;
         }
 
-        if(message.bMessageType == CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock) {
+        if(message.bMessageType == CCID_MESSAGE_TYPE_RDR_to_PC_Parameters) {
+            FURI_LOG_D(TAG, "Got Parameters");
+            if(seader_uart->T == 1) {
+                seader_t_1_set_IFSD(seader);
+            } else {
+                seader_worker_send_version(seader);
+                if(seader_worker->callback) {
+                    seader_worker->callback(SeaderWorkerEventSamPresent, seader_worker->context);
+                }
+            }
+        } else if(message.bMessageType == CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock) {
             if(hasSAM) {
                 if(message.bSlot == sam_slot) {
-                    seader_worker_process_sam_message(seader, message.payload, message.dwLength);
+                    if(seader_uart->T == 0) {
+                        seader_worker_process_sam_message(
+                            seader, message.payload, message.dwLength);
+                    } else if(seader_uart->T == 1) {
+                        seader_recv_t1(seader, &message);
+                    }
                 } else {
                     FURI_LOG_D(TAG, "Discarding message on non-sam slot");
                 }
@@ -331,20 +345,18 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
                     FURI_LOG_I(TAG, "SAM ATR!");
                     hasSAM = true;
                     sam_slot = message.bSlot;
-                    seader_worker_send_version(seader);
-                    if(seader_worker->callback) {
-                        seader_worker->callback(
-                            SeaderWorkerEventSamPresent, seader_worker->context);
+                    if(seader_uart->T == 0) {
+                        seader_ccid_GetParameters(seader_uart);
+                    } else if(seader_uart->T == 1) {
+                        seader_ccid_SetParameters(
+                            seader, sam_slot, message.payload, message.dwLength);
                     }
                 } else if(memcmp(SAM_ATR2, message.payload, sizeof(SAM_ATR2)) == 0) {
                     FURI_LOG_I(TAG, "SAM ATR2!");
                     hasSAM = true;
                     sam_slot = message.bSlot;
-                    seader_worker_send_version(seader);
-                    if(seader_worker->callback) {
-                        seader_worker->callback(
-                            SeaderWorkerEventSamPresent, seader_worker->context);
-                    }
+                    // I don't have an ATR2 to test with
+                    seader_ccid_GetParameters(seader_uart);
                 } else {
                     FURI_LOG_W(TAG, "Unknown ATR");
                     if(seader_worker->callback) {
@@ -353,7 +365,7 @@ size_t seader_ccid_process(Seader* seader, uint8_t* cmd, size_t cmd_len) {
                 }
             }
         } else {
-            FURI_LOG_W(TAG, "Unhandled CCID message type %d", message.bMessageType);
+            FURI_LOG_W(TAG, "Unhandled CCID message type %02x", message.bMessageType);
         }
     }
 

+ 0 - 2
seader/lib/loclass/optimized_cipher.c

@@ -144,7 +144,6 @@ static inline void loclass_opt_suc(
     bool add32Zeroes) {
     for(int i = 0; i < length; i++) {
         uint8_t head = in[i];
-#pragma GCC unroll 8
         for(int j = 0; j < 8; j++) {
             loclass_opt_successor(k, s, head);
             head >>= 1;
@@ -159,7 +158,6 @@ static inline void loclass_opt_suc(
 }
 
 static inline void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
-#pragma GCC unroll 4
     for(uint8_t times = 0; times < 4; times++) {
         uint8_t bout = 0;
         bout |= (s->r & 0x4) >> 2;

+ 1 - 1
seader/readme.md

@@ -43,7 +43,7 @@ Optionally 3d print a [case designed by sean](https://www.printables.com/model/5
 ### To Build ASN1 (if you change seader.asn1)
 
  * Install git version of [asnc1](https://github.com/vlm/asn1c) (`brew install asn1c --head` on macos)
- * Run `asn1c -D ./lib/asn1 -no-gen-example -pdu=all seader.asn` in in root to generate asn1c files
+ * Run `asn1c -D ./lib/asn1 -no-gen-example -pdu=all seader.asn1` in in root to generate asn1c files
 
 ## References
 

+ 42 - 11
seader/sam_api.c

@@ -6,7 +6,6 @@
 
 #define TAG "SAMAPI"
 
-#define APDU_HEADER_LEN                 5
 #define ASN1_PREFIX                     6
 #define ASN1_DEBUG                      true
 #define SEADER_ICLASS_SR_SIO_BASE_BLOCK 10
@@ -137,6 +136,7 @@ void seader_picopass_state_machine(Seader* seader, uint8_t* buffer, size_t len)
     bit_buffer_free(rx_buffer);
 }
 
+uint8_t APDU_HEADER_LEN = 5;
 bool seader_send_apdu(
     Seader* seader,
     uint8_t CLA,
@@ -144,30 +144,54 @@ bool seader_send_apdu(
     uint8_t P1,
     uint8_t P2,
     uint8_t* payload,
-    uint8_t length) {
+    uint8_t payloadLen) {
     SeaderWorker* seader_worker = seader->worker;
     SeaderUartBridge* seader_uart = seader_worker->uart;
 
-    if(APDU_HEADER_LEN + length > SEADER_UART_RX_BUF_SIZE) {
-        FURI_LOG_E(TAG, "Cannot send message, too long: %d", APDU_HEADER_LEN + length);
+    if(seader_uart->T == 1) {
+        APDU_HEADER_LEN = 7;
+    }
+
+    if(APDU_HEADER_LEN + payloadLen > SEADER_UART_RX_BUF_SIZE) {
+        FURI_LOG_E(TAG, "Cannot send message, too long: %d", APDU_HEADER_LEN + payloadLen);
+        return false;
+    }
+
+    uint8_t length = APDU_HEADER_LEN + payloadLen;
+    uint8_t* apdu = malloc(length);
+    if(!apdu) {
+        FURI_LOG_E(TAG, "Failed to allocate memory for apdu in seader_send_apdu");
         return false;
     }
 
-    uint8_t apdu[SEADER_UART_RX_BUF_SIZE];
     apdu[0] = CLA;
     apdu[1] = INS;
     apdu[2] = P1;
     apdu[3] = P2;
-    apdu[4] = length;
-    memcpy(apdu + APDU_HEADER_LEN, payload, length);
+
+    if(seader_uart->T == 1) {
+        apdu[4] = 0x00;
+        apdu[5] = 0x00;
+        apdu[6] = payloadLen;
+    } else {
+        apdu[4] = payloadLen;
+    }
+
+    memcpy(apdu + APDU_HEADER_LEN, payload, payloadLen);
 
     memset(display, 0, sizeof(display));
-    for(uint8_t i = 0; i < APDU_HEADER_LEN + length; i++) {
+    for(uint8_t i = 0; i < length; i++) {
         snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i]);
     }
     FURI_LOG_D(TAG, "seader_send_apdu %s", display);
 
-    seader_ccid_XfrBlock(seader_uart, apdu, APDU_HEADER_LEN + length);
+    if(seader_uart->T == 1) {
+        seader_send_t1(seader_uart, apdu, length);
+    } else {
+        seader_ccid_XfrBlock(seader_uart, apdu, length);
+    }
+    free(apdu);
+
     return true;
 }
 
@@ -629,6 +653,7 @@ void seader_iso15693_transmit(
     uint8_t* buffer,
     size_t len) {
     SeaderWorker* seader_worker = seader->worker;
+
     BitBuffer* tx_buffer = bit_buffer_alloc(len);
     BitBuffer* rx_buffer = bit_buffer_alloc(SEADER_POLLER_MAX_BUFFER_SIZE);
 
@@ -962,17 +987,19 @@ bool seader_worker_state_machine(
 
     switch(payload->present) {
     case Payload_PR_response:
+        FURI_LOG_D(TAG, "Payload_PR_response");
         seader_parse_response(seader, &payload->choice.response);
         processed = true;
         break;
     case Payload_PR_nfcCommand:
+        FURI_LOG_D(TAG, "Payload_PR_nfcCommand");
         if(online) {
             seader_parse_nfc_command(seader, &payload->choice.nfcCommand, spc);
             processed = true;
         }
         break;
     case Payload_PR_errorResponse:
-        FURI_LOG_W(TAG, "Error Response");
+        FURI_LOG_W(TAG, "Payload_PR_errorResponse");
         processed = true;
         view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit);
         break;
@@ -1012,8 +1039,12 @@ bool seader_process_success_response_i(
                 ->op->print_struct(
                     &asn_DEF_Payload, payload, 1, seader_print_struct_callback, payloadDebug);
             if(strlen(payloadDebug) > 0) {
-                FURI_LOG_D(TAG, "Payload: %s", payloadDebug);
+                FURI_LOG_D(TAG, "Received Payload: %s", payloadDebug);
+            } else {
+                FURI_LOG_D(TAG, "Received empty Payload");
             }
+        } else {
+            FURI_LOG_D(TAG, "Online mode");
         }
 #endif
 

+ 78 - 0
seader/scenes/seader_scene_apdu_runner.c

@@ -0,0 +1,78 @@
+#include "../seader_i.h"
+#include <dolphin/dolphin.h>
+
+#define TAG "Seader:Scene:APDURunner"
+
+char seader_scene_apdu_runner_update_text[24];
+
+void seader_apdu_runner_worker_callback(SeaderWorkerEvent event, void* context) {
+    Seader* seader = context;
+    view_dispatcher_send_custom_event(seader->view_dispatcher, event);
+}
+
+void seader_scene_apdu_runner_on_enter(void* context) {
+    Seader* seader = context;
+    // Setup view
+    Popup* popup = seader->popup;
+    popup_set_header(popup, "APDU Runner", 68, 30, AlignLeft, AlignTop);
+
+    // TODO: Make icon for interaction with SAM
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+
+    // Start worker
+    seader_worker_start(
+        seader->worker,
+        SeaderWorkerStateAPDURunner,
+        seader->uart,
+        seader_apdu_runner_worker_callback,
+        seader);
+
+    view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewPopup);
+}
+
+bool seader_scene_apdu_runner_on_event(void* context, SceneManagerEvent event) {
+    Seader* seader = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeLeft) {
+            scene_manager_search_and_switch_to_previous_scene(
+                seader->scene_manager, SeaderSceneSamPresent);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerUpdate) {
+            SeaderAPDURunnerContext apdu_runner_ctx = seader->apdu_runner_ctx;
+            Popup* popup = seader->popup;
+            snprintf(
+                seader_scene_apdu_runner_update_text,
+                sizeof(seader_scene_apdu_runner_update_text),
+                "APDU Runner\n%d/%d",
+                apdu_runner_ctx.current_line,
+                apdu_runner_ctx.total_lines);
+            popup_set_header(
+                popup, seader_scene_apdu_runner_update_text, 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerSuccess) {
+            notification_message(seader->notifications, &sequence_success);
+            Popup* popup = seader->popup;
+            popup_set_header(popup, "APDU Runner\nSuccess", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == SeaderWorkerEventAPDURunnerError) {
+            notification_message(seader->notifications, &sequence_error);
+            Popup* popup = seader->popup;
+            popup_set_header(popup, "APDU Runner\nError", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(
+            seader->scene_manager, SeaderSceneSamPresent);
+        consumed = true;
+    }
+    return consumed;
+}
+
+void seader_scene_apdu_runner_on_exit(void* context) {
+    Seader* seader = context;
+
+    // Clear view
+    popup_reset(seader->popup);
+}

+ 1 - 0
seader/scenes/seader_scene_config.h

@@ -17,3 +17,4 @@ ADD_SCENE(seader, sam_info, SamInfo)
 ADD_SCENE(seader, virtual_credential, VirtualCredential)
 ADD_SCENE(seader, formats, Formats)
 ADD_SCENE(seader, read_mfc, ReadMfc)
+ADD_SCENE(seader, apdu_runner, APDURunner)

+ 0 - 2
seader/scenes/seader_scene_read_14a.c

@@ -29,7 +29,6 @@ bool seader_scene_read_14a_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SeaderCustomEventWorkerExit) {
-            seader->credential->type = SeaderCredentialType14A;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerDetect) {
@@ -37,7 +36,6 @@ bool seader_scene_read_14a_on_event(void* context, SceneManagerEvent event) {
             popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerSuccess) {
-            seader->credential->type = SeaderCredentialType14A;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         }

+ 0 - 2
seader/scenes/seader_scene_read_mfc.c

@@ -33,7 +33,6 @@ bool seader_scene_read_mfc_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SeaderCustomEventWorkerExit) {
-            seader->credential->type = SeaderCredentialTypeMifareClassic;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerDetect) {
@@ -41,7 +40,6 @@ bool seader_scene_read_mfc_on_event(void* context, SceneManagerEvent event) {
             popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerSuccess) {
-            seader->credential->type = SeaderCredentialTypeMifareClassic;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         }

+ 0 - 2
seader/scenes/seader_scene_read_picopass.c

@@ -28,7 +28,6 @@ bool seader_scene_read_picopass_on_event(void* context, SceneManagerEvent event)
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SeaderCustomEventWorkerExit) {
-            seader->credential->type = SeaderCredentialTypePicopass;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerDetect) {
@@ -36,7 +35,6 @@ bool seader_scene_read_picopass_on_event(void* context, SceneManagerEvent event)
             popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
             consumed = true;
         } else if(event.event == SeaderCustomEventPollerSuccess) {
-            seader->credential->type = SeaderCredentialTypePicopass;
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
             consumed = true;
         }

+ 14 - 8
seader/scenes/seader_scene_sam_present.c

@@ -4,6 +4,7 @@ enum SubmenuIndex {
     SubmenuIndexRead14a,
     SubmenuIndexReadMfc,
     SubmenuIndexSaved,
+    SubmenuIndexAPDURunner,
     SubmenuIndexSamInfo,
     SubmenuIndexFwVersion,
 };
@@ -43,6 +44,14 @@ void seader_scene_sam_present_on_update(void* context) {
     submenu_add_item(
         submenu, "Saved", SubmenuIndexSaved, seader_scene_sam_present_submenu_callback, seader);
 
+    if(apdu_log_check_presence(SEADER_APDU_RUNNER_FILE_NAME)) {
+        submenu_add_item(
+            submenu,
+            "Run APDUs",
+            SubmenuIndexAPDURunner,
+            seader_scene_sam_present_submenu_callback,
+            seader);
+    }
     if(seader_worker->sam_version[0] != 0 && seader_worker->sam_version[1] != 0) {
         FuriString* fw_str = furi_string_alloc();
         furi_string_cat_printf(
@@ -72,24 +81,18 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(seader->scene_manager, SeaderSceneSamPresent, event.event);
+
         if(event.event == SubmenuIndexReadPicopass) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadPicopass);
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadPicopass);
             consumed = true;
         } else if(event.event == SubmenuIndexRead14a) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexRead14a);
             scene_manager_next_scene(seader->scene_manager, SeaderSceneRead14a);
             consumed = true;
         } else if(event.event == SubmenuIndexReadMfc) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadMfc);
             scene_manager_next_scene(seader->scene_manager, SeaderSceneReadMfc);
             consumed = true;
         } else if(event.event == SubmenuIndexSamInfo) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexSamInfo);
             scene_manager_next_scene(seader->scene_manager, SeaderSceneSamInfo);
             consumed = true;
         } else if(event.event == SubmenuIndexSaved) {
@@ -100,6 +103,9 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SeaderWorkerEventSamMissing) {
             scene_manager_next_scene(seader->scene_manager, SeaderSceneSamMissing);
             consumed = true;
+        } else if(event.event == SubmenuIndexAPDURunner) {
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneAPDURunner);
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_stop(seader->scene_manager);

+ 3 - 0
seader/seader_bridge.h

@@ -42,6 +42,9 @@ struct SeaderUartBridge {
     uint8_t rx_buf[SEADER_UART_RX_BUF_SIZE];
     uint8_t tx_buf[SEADER_UART_RX_BUF_SIZE];
     size_t tx_len;
+
+    // T=0 or T=1
+    uint8_t T;
 };
 
 typedef struct SeaderUartBridge SeaderUartBridge;

+ 10 - 0
seader/seader_i.h

@@ -53,8 +53,10 @@
 #include "ccid.h"
 #include "uart.h"
 #include "lrc.h"
+#include "t_1.h"
 #include "seader_worker.h"
 #include "seader_credential.h"
+#include "apdu_log.h"
 
 #define WORKER_ALL_RX_EVENTS                                                      \
     (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \
@@ -90,6 +92,11 @@ typedef enum {
     WorkerEvtCtrlLineSet = (1 << 7),
 } WorkerEvtFlags;
 
+typedef struct {
+    uint16_t total_lines;
+    uint16_t current_line;
+} SeaderAPDURunnerContext;
+
 struct Seader {
     bool revert_power;
     bool is_debug_enabled;
@@ -121,6 +128,9 @@ struct Seader {
 
     PluginManager* plugin_manager;
     PluginWiegand* plugin_wiegand;
+
+    APDULog* apdu_log;
+    SeaderAPDURunnerContext apdu_runner_ctx;
 };
 
 struct SeaderPollerContainer {

+ 17 - 3
seader/seader_worker.c

@@ -13,8 +13,6 @@
     (FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
      FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
 
-char display[SEADER_UART_RX_BUF_SIZE * 2 + 1] = {0};
-
 // Forward declaration
 void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* cardDetails);
 
@@ -117,11 +115,18 @@ bool seader_worker_process_sam_message(Seader* seader, uint8_t* apdu, uint32_t l
     if(len < 2) {
         return false;
     }
-    memset(display, 0, sizeof(display));
+
+    if(seader_worker->state == SeaderWorkerStateAPDURunner) {
+        return seader_apdu_runner_response(seader, apdu, len);
+    }
+
+    char* display = malloc(len * 2 + 1);
+    memset(display, 0, len * 2 + 1);
     for(uint8_t i = 0; i < len; i++) {
         snprintf(display + (i * 2), sizeof(display), "%02x", apdu[i]);
     }
     FURI_LOG_I(TAG, "APDU: %s", display);
+    free(display);
 
     uint8_t SW1 = apdu[len - 2];
     uint8_t SW2 = apdu[len - 1];
@@ -210,6 +215,10 @@ int32_t seader_worker_task(void* context) {
     } else if(seader_worker->state == SeaderWorkerStateVirtualCredential) {
         FURI_LOG_D(TAG, "Virtual Credential");
         seader_worker_virtual_credential(seader);
+    } else if(seader_worker->state == SeaderWorkerStateAPDURunner) {
+        FURI_LOG_D(TAG, "APDU Runner");
+        seader_apdu_runner_init(seader);
+        return 0;
     }
     seader_worker_change_state(seader_worker, SeaderWorkerStateReady);
 
@@ -286,6 +295,11 @@ NfcCommand seader_worker_poller_callback_iso14443_4a(NfcGenericEvent event, void
             seader_worker_poller_conversation(seader, &spc);
         } else if(seader_worker->stage == SeaderPollerEventTypeComplete) {
             ret = NfcCommandStop;
+        } else if(seader_worker->stage == SeaderPollerEventTypeFail) {
+            ret = NfcCommandStop;
+            view_dispatcher_send_custom_event(
+                seader->view_dispatcher, SeaderCustomEventWorkerExit);
+            FURI_LOG_W(TAG, "SeaderPollerEventTypeFail");
         }
     } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) {
         Iso14443_4aPollerEventData* data = iso14443_4a_event->data;

+ 5 - 0
seader/seader_worker.h

@@ -6,6 +6,7 @@
 #include "sam_api.h"
 #include "seader_credential.h"
 #include "seader_bridge.h"
+#include "apdu_runner.h"
 
 typedef struct SeaderWorker SeaderWorker;
 typedef struct CCID_Message CCID_Message;
@@ -19,6 +20,7 @@ typedef enum {
     // Main worker states
     SeaderWorkerStateCheckSam,
     SeaderWorkerStateVirtualCredential,
+    SeaderWorkerStateAPDURunner,
     // Transition
     SeaderWorkerStateStop,
 } SeaderWorkerState;
@@ -35,6 +37,9 @@ typedef enum {
     SeaderWorkerEventSamMissing,
     SeaderWorkerEventNoCardDetected,
     SeaderWorkerEventStartReading,
+    SeaderWorkerEventAPDURunnerUpdate,
+    SeaderWorkerEventAPDURunnerSuccess,
+    SeaderWorkerEventAPDURunnerError,
 } SeaderWorkerEvent;
 
 typedef enum {

+ 219 - 0
seader/t_1.c

@@ -0,0 +1,219 @@
+#include "t_1.h"
+
+#define TAG "Seader:T=1"
+
+// http://www.sat-digest.com/SatXpress/SmartCard/ISO7816-4.htm
+
+/* I know my T=1 is terrible, but I'm also only targetting one specific 'card' */
+
+#define MORE_BIT               0x20
+#define IFSD_VALUE             0xfe
+#define IFSC_VALUE             0xfe // Fom the SAM ATR
+#define R_BLOCK                0x80
+#define R_SEQUENCE_NUMBER_MASK 0x10
+
+// TODO: T1 struct
+uint8_t NAD = 0x00;
+uint8_t dPCB = 0x40; // Init to 0x40 so first call to next_pcb will return 0x00
+uint8_t cPCB = 0x00; // Init to 0x40 so first call to next_pcb will return 0x00
+
+uint8_t seader_next_dpcb() {
+    uint8_t next_pcb = dPCB ^ 0x40;
+    //FURI_LOG_D(TAG, "dPCB was: %02X, current dPCB: %02X", dPCB, next_pcb);
+    dPCB = next_pcb;
+    return dPCB;
+}
+
+uint8_t seader_next_cpcb() {
+    uint8_t next_pcb = cPCB ^ 0x40;
+    //FURI_LOG_D(TAG, "cPCB was: %02X, current cPCB: %02X", cPCB, next_pcb);
+    cPCB = next_pcb;
+    return cPCB;
+}
+
+void seader_t_1_set_IFSD(Seader* seader) {
+    SeaderWorker* seader_worker = seader->worker;
+    SeaderUartBridge* seader_uart = seader_worker->uart;
+    uint8_t frame[5];
+    uint8_t frame_len = 0;
+
+    frame[0] = NAD;
+    frame[1] = 0xC1; // S(IFS request)
+    frame[2] = 0x01;
+    frame[3] = IFSD_VALUE;
+    frame_len = 4;
+
+    frame_len = seader_add_lrc(frame, frame_len);
+
+    seader_ccid_XfrBlock(seader_uart, frame, frame_len);
+}
+
+void seader_t_1_send_ack(Seader* seader) {
+    SeaderWorker* seader_worker = seader->worker;
+    SeaderUartBridge* seader_uart = seader_worker->uart;
+    uint8_t frame[4];
+    uint8_t frame_len = 0;
+
+    frame[0] = NAD;
+    frame[1] = R_BLOCK | (seader_next_cpcb() >> 2);
+    frame[2] = 0x00;
+    frame_len = 3;
+
+    frame_len = seader_add_lrc(frame, frame_len);
+
+    //FURI_LOG_D(TAG, "Sending R-Block ACK: PCB: %02x", frame[1]);
+
+    seader_ccid_XfrBlock(seader_uart, frame, frame_len);
+}
+
+BitBuffer* seader_t_1_tx_buffer;
+size_t seader_t_1_tx_buffer_offset = 0;
+
+void seader_send_t1_chunk(SeaderUartBridge* seader_uart, uint8_t PCB, uint8_t* chunk, size_t len) {
+    uint8_t* frame = malloc(3 + len + 1);
+    uint8_t frame_len = 0;
+
+    frame[0] = NAD;
+    frame[1] = PCB;
+    frame[2] = len;
+    frame_len = 3;
+
+    if(len > 0) {
+        memcpy(frame + frame_len, chunk, len);
+        frame_len += len;
+    }
+
+    frame_len = seader_add_lrc(frame, frame_len);
+
+    seader_ccid_XfrBlock(seader_uart, frame, frame_len);
+    free(frame);
+}
+
+void seader_send_t1(SeaderUartBridge* seader_uart, uint8_t* apdu, size_t len) {
+    if(len > IFSC_VALUE) {
+        if(seader_t_1_tx_buffer == NULL) {
+            seader_t_1_tx_buffer = bit_buffer_alloc(768);
+            bit_buffer_copy_bytes(seader_t_1_tx_buffer, apdu, len);
+        }
+        size_t remaining =
+            (bit_buffer_get_size_bytes(seader_t_1_tx_buffer) - seader_t_1_tx_buffer_offset);
+        size_t copy_length = remaining > IFSC_VALUE ? IFSC_VALUE : remaining;
+
+        uint8_t* chunk =
+            (uint8_t*)bit_buffer_get_data(seader_t_1_tx_buffer) + seader_t_1_tx_buffer_offset;
+
+        if(remaining > IFSC_VALUE) {
+            uint8_t PCB = seader_next_dpcb() | MORE_BIT;
+            seader_send_t1_chunk(seader_uart, PCB, chunk, copy_length);
+        } else {
+            uint8_t PCB = seader_next_dpcb();
+            seader_send_t1_chunk(seader_uart, PCB, chunk, copy_length);
+        }
+
+        seader_t_1_tx_buffer_offset += copy_length;
+        if(seader_t_1_tx_buffer_offset >= bit_buffer_get_size_bytes(seader_t_1_tx_buffer)) {
+            bit_buffer_free(seader_t_1_tx_buffer);
+            seader_t_1_tx_buffer = NULL;
+            seader_t_1_tx_buffer_offset = 0;
+        }
+        return;
+    }
+
+    seader_send_t1_chunk(seader_uart, seader_next_dpcb(), apdu, len);
+}
+
+BitBuffer* seader_t_1_rx_buffer;
+
+bool seader_recv_t1(Seader* seader, CCID_Message* message) {
+    // remove/validate NAD, PCB, LEN, LRC
+    if(message->dwLength < 4) {
+        FURI_LOG_W(TAG, "Invalid T=1 frame: too short");
+        return false;
+    }
+    //uint8_t NAD = message->payload[0];
+    uint8_t rPCB = message->payload[1];
+    uint8_t LEN = message->payload[2];
+    //uint8_t LRC = message->payload[3 + LEN];
+    //FURI_LOG_D(TAG, "NAD: %02X, rPCB: %02X, LEN: %02X, LRC: %02X", NAD, rPCB, LEN, LRC);
+
+    if(rPCB == 0xE1) {
+        // S(IFS response)
+        seader_worker_send_version(seader);
+        SeaderWorker* seader_worker = seader->worker;
+        if(seader_worker->callback) {
+            seader_worker->callback(SeaderWorkerEventSamPresent, seader_worker->context);
+        }
+        return false;
+    }
+
+    if(rPCB == cPCB) {
+        seader_next_cpcb();
+        if(seader_t_1_rx_buffer != NULL) {
+            bit_buffer_append_bytes(seader_t_1_rx_buffer, message->payload + 3, LEN);
+
+            // TODO: validate LRC
+
+            seader_worker_process_sam_message(
+                seader,
+                (uint8_t*)bit_buffer_get_data(seader_t_1_rx_buffer),
+                bit_buffer_get_size_bytes(seader_t_1_rx_buffer));
+
+            bit_buffer_free(seader_t_1_rx_buffer);
+            seader_t_1_rx_buffer = NULL;
+            return true;
+        }
+
+        if(seader_validate_lrc(message->payload, message->dwLength) == false) {
+            return false;
+        }
+
+        // Skip NAD, PCB, LEN
+        message->payload = message->payload + 3;
+        message->dwLength = LEN;
+
+        if(message->dwLength == 0) {
+            //FURI_LOG_D(TAG, "Received T=1 frame with no data");
+            return true;
+        }
+        return seader_worker_process_sam_message(seader, message->payload, message->dwLength);
+    } else if(rPCB == (cPCB | MORE_BIT)) {
+        //FURI_LOG_D(TAG, "Received T=1 frame with more bit set");
+        if(seader_t_1_rx_buffer == NULL) {
+            seader_t_1_rx_buffer = bit_buffer_alloc(512);
+        }
+        bit_buffer_append_bytes(seader_t_1_rx_buffer, message->payload + 3, LEN);
+        seader_t_1_send_ack(seader);
+        return false;
+    } else if((rPCB & R_BLOCK) == R_BLOCK) {
+        uint8_t R_SEQ = (rPCB & R_SEQUENCE_NUMBER_MASK) >> 4;
+        uint8_t I_SEQ = (dPCB ^ 0x40) >> 6;
+        if(R_SEQ != I_SEQ) {
+            /*
+            FURI_LOG_D(
+                TAG,
+                "Received R-Block: Incorrect sequence.  Expected: %02X, Received: %02X",
+                I_SEQ,
+                R_SEQ);
+
+            */
+            // When this happens, the flipper freezes if it is doing NFC and my attempts to do events to stop that have failed
+            return false;
+        }
+
+        if(seader_t_1_tx_buffer != NULL) {
+            // Send more data, re-using the buffer to trigger the code path that sends the next block
+            SeaderWorker* seader_worker = seader->worker;
+            SeaderUartBridge* seader_uart = seader_worker->uart;
+            seader_send_t1(
+                seader_uart,
+                (uint8_t*)bit_buffer_get_data(seader_t_1_tx_buffer),
+                bit_buffer_get_size_bytes(seader_t_1_tx_buffer));
+            return false;
+        }
+    } else {
+        FURI_LOG_W(
+            TAG, "Invalid T=1 frame: PCB mismatch.  Expected: %02X, Received: %02X", cPCB, rPCB);
+    }
+
+    return false;
+}

+ 9 - 0
seader/t_1.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "ccid.h"
+
+typedef struct CCID_Message CCID_Message;
+
+void seader_send_t1(SeaderUartBridge* seader_uart, uint8_t* apdu, size_t len);
+bool seader_recv_t1(Seader* seader, CCID_Message* message);
+void seader_t_1_set_IFSD(Seader* seader);

+ 4 - 1
seader/uart.c

@@ -98,7 +98,7 @@ int32_t seader_uart_worker(void* context) {
     seader_uart->tx_sem = furi_semaphore_alloc(1, 1);
 
     seader_uart->tx_thread =
-        furi_thread_alloc_ex("SeaderUartTxWorker", 3 * 1024, seader_uart_tx_thread, seader);
+        furi_thread_alloc_ex("SeaderUartTxWorker", 1.5 * 1024, seader_uart_tx_thread, seader);
 
     seader_uart_serial_init(seader_uart, seader_uart->cfg.uart_ch);
     seader_uart_set_baudrate(seader_uart, seader_uart->cfg.baudrate);
@@ -159,6 +159,9 @@ int32_t seader_uart_worker(void* context) {
 SeaderUartBridge* seader_uart_enable(SeaderUartConfig* cfg, Seader* seader) {
     SeaderUartBridge* seader_uart = malloc(sizeof(SeaderUartBridge));
 
+    seader_uart->T = 0;
+    seader_uart->T = (seader->is_debug_enabled ? 1 : 0);
+
     memcpy(&(seader_uart->cfg_new), cfg, sizeof(SeaderUartConfig));
 
     seader_uart->thread =