Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX 2024-08-02 11:09:56 +03:00
commit 856fe752de
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
28 changed files with 656 additions and 303 deletions

View file

@ -6,10 +6,13 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/gui.h>
#include "iso7816_callbacks.h"
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_response.h"
#include "iso7816/iso7816_handler.h"
#include "iso7816/iso7816_t0_apdu.h"
#include "iso7816/iso7816_atr.h"
#include "iso7816/iso7816_response.h"
#include "ccid_test_app_commands.h"
typedef enum {
EventTypeInput,
@ -20,6 +23,7 @@ typedef struct {
ViewPort* view_port;
FuriMessageQueue* event_queue;
FuriHalUsbCcidConfig ccid_cfg;
Iso7816Handler* iso7816_handler;
} CcidTestApp;
typedef struct {
@ -63,6 +67,15 @@ uint32_t ccid_test_exit(void* context) {
CcidTestApp* ccid_test_app_alloc(void) {
CcidTestApp* app = malloc(sizeof(CcidTestApp));
//setup CCID USB
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
app->ccid_cfg.vid = 0x076B;
app->ccid_cfg.pid = 0x3A21;
app->iso7816_handler = iso7816_handler_alloc();
app->iso7816_handler->iso7816_answer_to_reset = iso7816_answer_to_reset;
app->iso7816_handler->iso7816_process_command = iso7816_process_command;
// Gui
app->gui = furi_record_open(RECORD_GUI);
@ -92,174 +105,26 @@ void ccid_test_app_free(CcidTestApp* app) {
furi_record_close(RECORD_GUI);
app->gui = NULL;
free(app->iso7816_handler);
// Free rest
free(app);
}
void ccid_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
UNUSED(context);
iso7816_icc_power_on_callback(atrBuffer, atrlen);
}
void ccid_xfr_datablock_callback(
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
void* context) {
UNUSED(context);
iso7816_xfr_datablock_callback(
pcToReaderDataBlock, pcToReaderDataBlockLen, readerToPcDataBlock, readerToPcDataBlockLen);
}
static const CcidCallbacks ccid_cb = {
ccid_icc_power_on_callback,
ccid_xfr_datablock_callback,
};
//Instruction 1: returns an OK response unconditionally
//APDU example: 0x01:0x01:0x00:0x00
//response: SW1=0x90, SW2=0x00
void handle_instruction_01(ISO7816_Response_APDU* responseAPDU) {
responseAPDU->DataLen = 0;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
}
//Instruction 2: expect command with no body, replies wit with a body with two bytes
//APDU example: 0x01:0x02:0x00:0x00:0x02
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
void handle_instruction_02(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
responseAPDU->Data[0] = 0x62;
responseAPDU->Data[1] = 0x63;
responseAPDU->DataLen = 2;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
//response SW1=0x90, SW2=0x00
void handle_instruction_03(
uint8_t p1,
uint8_t p2,
uint16_t lc,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc == 2) {
responseAPDU->DataLen = 0;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
void handle_instruction_04(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
const uint8_t* commandApduDataBuffer,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
for(uint16_t i = 0; i < lc; i++) {
responseAPDU->Data[i] = commandApduDataBuffer[i];
}
responseAPDU->DataLen = lc;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
void iso7816_answer_to_reset(Iso7816Atr* atr) {
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
atr->TS = 0x3B;
atr->T0 = 0x00;
}
void iso7816_process_command(
const ISO7816_Command_APDU* commandAPDU,
ISO7816_Response_APDU* responseAPDU) {
//example 1: sends a command with no body, receives a response with no body
//sends APDU 0x01:0x01:0x00:0x00
//receives SW1=0x90, SW2=0x00
if(commandAPDU->CLA == 0x01) {
switch(commandAPDU->INS) {
case 0x01:
handle_instruction_01(responseAPDU);
break;
case 0x02:
handle_instruction_02(
commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, commandAPDU->Le, responseAPDU);
break;
case 0x03:
handle_instruction_03(commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, responseAPDU);
break;
case 0x04:
handle_instruction_04(
commandAPDU->P1,
commandAPDU->P2,
commandAPDU->Lc,
commandAPDU->Le,
commandAPDU->Data,
responseAPDU);
break;
default:
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
}
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
}
}
static const Iso7816Callbacks iso87816_cb = {
iso7816_answer_to_reset,
iso7816_process_command,
};
int32_t ccid_test_app(void* p) {
UNUSED(p);
//setup view
CcidTestApp* app = ccid_test_app_alloc();
//setup CCID USB
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
app->ccid_cfg.vid = 0x076B;
app->ccid_cfg.pid = 0x3A21;
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
furi_hal_usb_ccid_set_callbacks((CcidCallbacks*)&ccid_cb, NULL);
furi_hal_usb_ccid_set_callbacks(
(CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler);
furi_hal_usb_ccid_insert_smartcard();
iso7816_set_callbacks((Iso7816Callbacks*)&iso87816_cb);
//handle button events
CcidTestAppEvent event;
while(1) {
@ -280,8 +145,6 @@ int32_t ccid_test_app(void* p) {
furi_hal_usb_ccid_set_callbacks(NULL, NULL);
furi_hal_usb_set_config(usb_mode_prev, NULL);
iso7816_set_callbacks(NULL);
//teardown view
ccid_test_app_free(app);
return 0;

View file

@ -0,0 +1,123 @@
#include "iso7816/iso7816_t0_apdu.h"
#include "iso7816/iso7816_response.h"
//Instruction 1: returns an OK response unconditionally
//APDU example: 0x01:0x01:0x00:0x00
//response: SW1=0x90, SW2=0x00
void handle_instruction_01(ISO7816_Response_APDU* response_apdu) {
response_apdu->DataLen = 0;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
}
//Instruction 2: expect command with no body, replies wit with a body with two bytes
//APDU example: 0x01:0x02:0x00:0x00:0x02
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
void handle_instruction_02(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
response_apdu->Data[0] = 0x62;
response_apdu->Data[1] = 0x63;
response_apdu->DataLen = 2;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
//response SW1=0x90, SW2=0x00
void handle_instruction_03(
uint8_t p1,
uint8_t p2,
uint16_t lc,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc == 2) {
response_apdu->DataLen = 0;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
void handle_instruction_04(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
const uint8_t* command_apdu_data_buffer,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
for(uint16_t i = 0; i < lc; i++) {
response_apdu->Data[i] = command_apdu_data_buffer[i];
}
response_apdu->DataLen = lc;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
void iso7816_answer_to_reset(Iso7816Atr* atr) {
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
atr->TS = 0x3B;
atr->T0 = 0x00;
}
void iso7816_process_command(
const ISO7816_Command_APDU* command_apdu,
ISO7816_Response_APDU* response_apdu) {
//example 1: sends a command with no body, receives a response with no body
//sends APDU 0x01:0x01:0x00:0x00
//receives SW1=0x90, SW2=0x00
if(command_apdu->CLA == 0x01) {
switch(command_apdu->INS) {
case 0x01:
handle_instruction_01(response_apdu);
break;
case 0x02:
handle_instruction_02(
command_apdu->P1,
command_apdu->P2,
command_apdu->Lc,
command_apdu->Le,
response_apdu);
break;
case 0x03:
handle_instruction_03(
command_apdu->P1, command_apdu->P2, command_apdu->Lc, response_apdu);
break;
case 0x04:
handle_instruction_04(
command_apdu->P1,
command_apdu->P2,
command_apdu->Lc,
command_apdu->Le,
command_apdu->Data,
response_apdu);
break;
default:
iso7816_set_response(response_apdu, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
}
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
}
}

View file

@ -0,0 +1,7 @@
#include "iso7816/iso7816_t0_apdu.h"
void iso7816_answer_to_reset(Iso7816Atr* atr);
void iso7816_process_command(
const ISO7816_Command_APDU* command_apdu,
ISO7816_Response_APDU* response_apdu);

View file

@ -0,0 +1,68 @@
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
#include <stdint.h>
#include <stddef.h>
#include <furi.h>
#include <furi_hal.h>
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_handler.h"
#include "iso7816_response.h"
void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) {
furi_check(context);
Iso7816Handler* handler = (Iso7816Handler*)context;
Iso7816Atr iso7816_atr;
handler->iso7816_answer_to_reset(&iso7816_atr);
furi_assert(iso7816_atr.T0 == 0x00);
uint8_t atr_buffer[2] = {iso7816_atr.TS, iso7816_atr.T0};
*atr_data_len = 2;
memcpy(atr_data, atr_buffer, sizeof(uint8_t) * (*atr_data_len));
}
//dataBlock points to the buffer
//dataBlockLen tells reader how nany bytes should be read
void iso7816_xfr_datablock_callback(
const uint8_t* pc_to_reader_datablock,
uint32_t pc_to_reader_datablock_len,
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len,
void* context) {
furi_check(context);
Iso7816Handler* handler = (Iso7816Handler*)context;
ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer;
ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer;
uint8_t result = iso7816_read_command_apdu(
command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len);
if(result == ISO7816_READ_COMMAND_APDU_OK) {
handler->iso7816_process_command(command_apdu, response_apdu);
furi_assert(response_apdu->DataLen < CCID_SHORT_APDU_SIZE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
iso7816_write_response_apdu(response_apdu, reader_to_pc_datablock, reader_to_pc_datablock_len);
}
Iso7816Handler* iso7816_handler_alloc() {
Iso7816Handler* handler = malloc(sizeof(Iso7816Handler));
handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback;
handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback;
return handler;
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
#include "iso7816_t0_apdu.h"
typedef struct {
CcidCallbacks ccid_callbacks;
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
void (*iso7816_process_command)(
const ISO7816_Command_APDU* command,
ISO7816_Response_APDU* response);
uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
} Iso7816Handler;
Iso7816Handler* iso7816_handler_alloc();

View file

@ -61,24 +61,25 @@ uint8_t iso7816_read_command_apdu(
//data buffer contains the whole APU response (response + trailer (SW1+SW2))
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen) {
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len) {
uint32_t responseDataBufferIndex = 0;
//response body
if(response->DataLen > 0) {
while(responseDataBufferIndex < response->DataLen) {
readerToPcDataBlock[responseDataBufferIndex] = response->Data[responseDataBufferIndex];
reader_to_pc_datablock[responseDataBufferIndex] =
response->Data[responseDataBufferIndex];
responseDataBufferIndex++;
}
}
//trailer
readerToPcDataBlock[responseDataBufferIndex] = response->SW1;
reader_to_pc_datablock[responseDataBufferIndex] = response->SW1;
responseDataBufferIndex++;
readerToPcDataBlock[responseDataBufferIndex] = response->SW2;
reader_to_pc_datablock[responseDataBufferIndex] = response->SW2;
responseDataBufferIndex++;
*readerToPcDataBlockLen = responseDataBufferIndex;
*reader_to_pc_datablock_len = responseDataBufferIndex;
}

View file

@ -31,12 +31,11 @@ typedef struct {
uint8_t Data[0];
} FURI_PACKED ISO7816_Response_APDU;
void iso7816_answer_to_reset(Iso7816Atr* atr);
uint8_t iso7816_read_command_apdu(
ISO7816_Command_APDU* command,
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen);
const uint8_t* pc_to_reader_datablock,
uint32_t pc_to_reader_datablock_len);
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen);
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len);

View file

@ -1,65 +0,0 @@
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
#include <stdint.h>
#include <stddef.h>
#include <furi.h>
#include <furi_hal.h>
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_callbacks.h"
#include "iso7816_response.h"
static Iso7816Callbacks* callbacks = NULL;
static uint8_t commandApduBuffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
static uint8_t responseApduBuffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
void iso7816_set_callbacks(Iso7816Callbacks* cb) {
callbacks = cb;
}
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen) {
Iso7816Atr atr;
callbacks->iso7816_answer_to_reset(&atr);
furi_assert(atr.T0 == 0x00);
uint8_t AtrBuffer[2] = {atr.TS, atr.T0};
*atrlen = 2;
memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
}
//dataBlock points to the buffer
//dataBlockLen tells reader how nany bytes should be read
void iso7816_xfr_datablock_callback(
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen) {
ISO7816_Response_APDU* responseAPDU = (ISO7816_Response_APDU*)&responseApduBuffer;
if(callbacks != NULL) {
ISO7816_Command_APDU* commandAPDU = (ISO7816_Command_APDU*)&commandApduBuffer;
uint8_t result =
iso7816_read_command_apdu(commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
if(result == ISO7816_READ_COMMAND_APDU_OK) {
callbacks->iso7816_process_command(commandAPDU, responseAPDU);
furi_assert(responseAPDU->DataLen < CCID_SHORT_APDU_SIZE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INTERNAL_EXCEPTION);
}
iso7816_write_response_apdu(responseAPDU, readerToPcDataBlock, readerToPcDataBlockLen);
}

View file

@ -1,21 +0,0 @@
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
#include "iso7816_t0_apdu.h"
typedef struct {
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
void (*iso7816_process_command)(
const ISO7816_Command_APDU* command,
ISO7816_Response_APDU* response);
} Iso7816Callbacks;
void iso7816_set_callbacks(Iso7816Callbacks* cb);
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen);
void iso7816_xfr_datablock_callback(
const uint8_t* dataBlock,
uint32_t dataBlockLen,
uint8_t* responseDataBlock,
uint32_t* responseDataBlockLen);

View file

@ -0,0 +1,85 @@
#include "nfc_detected_protocols.h"
#include <furi.h>
struct NfcDetectedProtocols {
uint32_t protocols_detected_num;
NfcProtocol protocols_detected[NfcProtocolNum];
uint32_t selected_idx;
};
NfcDetectedProtocols* nfc_detected_protocols_alloc(void) {
NfcDetectedProtocols* instance = malloc(sizeof(NfcDetectedProtocols));
instance->protocols_detected_num = 0;
instance->selected_idx = 0;
return instance;
}
void nfc_detected_protocols_free(NfcDetectedProtocols* instance) {
furi_assert(instance);
free(instance);
}
void nfc_detected_protocols_reset(NfcDetectedProtocols* instance) {
furi_assert(instance);
instance->protocols_detected_num = 0;
memset(instance->protocols_detected, 0, sizeof(instance->protocols_detected));
instance->selected_idx = 0;
}
void nfc_detected_protocols_select(NfcDetectedProtocols* instance, uint32_t idx) {
furi_assert(instance);
instance->selected_idx = idx;
}
void nfc_detected_protocols_set(
NfcDetectedProtocols* instance,
const NfcProtocol* types,
uint32_t count) {
furi_assert(instance);
furi_assert(types);
furi_assert(count < NfcProtocolNum);
memcpy(instance->protocols_detected, types, count);
instance->protocols_detected_num = count;
instance->selected_idx = 0;
}
uint32_t nfc_detected_protocols_get_num(NfcDetectedProtocols* instance) {
furi_assert(instance);
return instance->protocols_detected_num;
}
NfcProtocol nfc_detected_protocols_get_protocol(NfcDetectedProtocols* instance, uint32_t idx) {
furi_assert(instance);
furi_assert(idx < instance->protocols_detected_num);
return instance->protocols_detected[idx];
}
void nfc_detected_protocols_fill_all_protocols(NfcDetectedProtocols* instance) {
furi_assert(instance);
instance->protocols_detected_num = NfcProtocolNum;
for(uint32_t i = 0; i < NfcProtocolNum; i++) {
instance->protocols_detected[i] = i;
}
}
NfcProtocol nfc_detected_protocols_get_selected(NfcDetectedProtocols* instance) {
furi_assert(instance);
return instance->protocols_detected[instance->selected_idx];
}
uint32_t nfc_detected_protocols_get_selected_idx(NfcDetectedProtocols* instance) {
furi_assert(instance);
return instance->selected_idx;
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <stdint.h>
#include <nfc/protocols/nfc_protocol.h>
typedef struct NfcDetectedProtocols NfcDetectedProtocols;
NfcDetectedProtocols* nfc_detected_protocols_alloc(void);
void nfc_detected_protocols_free(NfcDetectedProtocols* instance);
void nfc_detected_protocols_reset(NfcDetectedProtocols* instance);
void nfc_detected_protocols_select(NfcDetectedProtocols* instance, uint32_t idx);
void nfc_detected_protocols_set(
NfcDetectedProtocols* instance,
const NfcProtocol* types,
uint32_t count);
uint32_t nfc_detected_protocols_get_num(NfcDetectedProtocols* instance);
NfcProtocol nfc_detected_protocols_get_protocol(NfcDetectedProtocols* instance, uint32_t idx);
void nfc_detected_protocols_fill_all_protocols(NfcDetectedProtocols* instance);
NfcProtocol nfc_detected_protocols_get_selected(NfcDetectedProtocols* instance);
uint32_t nfc_detected_protocols_get_selected_idx(NfcDetectedProtocols* instance);

View file

@ -150,8 +150,7 @@ static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) {
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
const NfcProtocol protocol = nfc_detected_protocols_get_selected(instance->detected_protocols);
instance->poller = nfc_poller_alloc(instance->nfc, protocol);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
@ -186,7 +185,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
consumed = true;
} else {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
nfc_detected_protocols_get_selected(instance->detected_protocols);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.event == NfcCustomEventPollerFailure) {
@ -199,7 +198,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
consumed = true;
} else if(event.event == NfcCustomEventCardDetected) {
const NfcProtocol protocol =
instance->protocols_detected[instance->protocols_detected_selected_idx];
nfc_detected_protocols_get_selected(instance->detected_protocols);
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
}
} else if(event.type == SceneManagerEventTypeBack) {

View file

@ -50,6 +50,7 @@ NfcApp* nfc_app_alloc(void) {
instance->nfc = nfc_alloc();
instance->detected_protocols = nfc_detected_protocols_alloc();
instance->felica_auth = felica_auth_alloc();
instance->mf_ul_auth = mf_ultralight_auth_alloc();
instance->slix_unlock = slix_unlock_alloc();
@ -142,6 +143,7 @@ void nfc_app_free(NfcApp* instance) {
nfc_free(instance->nfc);
nfc_detected_protocols_free(instance->detected_protocols);
felica_auth_free(instance->felica_auth);
mf_ultralight_auth_free(instance->mf_ul_auth);
slix_unlock_free(instance->slix_unlock);
@ -433,23 +435,6 @@ void nfc_show_loading_popup(void* context, bool show) {
}
}
void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count) {
furi_assert(instance);
furi_assert(types);
furi_assert(count < NfcProtocolNum);
memcpy(instance->protocols_detected, types, count);
instance->protocols_detected_num = count;
instance->protocols_detected_selected_idx = 0;
}
void nfc_app_reset_detected_protocols(NfcApp* instance) {
furi_assert(instance);
instance->protocols_detected_selected_idx = 0;
instance->protocols_detected_num = 0;
}
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) {
furi_assert(instance);
furi_assert(string);

View file

@ -26,6 +26,7 @@
#include "views/dict_attack.h"
#include <nfc/scenes/nfc_scene.h>
#include "helpers/nfc_detected_protocols.h"
#include "helpers/nfc_custom_event.h"
#include "helpers/mf_ultralight_auth.h"
#include "helpers/mf_user_dict.h"
@ -107,9 +108,7 @@ struct NfcApp {
FuriString* text_box_store;
uint8_t byte_input_store[NFC_BYTE_INPUT_STORE_SIZE];
uint32_t protocols_detected_num;
NfcProtocol protocols_detected[NfcProtocolNum];
uint32_t protocols_detected_selected_idx;
NfcDetectedProtocols* detected_protocols;
RpcAppSystem* rpc_ctx;
NfcRpcState rpc_state;
@ -194,8 +193,4 @@ bool nfc_save_file(NfcApp* instance, FuriString* path);
void nfc_make_app_folder(NfcApp* instance);
void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
void nfc_app_reset_detected_protocols(NfcApp* instance);
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);

View file

@ -229,7 +229,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
}
furi_string_printf(
parsed_data, "\e#Plantain\nNo.: %llu?\nBalance:%lu\n", card_number, balance);
parsed_data, "\e#Plantain\nNo.: %lluX\nBalance: %lu\n", card_number, balance);
parsed = true;
} while(false);

View file

@ -157,7 +157,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_string_printf(
parsed_data,
"\e#Troika+Plantain\nPN: %llu?\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
"\e#Troika+Plantain\nPN: %lluX\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
card_number,
balance,
troika_number,

View file

@ -7,7 +7,8 @@ void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) {
NfcApp* instance = context;
if(event.type == NfcScannerEventTypeDetected) {
nfc_app_set_detected_protocols(instance, event.data.protocols, event.data.protocol_num);
nfc_detected_protocols_set(
instance->detected_protocols, event.data.protocols, event.data.protocol_num);
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit);
}
}
@ -23,7 +24,7 @@ void nfc_scene_detect_on_enter(void* context) {
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
nfc_app_reset_detected_protocols(instance);
nfc_detected_protocols_reset(instance->detected_protocols);
instance->scanner = nfc_scanner_alloc(instance->nfc);
nfc_scanner_start(instance->scanner, nfc_scene_detect_scan_callback, instance);
@ -37,7 +38,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventWorkerExit) {
if(instance->protocols_detected_num > 1) {
if(nfc_detected_protocols_get_num(instance->detected_protocols) > 1) {
notification_message(instance->notifications, &sequence_single_vibro);
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
} else {

View file

@ -57,7 +57,8 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultRight) {
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
nfc_detected_protocols_set(
nfc->detected_protocols, mfu_protocol, COUNT_OF(mfu_protocol));
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
dolphin_deed(DolphinDeedNfcRead);
consumed = true;
@ -77,7 +78,8 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultCenter) {
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
nfc_detected_protocols_set(
nfc->detected_protocols, mfu_protocol, COUNT_OF(mfu_protocol));
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
dolphin_deed(DolphinDeedNfcRead);
consumed = true;

View file

@ -14,21 +14,19 @@ void nfc_scene_select_protocol_on_enter(void* context) {
const char* prefix;
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneExtraActions)) {
prefix = "Read";
instance->protocols_detected_num = NfcProtocolNum;
for(uint32_t i = 0; i < NfcProtocolNum; i++) {
instance->protocols_detected[i] = i;
}
nfc_detected_protocols_fill_all_protocols(instance->detected_protocols);
} else {
prefix = "Read as";
submenu_set_header(submenu, "Multi-protocol card");
}
for(uint32_t i = 0; i < instance->protocols_detected_num; i++) {
for(uint32_t i = 0; i < nfc_detected_protocols_get_num(instance->detected_protocols); i++) {
furi_string_printf(
temp_str,
"%s %s",
prefix,
nfc_device_get_protocol_name(instance->protocols_detected[i]));
nfc_device_get_protocol_name(
nfc_detected_protocols_get_protocol(instance->detected_protocols, i)));
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
submenu_add_item(
@ -40,9 +38,8 @@ void nfc_scene_select_protocol_on_enter(void* context) {
}
furi_string_free(temp_str);
const uint32_t state =
scene_manager_get_scene_state(instance->scene_manager, NfcSceneSelectProtocol);
submenu_set_selected_item(submenu, state);
submenu_set_selected_item(
submenu, nfc_detected_protocols_get_selected_idx(instance->detected_protocols));
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
}
@ -52,10 +49,8 @@ bool nfc_scene_select_protocol_on_event(void* context, SceneManagerEvent event)
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
instance->protocols_detected_selected_idx = event.event;
nfc_detected_protocols_select(instance->detected_protocols, event.event);
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
scene_manager_set_scene_state(
instance->scene_manager, NfcSceneSelectProtocol, event.event);
consumed = true;
} else if(event.type == SceneManagerEventTypeBack) {
if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) {

View file

@ -25,7 +25,7 @@ void nfc_scene_start_on_enter(void* context) {
nfc_device_clear(nfc->nfc_device);
iso14443_3a_reset(nfc->iso14443_3a_edit_data);
// Reset detected protocols list
nfc_app_reset_detected_protocols(nfc);
nfc_detected_protocols_reset(nfc->detected_protocols);
submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc);
submenu_add_item(

View file

@ -47,3 +47,4 @@ The file stores a single RFID key of the type defined by the `Key type` paramete
| PAC/Stanley | PAC/Stanley |
| Keri | Keri |
| Gallagher | Gallagher |
| GProxII | Guardall GProx II |

View file

@ -19,6 +19,7 @@
#include "protocol_gallagher.h"
#include "protocol_nexwatch.h"
#include "protocol_securakey.h"
#include "protocol_gproxii.h"
const ProtocolBase* lfrfid_protocols[] = {
[LFRFIDProtocolEM4100] = &protocol_em4100,
@ -43,4 +44,5 @@ const ProtocolBase* lfrfid_protocols[] = {
[LFRFIDProtocolGallagher] = &protocol_gallagher,
[LFRFIDProtocolNexwatch] = &protocol_nexwatch,
[LFRFIDProtocolSecurakey] = &protocol_securakey,
[LFRFIDProtocolGProxII] = &protocol_gproxii,
};

View file

@ -30,6 +30,7 @@ typedef enum {
LFRFIDProtocolGallagher,
LFRFIDProtocolNexwatch,
LFRFIDProtocolSecurakey,
LFRFIDProtocolGProxII,
LFRFIDProtocolMax,
} LFRFIDProtocol;

View file

@ -0,0 +1,261 @@
#include <furi.h>
#include "toolbox/level_duration.h"
#include "protocol_gproxii.h"
#include <toolbox/manchester_decoder.h>
#include <bit_lib/bit_lib.h>
#include "lfrfid_protocols.h"
#define GPROXII_PREAMBLE_BIT_SIZE (6)
#define GPROXII_ENCODED_BIT_SIZE (90)
#define GPROXII_ENCODED_BYTE_FULL_SIZE \
(((GPROXII_PREAMBLE_BIT_SIZE + GPROXII_ENCODED_BIT_SIZE) / 8))
#define GPROXII_DATA_SIZE (12)
#define GPROXII_SHORT_TIME (256)
#define GPROXII_LONG_TIME (512)
#define GPROXII_JITTER_TIME (120)
#define GPROXII_SHORT_TIME_LOW (GPROXII_SHORT_TIME - GPROXII_JITTER_TIME)
#define GPROXII_SHORT_TIME_HIGH (GPROXII_SHORT_TIME + GPROXII_JITTER_TIME)
#define GPROXII_LONG_TIME_LOW (GPROXII_LONG_TIME - GPROXII_JITTER_TIME)
#define GPROXII_LONG_TIME_HIGH (GPROXII_LONG_TIME + GPROXII_JITTER_TIME)
typedef struct {
bool last_short;
bool last_level;
size_t encoded_index;
uint8_t decoded_data[GPROXII_ENCODED_BYTE_FULL_SIZE];
uint8_t data[GPROXII_ENCODED_BYTE_FULL_SIZE];
} ProtocolGProxII;
ProtocolGProxII* protocol_gproxii_alloc(void) {
ProtocolGProxII* protocol = malloc(sizeof(ProtocolGProxII));
return protocol;
}
void protocol_gproxii_free(ProtocolGProxII* protocol) {
free(protocol);
}
uint8_t* protocol_gproxii_get_data(ProtocolGProxII* proto) {
return proto->data;
}
void protocol_gproxii_decoder_start(ProtocolGProxII* protocol) {
memset(protocol->data, 0, GPROXII_ENCODED_BYTE_FULL_SIZE);
protocol->last_short = false;
}
static bool protocol_gproxii_can_be_decoded(ProtocolGProxII* protocol) {
// 96 bit with 5 bit zero parity
// 0 10 20 30 40 50 60 70 80 90
// | | | | | | | | | |
// 012345 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5 6789 0 1234 5
// ------------------------------------------------------------------------------------------------------------------------------------
// 111110 0000 0 1001 0 1101 0 1111 0 1000 0 1001 0 0000 0 1001 0 0000 0 1001 0 0000 0 1001 0 0000 0 1001 0 0000 0 1000 0 0000 0 1001 0
// Remove header and reverse bytes on the remaining 72 bits
//
// 0 10 20 30 40 50 60 70
// | | | | | | | |
// 01234567 89012345 67890123 45678901 23456789 01234567 89012345 67890123 45678901
// --------------------------------------------------------------------------------
// 00001001 11011111 10001001 00001001 00001001 00001001 00001001 00001000 00001001 - Without parity
// 10010000 11111011 10010001 10010000 10010000 10010000 10010000 00010000 10010000 - Reversed
// 10010000 01101011 00000001 00000000 00000000 00000000 00000000 10000000 00000000 - XOR all bytes from 1 using byte 0
// 72 Bit Guardall/Verex/Chubb GProx II 26 bit key with 16 bit profile
// 0 10 20 30 40 50 60 70
// | | | | | | | |
// 01234567 890123 45 6789012345678901 2 34567890 1234567890123456 7 89012345678901
// --------------------------------------------------------------------------------
// XORVALUE LLLLLL DD PPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUUUUUUUUUUUU
// 10010000 011010 11 0000000100000000 0 00000000 0000000000000001 0 00000000000000 - Profile: 256 FC: 0 Card: 1
// 72 Bit Guardall/Verex/Chubb GProx II 36 bit key with 26 bit profile
// 0 10 20 30 40 50 60 70
// | | | | | | | |
// 01234567 890123 45 67890123456789012345678901 2 34567890 1234567890123456 7 8901
// --------------------------------------------------------------------------------
// XORVALUE LLLLLL DD PPPPPPPPPPPPPPPPPPPPPPPPPP E FFFFFFFF CCCCCCCCCCCCCCCC O UUUU
// 10111000 100100 10 00000001000000000000000000 1 01000000 1000100010111000 1 0000 - Profile: 262144 FC: 64 Card: 35000
// X = XOR Key, L = Message length, D = 2 bit check digits, P = Profile, E = Wiegand leading even parity
// F = Faclity code, C = Card number, O = Wiegand trailing odd parity, U = Unused bits
// Check 6 bits preamble 111110
if(bit_lib_get_bits(protocol->data, 0, 6) != 0b111110) return false;
// Check always 0 parity on every 5th bit after preamble
if(bit_lib_test_parity(protocol->data, 5, GPROXII_ENCODED_BIT_SIZE, BitLibParityAlways0, 5))
return false;
// Start GProx II decode
bit_lib_copy_bits(protocol->decoded_data, 0, GPROXII_ENCODED_BIT_SIZE, protocol->data, 6);
// Remove parity
bit_lib_remove_bit_every_nth(protocol->decoded_data, 0, GPROXII_ENCODED_BIT_SIZE, 5);
// Reverse bytes
for(int i = 0; i < 9; i++) {
protocol->decoded_data[i] = bit_lib_reverse_8_fast(protocol->decoded_data[i]);
}
// DeXOR from byte 1 using byte 0
for(int i = 1; i < 9; i++) {
protocol->decoded_data[i] = protocol->decoded_data[0] ^ protocol->decoded_data[i];
}
// Check card length is either 26 or 36
int card_len = bit_lib_get_bits(protocol->decoded_data, 8, 6);
if(card_len == 26 || card_len == 36) {
return true;
} else {
return false; // If we don't get a 26 or 36 it's not a known card type
}
}
bool protocol_gproxii_decoder_feed(ProtocolGProxII* protocol, bool level, uint32_t duration) {
UNUSED(level);
bool pushed = false;
// Bi-Phase Manchester decoding inverse. Short = 1, Long = 0
if(duration >= GPROXII_SHORT_TIME_LOW && duration <= GPROXII_SHORT_TIME_HIGH) {
if(protocol->last_short == false) {
protocol->last_short = true;
} else {
pushed = true;
bit_lib_push_bit(protocol->data, GPROXII_ENCODED_BYTE_FULL_SIZE, true);
protocol->last_short = false;
}
} else if(duration >= GPROXII_LONG_TIME_LOW && duration <= GPROXII_LONG_TIME_HIGH) {
if(protocol->last_short == false) {
pushed = true;
bit_lib_push_bit(protocol->data, GPROXII_ENCODED_BYTE_FULL_SIZE, false);
} else {
// reset
protocol->last_short = false;
}
} else {
// reset
protocol->last_short = false;
}
if(pushed && protocol_gproxii_can_be_decoded(protocol)) {
return true;
}
return false;
}
bool protocol_gproxii_encoder_start(ProtocolGProxII* protocol) {
protocol->encoded_index = 0;
protocol->last_short = false;
protocol->last_level = false;
return true;
}
LevelDuration protocol_gproxii_encoder_yield(ProtocolGProxII* protocol) {
uint32_t duration;
protocol->last_level = !protocol->last_level;
bool bit = bit_lib_get_bit(protocol->data, protocol->encoded_index);
// Bi-Phase Manchester encoder inverted
if(bit) {
// two short pulses for 1
duration = GPROXII_SHORT_TIME / 8;
if(protocol->last_short) {
bit_lib_increment_index(protocol->encoded_index, 96);
protocol->last_short = false;
} else {
protocol->last_short = true;
}
} else {
// one long pulse for 0
duration = GPROXII_LONG_TIME / 8;
bit_lib_increment_index(protocol->encoded_index, 96);
}
return level_duration_make(protocol->last_level, duration);
}
void protocol_gproxii_render_data(ProtocolGProxII* protocol, FuriString* result) {
int xor_code = bit_lib_get_bits(protocol->decoded_data, 0, 8);
int card_len = bit_lib_get_bits(protocol->decoded_data, 8, 6);
int crc_code = bit_lib_get_bits(protocol->decoded_data, 14, 2);
if(card_len == 26) { // 26 Bit card
// Print FC, Card and Length
furi_string_cat_printf(
result,
"FC: %hhu Card: %hu LEN: %hhu\n",
bit_lib_get_bits(protocol->decoded_data, 33, 8),
bit_lib_get_bits_16(protocol->decoded_data, 41, 16),
card_len);
// XOR Key, CRC and Profile
furi_string_cat_printf(
result,
"XOR: %hhu CRC: %hhu P: %04hX",
xor_code,
crc_code,
bit_lib_get_bits_16(protocol->decoded_data, 16, 16));
} else if(card_len == 36) { // 36 Bit card
// Print FC, Card and Length
furi_string_cat_printf(
result,
"FC: %hhu Card: %hu LEN: %hhu\n",
bit_lib_get_bits(protocol->decoded_data, 43, 8),
bit_lib_get_bits_16(protocol->decoded_data, 51, 16),
card_len);
// XOR Key, CRC and Profile
furi_string_cat_printf(
result,
"XOR: %hhu CRC: %hhu P: %06lX",
xor_code,
crc_code,
bit_lib_get_bits_32(protocol->decoded_data, 16, 26));
} else {
furi_string_cat_printf(result, "Read Error\n");
}
}
bool protocol_gproxii_write_data(ProtocolGProxII* protocol, void* data) {
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
bool result = false;
if(request->write_type == LFRFIDWriteTypeT5577) {
request->t5577.block[0] = LFRFID_T5577_MODULATION_BIPHASE | LFRFID_T5577_BITRATE_RF_64 |
(3 << LFRFID_T5577_MAXBLOCK_SHIFT);
request->t5577.block[1] = bit_lib_get_bits_32(protocol->data, 0, 32);
request->t5577.block[2] = bit_lib_get_bits_32(protocol->data, 32, 32);
request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32);
request->t5577.blocks_to_write = 4;
result = true;
}
return result;
}
const ProtocolBase protocol_gproxii = {
.name = "GProxII",
.manufacturer = "Guardall",
.data_size = GPROXII_DATA_SIZE,
.features = LFRFIDFeatureASK,
.validate_count = 3,
.alloc = (ProtocolAlloc)protocol_gproxii_alloc,
.free = (ProtocolFree)protocol_gproxii_free,
.get_data = (ProtocolGetData)protocol_gproxii_get_data,
.decoder =
{
.start = (ProtocolDecoderStart)protocol_gproxii_decoder_start,
.feed = (ProtocolDecoderFeed)protocol_gproxii_decoder_feed,
},
.encoder =
{
.start = (ProtocolEncoderStart)protocol_gproxii_encoder_start,
.yield = (ProtocolEncoderYield)protocol_gproxii_encoder_yield,
},
.render_data = (ProtocolRenderData)protocol_gproxii_render_data,
.render_brief_data = (ProtocolRenderData)protocol_gproxii_render_data,
.write_data = (ProtocolWriteData)protocol_gproxii_write_data,
};

View file

@ -0,0 +1,4 @@
#pragma once
#include <toolbox/protocols/protocol.h>
extern const ProtocolBase protocol_gproxii;