From c41dd5f9f690777eefd08eaeb43db52f599ce38c Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Wed, 16 Oct 2019 09:36:37 +0200 Subject: [PATCH 1/4] fix 'hf iclass reader' * code deduplication. Use functions from iso15693.c * speedup CodeIso15693AsReader() * invert reader command coding. 0 now means 'unmodulated' ( = field on) * decode SOF only as a valid tag response in Handle15693SamplesFromTag() * complete decoding of EOF in Handle15693SamplesFromTag() * determine and write correct times to trace * FPGA-change: generate shorter frame signal to allow proper sync in StartCountSspClk() * modify StartCountSspClk() for 16bit SSC transfers * whitespace in util.c * add specific LogTrace_ISO15693() with scaled down duration. Modify cmdhflist.c accordingly. * allow 'hf 15 raw' with single byte commands * check for buffer overflow, card timeout and single SOF in 'hf 15 raw' --- armsrc/iclass.c | 324 ++++++++------------------------ armsrc/iso15693.c | 446 +++++++++++++++++++++++++-------------------- armsrc/iso15693.h | 15 +- armsrc/util.c | 153 ++++++++-------- armsrc/util.h | 2 +- client/cmdhf15.c | 31 ++-- client/cmdhflist.c | 6 - fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_reader.v | 4 +- 9 files changed, 446 insertions(+), 535 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 2533d1f9..392271bd 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -57,14 +57,17 @@ #include "usb_cdc.h" // for usb_poll_validate_length #include "fpgaloader.h" -static int timeout = 4096; - // iCLASS has a slightly different timing compared to ISO15693. According to the picopass data sheet the tag response is expected 330us after // the reader command. This is measured from end of reader EOF to first modulation of the tag's SOF which starts with a 56,64us unmodulated period. // 330us = 140 ssp_clk cycles @ 423,75kHz when simulating. // 56,64us = 24 ssp_clk_cycles -#define DELAY_ICLASS_VCD_TO_VICC_SIM 140 -#define TAG_SOF_UNMODULATED 24 +#define DELAY_ICLASS_VCD_TO_VICC_SIM (140 - 24) +// times in ssp_clk_cycles @ 3,3625MHz when acting as reader +#define DELAY_ICLASS_VICC_TO_VCD_READER DELAY_ISO15693_VICC_TO_VCD_READER +// times in samples @ 212kHz when acting as reader +#define ICLASS_READER_TIMEOUT_ACTALL 330 // 1558us, nominal 330us + 7slots*160us = 1450us +#define ICLASS_READER_TIMEOUT_OTHERS 80 // 380us, nominal 330us + //----------------------------------------------------------------------------- // The software UART that receives commands from the reader, and its state @@ -698,7 +701,7 @@ void RAMFUNC SnoopIClass(void) { //if (!LogTrace(NULL, 0, Uart.endTime*16 - DELAY_READER_AIR2ARM_AS_SNIFFER, 0, true)) break; uint8_t parity[MAX_PARITY_SIZE]; GetParity(Uart.output, Uart.byteCnt, parity); - LogTrace(Uart.output, Uart.byteCnt, time_start, time_stop, parity, true); + LogTrace_ISO15693(Uart.output, Uart.byteCnt, time_start*32, time_stop*32, parity, true); /* And ready to receive another command. */ Uart.state = STATE_UNSYNCD; @@ -723,7 +726,7 @@ void RAMFUNC SnoopIClass(void) { uint8_t parity[MAX_PARITY_SIZE]; GetParity(Demod.output, Demod.len, parity); - LogTrace(Demod.output, Demod.len, time_start, time_stop, parity, false); + LogTrace_ISO15693(Demod.output, Demod.len, time_start*32, time_stop*32, parity, false); // And ready to receive another response. memset(&Demod, 0, sizeof(Demod)); @@ -1230,9 +1233,9 @@ int doIClassSimulation(int simulationMode, uint8_t *reader_mac_buf) { A legit tag has about 273,4us delay between reader EOT and tag SOF. **/ if (modulated_response_size > 0) { - uint32_t response_time = reader_eof_time + DELAY_ICLASS_VCD_TO_VICC_SIM - TAG_SOF_UNMODULATED - DELAY_ARM_TO_READER_SIM; + uint32_t response_time = reader_eof_time + DELAY_ICLASS_VCD_TO_VICC_SIM; TransmitTo15693Reader(modulated_response, modulated_response_size, &response_time, 0, false); - LogTrace(trace_data, trace_data_size, response_time + DELAY_ARM_TO_READER_SIM, response_time + (modulated_response_size << 6) + DELAY_ARM_TO_READER_SIM, NULL, false); + LogTrace_ISO15693(trace_data, trace_data_size, response_time*32, response_time*32 + modulated_response_size/2, NULL, false); } } @@ -1327,214 +1330,22 @@ void SimulateIClass(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain /// THE READER CODE -//----------------------------------------------------------------------------- -// Transmit the command (to the tag) that was placed in ToSend[]. -//----------------------------------------------------------------------------- -static void TransmitIClassCommand(const uint8_t *cmd, int len, int *samples, int *wait) { - int c; - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_READER_MOD); - AT91C_BASE_SSC->SSC_THR = 0x00; - FpgaSetupSsc(FPGA_MAJOR_MODE_HF_ISO14443A); +static void ReaderTransmitIClass(uint8_t *frame, int len, uint32_t *start_time) { - if (wait) { - if (*wait < 10) *wait = 10; + CodeIso15693AsReader(frame, len); - for (c = 0; c < *wait;) { - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x00; // For exact timing! - c++; - } - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - volatile uint32_t r = AT91C_BASE_SSC->SSC_RHR; - (void)r; - } - WDT_HIT(); - } - } + TransmitTo15693Tag(ToSend, ToSendMax, start_time); - uint8_t sendbyte; - bool firstpart = true; - c = 0; - for (;;) { - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - - // DOUBLE THE SAMPLES! - if (firstpart) { - sendbyte = (cmd[c] & 0xf0) | (cmd[c] >> 4); - } else { - sendbyte = (cmd[c] & 0x0f) | (cmd[c] << 4); - c++; - } - if (sendbyte == 0xff) { - sendbyte = 0xfe; - } - AT91C_BASE_SSC->SSC_THR = sendbyte; - firstpart = !firstpart; - - if (c >= len) { - break; - } - } - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - volatile uint32_t r = AT91C_BASE_SSC->SSC_RHR; - (void)r; - } - WDT_HIT(); - } - if (samples && wait) *samples = (c + *wait) << 3; + uint32_t end_time = *start_time + 32*(8*ToSendMax-4); // substract the 4 padding bits after EOF + LogTrace_ISO15693(frame, len, *start_time*4, end_time*4, NULL, true); } -//----------------------------------------------------------------------------- -// Prepare iClass reader command to send to FPGA -//----------------------------------------------------------------------------- -void CodeIClassCommand(const uint8_t *cmd, int len) { - int i, j, k; - - ToSendReset(); - - // Start of Communication: 1 out of 4 - ToSend[++ToSendMax] = 0xf0; - ToSend[++ToSendMax] = 0x00; - ToSend[++ToSendMax] = 0x0f; - ToSend[++ToSendMax] = 0x00; - - // Modulate the bytes - for (i = 0; i < len; i++) { - uint8_t b = cmd[i]; - for (j = 0; j < 4; j++) { - for (k = 0; k < 4; k++) { - if (k == (b & 3)) { - ToSend[++ToSendMax] = 0x0f; - } else { - ToSend[++ToSendMax] = 0x00; - } - } - b >>= 2; - } - } - - // End of Communication - ToSend[++ToSendMax] = 0x00; - ToSend[++ToSendMax] = 0x00; - ToSend[++ToSendMax] = 0xf0; - ToSend[++ToSendMax] = 0x00; - - // Convert from last character reference to length - ToSendMax++; -} - -static void ReaderTransmitIClass(uint8_t *frame, int len) { - int wait = 0; - int samples = 0; - - // This is tied to other size changes - CodeIClassCommand(frame, len); - - // Select the card - TransmitIClassCommand(ToSend, ToSendMax, &samples, &wait); - if (trigger) - LED_A_ON(); - - // Store reader command in buffer - uint8_t par[MAX_PARITY_SIZE]; - GetParity(frame, len, par); - LogTrace(frame, len, rsamples, rsamples, par, true); -} - -//----------------------------------------------------------------------------- -// Wait a certain time for tag response -// If a response is captured return true -// If it takes too long return false -//----------------------------------------------------------------------------- -static int GetIClassAnswer(uint8_t *receivedResponse, int maxLen, int *samples, int *elapsed) { - //uint8_t *buffer - // buffer needs to be 512 bytes - int c; - - // Set FPGA mode to "reader listen mode", no modulation (listen - // only, since we are receiving, not transmitting). - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_READER_LISTEN); - - // Now get the answer from the card - Demod.output = receivedResponse; - Demod.len = 0; - Demod.state = DEMOD_UNSYNCD; - - uint8_t b; - if (elapsed) *elapsed = 0; - - bool skip = false; - - c = 0; - for (;;) { - WDT_HIT(); - - if (BUTTON_PRESS()) return false; - - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) { - AT91C_BASE_SSC->SSC_THR = 0x00; // To make use of exact timing of next command from reader!! - if (elapsed) (*elapsed)++; - } - if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) { - if (c < timeout) { - c++; - } else { - return false; - } - b = (uint8_t)AT91C_BASE_SSC->SSC_RHR; - skip = !skip; - if (skip) continue; - - if (ManchesterDecoding(b & 0x0f)) { - *samples = c << 3; - return true; - } - } - } -} - -static int ReaderReceiveIClass(uint8_t *receivedAnswer) { - int samples = 0; - if (!GetIClassAnswer(receivedAnswer, 160, &samples, 0)) { - return false; - } - rsamples += samples; - uint8_t parity[MAX_PARITY_SIZE]; - GetParity(receivedAnswer, Demod.len, parity); - LogTrace(receivedAnswer, Demod.len, rsamples, rsamples, parity, false); - if (samples == 0) return false; - return Demod.len; -} - -static void setupIclassReader() { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - // Reset trace buffer - set_tracing(true); - clear_trace(); - - // Setup SSC - FpgaSetupSsc(FPGA_MAJOR_MODE_HF_ISO14443A); - // Start from off (no field generated) - // Signal field is off with the appropriate LED - LED_D_OFF(); - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - SpinDelay(200); - - SetAdcMuxFor(GPIO_MUXSEL_HIPKD); - - // Now give it time to spin up. - // Signal field is on with the appropriate LED - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_READER_MOD); - SpinDelay(200); - LED_A_ON(); - -} - -static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint8_t* resp, uint8_t expected_size, uint8_t retries) { +static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint8_t* resp, size_t max_resp_size, + uint8_t expected_size, uint8_t retries, uint32_t start_time, uint32_t *eof_time) { while (retries-- > 0) { - ReaderTransmitIClass(command, cmdsize); - if (expected_size == ReaderReceiveIClass(resp)) { + ReaderTransmitIClass(command, cmdsize, &start_time); + if (expected_size == GetIso15693AnswerFromTag(resp, max_resp_size, ICLASS_READER_TIMEOUT_OTHERS, eof_time)) { return true; } } @@ -1548,12 +1359,11 @@ static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint * 1 = Got CSN * 2 = Got CSN and CC */ -static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key) { - static uint8_t act_all[] = { 0x0a }; - //static uint8_t identify[] = { 0x0c }; - static uint8_t identify[] = { 0x0c, 0x00, 0x73, 0x33 }; - static uint8_t select[] = { 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - static uint8_t readcheck_cc[]= { 0x88, 0x02 }; +static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key, uint32_t *eof_time) { + uint8_t act_all[] = { 0x0a }; + uint8_t identify[] = { 0x0c }; + uint8_t select[] = { 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t readcheck_cc[] = { 0x88, 0x02 }; if (use_credit_key) readcheck_cc[0] = 0x18; else @@ -1562,24 +1372,28 @@ static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key) { uint8_t resp[ICLASS_BUFFER_SIZE]; uint8_t read_status = 0; + uint32_t start_time = GetCountSspClk(); // Send act_all - ReaderTransmitIClass(act_all, 1); + ReaderTransmitIClass(act_all, 1, &start_time); // Card present? - if (!ReaderReceiveIClass(resp)) return read_status;//Fail - + if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_ACTALL, eof_time) < 0) return read_status;//Fail + //Send Identify - ReaderTransmitIClass(identify, 1); + start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + ReaderTransmitIClass(identify, 1, &start_time); + // FpgaDisableTracing(); // DEBUGGING //We expect a 10-byte response here, 8 byte anticollision-CSN and 2 byte CRC - uint8_t len = ReaderReceiveIClass(resp); + uint8_t len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); if (len != 10) return read_status;//Fail //Copy the Anti-collision CSN to our select-packet memcpy(&select[1], resp, 8); //Select the card - ReaderTransmitIClass(select, sizeof(select)); + start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + ReaderTransmitIClass(select, sizeof(select), &start_time); //We expect a 10-byte response here, 8 byte CSN and 2 byte CRC - len = ReaderReceiveIClass(resp); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); if (len != 10) return read_status;//Fail //Success - level 1, we got CSN @@ -1590,8 +1404,9 @@ static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key) { read_status = 1; // Card selected, now read e-purse (cc) (only 8 bytes no CRC) - ReaderTransmitIClass(readcheck_cc, sizeof(readcheck_cc)); - if (ReaderReceiveIClass(resp) == 8) { + start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + ReaderTransmitIClass(readcheck_cc, sizeof(readcheck_cc), &start_time); + if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time) == 8) { //Save CC (e-purse) in response data memcpy(card_data+8, resp, 8); read_status++; @@ -1600,8 +1415,8 @@ static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key) { return read_status; } -static uint8_t handshakeIclassTag(uint8_t *card_data) { - return handshakeIclassTag_ext(card_data, false); +static uint8_t handshakeIclassTag(uint8_t *card_data, uint32_t *eof_time) { + return handshakeIclassTag_ext(card_data, false, eof_time); } @@ -1633,8 +1448,13 @@ void ReaderIClass(uint8_t arg0) { uint8_t flagReadAA = arg0 & FLAG_ICLASS_READER_AA; set_tracing(true); - setupIclassReader(); + clear_trace(); + Iso15693InitReader(); + StartCountSspClk(); + uint32_t start_time = 0; + uint32_t eof_time = 0; + uint16_t tryCnt = 0; bool userCancelled = BUTTON_PRESS() || usb_poll_validate_length(); while (!userCancelled) { @@ -1649,29 +1469,31 @@ void ReaderIClass(uint8_t arg0) { } WDT_HIT(); - read_status = handshakeIclassTag_ext(card_data, use_credit_key); + read_status = handshakeIclassTag_ext(card_data, use_credit_key, &eof_time); if (read_status == 0) continue; if (read_status == 1) result_status = FLAG_ICLASS_READER_CSN; if (read_status == 2) result_status = FLAG_ICLASS_READER_CSN | FLAG_ICLASS_READER_CC; + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; // handshakeIclass returns CSN|CC, but the actual block // layout is CSN|CONFIG|CC, so here we reorder the data, // moving CC forward 8 bytes memcpy(card_data+16, card_data+8, 8); //Read block 1, config if (flagReadConfig) { - if (sendCmdGetResponseWithRetries(readConf, sizeof(readConf), resp, 10, 10)) { + if (sendCmdGetResponseWithRetries(readConf, sizeof(readConf), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { result_status |= FLAG_ICLASS_READER_CONF; memcpy(card_data+8, resp, 8); } else { Dbprintf("Failed to dump config block"); } + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; } //Read block 5, AA if (flagReadAA) { - if (sendCmdGetResponseWithRetries(readAA, sizeof(readAA), resp, 10, 10)) { + if (sendCmdGetResponseWithRetries(readAA, sizeof(readAA), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { result_status |= FLAG_ICLASS_READER_AA; memcpy(card_data + (8*5), resp, 8); } else { @@ -1746,9 +1568,14 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { uint8_t resp[ICLASS_BUFFER_SIZE]; - setupIclassReader(); set_tracing(true); + clear_trace(); + Iso15693InitReader(); + StartCountSspClk(); + uint32_t start_time = 0; + uint32_t eof_time = 0; + while (!BUTTON_PRESS()) { WDT_HIT(); @@ -1758,28 +1585,34 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { break; } - uint8_t read_status = handshakeIclassTag(card_data); + uint8_t read_status = handshakeIclassTag(card_data, &eof_time); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + if (read_status < 2) continue; //for now replay captured auth (as cc not updated) memcpy(check+5, MAC, 4); - if (!sendCmdGetResponseWithRetries(check, sizeof(check), resp, 4, 5)) { + if (!sendCmdGetResponseWithRetries(check, sizeof(check), resp, sizeof(resp), 4, 5, start_time, &eof_time)) { + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf("Error: Authentication Fail!"); continue; } + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; //first get configuration block (block 1) crc = block_crc_LUT[1]; read[1] = 1; read[2] = crc >> 8; read[3] = crc & 0xff; - if (!sendCmdGetResponseWithRetries(read, sizeof(read),resp, 10, 10)) { + if (!sendCmdGetResponseWithRetries(read, sizeof(read), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf("Dump config (block 1) failed"); continue; } + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; mem = resp[5]; memory.k16 = (mem & 0x80); memory.book = (mem & 0x20); @@ -1800,7 +1633,8 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { read[2] = crc >> 8; read[3] = crc & 0xff; - if (sendCmdGetResponseWithRetries(read, sizeof(read), resp, 10, 10)) { + if (sendCmdGetResponseWithRetries(read, sizeof(read), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf(" %02x: %02x %02x %02x %02x %02x %02x %02x %02x", block, resp[0], resp[1], resp[2], resp[3], resp[4], resp[5], @@ -1857,7 +1691,8 @@ void iClass_Authentication(uint8_t *MAC) { uint8_t resp[ICLASS_BUFFER_SIZE]; memcpy(check+5, MAC, 4); bool isOK; - isOK = sendCmdGetResponseWithRetries(check, sizeof(check), resp, 4, 6); + uint32_t eof_time; + isOK = sendCmdGetResponseWithRetries(check, sizeof(check), resp, sizeof(resp), 4, 6, 0, &eof_time); cmd_send(CMD_ACK,isOK, 0, 0, 0, 0); } @@ -1867,11 +1702,12 @@ static bool iClass_ReadBlock(uint8_t blockNo, uint8_t *readdata) { uint16_t rdCrc = iclass_crc16(&bl, 1); readcmd[2] = rdCrc >> 8; readcmd[3] = rdCrc & 0xff; - uint8_t resp[] = {0,0,0,0,0,0,0,0,0,0}; + uint8_t resp[10]; bool isOK = false; - + uint32_t eof_time; + //readcmd[1] = blockNo; - isOK = sendCmdGetResponseWithRetries(readcmd, sizeof(readcmd), resp, 10, 10); + isOK = sendCmdGetResponseWithRetries(readcmd, sizeof(readcmd), resp, sizeof(resp), 10, 10, 0, &eof_time); memcpy(readdata, resp, sizeof(resp)); return isOK; @@ -1929,16 +1765,18 @@ static bool iClass_WriteBlock_ext(uint8_t blockNo, uint8_t *data) { uint16_t wrCrc = iclass_crc16(wrCmd, 13); write[14] = wrCrc >> 8; write[15] = wrCrc & 0xff; - uint8_t resp[] = {0,0,0,0,0,0,0,0,0,0}; + uint8_t resp[10]; bool isOK = false; - - isOK = sendCmdGetResponseWithRetries(write, sizeof(write), resp, sizeof(resp), 10); + uint32_t eof_time = 0; + + isOK = sendCmdGetResponseWithRetries(write, sizeof(write), resp, sizeof(resp), 10, 10, 0, &eof_time); + uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; if (isOK) { //if reader responded correctly //Dbprintf("WriteResp: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",resp[0],resp[1],resp[2],resp[3],resp[4],resp[5],resp[6],resp[7],resp[8],resp[9]); if (memcmp(write+2, resp, 8)) { //if response is not equal to write values if (blockNo != 3 && blockNo != 4) { //if not programming key areas (note key blocks don't get programmed with actual key data it is xor data) //error try again - isOK = sendCmdGetResponseWithRetries(write, sizeof(write), resp, sizeof(resp), 10); + isOK = sendCmdGetResponseWithRetries(write, sizeof(write), resp, sizeof(resp), 10, 10, start_time, &eof_time); } } } diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 85af0859..fff8b370 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -64,8 +64,26 @@ #define arraylen(x) (sizeof(x)/sizeof((x)[0])) +// Delays in SSP_CLK ticks. +// SSP_CLK runs at 13,56MHz / 32 = 423.75kHz when simulating a tag +#define DELAY_READER_TO_ARM 8 +#define DELAY_ARM_TO_READER 0 +//SSP_CLK runs at 13.56MHz / 4 = 3,39MHz when acting as reader. All values should be multiples of 16 +#define DELAY_TAG_TO_ARM 32 +#define DELAY_ARM_TO_TAG 16 + static int DEBUG = 0; + +// specific LogTrace function for ISO15693: the duration needs to be scaled because otherwise it won't fit into a uint16_t +bool LogTrace_ISO15693(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_start, uint32_t timestamp_end, uint8_t *parity, bool readerToTag) { + uint32_t duration = timestamp_end - timestamp_start; + duration /= 32; + timestamp_end = timestamp_start + duration; + return LogTrace(btBytes, iLen, timestamp_start, timestamp_end, parity, readerToTag); +} + + /////////////////////////////////////////////////////////////////////// // ISO 15693 Part 2 - Air Interface // This section basically contains transmission and receiving of bits @@ -84,84 +102,37 @@ static int DEBUG = 0; // resulting data rate is 26.48 kbit/s (fc/512) // cmd ... data // n ... length of data -static void CodeIso15693AsReader(uint8_t *cmd, int n) -{ - int i, j; +void CodeIso15693AsReader(uint8_t *cmd, int n) { ToSendReset(); - // Give it a bit of slack at the beginning - for(i = 0; i < 24; i++) { - ToSendStuffBit(1); - } - // SOF for 1of4 - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); - for(i = 0; i < n; i++) { - for(j = 0; j < 8; j += 2) { - int these = (cmd[i] >> j) & 3; + ToSend[++ToSendMax] = 0x84; //10000100 + + // data + for (int i = 0; i < n; i++) { + for (int j = 0; j < 8; j += 2) { + int these = (cmd[i] >> j) & 0x03; switch(these) { case 0: - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); + ToSend[++ToSendMax] = 0x40; //01000000 break; case 1: - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); + ToSend[++ToSendMax] = 0x10; //00010000 break; case 2: - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); + ToSend[++ToSendMax] = 0x04; //00000100 break; case 3: - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); + ToSend[++ToSendMax] = 0x01; //00000001 break; } } } + // EOF - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - - // Fill remainder of last byte with 1 - for(i = 0; i < 4; i++) { - ToSendStuffBit(1); - } - + ToSend[++ToSendMax] = 0x20; //0010 + 0000 padding + ToSendMax++; } @@ -170,46 +141,26 @@ static void CodeIso15693AsReader(uint8_t *cmd, int n) // is designed for more robust communication over longer distances static void CodeIso15693AsReader256(uint8_t *cmd, int n) { - int i, j; - ToSendReset(); - // Give it a bit of slack at the beginning - for(i = 0; i < 24; i++) { - ToSendStuffBit(1); - } - // SOF for 1of256 - ToSendStuffBit(0); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); + ToSend[++ToSendMax] = 0x81; //10000001 - for(i = 0; i < n; i++) { - for (j = 0; j<=255; j++) { - if (cmd[i]==j) { - ToSendStuffBit(1); + // data + for(int i = 0; i < n; i++) { + for (int j = 0; j <= 255; j++) { + if (cmd[i] == j) { ToSendStuffBit(0); + ToSendStuffBit(1); } else { - ToSendStuffBit(1); - ToSendStuffBit(1); + ToSendStuffBit(0); + ToSendStuffBit(0); } } } - // EOF - ToSendStuffBit(1); - ToSendStuffBit(1); - ToSendStuffBit(0); - ToSendStuffBit(1); - // Fill remainder of last byte with 1 - for(i = 0; i < 4; i++) { - ToSendStuffBit(1); - } + // EOF + ToSend[++ToSendMax] = 0x20; //0010 + 0000 padding ToSendMax++; } @@ -295,27 +246,38 @@ void CodeIso15693AsTag(uint8_t *cmd, size_t len) { // Transmit the command (to the tag) that was placed in cmd[]. -static void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t start_time) -{ - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_FULL_MOD); - FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER); +void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t *start_time) { - while (GetCountSspClk() < start_time) ; + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_FULL_MOD); + + *start_time = (*start_time - DELAY_ARM_TO_TAG) & 0xfffffff0; + + while (GetCountSspClk() > *start_time) { // we may miss the intended time + *start_time += 16; // next possible time + } + + + while (GetCountSspClk() < *start_time) + /* wait */ ; LED_B_ON(); - for(int c = 0; c < len; c++) { + for (int c = 0; c < len; c++) { uint8_t data = cmd[c]; for (int i = 0; i < 8; i++) { - uint16_t send_word = (data & 0x80) ? 0x0000 : 0xffff; + uint16_t send_word = (data & 0x80) ? 0xffff : 0x0000; while (!(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY))) ; AT91C_BASE_SSC->SSC_THR = send_word; while (!(AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY))) ; AT91C_BASE_SSC->SSC_THR = send_word; + data <<= 1; } WDT_HIT(); } LED_B_OFF(); + + *start_time = *start_time + DELAY_ARM_TO_TAG; + } @@ -326,7 +288,7 @@ void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, // don't use the FPGA_HF_SIMULATOR_MODULATE_424K_8BIT minor mode. It would spoil GetCountSspClk() FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_424K); - uint32_t modulation_start_time = *start_time + 3 * 8; // no need to transfer the unmodulated start of SOF + uint32_t modulation_start_time = *start_time - DELAY_ARM_TO_READER + 3 * 8; // no need to transfer the unmodulated start of SOF while (GetCountSspClk() > (modulation_start_time & 0xfffffff8) + 3) { // we will miss the intended time if (slot_time) { @@ -341,7 +303,7 @@ void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, uint8_t shift_delay = modulation_start_time & 0x00000007; - *start_time = modulation_start_time - 3 * 8; + *start_time = modulation_start_time + DELAY_ARM_TO_READER - 3 * 8; LED_C_ON(); uint8_t bits_to_shift = 0x00; @@ -386,15 +348,18 @@ void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, // false if we are still waiting for some more //============================================================================= -#define NOISE_THRESHOLD 160 // don't try to correlate noise +#define NOISE_THRESHOLD 160 // don't try to correlate noise +#define MAX_PREVIOUS_AMPLITUDE (-1 - NOISE_THRESHOLD) typedef struct DecodeTag { enum { STATE_TAG_SOF_LOW, + STATE_TAG_SOF_RISING_EDGE, STATE_TAG_SOF_HIGH, STATE_TAG_SOF_HIGH_END, STATE_TAG_RECEIVING_DATA, - STATE_TAG_EOF + STATE_TAG_EOF, + STATE_TAG_EOF_TAIL } state; int bitCount; int posCount; @@ -409,6 +374,9 @@ typedef struct DecodeTag { uint8_t *output; int len; int sum1, sum2; + int threshold_sof; + int threshold_half; + uint16_t previous_amplitude; } DecodeTag_t; @@ -416,40 +384,58 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 { switch(DecodeTag->state) { case STATE_TAG_SOF_LOW: - // waiting for 12 times low (11 times low is accepted as well) - if (amplitude < NOISE_THRESHOLD) { - DecodeTag->posCount++; - } else { + // waiting for a rising edge + if (amplitude > NOISE_THRESHOLD + DecodeTag->previous_amplitude) { if (DecodeTag->posCount > 10) { - DecodeTag->posCount = 1; - DecodeTag->sum1 = 0; - DecodeTag->state = STATE_TAG_SOF_HIGH; + DecodeTag->threshold_sof = amplitude - DecodeTag->previous_amplitude; + DecodeTag->threshold_half = 0; + DecodeTag->state = STATE_TAG_SOF_RISING_EDGE; } else { DecodeTag->posCount = 0; } + } else { + DecodeTag->posCount++; + DecodeTag->previous_amplitude = amplitude; } break; + case STATE_TAG_SOF_RISING_EDGE: + if (amplitude - DecodeTag->previous_amplitude > DecodeTag->threshold_sof) { // edge still rising + if (amplitude - DecodeTag->threshold_sof > DecodeTag->threshold_sof) { // steeper edge, take this as time reference + DecodeTag->posCount = 1; + } else { + DecodeTag->posCount = 2; + } + DecodeTag->threshold_sof = (amplitude - DecodeTag->previous_amplitude) / 2; + } else { + DecodeTag->posCount = 2; + DecodeTag->threshold_sof = DecodeTag->threshold_sof/2; + } + // DecodeTag->posCount = 2; + DecodeTag->state = STATE_TAG_SOF_HIGH; + break; + case STATE_TAG_SOF_HIGH: // waiting for 10 times high. Take average over the last 8 - if (amplitude > NOISE_THRESHOLD) { + if (amplitude > DecodeTag->threshold_sof) { DecodeTag->posCount++; if (DecodeTag->posCount > 2) { - DecodeTag->sum1 += amplitude; // keep track of average high value + DecodeTag->threshold_half += amplitude; // keep track of average high value } if (DecodeTag->posCount == 10) { - DecodeTag->sum1 >>= 4; // calculate half of average high value (8 samples) + DecodeTag->threshold_half >>= 2; // (4 times 1/2 average) DecodeTag->state = STATE_TAG_SOF_HIGH_END; } } else { // high phase was too short DecodeTag->posCount = 1; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; } break; case STATE_TAG_SOF_HIGH_END: - // waiting for a falling edge - if (amplitude < DecodeTag->sum1) { // signal drops below 50% average high: a falling edge + // check for falling edge + if (DecodeTag->posCount == 13 && amplitude < DecodeTag->threshold_sof) { DecodeTag->lastBit = SOF_PART1; // detected 1st part of SOF (12 samples low and 12 samples high) DecodeTag->shiftReg = 0; DecodeTag->bitCount = 0; @@ -458,11 +444,18 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->sum2 = 0; DecodeTag->posCount = 2; DecodeTag->state = STATE_TAG_RECEIVING_DATA; + // FpgaDisableTracing(); // DEBUGGING + // Dbprintf("amplitude = %d, threshold_sof = %d, threshold_half/4 = %d, previous_amplitude = %d", + // amplitude, + // DecodeTag->threshold_sof, + // DecodeTag->threshold_half/4, + // DecodeTag->previous_amplitude); // DEBUGGING LED_C_ON(); } else { DecodeTag->posCount++; if (DecodeTag->posCount > 13) { // high phase too long DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -480,18 +473,16 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->sum2 += amplitude; } if (DecodeTag->posCount == 8) { - int32_t corr_1 = DecodeTag->sum2 - DecodeTag->sum1; - int32_t corr_0 = -corr_1; - int32_t corr_EOF = (DecodeTag->sum1 + DecodeTag->sum2) / 2; - if (corr_EOF > corr_0 && corr_EOF > corr_1) { + if (DecodeTag->sum1 > DecodeTag->threshold_half && DecodeTag->sum2 > DecodeTag->threshold_half) { // modulation in both halves if (DecodeTag->lastBit == LOGIC0) { // this was already part of EOF DecodeTag->state = STATE_TAG_EOF; } else { DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } - } else if (corr_1 > corr_0) { + } else if (DecodeTag->sum1 < DecodeTag->threshold_half && DecodeTag->sum2 > DecodeTag->threshold_half) { // modulation in second half // logic 1 if (DecodeTag->lastBit == SOF_PART1) { // still part of SOF DecodeTag->lastBit = SOF_PART2; // SOF completed @@ -503,20 +494,21 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 if (DecodeTag->bitCount == 8) { DecodeTag->output[DecodeTag->len] = DecodeTag->shiftReg; DecodeTag->len++; + // if (DecodeTag->shiftReg == 0x12 && DecodeTag->len == 1) FpgaDisableTracing(); // DEBUGGING if (DecodeTag->len > DecodeTag->max_len) { // buffer overflow, give up - DecodeTag->posCount = 0; - DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); + return true; } DecodeTag->bitCount = 0; DecodeTag->shiftReg = 0; } } - } else { + } else if (DecodeTag->sum1 > DecodeTag->threshold_half && DecodeTag->sum2 < DecodeTag->threshold_half) { // modulation in first half // logic 0 if (DecodeTag->lastBit == SOF_PART1) { // incomplete SOF DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } else { @@ -526,9 +518,11 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 if (DecodeTag->bitCount == 8) { DecodeTag->output[DecodeTag->len] = DecodeTag->shiftReg; DecodeTag->len++; + // if (DecodeTag->shiftReg == 0x12 && DecodeTag->len == 1) FpgaDisableTracing(); // DEBUGGING if (DecodeTag->len > DecodeTag->max_len) { // buffer overflow, give up DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -536,6 +530,15 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->shiftReg = 0; } } + } else { // no modulation + if (DecodeTag->lastBit == SOF_PART2) { // only SOF (this is OK for iClass) + LED_C_OFF(); + return true; + } else { + DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } } DecodeTag->posCount = 0; } @@ -553,21 +556,42 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->sum2 += amplitude; } if (DecodeTag->posCount == 8) { - int32_t corr_1 = DecodeTag->sum2 - DecodeTag->sum1; - int32_t corr_0 = -corr_1; - int32_t corr_EOF = (DecodeTag->sum1 + DecodeTag->sum2) / 2; - if (corr_EOF > corr_0 || corr_1 > corr_0) { + if (DecodeTag->sum1 > DecodeTag->threshold_half && DecodeTag->sum2 < DecodeTag->threshold_half) { // modulation in first half DecodeTag->posCount = 0; + DecodeTag->state = STATE_TAG_EOF_TAIL; + } else { + DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); - } else { - LED_C_OFF(); - return true; } } DecodeTag->posCount++; break; + case STATE_TAG_EOF_TAIL: + if (DecodeTag->posCount == 1) { + DecodeTag->sum1 = 0; + DecodeTag->sum2 = 0; + } + if (DecodeTag->posCount <= 4) { + DecodeTag->sum1 += amplitude; + } else { + DecodeTag->sum2 += amplitude; + } + if (DecodeTag->posCount == 8) { + if (DecodeTag->sum1 < DecodeTag->threshold_half && DecodeTag->sum2 < DecodeTag->threshold_half) { // no modulation in both halves + LED_C_OFF(); + return true; + } else { + DecodeTag->posCount = 0; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->state = STATE_TAG_SOF_LOW; + LED_C_OFF(); + } + } + DecodeTag->posCount++; + break; } return false; @@ -576,6 +600,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data, uint16_t max_len) { + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->posCount = 0; DecodeTag->state = STATE_TAG_SOF_LOW; DecodeTag->output = data; @@ -587,18 +612,19 @@ static void DecodeTagReset(DecodeTag_t *DecodeTag) { DecodeTag->posCount = 0; DecodeTag->state = STATE_TAG_SOF_LOW; + DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; } /* * Receive and decode the tag response, also log to tracebuffer */ -static int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, int timeout) -{ - int samples = 0; - bool gotFrame = false; +int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time) { - uint16_t *dmaBuf = (uint16_t*)BigBuf_malloc(ISO15693_DMA_BUFFER_SIZE*sizeof(uint16_t)); + int samples = 0; + int ret = 0; + + uint16_t dmaBuf[ISO15693_DMA_BUFFER_SIZE]; // the Decoder data structure DecodeTag_t DecodeTag = { 0 }; @@ -613,6 +639,7 @@ static int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, int tim // Setup and start DMA. FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER); FpgaSetupSscDma((uint8_t*) dmaBuf, ISO15693_DMA_BUFFER_SIZE); + uint32_t dma_start_time = 0; uint16_t *upTo = dmaBuf; for(;;) { @@ -620,12 +647,19 @@ static int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, int tim if (behindBy == 0) continue; + samples++; + if (samples == 1) { + // DMA has transferred the very first data + dma_start_time = GetCountSspClk() & 0xfffffff0; + } + uint16_t tagdata = *upTo++; if(upTo >= dmaBuf + ISO15693_DMA_BUFFER_SIZE) { // we have read all of the DMA buffer content. upTo = dmaBuf; // start reading the circular buffer from the beginning if(behindBy > (9*ISO15693_DMA_BUFFER_SIZE/10)) { Dbprintf("About to blow circular buffer - aborted! behindBy=%d", behindBy); + ret = -1; break; } } @@ -634,30 +668,42 @@ static int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, int tim AT91C_BASE_PDC_SSC->PDC_RNCR = ISO15693_DMA_BUFFER_SIZE; // DMA Next Counter registers } - samples++; - if (Handle15693SamplesFromTag(tagdata, &DecodeTag)) { - gotFrame = true; + *eof_time = dma_start_time + samples*16 - DELAY_TAG_TO_ARM; // end of EOF + if (DecodeTag.lastBit == SOF_PART2) { + *eof_time -= 8*16; // needed 8 additional samples to confirm single SOF (iCLASS) + } + if (DecodeTag.len > DecodeTag.max_len) { + ret = -2; // buffer overflow + } break; } if (samples > timeout && DecodeTag.state < STATE_TAG_RECEIVING_DATA) { - DecodeTag.len = 0; + ret = -1; // timeout break; } } FpgaDisableSscDma(); - BigBuf_free(); - if (DEBUG) Dbprintf("samples = %d, gotFrame = %d, Decoder: state = %d, len = %d, bitCount = %d, posCount = %d", - samples, gotFrame, DecodeTag.state, DecodeTag.len, DecodeTag.bitCount, DecodeTag.posCount); + if (DEBUG) Dbprintf("samples = %d, ret = %d, Decoder: state = %d, lastBit = %d, len = %d, bitCount = %d, posCount = %d", + samples, ret, DecodeTag.state, DecodeTag.lastBit, DecodeTag.len, DecodeTag.bitCount, DecodeTag.posCount); - if (DecodeTag.len > 0) { - LogTrace(DecodeTag.output, DecodeTag.len, 0, 0, NULL, false); + if (ret < 0) { + return ret; } + uint32_t sof_time = *eof_time + - DecodeTag.len * 8 * 8 * 16 // time for byte transfers + - 32 * 16 // time for SOF transfer + - (DecodeTag.lastBit != SOF_PART2?32*16:0); // time for EOF transfer + + if (DEBUG) Dbprintf("timing: sof_time = %d, eof_time = %d", sof_time, *eof_time); + + LogTrace_ISO15693(DecodeTag.output, DecodeTag.len, sof_time*4, *eof_time*4, NULL, false); + return DecodeTag.len; } @@ -977,7 +1023,7 @@ int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eo for (int i = 7; i >= 0; i--) { if (Handle15693SampleFromReader((b >> i) & 0x01, &DecodeReader)) { - *eof_time = dma_start_time + samples - DELAY_READER_TO_ARM_SIM; // end of EOF + *eof_time = dma_start_time + samples - DELAY_READER_TO_ARM; // end of EOF gotFrame = true; break; } @@ -1006,7 +1052,7 @@ int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eo - DecodeReader.byteCount * (DecodeReader.Coding==CODING_1_OUT_OF_4?128:2048) // time for byte transfers - 32 // time for SOF transfer - 16; // time for EOF transfer - LogTrace(DecodeReader.output, DecodeReader.byteCount, sof_time, *eof_time, NULL, true); + LogTrace_ISO15693(DecodeReader.output, DecodeReader.byteCount, sof_time*32, *eof_time*32, NULL, true); } return DecodeReader.byteCount; @@ -1060,7 +1106,8 @@ void AcquireRawAdcSamplesIso15693(void) SpinDelay(100); // Now send the command - TransmitTo15693Tag(ToSend, ToSendMax, 0); + uint32_t start_time = 0; + TransmitTo15693Tag(ToSend, ToSendMax, &start_time); // wait for last transfer to complete while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY)) ; @@ -1156,7 +1203,7 @@ void SnoopIso15693(void) if (Handle15693SampleFromReader(snoopdata & 0x02, &DecodeReader)) { FpgaDisableSscDma(); ExpectTagAnswer = true; - LogTrace(DecodeReader.output, DecodeReader.byteCount, samples, samples, NULL, true); + LogTrace_ISO15693(DecodeReader.output, DecodeReader.byteCount, samples*64, samples*64, NULL, true); /* And ready to receive another command. */ DecodeReaderReset(&DecodeReader); /* And also reset the demod code, which might have been */ @@ -1168,7 +1215,7 @@ void SnoopIso15693(void) if (Handle15693SampleFromReader(snoopdata & 0x01, &DecodeReader)) { FpgaDisableSscDma(); ExpectTagAnswer = true; - LogTrace(DecodeReader.output, DecodeReader.byteCount, samples, samples, NULL, true); + LogTrace_ISO15693(DecodeReader.output, DecodeReader.byteCount, samples*64, samples*64, NULL, true); /* And ready to receive another command. */ DecodeReaderReset(&DecodeReader); /* And also reset the demod code, which might have been */ @@ -1184,7 +1231,7 @@ void SnoopIso15693(void) if (Handle15693SamplesFromTag(snoopdata >> 2, &DecodeTag)) { FpgaDisableSscDma(); //Use samples as a time measurement - LogTrace(DecodeTag.output, DecodeTag.len, samples, samples, NULL, false); + LogTrace_ISO15693(DecodeTag.output, DecodeTag.len, samples*64, samples*64, NULL, false); // And ready to receive another response. DecodeTagReset(&DecodeTag); DecodeReaderReset(&DecodeReader); @@ -1213,10 +1260,8 @@ void SnoopIso15693(void) // Initialize the proxmark as iso15k reader -static void Iso15693InitReader() { +void Iso15693InitReader() { FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - // Setup SSC - // FpgaSetupSsc(); // Start from off (no field generated) LED_D_OFF(); @@ -1300,17 +1345,20 @@ static void BuildInventoryResponse(uint8_t *uid) // init ... should we initialize the reader? // speed ... 0 low speed, 1 hi speed // *recv will contain the tag's answer -// return: lenght of received data -int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time) { +// return: length of received data, or -1 for timeout +int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint32_t *eof_time) { LED_A_ON(); LED_B_OFF(); LED_C_OFF(); - if (init) Iso15693InitReader(); - - int answerLen=0; - + if (init) { + Iso15693InitReader(); + StartCountSspClk(); + } + + int answerLen = 0; + if (!speed) { // low speed (1 out of 256) CodeIso15693AsReader256(send, sendlen); @@ -1319,11 +1367,14 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, CodeIso15693AsReader(send, sendlen); } - TransmitTo15693Tag(ToSend, ToSendMax, start_time); + if (start_time == 0) { + start_time = GetCountSspClk(); + } + TransmitTo15693Tag(ToSend, ToSendMax, &start_time); // Now wait for a response if (recv != NULL) { - answerLen = GetIso15693AnswerFromTag(recv, max_recv_len, DELAY_ISO15693_VCD_TO_VICC_READER * 2); + answerLen = GetIso15693AnswerFromTag(recv, max_recv_len, DELAY_ISO15693_VCD_TO_VICC_READER * 2, eof_time); } LED_A_OFF(); @@ -1445,11 +1496,13 @@ void ReaderIso15693(uint32_t parameter) // Now send the IDENTIFY command BuildIdentifyRequest(); - TransmitTo15693Tag(ToSend, ToSendMax, 0); + uint32_t start_time = 0; + TransmitTo15693Tag(ToSend, ToSendMax, &start_time); // Now wait for a response - answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC_READER * 2) ; - uint32_t start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD_READER; + uint32_t eof_time; + answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC_READER * 2, &eof_time) ; + start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; if (answerLen >=12) // we should do a better check than this { @@ -1487,9 +1540,9 @@ void ReaderIso15693(uint32_t parameter) if (answerLen >= 12 && DEBUG) { for (int i = 0; i < 32; i++) { // sanity check, assume max 32 pages BuildReadBlockRequest(TagUID, i); - TransmitTo15693Tag(ToSend, ToSendMax, start_time); - int answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC_READER * 2); - start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD_READER; + TransmitTo15693Tag(ToSend, ToSendMax, &start_time); + int answerLen = GetIso15693AnswerFromTag(answer, sizeof(answer), DELAY_ISO15693_VCD_TO_VICC_READER * 2, &eof_time); + start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; if (answerLen > 0) { Dbprintf("READ SINGLE BLOCK %d returned %d octets:", i, answerLen); DbdecodeIso15693Answer(answerLen, answer); @@ -1535,7 +1588,7 @@ void SimTagIso15693(uint32_t parameter, uint8_t *uid) if ((cmd_len >= 5) && (cmd[0] & ISO15693_REQ_INVENTORY) && (cmd[1] == ISO15693_INVENTORY)) { // TODO: check more flags bool slow = !(cmd[0] & ISO15693_REQ_DATARATE_HIGH); - start_time = eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM - DELAY_ARM_TO_READER_SIM; + start_time = eof_time + DELAY_ISO15693_VCD_TO_VICC_SIM; TransmitTo15693Reader(ToSend, ToSendMax, &start_time, 0, slow); } @@ -1557,11 +1610,8 @@ void BruteforceIso15693Afi(uint32_t speed) uint8_t data[6]; uint8_t recv[ISO15693_MAX_RESPONSE_LENGTH]; - - int datalen=0, recvlen=0; - - Iso15693InitReader(); - StartCountSspClk(); + int datalen = 0, recvlen = 0; + uint32_t eof_time; // first without AFI // Tags should respond without AFI and with AFI=0 even when AFI is active @@ -1570,8 +1620,9 @@ void BruteforceIso15693Afi(uint32_t speed) data[1] = ISO15693_INVENTORY; data[2] = 0; // mask length datalen = Iso15693AddCrc(data,3); - recvlen = SendDataTag(data, datalen, false, speed, recv, sizeof(recv), 0); - uint32_t start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD_READER; + uint32_t start_time = GetCountSspClk(); + recvlen = SendDataTag(data, datalen, true, speed, recv, sizeof(recv), 0, &eof_time); + start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; WDT_HIT(); if (recvlen>=12) { Dbprintf("NoAFI UID=%s", Iso15693sprintUID(NULL, &recv[2])); @@ -1587,8 +1638,8 @@ void BruteforceIso15693Afi(uint32_t speed) for (int i = 0; i < 256; i++) { data[2] = i & 0xFF; datalen = Iso15693AddCrc(data,4); - recvlen = SendDataTag(data, datalen, false, speed, recv, sizeof(recv), start_time); - start_time = GetCountSspClk() + DELAY_ISO15693_VICC_TO_VCD_READER; + recvlen = SendDataTag(data, datalen, false, speed, recv, sizeof(recv), start_time, &eof_time); + start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; WDT_HIT(); if (recvlen >= 12) { Dbprintf("AFI=%i UID=%s", i, Iso15693sprintUID(NULL, &recv[2])); @@ -1605,7 +1656,8 @@ void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint int recvlen = 0; uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; - + uint32_t eof_time; + LED_A_ON(); if (DEBUG) { @@ -1613,24 +1665,27 @@ void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint Dbhexdump(datalen, data, false); } - recvlen = SendDataTag(data, datalen, true, speed, (recv?recvbuf:NULL), sizeof(recvbuf), 0); - - if (recv) { - if (DEBUG) { - Dbprintf("RECV:"); - Dbhexdump(recvlen, recvbuf, false); - DbdecodeIso15693Answer(recvlen, recvbuf); - } - - cmd_send(CMD_ACK, recvlen>ISO15693_MAX_RESPONSE_LENGTH?ISO15693_MAX_RESPONSE_LENGTH:recvlen, 0, 0, recvbuf, ISO15693_MAX_RESPONSE_LENGTH); - - } + recvlen = SendDataTag(data, datalen, true, speed, (recv?recvbuf:NULL), sizeof(recvbuf), 0, &eof_time); // for the time being, switch field off to protect rdv4.0 // note: this prevents using hf 15 cmd with s option - which isn't implemented yet anyway FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); LED_D_OFF(); + if (recv) { + if (DEBUG) { + Dbprintf("RECV:"); + if (recvlen > 0) { + Dbhexdump(recvlen, recvbuf, false); + DbdecodeIso15693Answer(recvlen, recvbuf); + } + } + if (recvlen > ISO15693_MAX_RESPONSE_LENGTH) { + recvlen = ISO15693_MAX_RESPONSE_LENGTH; + } + cmd_send(CMD_ACK, recvlen, 0, 0, recvbuf, ISO15693_MAX_RESPONSE_LENGTH); + } + LED_A_OFF(); } @@ -1648,7 +1703,8 @@ void SetTag15693Uid(uint8_t *uid) int recvlen = 0; uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; - + uint32_t eof_time; + LED_A_ON(); // Command 1 : 02213E00000000 @@ -1687,7 +1743,7 @@ void SetTag15693Uid(uint8_t *uid) cmd[3][5] = uid[1]; cmd[3][6] = uid[0]; - for (int i=0; i<4; i++) { + for (int i = 0; i < 4; i++) { // Add the CRC crc = Iso15693Crc(cmd[i], 7); cmd[i][7] = crc & 0xff; @@ -1698,12 +1754,14 @@ void SetTag15693Uid(uint8_t *uid) Dbhexdump(sizeof(cmd[i]), cmd[i], false); } - recvlen = SendDataTag(cmd[i], sizeof(cmd[i]), true, 1, recvbuf, sizeof(recvbuf), 0); + recvlen = SendDataTag(cmd[i], sizeof(cmd[i]), true, 1, recvbuf, sizeof(recvbuf), 0, &eof_time); if (DEBUG) { Dbprintf("RECV:"); - Dbhexdump(recvlen, recvbuf, false); - DbdecodeIso15693Answer(recvlen, recvbuf); + if (recvlen > 0) { + Dbhexdump(recvlen, recvbuf, false); + DbdecodeIso15693Answer(recvlen, recvbuf); + } } cmd_send(CMD_ACK, recvlen>ISO15693_MAX_RESPONSE_LENGTH?ISO15693_MAX_RESPONSE_LENGTH:recvlen, 0, 0, recvbuf, ISO15693_MAX_RESPONSE_LENGTH); diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 7d2e7598..96a2b39b 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -17,16 +17,18 @@ // Delays in SSP_CLK ticks. // SSP_CLK runs at 13,56MHz / 32 = 423.75kHz when simulating a tag -#define DELAY_READER_TO_ARM_SIM 8 -#define DELAY_ARM_TO_READER_SIM 0 -#define DELAY_ISO15693_VCD_TO_VICC_SIM 132 // 132/423.75kHz = 311.5us from end of command EOF to start of tag response -//SSP_CLK runs at 13.56MHz / 4 = 3,39MHz when acting as reader +#define DELAY_ISO15693_VCD_TO_VICC_SIM 132 // 132/423.75kHz = 311.5us from end of command EOF to start of tag response +//SSP_CLK runs at 13.56MHz / 4 = 3,39MHz when acting as reader. All values should be multiples of 16 #define DELAY_ISO15693_VCD_TO_VICC_READER 1056 // 1056/3,39MHz = 311.5us from end of command EOF to start of tag response -#define DELAY_ISO15693_VICC_TO_VCD_READER 1017 // 1017/3.39MHz = 300us between end of tag response and next reader command +#define DELAY_ISO15693_VICC_TO_VCD_READER 1024 // 1024/3.39MHz = 302.1us between end of tag response and next reader command +void Iso15693InitReader(); +void CodeIso15693AsReader(uint8_t *cmd, int n); void CodeIso15693AsTag(uint8_t *cmd, size_t len); -int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eof_time); void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, uint32_t slot_time, bool slow); +int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eof_time); +void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t *start_time); +int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time); void SnoopIso15693(void); void AcquireRawAdcSamplesIso15693(void); void ReaderIso15693(uint32_t parameter); @@ -35,5 +37,6 @@ void BruteforceIso15693Afi(uint32_t speed); void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint8_t data[]); void SetTag15693Uid(uint8_t *uid); void SetDebugIso15693(uint32_t flag); +bool LogTrace_ISO15693(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_start, uint32_t timestamp_end, uint8_t *parity, bool readerToTag); #endif diff --git a/armsrc/util.c b/armsrc/util.c index aac68a34..4bff3a26 100644 --- a/armsrc/util.c +++ b/armsrc/util.c @@ -21,7 +21,7 @@ void print_result(char *name, uint8_t *buf, size_t len) { if ( len % 16 == 0 ) { for(; p-buf < len; p += 16) - Dbprintf("[%s:%d/%d] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + Dbprintf("[%s:%d/%d] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", name, p-buf, len, @@ -30,7 +30,7 @@ void print_result(char *name, uint8_t *buf, size_t len) { } else { for(; p-buf < len; p += 8) - Dbprintf("[%s:%d/%d] %02x %02x %02x %02x %02x %02x %02x %02x", name, p-buf, len, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + Dbprintf("[%s:%d/%d] %02x %02x %02x %02x %02x %02x %02x %02x", name, p-buf, len, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); } } @@ -68,17 +68,17 @@ uint64_t bytes_to_num(uint8_t* src, size_t len) // RotateLeft - Ultralight, Desfire void rol(uint8_t *data, const size_t len){ - uint8_t first = data[0]; - for (size_t i = 0; i < len-1; i++) { - data[i] = data[i+1]; - } - data[len-1] = first; + uint8_t first = data[0]; + for (size_t i = 0; i < len-1; i++) { + data[i] = data[i+1]; + } + data[len-1] = first; } void lsl (uint8_t *data, size_t len) { - for (size_t n = 0; n < len - 1; n++) { - data[n] = (data[n] << 1) | (data[n+1] >> 7); - } - data[len - 1] <<= 1; + for (size_t n = 0; n < len - 1; n++) { + data[n] = (data[n] << 1) | (data[n+1] >> 7); + } + data[len - 1] <<= 1; } void LEDsoff() @@ -309,16 +309,16 @@ void FormatVersionInformation(char *dst, int len, const char *prefix, void *vers // ------------------------------------------------------------------------- // test procedure: // -// ti = GetTickCount(); -// SpinDelay(1000); -// ti = GetTickCount() - ti; -// Dbprintf("timer(1s): %d t=%d", ti, GetTickCount()); +// ti = GetTickCount(); +// SpinDelay(1000); +// ti = GetTickCount() - ti; +// Dbprintf("timer(1s): %d t=%d", ti, GetTickCount()); void StartTickCount() { // This timer is based on the slow clock. The slow clock frequency is between 22kHz and 40kHz. // We can determine the actual slow clock frequency by looking at the Main Clock Frequency Register. - uint16_t mainf = AT91C_BASE_PMC->PMC_MCFR & 0xffff; // = 16 * main clock frequency (16MHz) / slow clock frequency + uint16_t mainf = AT91C_BASE_PMC->PMC_MCFR & 0xffff; // = 16 * main clock frequency (16MHz) / slow clock frequency // set RealTimeCounter divider to count at 1kHz: AT91C_BASE_RTTC->RTTC_RTMR = AT91C_RTTC_RTTRST | ((256000 + (mainf/2)) / mainf); // note: worst case precision is approx 2.5% @@ -334,12 +334,12 @@ uint32_t RAMFUNC GetTickCount(){ // ------------------------------------------------------------------------- -// microseconds timer +// microseconds timer // ------------------------------------------------------------------------- void StartCountUS() { AT91C_BASE_PMC->PMC_PCER |= (0x1 << 12) | (0x1 << 13) | (0x1 << 14); -// AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC1XC1S_TIOA0; +// AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC1XC1S_TIOA0; AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE; // fast clock @@ -349,10 +349,10 @@ void StartCountUS() AT91C_TC_ACPC_SET | AT91C_TC_ASWTRG_SET; AT91C_BASE_TC0->TC_RA = 1; AT91C_BASE_TC0->TC_RC = 0xBFFF + 1; // 0xC000 - - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // timer disable + + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // timer disable AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_XC1; // from timer 0 - + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN; AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN; AT91C_BASE_TCB->TCB_BCR = 1; @@ -375,61 +375,72 @@ uint32_t RAMFUNC GetDeltaCountUS(){ // ------------------------------------------------------------------------- -// Timer for iso14443 commands. Uses ssp_clk from FPGA +// Timer for iso14443 commands. Uses ssp_clk from FPGA // ------------------------------------------------------------------------- void StartCountSspClk() { AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC0) | (1 << AT91C_ID_TC1) | (1 << AT91C_ID_TC2); // Enable Clock to all timers - AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_TIOA1 // XC0 Clock = TIOA1 - | AT91C_TCB_TC1XC1S_NONE // XC1 Clock = none - | AT91C_TCB_TC2XC2S_TIOA0; // XC2 Clock = TIOA0 + AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_TIOA1 // XC0 Clock = TIOA1 + | AT91C_TCB_TC1XC1S_NONE // XC1 Clock = none + | AT91C_TCB_TC2XC2S_TIOA0; // XC2 Clock = TIOA0 // configure TC1 to create a short pulse on TIOA1 when a rising edge on TIOB1 (= ssp_clk from FPGA) occurs: - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // disable TC1 + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; // disable TC1 AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_TIMER_DIV1_CLOCK // TC1 Clock = MCK(48MHz)/2 = 24MHz - | AT91C_TC_CPCSTOP // Stop clock on RC compare - | AT91C_TC_EEVTEDG_RISING // Trigger on rising edge of Event - | AT91C_TC_EEVT_TIOB // Event-Source: TIOB1 (= ssp_clk from FPGA = 13,56MHz/16 ... 13,56MHz/4) - | AT91C_TC_ENETRG // Enable external trigger event - | AT91C_TC_WAVESEL_UP // Upmode without automatic trigger on RC compare - | AT91C_TC_WAVE // Waveform Mode - | AT91C_TC_AEEVT_SET // Set TIOA1 on external event - | AT91C_TC_ACPC_CLEAR; // Clear TIOA1 on RC Compare - AT91C_BASE_TC1->TC_RC = 0x02; // RC Compare value = 0x02 + | AT91C_TC_CPCSTOP // Stop clock on RC compare + | AT91C_TC_EEVTEDG_RISING // Trigger on rising edge of Event + | AT91C_TC_EEVT_TIOB // Event-Source: TIOB1 (= ssp_clk from FPGA = 13,56MHz/16 ... 13,56MHz/4) + | AT91C_TC_ENETRG // Enable external trigger event + | AT91C_TC_WAVESEL_UP // Upmode without automatic trigger on RC compare + | AT91C_TC_WAVE // Waveform Mode + | AT91C_TC_AEEVT_SET // Set TIOA1 on external event + | AT91C_TC_ACPC_CLEAR; // Clear TIOA1 on RC Compare + AT91C_BASE_TC1->TC_RC = 0x02; // RC Compare value = 0x02 // use TC0 to count TIOA1 pulses - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // disable TC0 - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_XC0 // TC0 clock = XC0 clock = TIOA1 - | AT91C_TC_WAVE // Waveform Mode - | AT91C_TC_WAVESEL_UP // just count - | AT91C_TC_ACPA_CLEAR // Clear TIOA0 on RA Compare - | AT91C_TC_ACPC_SET; // Set TIOA0 on RC Compare - AT91C_BASE_TC0->TC_RA = 1; // RA Compare value = 1; pulse width to TC2 - AT91C_BASE_TC0->TC_RC = 0; // RC Compare value = 0; increment TC2 on overflow + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // disable TC0 + AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_XC0 // TC0 clock = XC0 clock = TIOA1 + | AT91C_TC_WAVE // Waveform Mode + | AT91C_TC_WAVESEL_UP // just count + | AT91C_TC_ACPA_CLEAR // Clear TIOA0 on RA Compare + | AT91C_TC_ACPC_SET; // Set TIOA0 on RC Compare + AT91C_BASE_TC0->TC_RA = 1; // RA Compare value = 1; pulse width to TC2 + AT91C_BASE_TC0->TC_RC = 0; // RC Compare value = 0; increment TC2 on overflow // use TC2 to count TIOA0 pulses (giving us a 32bit counter (TC0/TC2) clocked by ssp_clk) - AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKDIS; // disable TC2 - AT91C_BASE_TC2->TC_CMR = AT91C_TC_CLKS_XC2 // TC2 clock = XC2 clock = TIOA0 - | AT91C_TC_WAVE // Waveform Mode - | AT91C_TC_WAVESEL_UP; // just count - - AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN; // enable TC0 - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN; // enable TC1 - AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKEN; // enable TC2 + AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKDIS; // disable TC2 + AT91C_BASE_TC2->TC_CMR = AT91C_TC_CLKS_XC2 // TC2 clock = XC2 clock = TIOA0 + | AT91C_TC_WAVE // Waveform Mode + | AT91C_TC_WAVESEL_UP; // just count + + AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN; // enable TC0 + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN; // enable TC1 + AT91C_BASE_TC2->TC_CCR = AT91C_TC_CLKEN; // enable TC2 // - // synchronize the counter with the ssp_frame signal. Note: FPGA must be in a FPGA mode with SSC transfer, otherwise SSC_FRAME and SSC_CLK signals would not be present + // synchronize the counter with the ssp_frame signal. Note: FPGA must be in a FPGA mode with SSC transfer, otherwise SSC_FRAME and SSC_CLK signals would not be present // - while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_FRAME)); // wait for ssp_frame to go high (start of frame) - while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_FRAME); // wait for ssp_frame to be low - while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high - // note: up to now two ssp_clk rising edges have passed since the rising edge of ssp_frame + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_FRAME); // wait for ssp_frame to be low + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_FRAME)); // wait for ssp_frame to go high (start of frame) + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 1st ssp_clk after start of frame + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK); // wait for ssp_clk to go low; + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 2nd ssp_clk after start of frame + if ((AT91C_BASE_SSC->SSC_RFMR & SSC_FRAME_MODE_BITS_IN_WORD(32)) == SSC_FRAME_MODE_BITS_IN_WORD(16)) { + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK); // wait for ssp_clk to go low; + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 3rd ssp_clk after start of frame + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK); // wait for ssp_clk to go low; + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 4th ssp_clk after start of frame + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK); // wait for ssp_clk to go low; + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 5th ssp_clk after start of frame + while(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK); // wait for ssp_clk to go low; + while(!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_CLK)); // wait for ssp_clk to go high; 6th ssp_clk after start of frame + } // it is now safe to assert a sync signal. This sets all timers to 0 on next active clock edge - AT91C_BASE_TCB->TCB_BCR = 1; // assert Sync (set all timers to 0 on next active clock edge) - // at the next (3rd) ssp_clk rising edge, TC1 will be reset (and not generate a clock signal to TC0) - // at the next (4th) ssp_clk rising edge, TC0 (the low word of our counter) will be reset. From now on, + AT91C_BASE_TCB->TCB_BCR = 1; // assert Sync (set all timers to 0 on next active clock edge) + // at the next (3rd/7th) ssp_clk rising edge, TC1 will be reset (and not generate a clock signal to TC0) + // at the next (4th/8th) ssp_clk rising edge, TC0 (the low word of our counter) will be reset. From now on, // whenever the last three bits of our counter go 0, we can be sure to be in the middle of a frame transfer. - // (just started with the transfer of the 4th Bit). + // (just started with the transfer of the 3rd Bit). // The high word of the counter (TC2) will not reset until the low word (TC0) overflows. Therefore need to wait quite some time before // we can use the counter. while (AT91C_BASE_TC0->TC_CV < 0xFFFF); @@ -445,19 +456,17 @@ void ResetSspClk(void) { while (AT91C_BASE_TC2->TC_CV > 0); } - uint32_t GetCountSspClk(){ uint32_t hi, lo; - do { + do { hi = AT91C_BASE_TC2->TC_CV; lo = AT91C_BASE_TC0->TC_CV; - } while(hi != AT91C_BASE_TC2->TC_CV); - + } while (hi != AT91C_BASE_TC2->TC_CV); + return (hi << 16) | lo; } - // ------------------------------------------------------------------------- // Timer for bitbanging, or LF stuff when you need a very precis timer // 1us = 1.5ticks @@ -476,11 +485,11 @@ void StartTicks(void){ AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // re-enable timer and wait for TC0 // second configure TC0 (lower, 0x0000FFFF) 16 bit counter - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32 - AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | - AT91C_TC_ACPA_CLEAR | // RA comperator clears TIOA (carry bit) - AT91C_TC_ACPC_SET | // RC comperator sets TIOA (carry bit) - AT91C_TC_ASWTRG_SET; // SWTriger sets TIOA (carry bit) + AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK | // MCK(48MHz) / 32 + AT91C_TC_WAVE | AT91C_TC_WAVESEL_UP_AUTO | + AT91C_TC_ACPA_CLEAR | // RA comperator clears TIOA (carry bit) + AT91C_TC_ACPC_SET | // RC comperator sets TIOA (carry bit) + AT91C_TC_ASWTRG_SET; // SWTriger sets TIOA (carry bit) AT91C_BASE_TC0->TC_RC = 0; // set TIOA (carry bit) on overflow, return to zero AT91C_BASE_TC0->TC_RA = 1; // clear carry bit on next clock cycle AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; // reset and re-enable timer @@ -517,7 +526,7 @@ void WaitTicks(uint32_t ticks){ } -// Wait / Spindelay in us (microseconds) +// Wait / Spindelay in us (microseconds) // 1us = 1.5ticks. void WaitUS(uint16_t us){ WaitTicks( (uint32_t)us * 3 / 2 ) ; @@ -546,7 +555,7 @@ void ResetTimer(AT91PS_TC timer){ // stop clock void StopTicks(void){ AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; - AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; + AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS; } diff --git a/armsrc/util.h b/armsrc/util.h index 7b3d0849..14ac5e10 100644 --- a/armsrc/util.h +++ b/armsrc/util.h @@ -66,7 +66,7 @@ uint32_t RAMFUNC GetDeltaCountUS(); void StartCountSspClk(); void ResetSspClk(void); -uint32_t RAMFUNC GetCountSspClk(); +uint32_t GetCountSspClk(); extern void StartTicks(void); extern uint32_t GetTicks(void); diff --git a/client/cmdhf15.c b/client/cmdhf15.c index c2a13354..6bcad201 100644 --- a/client/cmdhf15.c +++ b/client/cmdhf15.c @@ -464,7 +464,7 @@ int CmdHF15CmdRaw (const char *cmd) { char *hexout; - if (strlen(cmd)<3) { + if (strlen(cmd)<2) { PrintAndLog("Usage: hf 15 cmd raw [-r] [-2] [-c] <0A 0B 0C ... hex>"); PrintAndLog(" -r do not read response"); PrintAndLog(" -2 use slower '1 out of 256' mode"); @@ -526,22 +526,31 @@ int CmdHF15CmdRaw (const char *cmd) { SendCommand(&c); if (reply) { - if (WaitForResponseTimeout(CMD_ACK,&resp,1000)) { + if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { recv = resp.d.asBytes; - PrintAndLog("received %i octets",resp.arg[0]); - hexout = (char *)malloc(resp.arg[0] * 3 + 1); - if (hexout != NULL) { - for (int i = 0; i < resp.arg[0]; i++) { // data in hex - sprintf(&hexout[i * 3], "%02X ", recv[i]); + int recv_len = resp.arg[0]; + if (recv_len == 0) { + PrintAndLog("received SOF only. Maybe Picopass/iCLASS?"); + } else if (recv_len > 0) { + PrintAndLog("received %i octets", recv_len); + hexout = (char *)malloc(resp.arg[0] * 3 + 1); + if (hexout != NULL) { + for (int i = 0; i < resp.arg[0]; i++) { // data in hex + sprintf(&hexout[i * 3], "%02X ", recv[i]); + } + PrintAndLog("%s", hexout); + free(hexout); } - PrintAndLog("%s", hexout); - free(hexout); + } else if (recv_len == -1) { + PrintAndLog("card didn't respond"); + } else if (recv_len == -2) { + PrintAndLog("receive buffer overflow"); } } else { PrintAndLog("timeout while waiting for reply."); } - - } // if reply + } + return 0; } diff --git a/client/cmdhflist.c b/client/cmdhflist.c index 07a286cc..29d10d92 100644 --- a/client/cmdhflist.c +++ b/client/cmdhflist.c @@ -903,8 +903,6 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui // adjust for different time scales if (protocol == ICLASS || protocol == ISO_15693) { - first_timestamp *= 32; - timestamp *= 32; duration *= 32; } @@ -1037,10 +1035,6 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui if (showWaitCycles && !isResponse && next_record_is_response(tracepos, trace)) { uint32_t next_timestamp = *((uint32_t *)(trace + tracepos)); - // adjust for different time scales - if (protocol == ICLASS || protocol == ISO_15693) { - next_timestamp *= 32; - } PrintAndLog(" %10d | %10d | %s | fdt (Frame Delay Time): %d", (EndOfTransmissionTimestamp - first_timestamp), diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 44b2428040ab74e66878d78bf7cc7a79ee3c7266..3899059714dcdd9904577d9aaa21d64cf3efdbd5 100644 GIT binary patch literal 42175 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�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
+G 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 z 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 Date: Tue, 22 Oct 2019 21:02:02 +0200 Subject: [PATCH 2/4] 'hf 14b' formatting * renaming a few functions * whitespace * moving a bit towards RRG repo --- client/cmdhf.c | 2 +- client/cmdhf14b.c | 401 ++++++++++++++++++++++++++-------------------- client/cmdhf14b.h | 2 +- 3 files changed, 226 insertions(+), 179 deletions(-) diff --git a/client/cmdhf.c b/client/cmdhf.c index 6d25cac0..83170cae 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -61,7 +61,7 @@ int CmdHFSearch(const char *Cmd){ return ans; } //14b is longest test currently (and rarest chip type) ... put last - ans = HF14BInfo(false); + ans = infoHF14B(false); if (ans) { PrintAndLog("\nValid ISO14443B Tag Found - Quiting Search\n"); return ans; diff --git a/client/cmdhf14b.c b/client/cmdhf14b.c index 7cd55476..8a83df8f 100644 --- a/client/cmdhf14b.c +++ b/client/cmdhf14b.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "iso14443crc.h" #include "comms.h" #include "graph.h" @@ -25,101 +26,98 @@ #include "taginfo.h" -static int CmdHelp(const char *Cmd); - -int CmdHF14BList(const char *Cmd) -{ +int CmdHF14BList(const char *Cmd) { PrintAndLog("Deprecated command, use 'hf list 14b' instead"); - return 0; } -int CmdHF14BSim(const char *Cmd) -{ + +int CmdHF14BSim(const char *Cmd) { UsbCommand c={CMD_SIMULATE_TAG_ISO_14443B}; clearCommandBuffer(); SendCommand(&c); return 0; } -int CmdHF14BSnoop(const char *Cmd) -{ + +int CmdHF14BSnoop(const char *Cmd) { UsbCommand c = {CMD_SNOOP_ISO_14443B}; clearCommandBuffer(); SendCommand(&c); return 0; } + /* New command to read the contents of a SRI512 tag * SRI512 tags are ISO14443-B modulated memory tags, * this command just dumps the contents of the memory */ -int CmdSri512Read(const char *Cmd) -{ +int CmdSri512Read(const char *Cmd) { UsbCommand c = {CMD_READ_SRI512_TAG, {strtol(Cmd, NULL, 0), 0, 0}}; clearCommandBuffer(); SendCommand(&c); return 0; } + /* New command to read the contents of a SRIX4K tag * SRIX4K tags are ISO14443-B modulated memory tags, * this command just dumps the contents of the memory/ */ -int CmdSrix4kRead(const char *Cmd) -{ +int CmdSrix4kRead(const char *Cmd) { UsbCommand c = {CMD_READ_SRIX4K_TAG, {strtol(Cmd, NULL, 0), 0, 0}}; clearCommandBuffer(); SendCommand(&c); return 0; } -int rawClose(void){ + +static bool switch_off_field_14b(void) { UsbCommand resp; UsbCommand c = {CMD_ISO_14443B_COMMAND, {0, 0, 0}}; clearCommandBuffer(); SendCommand(&c); - if (!WaitForResponseTimeout(CMD_ACK,&resp,1000)) { - return 0; + if (!WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { + return false; } - return 0; + return false; } -int HF14BCmdRaw(bool reply, bool *crc, bool power, uint8_t *data, uint8_t *datalen, bool verbose){ + +int HF14BCmdRaw(bool reply, bool *crc, bool power, uint8_t *data, uint8_t *datalen, bool verbose) { UsbCommand resp; UsbCommand c = {CMD_ISO_14443B_COMMAND, {0, 0, 0}}; // len,recv,power - if(*crc) - { + if (*crc) { uint8_t first, second; ComputeCrc14443(CRC_14443_B, data, *datalen, &first, &second); data[*datalen] = first; data[*datalen + 1] = second; *datalen += 2; } - + c.arg[0] = *datalen; c.arg[1] = reply; c.arg[2] = power; - memcpy(c.d.asBytes,data,*datalen); + memcpy(c.d.asBytes,data, *datalen); clearCommandBuffer(); SendCommand(&c); - - if (!reply) return 1; - if (!WaitForResponseTimeout(CMD_ACK,&resp,1000)) { + if (!reply) return 1; + + if (!WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { if (verbose) PrintAndLog("timeout while waiting for reply."); return 0; } *datalen = resp.arg[0]; if (verbose) PrintAndLog("received %u octets", *datalen); - if(*datalen<2) return 0; + if (*datalen < 2) return 0; memcpy(data, resp.d.asBytes, *datalen); if (verbose) PrintAndLog("%s", sprint_hex(data, *datalen)); uint8_t first, second; ComputeCrc14443(CRC_14443_B, data, *datalen-2, &first, &second); - if(data[*datalen-2] == first && data[*datalen-1] == second) { + if (data[*datalen-2] == first && data[*datalen-1] == second) { if (verbose) PrintAndLog("CRC OK"); *crc = true; } else { @@ -129,7 +127,8 @@ int HF14BCmdRaw(bool reply, bool *crc, bool power, uint8_t *data, uint8_t *datal return 1; } -int CmdHF14BCmdRaw (const char *Cmd) { + +static int CmdHF14BCmdRaw (const char *Cmd) { bool reply = true; bool crc = false; bool power = false; @@ -140,7 +139,7 @@ int CmdHF14BCmdRaw (const char *Cmd) { uint8_t datalen = 0; unsigned int temp; int i = 0; - if (strlen(Cmd)<3) { + if (strlen(Cmd) < 3) { PrintAndLog("Usage: hf 14b raw [-r] [-c] [-p] [-s || -ss] <0A 0B 0C ... hex>"); PrintAndLog(" -r do not read response"); PrintAndLog(" -c calculate and append CRC"); @@ -151,28 +150,28 @@ int CmdHF14BCmdRaw (const char *Cmd) { } // strip - while (*Cmd==' ' || *Cmd=='\t') Cmd++; - - while (Cmd[i]!='\0') { - if (Cmd[i]==' ' || Cmd[i]=='\t') { i++; continue; } - if (Cmd[i]=='-') { + while (*Cmd == ' ' || *Cmd == '\t') Cmd++; + + while (Cmd[i] != '\0') { + if (Cmd[i] == ' ' || Cmd[i] == '\t') { i++; continue; } + if (Cmd[i] == '-') { switch (Cmd[i+1]) { - case 'r': - case 'R': + case 'r': + case 'R': reply = false; break; case 'c': - case 'C': + case 'C': crc = true; break; - case 'p': - case 'P': + case 'p': + case 'P': power = true; break; case 's': case 'S': select = true; - if (Cmd[i+2]=='s' || Cmd[i+2]=='S') { + if (Cmd[i+2] == 's' || Cmd[i+2] == 'S') { SRx = true; i++; } @@ -181,34 +180,33 @@ int CmdHF14BCmdRaw (const char *Cmd) { PrintAndLog("Invalid option"); return 0; } - i+=2; + i += 2; continue; } - if ((Cmd[i]>='0' && Cmd[i]<='9') || - (Cmd[i]>='a' && Cmd[i]<='f') || - (Cmd[i]>='A' && Cmd[i]<='F') ) { - buf[strlen(buf)+1]=0; - buf[strlen(buf)]=Cmd[i]; + if ((Cmd[i] >= '0' && Cmd[i] <= '9') || + (Cmd[i] >= 'a' && Cmd[i] <= 'f') || + (Cmd[i] >= 'A' && Cmd[i] <= 'F') ) { + buf[strlen(buf)+1] = 0; + buf[strlen(buf)] = Cmd[i]; i++; - - if (strlen(buf)>=2) { - sscanf(buf,"%x",&temp); - data[datalen++]=(uint8_t)(temp & 0xff); - *buf=0; + + if (strlen(buf) >= 2) { + sscanf(buf, "%x", &temp); + data[datalen++] = (uint8_t)(temp & 0xff); + *buf = 0; } continue; } PrintAndLog("Invalid char on input"); return 0; } - if (datalen == 0) - { + if (datalen == 0) { PrintAndLog("Missing data input"); return 0; } - if (select){ //auto select 14b tag - uint8_t cmd2[16]; + if (select) { //auto select 14b tag + uint8_t cmd2[16]; bool crc2 = true; uint8_t cmdLen; @@ -225,11 +223,11 @@ int CmdHF14BCmdRaw (const char *Cmd) { cmd2[2] = 0x08; } - if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false) == 0) return switch_off_field_14b(); + + if (SRx && (cmdLen != 3 || !crc2) ) return switch_off_field_14b(); + else if (cmd2[0] != 0x50 || cmdLen != 14 || !crc2) return switch_off_field_14b(); - if ( SRx && (cmdLen != 3 || !crc2) ) return rawClose(); - else if (cmd2[0] != 0x50 || cmdLen != 14 || !crc2) return rawClose(); - uint8_t chipID = 0; if (SRx) { // select @@ -239,7 +237,7 @@ int CmdHF14BCmdRaw (const char *Cmd) { cmdLen = 2; } else { // attrib - cmd2[0] = 0x1D; + cmd2[0] = 0x1D; // UID from cmd2[1 - 4] cmd2[5] = 0x00; cmd2[6] = 0x08; @@ -248,39 +246,40 @@ int CmdHF14BCmdRaw (const char *Cmd) { cmdLen = 9; } - if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false) == 0) return switch_off_field_14b(); - if (cmdLen != 3 || !crc2) return rawClose(); - if (SRx && cmd2[0] != chipID) return rawClose(); + if (cmdLen != 3 || !crc2) return switch_off_field_14b(); + if (SRx && cmd2[0] != chipID) return switch_off_field_14b(); } return HF14BCmdRaw(reply, &crc, power, data, &datalen, true); } + // print full atqb info -static void print_atqb_resp(uint8_t *data){ +static void print_atqb_resp(uint8_t *data) { //PrintAndLog (" UID: %s", sprint_hex(data+1,4)); - PrintAndLog (" App Data: %s", sprint_hex(data+5,4)); - PrintAndLog (" Protocol: %s", sprint_hex(data+9,3)); + PrintAndLog(" App Data: %s", sprint_hex(data+5,4)); + PrintAndLog(" Protocol: %s", sprint_hex(data+9,3)); uint8_t BitRate = data[9]; - if (!BitRate) + if (!BitRate) PrintAndLog (" Bit Rate: 106 kbit/s only PICC <-> PCD"); if (BitRate & 0x10) PrintAndLog (" Bit Rate: 212 kbit/s PICC -> PCD supported"); if (BitRate & 0x20) - PrintAndLog (" Bit Rate: 424 kbit/s PICC -> PCD supported"); + PrintAndLog (" Bit Rate: 424 kbit/s PICC -> PCD supported"); if (BitRate & 0x40) - PrintAndLog (" Bit Rate: 847 kbit/s PICC -> PCD supported"); + PrintAndLog (" Bit Rate: 847 kbit/s PICC -> PCD supported"); if (BitRate & 0x01) PrintAndLog (" Bit Rate: 212 kbit/s PICC <- PCD supported"); if (BitRate & 0x02) - PrintAndLog (" Bit Rate: 424 kbit/s PICC <- PCD supported"); + PrintAndLog (" Bit Rate: 424 kbit/s PICC <- PCD supported"); if (BitRate & 0x04) - PrintAndLog (" Bit Rate: 847 kbit/s PICC <- PCD supported"); - if (BitRate & 0x80) + PrintAndLog (" Bit Rate: 847 kbit/s PICC <- PCD supported"); + if (BitRate & 0x80) PrintAndLog (" Same bit rate <-> required"); - uint16_t maxFrame = data[10]>>4; - if (maxFrame < 5) + uint16_t maxFrame = data[10] >> 4; + if (maxFrame < 5) maxFrame = 8*maxFrame + 16; else if (maxFrame == 5) maxFrame = 64; @@ -293,7 +292,7 @@ static void print_atqb_resp(uint8_t *data){ else maxFrame = 257; - PrintAndLog ("Max Frame Size: %u%s",maxFrame, (maxFrame == 257) ? "+ RFU" : ""); + PrintAndLog ("Max Frame Size: %u%s", maxFrame, (maxFrame == 257) ? "+ RFU" : ""); uint8_t protocolT = data[10] & 0xF; PrintAndLog (" Protocol Type: Protocol is %scompliant with ISO/IEC 14443-4",(protocolT) ? "" : "not " ); @@ -302,32 +301,32 @@ static void print_atqb_resp(uint8_t *data){ PrintAndLog (" Frame Options: NAD is %ssupported",(data[11]&2) ? "" : "not "); PrintAndLog (" Frame Options: CID is %ssupported",(data[11]&1) ? "" : "not "); PrintAndLog ("Max Buf Length: %u (MBLI) %s",data[14]>>4, (data[14] & 0xF0) ? "" : "not supported"); - + return; } -int print_ST_Lock_info(uint8_t model){ +int print_ST_Lock_info(uint8_t model) { //assume connection open and tag selected... uint8_t data[16] = {0x00}; uint8_t datalen = 2; bool crc = true; uint8_t resplen; - uint8_t blk1; + uint8_t blk1; data[0] = 0x08; - if (model == 0x2) { //SR176 has special command: - data[1] = 0xf; - resplen = 4; + if (model == 0x02) { //SR176 has special command: + data[1] = 0x0f; + resplen = 4; } else { data[1] = 0xff; resplen = 6; } //std read cmd - if (HF14BCmdRaw(true, &crc, true, data, &datalen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc, true, data, &datalen, false) == 0) return switch_off_field_14b(); - if (datalen != resplen || !crc) return rawClose(); + if (datalen != resplen || !crc) return switch_off_field_14b(); PrintAndLog("Chip Write Protection Bits:"); // now interpret the data @@ -339,7 +338,7 @@ int print_ST_Lock_info(uint8_t model){ blk1 = 9; PrintAndLog(" raw: %s",printBits(1,data+3)); PrintAndLog(" 07/08:%slocked", (data[3] & 1) ? " not " : " " ); - for (uint8_t i = 1; i<8; i++){ + for (uint8_t i = 1; i < 8; i++){ PrintAndLog(" %02u:%slocked", blk1, (data[3] & (1 << i)) ? " not " : " " ); blk1++; } @@ -349,9 +348,9 @@ int print_ST_Lock_info(uint8_t model){ case 0xC: // (SRT512) //need data[2] and data[3] blk1 = 0; - PrintAndLog(" raw: %s",printBits(2,data+2)); - for (uint8_t b=2; b<4; b++){ - for (uint8_t i=0; i<8; i++){ + PrintAndLog(" raw: %s", printBits(2,data+2)); + for (uint8_t b = 2; b < 4; b++) { + for (uint8_t i = 0; i < 8; i++) { PrintAndLog(" %02u:%slocked", blk1, (data[b] & (1 << i)) ? " not " : " " ); blk1++; } @@ -360,29 +359,31 @@ int print_ST_Lock_info(uint8_t model){ case 0x2: // (SR176) //need data[2] blk1 = 0; - PrintAndLog(" raw: %s",printBits(1,data+2)); - for (uint8_t i = 0; i<8; i++){ + PrintAndLog(" raw: %s",printBits(1, data+2)); + for (uint8_t i = 0; i < 8; i++){ PrintAndLog(" %02u/%02u:%slocked", blk1, blk1+1, (data[2] & (1 << i)) ? " " : " not " ); - blk1+=2; + blk1 += 2; } break; default: - return rawClose(); + return switch_off_field_14b(); } return 1; } + // print UID info from SRx chips (ST Microelectronics) -static void print_st_general_info(uint8_t *data){ +static void print_st_general_info(uint8_t *data) { //uid = first 8 bytes in data - PrintAndLog(" UID: %s", sprint_hex(SwapEndian64(data,8,8),8)); + PrintAndLog(" UID: %s", sprint_hex(SwapEndian64(data, 8, 8), 8)); PrintAndLog(" MFG: %02X, %s", data[6], getManufacturerName(data[6])); PrintAndLog(" Chip: %02X, %s", data[5], getChipInfo(data[6], data[5])); return; } + // 14b get and print UID only (general info) -int HF14BStdReader(uint8_t *data, uint8_t *datalen){ +int HF14BStdReader(uint8_t *data, uint8_t *datalen) { //05 00 00 = find one tag in field //1d xx xx xx xx 00 08 01 00 = attrib xx=UID (resp 10 [f9 e0]) //a3 = ? (resp 03 [e2 c2]) @@ -407,18 +408,18 @@ int HF14BStdReader(uint8_t *data, uint8_t *datalen){ data[1] = 0x00; data[2] = 0x08; - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc, true, data, datalen, false) == 0) return switch_off_field_14b(); - if (data[0] != 0x50 || *datalen != 14 || !crc) return rawClose(); + if (data[0] != 0x50 || *datalen != 14 || !crc) return switch_off_field_14b(); PrintAndLog ("\n14443-3b tag found:"); - PrintAndLog (" UID: %s", sprint_hex(data+1,4)); + PrintAndLog (" UID: %s", sprint_hex(data+1, 4)); - uint8_t cmd2[16]; + uint8_t cmd2[16]; uint8_t cmdLen = 3; bool crc2 = true; - cmd2[0] = 0x1D; + cmd2[0] = 0x1D; // UID from data[1 - 4] cmd2[1] = data[1]; cmd2[2] = data[2]; @@ -431,28 +432,29 @@ int HF14BStdReader(uint8_t *data, uint8_t *datalen){ cmdLen = 9; // attrib - if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc2, true, cmd2, &cmdLen, false) == 0) return switch_off_field_14b(); - if (cmdLen != 3 || !crc2) return rawClose(); + if (cmdLen != 3 || !crc2) return switch_off_field_14b(); // add attrib responce to data data[14] = cmd2[0]; - rawClose(); + switch_off_field_14b(); return 1; } + // 14b get and print Full Info (as much as we know) -int HF14BStdInfo(uint8_t *data, uint8_t *datalen){ - if (!HF14BStdReader(data,datalen)) return 0; +static bool HF14B_Std_Info(uint8_t *data, uint8_t *datalen) { + if (!HF14BStdReader(data, datalen)) return false; //add more info here print_atqb_resp(data); - - return 1; + return true; } + // SRx get and print general info about SRx chip from UID -int HF14B_ST_Reader(uint8_t *data, uint8_t *datalen, bool closeCon){ +static bool HF14B_ST_Reader(uint8_t *data, uint8_t *datalen, bool closeCon){ bool crc = true; *datalen = 2; //wake cmd @@ -461,9 +463,9 @@ int HF14B_ST_Reader(uint8_t *data, uint8_t *datalen, bool closeCon){ //leave power on // verbose on for now for testing - turn off when functional - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc, true, data, datalen, false) == 0) return switch_off_field_14b(); - if (*datalen != 3 || !crc) return rawClose(); + if (*datalen != 3 || !crc) return switch_off_field_14b(); uint8_t chipID = data[0]; // select @@ -472,118 +474,143 @@ int HF14B_ST_Reader(uint8_t *data, uint8_t *datalen, bool closeCon){ *datalen = 2; //leave power on - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc, true, data, datalen, false) == 0) return switch_off_field_14b(); - if (*datalen != 3 || !crc || data[0] != chipID) return rawClose(); + if (*datalen != 3 || !crc || data[0] != chipID) return switch_off_field_14b(); // get uid data[0] = 0x0B; *datalen = 1; //leave power on - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)==0) return rawClose(); + if (HF14BCmdRaw(true, &crc, true, data, datalen, false) == 0) return switch_off_field_14b(); - if (*datalen != 10 || !crc) return rawClose(); + if (*datalen != 10 || !crc) return switch_off_field_14b(); //power off ? - if (closeCon) rawClose(); + if (closeCon) switch_off_field_14b(); PrintAndLog("\n14443-3b ST tag found:"); print_st_general_info(data); return 1; } -// SRx get and print full info (needs more info...) -int HF14B_ST_Info(uint8_t *data, uint8_t *datalen){ - if (!HF14B_ST_Reader(data, datalen, false)) return 0; - - //add locking bit information here. - if (print_ST_Lock_info(data[5]>>2)) - rawClose(); - return 1; +// SRx get and print full info (needs more info...) +static bool HF14B_ST_Info(bool verbose) { + uint8_t data[100]; + uint8_t datalen; + + if (!HF14B_ST_Reader(data, &datalen, false)) return false; + + //add locking bit information here. + if (print_ST_Lock_info(data[5] >> 2)) + switch_off_field_14b(); + + return true; } + // test for other 14b type tags (mimic another reader - don't have tags to identify) -int HF14B_Other_Reader(uint8_t *data, uint8_t *datalen){ +static bool HF14B_Other_Reader(bool verbose) { + uint8_t data[4]; + uint8_t datalen; + bool crc = true; - *datalen = 4; + datalen = 4; //std read cmd data[0] = 0x00; data[1] = 0x0b; data[2] = 0x3f; data[3] = 0x80; - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)!=0) { - if (*datalen > 2 || !crc) { + if (HF14BCmdRaw(true, &crc, true, data, &datalen, false) != 0) { + if (datalen > 2 || !crc) { PrintAndLog ("\n14443-3b tag found:"); PrintAndLog ("Unknown tag type answered to a 0x000b3f80 command ans:"); - PrintAndLog ("%s",sprint_hex(data,*datalen)); - rawClose(); - return 1; + PrintAndLog ("%s", sprint_hex(data, datalen)); + switch_off_field_14b(); + return true; } } crc = false; - *datalen = 1; + datalen = 1; data[0] = 0x0a; - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)!=0) { - if (*datalen > 0) { + if (HF14BCmdRaw(true, &crc, true, data, &datalen, false) != 0) { + if (datalen > 0) { PrintAndLog ("\n14443-3b tag found:"); PrintAndLog ("Unknown tag type answered to a 0x0A command ans:"); - PrintAndLog ("%s",sprint_hex(data,*datalen)); - rawClose(); - return 1; + PrintAndLog ("%s", sprint_hex(data, datalen)); + switch_off_field_14b(); + return true; } } - + crc = false; - *datalen = 1; + datalen = 1; data[0] = 0x0c; - if (HF14BCmdRaw(true, &crc, true, data, datalen, false)!=0) { - if (*datalen > 0) { + if (HF14BCmdRaw(true, &crc, true, data, &datalen, false) != 0) { + if (datalen > 0) { PrintAndLog ("\n14443-3b tag found:"); PrintAndLog ("Unknown tag type answered to a 0x0C command ans:"); - PrintAndLog ("%s",sprint_hex(data,*datalen)); - rawClose(); - return 1; + PrintAndLog ("%s", sprint_hex(data, datalen)); + switch_off_field_14b(); + return true; } } - rawClose(); + switch_off_field_14b(); + return false; +} + + +// get and print all info known about any known 14b tag +static int usage_hf_14b_info(void) { + PrintAndLogEx(NORMAL, "Usage: hf 14b info [h] [s]"); + PrintAndLogEx(NORMAL, "Options:"); + PrintAndLogEx(NORMAL, " h this help"); + PrintAndLogEx(NORMAL, " s silently"); + PrintAndLogEx(NORMAL, "Example:"); + PrintAndLogEx(NORMAL, " hf 14b info"); return 0; } -// get and print all info known about any known 14b tag -int HF14BInfo(bool verbose){ +int infoHF14B(bool verbose) { uint8_t data[100]; - uint8_t datalen = 5; - + uint8_t datalen; + // try std 14b (atqb) - if (HF14BStdInfo(data, &datalen)) return 1; + if (HF14B_Std_Info(data, &datalen)) return 1; // try st 14b - if (HF14B_ST_Info(data, &datalen)) return 1; + if (HF14B_ST_Info(verbose)) return 1; // try unknown 14b read commands (to be identified later) // could be read of calypso, CEPAS, moneo, or pico pass. - if (HF14B_Other_Reader(data, &datalen)) return 1; + if (HF14B_Other_Reader(verbose)) return 1; if (verbose) PrintAndLog("no 14443B tag found"); return 0; } + // menu command to get and print all info known about any known 14b tag -int CmdHF14Binfo(const char *Cmd){ - return HF14BInfo(true); +static int CmdHF14Binfo(const char *Cmd){ + char cmdp = tolower(param_getchar(Cmd, 0)); + if (cmdp == 'h') return usage_hf_14b_info(); + + bool verbose = !(cmdp == 's'); + return infoHF14B(verbose); } + // get and print general info about all known 14b chips -int HF14BReader(bool verbose){ +int readHF14B(bool verbose){ uint8_t data[100]; uint8_t datalen = 5; - + // try std 14b (atqb) if (HF14BStdReader(data, &datalen)) return 1; @@ -592,33 +619,50 @@ int HF14BReader(bool verbose){ // try unknown 14b read commands (to be identified later) // could be read of calypso, CEPAS, moneo, or pico pass. - if (HF14B_Other_Reader(data, &datalen)) return 1; + if (HF14B_Other_Reader(verbose)) return 1; if (verbose) PrintAndLog("no 14443B tag found"); return 0; } + // menu command to get and print general info about all known 14b chips -int CmdHF14BReader(const char *Cmd){ - return HF14BReader(true); +static int usage_hf_14b_reader(void) { + PrintAndLogEx(NORMAL, "Usage: hf 14b reader [h] [s]"); + PrintAndLogEx(NORMAL, "Options:"); + PrintAndLogEx(NORMAL, " h this help"); + PrintAndLogEx(NORMAL, " s silently"); + PrintAndLogEx(NORMAL, "Example:"); + PrintAndLogEx(NORMAL, " hf 14b reader"); + return 0; } -int CmdSriWrite( const char *Cmd){ + +static int CmdHF14BReader(const char *Cmd) { + char cmdp = tolower(param_getchar(Cmd, 0)); + if (cmdp == 'h') return usage_hf_14b_reader(); + + bool verbose = !(cmdp == 's'); + return readHF14B(verbose); +} + + +int CmdSriWrite(const char *Cmd) { /* * For SRIX4K blocks 00 - 7F * hf 14b raw -c -p 09 $srix4kwblock $srix4kwdata * * For SR512 blocks 00 - 0F * hf 14b raw -c -p 09 $sr512wblock $sr512wdata - * + * * Special block FF = otp_lock_reg block. * Data len 4 bytes- */ - char cmdp = param_getchar(Cmd, 0); + char cmdp = param_getchar(Cmd, 0); uint8_t blockno = -1; uint8_t data[4] = {0x00}; bool isSrix4k = true; - char str[20]; + char str[20]; if (strlen(Cmd) < 1 || cmdp == 'h' || cmdp == 'H') { PrintAndLog("Usage: hf 14b write <1|2> "); @@ -634,43 +678,46 @@ int CmdSriWrite( const char *Cmd){ if ( cmdp == '2' ) isSrix4k = false; - + //blockno = param_get8(Cmd, 1); - - if ( param_gethex(Cmd,1, &blockno, 2) ) { + + if (param_gethex(Cmd,1, &blockno, 2) ) { PrintAndLog("Block number must include 2 HEX symbols"); return 0; } - - if ( isSrix4k ){ - if ( blockno > 0x7f && blockno != 0xff ){ + + if (isSrix4k) { + if (blockno > 0x7f && blockno != 0xff){ PrintAndLog("Block number out of range"); return 0; - } + } } else { - if ( blockno > 0x0f && blockno != 0xff ){ + if (blockno > 0x0f && blockno != 0xff){ PrintAndLog("Block number out of range"); return 0; - } + } } - + if (param_gethex(Cmd, 2, data, 8)) { PrintAndLog("Data must include 8 HEX symbols"); return 0; } - - if ( blockno == 0xff) - PrintAndLog("[%s] Write special block %02X [ %s ]", (isSrix4k)?"SRIX4K":"SRI512" , blockno, sprint_hex(data,4) ); + + if (blockno == 0xff) + PrintAndLog("[%s] Write special block %02X [ %s ]", (isSrix4k)?"SRIX4K":"SRI512", blockno, sprint_hex(data, 4)); else - PrintAndLog("[%s] Write block %02X [ %s ]", (isSrix4k)?"SRIX4K":"SRI512", blockno, sprint_hex(data,4) ); - + PrintAndLog("[%s] Write block %02X [ %s ]", (isSrix4k)?"SRIX4K":"SRI512", blockno, sprint_hex(data, 4)); + sprintf(str, "-c 09 %02x %02x%02x%02x%02x", blockno, data[0], data[1], data[2], data[3]); CmdHF14BCmdRaw(str); return 0; } -static command_t CommandTable[] = + +static int CmdHelp(const char *Cmd); + +static command_t CommandTable[] = { {"help", CmdHelp, 1, "This help"}, {"info", CmdHF14Binfo, 0, "Find and print details about a 14443B tag"}, diff --git a/client/cmdhf14b.h b/client/cmdhf14b.h index 4fcae927..4897d879 100644 --- a/client/cmdhf14b.h +++ b/client/cmdhf14b.h @@ -21,6 +21,6 @@ int CmdHF14BSnoop(const char *Cmd); int CmdSri512Read(const char *Cmd); int CmdSrix4kRead(const char *Cmd); int CmdHF14BWrite( const char *cmd); -int HF14BInfo(bool verbose); +int infoHF14B(bool verbose); #endif From a3bef9863b9e06a820c7559b9caffeaaa1bb575b Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Wed, 23 Oct 2019 09:09:13 +0200 Subject: [PATCH 3/4] iso14443b: trying to approach iClass * decode and handle SOF only responses in Handle14443bSamplesDemod() * allow 1 byte commands with 'hf 14b raw' --- armsrc/iso14443b.c | 94 ++++++++++++++++++++++++---------------------- client/cmdhf14b.c | 20 ++++++++-- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index f276158f..6434409d 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -586,9 +586,10 @@ static RAMFUNC int Handle14443bSamplesDemod(int ci, int cq) Demod.state = DEMOD_UNSYNCD; } else { LED_C_ON(); // Got SOF - Demod.state = DEMOD_AWAITING_START_BIT; Demod.posCount = 0; + Demod.bitCount = 0; Demod.len = 0; + Demod.state = DEMOD_AWAITING_START_BIT; /* this had been used to add RSSI (Received Signal Strength Indication) to traces. Currently not implemented. Demod.metricN = 0; Demod.metric = 0; @@ -605,13 +606,16 @@ static RAMFUNC int Handle14443bSamplesDemod(int ci, int cq) case DEMOD_AWAITING_START_BIT: Demod.posCount++; MAKE_SOFT_DECISION(); - if(v > 0) { - if(Demod.posCount > 3*2) { // max 19us between characters = 16 1/fs, max 3 etu after low phase of SOF = 24 1/fs - Demod.state = DEMOD_UNSYNCD; + if (v > 0) { + if (Demod.posCount > 3*2) { // max 19us between characters = 16 1/fs, max 3 etu after low phase of SOF = 24 1/fs LED_C_OFF(); + if (Demod.bitCount == 0 && Demod.len == 0) { // received SOF only, this is valid for iClass/Picopass + return true; + } else { + Demod.state = DEMOD_UNSYNCD; + } } } else { // start bit detected - Demod.bitCount = 0; Demod.posCount = 1; // this was the first half Demod.thisBit = v; Demod.shiftReg = 0; @@ -621,7 +625,7 @@ static RAMFUNC int Handle14443bSamplesDemod(int ci, int cq) case DEMOD_RECEIVING_DATA: MAKE_SOFT_DECISION(); - if(Demod.posCount == 0) { // first half of bit + if (Demod.posCount == 0) { // first half of bit Demod.thisBit = v; Demod.posCount = 1; } else { // second half of bit @@ -637,22 +641,23 @@ static RAMFUNC int Handle14443bSamplesDemod(int ci, int cq) */ Demod.shiftReg >>= 1; - if(Demod.thisBit > 0) { // logic '1' + if (Demod.thisBit > 0) { // logic '1' Demod.shiftReg |= 0x200; } Demod.bitCount++; - if(Demod.bitCount == 10) { + if (Demod.bitCount == 10) { uint16_t s = Demod.shiftReg; - if((s & 0x200) && !(s & 0x001)) { // stop bit == '1', start bit == '0' + if ((s & 0x200) && !(s & 0x001)) { // stop bit == '1', start bit == '0' uint8_t b = (s >> 1); Demod.output[Demod.len] = b; Demod.len++; + Demod.bitCount = 0; Demod.state = DEMOD_AWAITING_START_BIT; } else { Demod.state = DEMOD_UNSYNCD; LED_C_OFF(); - if(s == 0x000) { + if (s == 0x000) { // This is EOF (start, stop and all data bits == '0' return true; } @@ -693,8 +698,8 @@ static void DemodInit(uint8_t *data) * Demodulate the samples we received from the tag, also log to tracebuffer * quiet: set to 'true' to disable debug output */ -static void GetSamplesFor14443bDemod(int timeout, bool quiet) -{ +static int GetSamplesFor14443bDemod(int timeout, bool quiet) { + int ret = 0; int maxBehindBy = 0; bool gotFrame = false; int lastRxCounter, samples = 0; @@ -750,12 +755,14 @@ static void GetSamplesFor14443bDemod(int timeout, bool quiet) } samples++; - if(Handle14443bSamplesDemod(ci, cq)) { + if (Handle14443bSamplesDemod(ci, cq)) { + ret = Demod.len; gotFrame = true; break; } if(samples > timeout && Demod.state < DEMOD_PHASE_REF_TRAINING) { + ret = -1; LED_C_OFF(); break; } @@ -764,10 +771,14 @@ static void GetSamplesFor14443bDemod(int timeout, bool quiet) FpgaDisableSscDma(); if (!quiet) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Demod.len = %d, Demod.sumI = %d, Demod.sumQ = %d", maxBehindBy, samples, gotFrame, Demod.len, Demod.sumI, Demod.sumQ); - //Tracing - if (Demod.len > 0) { - LogTrace(Demod.output, Demod.len, 0, 0, NULL, false); + + if (ret < 0) { + return ret; } + //Tracing + LogTrace(Demod.output, Demod.len, 0, 0, NULL, false); + + return ret; } @@ -858,8 +869,7 @@ static void CodeAndTransmit14443bAsReader(const uint8_t *cmd, int len) /* Sends an APDU to the tag * TODO: check CRC and preamble */ -int iso14443b_apdu(uint8_t const *message, size_t message_length, uint8_t *response) -{ +int iso14443b_apdu(uint8_t const *message, size_t message_length, uint8_t *response) { LED_A_ON(); uint8_t message_frame[message_length + 4]; // PCB @@ -874,21 +884,19 @@ int iso14443b_apdu(uint8_t const *message, size_t message_length, uint8_t *respo // send CodeAndTransmit14443bAsReader(message_frame, message_length + 4); // get response - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + int ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); FpgaDisableTracing(); - if(Demod.len < 3) - { + if (ret < 3) { LED_A_OFF(); return 0; } // TODO: Check CRC // copy response contents - if(response != NULL) - { + if (response != NULL) { memcpy(response, Demod.output, Demod.len); } LED_A_OFF(); - return Demod.len; + return ret; } /* Perform the ISO 14443 B Card Selection procedure @@ -907,10 +915,9 @@ int iso14443b_select_card() // first, wake up the tag CodeAndTransmit14443bAsReader(wupb, sizeof(wupb)); - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + int ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); // ATQB too short? - if (Demod.len < 14) - { + if (ret < 14) { return 2; } @@ -922,10 +929,9 @@ int iso14443b_select_card() attrib[7] = Demod.output[10] & 0x0F; ComputeCrc14443(CRC_14443_B, attrib, 9, attrib + 9, attrib + 10); CodeAndTransmit14443bAsReader(attrib, sizeof(attrib)); - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); // Answer to ATTRIB too short? - if(Demod.len < 3) - { + if (ret < 3) { return 2; } // reset PCB block number @@ -985,9 +991,9 @@ void ReadSTMemoryIso14443b(uint32_t dwLast) // First command: wake up the tag using the INITIATE command uint8_t cmd1[] = {0x06, 0x00, 0x97, 0x5b}; CodeAndTransmit14443bAsReader(cmd1, sizeof(cmd1)); - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + int ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); - if (Demod.len == 0) { + if (ret < 0) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); DbpString("No response from tag"); LEDsoff(); @@ -1003,7 +1009,7 @@ void ReadSTMemoryIso14443b(uint32_t dwLast) cmd1[1] = Demod.output[0]; ComputeCrc14443(CRC_14443_B, cmd1, 2, &cmd1[2], &cmd1[3]); CodeAndTransmit14443bAsReader(cmd1, sizeof(cmd1)); - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); if (Demod.len != 3) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); Dbprintf("Expected 3 bytes from tag, got %d", Demod.len); @@ -1031,8 +1037,8 @@ void ReadSTMemoryIso14443b(uint32_t dwLast) cmd1[0] = 0x0B; ComputeCrc14443(CRC_14443_B, cmd1, 1 , &cmd1[1], &cmd1[2]); CodeAndTransmit14443bAsReader(cmd1, 3); // Only first three bytes for this one - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); - if (Demod.len != 10) { + ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + if (ret != 10) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); Dbprintf("Expected 10 bytes from tag, got %d", Demod.len); LEDsoff(); @@ -1062,8 +1068,8 @@ void ReadSTMemoryIso14443b(uint32_t dwLast) cmd1[1] = i; ComputeCrc14443(CRC_14443_B, cmd1, 2, &cmd1[2], &cmd1[3]); CodeAndTransmit14443bAsReader(cmd1, sizeof(cmd1)); - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); - if (Demod.len != 6) { // Check if we got an answer from the tag + ret = GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + if (ret != 6) { // Check if we got an answer from the tag FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); DbpString("Expected 6 bytes from tag, got less..."); LEDsoff(); @@ -1071,7 +1077,7 @@ void ReadSTMemoryIso14443b(uint32_t dwLast) } // The check the CRC of the answer (use cmd1 as temporary variable): ComputeCrc14443(CRC_14443_B, Demod.output, 4, &cmd1[2], &cmd1[3]); - if(cmd1[2] != Demod.output[4] || cmd1[3] != Demod.output[5]) { + if (cmd1[2] != Demod.output[4] || cmd1[3] != Demod.output[5]) { Dbprintf("CRC Error reading block! Expected: %04x got: %04x", (cmd1[2]<<8)+cmd1[3], (Demod.output[4]<<8)+Demod.output[5]); // Do not return;, let's go on... (we should retry, maybe ?) @@ -1213,8 +1219,8 @@ void RAMFUNC SnoopIso14443b(void) ReaderIsActive = (Uart.state > STATE_GOT_FALLING_EDGE_OF_SOF); } - if(!ReaderIsActive && triggered) { // no need to try decoding tag data if the reader is sending or not yet triggered - if(Handle14443bSamplesDemod(ci/2, cq/2)) { + if (!ReaderIsActive && triggered) { // no need to try decoding tag data if the reader is sending or not yet triggered + if (Handle14443bSamplesDemod(ci/2, cq/2) >= 0) { //Use samples as a time measurement LogTrace(Demod.output, Demod.len, samples, samples, NULL, false); // And ready to receive another response. @@ -1265,17 +1271,17 @@ void SendRawCommand14443B(uint32_t datalen, uint32_t recv, uint8_t powerfield, u CodeAndTransmit14443bAsReader(data, datalen); - if(recv) { - GetSamplesFor14443bDemod(RECEIVE_SAMPLES_TIMEOUT, true); + if (recv) { + int ret = GetSamplesFor14443bDemod(5*RECEIVE_SAMPLES_TIMEOUT, true); FpgaDisableTracing(); uint16_t iLen = MIN(Demod.len, USB_CMD_DATA_SIZE); - cmd_send(CMD_ACK, iLen, 0, 0, Demod.output, iLen); + cmd_send(CMD_ACK, ret, 0, 0, Demod.output, iLen); } FpgaDisableTracing(); } - if(!powerfield) { + if (!powerfield) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); LED_D_OFF(); } diff --git a/client/cmdhf14b.c b/client/cmdhf14b.c index 8a83df8f..4685f3f5 100644 --- a/client/cmdhf14b.c +++ b/client/cmdhf14b.c @@ -108,9 +108,21 @@ int HF14BCmdRaw(bool reply, bool *crc, bool power, uint8_t *data, uint8_t *datal if (verbose) PrintAndLog("timeout while waiting for reply."); return 0; } - *datalen = resp.arg[0]; - if (verbose) PrintAndLog("received %u octets", *datalen); - if (*datalen < 2) return 0; + + int ret = resp.arg[0]; + if (verbose) { + if (ret < 0) { + PrintAndLog("tag didn't respond"); + } else if (ret == 0) { + PrintAndLog("received SOF only (maybe iCLASS/Picopass)"); + } else { + PrintAndLog("received %u octets", ret); + } + } + + *datalen = ret; + + if (ret < 2) return 0; memcpy(data, resp.d.asBytes, *datalen); if (verbose) PrintAndLog("%s", sprint_hex(data, *datalen)); @@ -139,7 +151,7 @@ static int CmdHF14BCmdRaw (const char *Cmd) { uint8_t datalen = 0; unsigned int temp; int i = 0; - if (strlen(Cmd) < 3) { + if (strlen(Cmd) < 2) { PrintAndLog("Usage: hf 14b raw [-r] [-c] [-p] [-s || -ss] <0A 0B 0C ... hex>"); PrintAndLog(" -r do not read response"); PrintAndLog(" -c calculate and append CRC"); From ece38ef311b28eefb1d716937139aad9ee00985c Mon Sep 17 00:00:00 2001 From: pwpiwi Date: Sun, 27 Oct 2019 16:51:27 +0100 Subject: [PATCH 4/4] fix 'hf iclass reader' and 'hf iclass readblk' * don't do READCHECK when not trying to authenticate * standard LED handling * remove unused FLAG_ICLASS_READER_ONLY_ONCE and FLAG_ICLASS_READER_ONE_TRY * sanity check for negative times in TransmitTo15693Tag() * increase reader timeout for 'hf 15' functions to be enough for slot 7 answers to ACTALL * add 'hf iclass permute' inspired by RRG repository * whitespace fixes --- armsrc/appmain.c | 7 +- armsrc/iclass.c | 277 ++++++------ armsrc/iclass.h | 4 +- armsrc/iso15693.c | 112 +++-- armsrc/iso15693.h | 2 + client/cmdhficlass.c | 821 ++++++++++++++++++++--------------- client/cmdhficlass.h | 24 +- client/loclass/elite_crack.h | 2 +- include/usb_cmd.h | 7 +- 9 files changed, 674 insertions(+), 582 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e3bd1fe0..56da5434 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1327,8 +1327,11 @@ void UsbPacketReceived(uint8_t *packet, int len) case CMD_ICLASS_READBLOCK: iClass_ReadBlk(c->arg[0]); break; - case CMD_ICLASS_AUTHENTICATION: //check - iClass_Authentication(c->d.asBytes); + case CMD_ICLASS_CHECK: + iClass_Check(c->d.asBytes); + break; + case CMD_ICLASS_READCHECK: + iClass_Readcheck(c->arg[0], c->arg[1]); break; case CMD_ICLASS_DUMP: iClass_Dump(c->arg[0], c->arg[1]); diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 392271bd..1a729f3f 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -63,9 +63,9 @@ // 56,64us = 24 ssp_clk_cycles #define DELAY_ICLASS_VCD_TO_VICC_SIM (140 - 24) // times in ssp_clk_cycles @ 3,3625MHz when acting as reader -#define DELAY_ICLASS_VICC_TO_VCD_READER DELAY_ISO15693_VICC_TO_VCD_READER +#define DELAY_ICLASS_VICC_TO_VCD_READER DELAY_ISO15693_VICC_TO_VCD_READER // times in samples @ 212kHz when acting as reader -#define ICLASS_READER_TIMEOUT_ACTALL 330 // 1558us, nominal 330us + 7slots*160us = 1450us +#define ICLASS_READER_TIMEOUT_ACTALL 330 // 1558us, nominal 330us + 7slots*160us = 1450us #define ICLASS_READER_TIMEOUT_OTHERS 80 // 380us, nominal 330us @@ -695,7 +695,6 @@ void RAMFUNC SnoopIClass(void) { if (OutOfNDecoding((smpl & 0xF0) >> 4)) { rsamples = samples - Uart.samples; time_stop = (GetCountSspClk()-time_0) << 4; - LED_C_ON(); //if (!LogTrace(Uart.output, Uart.byteCnt, rsamples, Uart.parityBits,true)) break; //if (!LogTrace(NULL, 0, Uart.endTime*16 - DELAY_READER_AIR2ARM_AS_SNIFFER, 0, true)) break; @@ -708,7 +707,6 @@ void RAMFUNC SnoopIClass(void) { /* And also reset the demod code, which might have been */ /* false-triggered by the commands from the reader. */ Demod.state = DEMOD_UNSYNCD; - LED_B_OFF(); Uart.byteCnt = 0; } else { time_start = (GetCountSspClk()-time_0) << 4; @@ -722,7 +720,6 @@ void RAMFUNC SnoopIClass(void) { time_stop = (GetCountSspClk()-time_0) << 4; rsamples = samples - Demod.samples; - LED_B_ON(); uint8_t parity[MAX_PARITY_SIZE]; GetParity(Demod.output, Demod.len, parity); @@ -732,7 +729,6 @@ void RAMFUNC SnoopIClass(void) { memset(&Demod, 0, sizeof(Demod)); Demod.output = tagToReaderResponse; Demod.state = DEMOD_UNSYNCD; - LED_C_OFF(); } else { time_start = (GetCountSspClk()-time_0) << 4; } @@ -1341,7 +1337,7 @@ static void ReaderTransmitIClass(uint8_t *frame, int len, uint32_t *start_time) } -static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint8_t* resp, size_t max_resp_size, +static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint8_t* resp, size_t max_resp_size, uint8_t expected_size, uint8_t retries, uint32_t start_time, uint32_t *eof_time) { while (retries-- > 0) { ReaderTransmitIClass(command, cmdsize, &start_time); @@ -1353,39 +1349,31 @@ static bool sendCmdGetResponseWithRetries(uint8_t* command, size_t cmdsize, uint } /** - * @brief Talks to an iclass tag, sends the commands to get CSN and CC. - * @param card_data where the CSN and CC are stored for return - * @return 0 = fail - * 1 = Got CSN - * 2 = Got CSN and CC + * @brief Selects an iclass tag + * @param card_data where the CSN is stored for return + * @return false = fail + * true = success */ -static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key, uint32_t *eof_time) { +static bool selectIclassTag(uint8_t *card_data, uint32_t *eof_time) { uint8_t act_all[] = { 0x0a }; uint8_t identify[] = { 0x0c }; uint8_t select[] = { 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - uint8_t readcheck_cc[] = { 0x88, 0x02 }; - if (use_credit_key) - readcheck_cc[0] = 0x18; - else - readcheck_cc[0] = 0x88; uint8_t resp[ICLASS_BUFFER_SIZE]; - uint8_t read_status = 0; uint32_t start_time = GetCountSspClk(); // Send act_all ReaderTransmitIClass(act_all, 1, &start_time); // Card present? - if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_ACTALL, eof_time) < 0) return read_status;//Fail - + if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_ACTALL, eof_time) < 0) return false;//Fail + //Send Identify start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; ReaderTransmitIClass(identify, 1, &start_time); - // FpgaDisableTracing(); // DEBUGGING //We expect a 10-byte response here, 8 byte anticollision-CSN and 2 byte CRC uint8_t len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); - if (len != 10) return read_status;//Fail + if (len != 10) return false;//Fail //Copy the Anti-collision CSN to our select-packet memcpy(&select[1], resp, 8); @@ -1394,54 +1382,33 @@ static uint8_t handshakeIclassTag_ext(uint8_t *card_data, bool use_credit_key, u ReaderTransmitIClass(select, sizeof(select), &start_time); //We expect a 10-byte response here, 8 byte CSN and 2 byte CRC len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); - if (len != 10) return read_status;//Fail + if (len != 10) return false;//Fail - //Success - level 1, we got CSN + //Success - we got CSN //Save CSN in response data memcpy(card_data, resp, 8); - //Flag that we got to at least stage 1, read CSN - read_status = 1; - - // Card selected, now read e-purse (cc) (only 8 bytes no CRC) - start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; - ReaderTransmitIClass(readcheck_cc, sizeof(readcheck_cc), &start_time); - if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time) == 8) { - //Save CC (e-purse) in response data - memcpy(card_data+8, resp, 8); - read_status++; - } - - return read_status; -} - -static uint8_t handshakeIclassTag(uint8_t *card_data, uint32_t *eof_time) { - return handshakeIclassTag_ext(card_data, false, eof_time); + return true; } -// Reader iClass Anticollission +// Select an iClass tag and read all blocks which are always readable without authentication void ReaderIClass(uint8_t arg0) { + LED_A_ON(); + uint8_t card_data[6 * 8] = {0}; memset(card_data, 0xFF, sizeof(card_data)); - uint8_t last_csn[8] = {0,0,0,0,0,0,0,0}; uint8_t resp[ICLASS_BUFFER_SIZE]; - memset(resp, 0xFF, sizeof(resp)); //Read conf block CRC(0x01) => 0xfa 0x22 - uint8_t readConf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22}; + uint8_t readConf[] = {ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22}; + //Read e-purse block CRC(0x02) => 0x61 0x10 + uint8_t readEpurse[] = {ICLASS_CMD_READ_OR_IDENTIFY, 0x02, 0x61, 0x10}; //Read App Issuer Area block CRC(0x05) => 0xde 0x64 - uint8_t readAA[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64}; + uint8_t readAA[] = {ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64}; - int read_status= 0; uint8_t result_status = 0; - // flag to read until one tag is found successfully - bool abort_after_read = arg0 & FLAG_ICLASS_READER_ONLY_ONCE; - // flag to only try 5 times to find one tag then return - bool try_once = arg0 & FLAG_ICLASS_READER_ONE_TRY; - // if neither abort_after_read nor try_once then continue reading until button pressed. - bool use_credit_key = arg0 & FLAG_ICLASS_READER_CEDITKEY; // test flags for what blocks to be sure to read uint8_t flagReadConfig = arg0 & FLAG_ICLASS_READER_CONF; uint8_t flagReadCC = arg0 & FLAG_ICLASS_READER_CC; @@ -1454,93 +1421,57 @@ void ReaderIClass(uint8_t arg0) { StartCountSspClk(); uint32_t start_time = 0; uint32_t eof_time = 0; + + if (selectIclassTag(resp, &eof_time)) { + result_status = FLAG_ICLASS_READER_CSN; + memcpy(card_data, resp, 8); + } - uint16_t tryCnt = 0; - bool userCancelled = BUTTON_PRESS() || usb_poll_validate_length(); - while (!userCancelled) { - // if only looking for one card try 2 times if we missed it the first time - if (try_once && tryCnt > 2) { - break; + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + + //Read block 1, config + if (flagReadConfig) { + if (sendCmdGetResponseWithRetries(readConf, sizeof(readConf), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { + result_status |= FLAG_ICLASS_READER_CONF; + memcpy(card_data+8, resp, 8); + } else { + Dbprintf("Failed to read config block"); } - tryCnt++; - if (!get_tracing()) { - DbpString("Trace full"); - break; - } - WDT_HIT(); - - read_status = handshakeIclassTag_ext(card_data, use_credit_key, &eof_time); - - if (read_status == 0) continue; - if (read_status == 1) result_status = FLAG_ICLASS_READER_CSN; - if (read_status == 2) result_status = FLAG_ICLASS_READER_CSN | FLAG_ICLASS_READER_CC; - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; - // handshakeIclass returns CSN|CC, but the actual block - // layout is CSN|CONFIG|CC, so here we reorder the data, - // moving CC forward 8 bytes - memcpy(card_data+16, card_data+8, 8); - //Read block 1, config - if (flagReadConfig) { - if (sendCmdGetResponseWithRetries(readConf, sizeof(readConf), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { - result_status |= FLAG_ICLASS_READER_CONF; - memcpy(card_data+8, resp, 8); - } else { - Dbprintf("Failed to dump config block"); - } - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; - } - - //Read block 5, AA - if (flagReadAA) { - if (sendCmdGetResponseWithRetries(readAA, sizeof(readAA), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { - result_status |= FLAG_ICLASS_READER_AA; - memcpy(card_data + (8*5), resp, 8); - } else { - //Dbprintf("Failed to dump AA block"); - } - } - - // 0 : CSN - // 1 : Configuration - // 2 : e-purse - // 3 : kd / debit / aa2 (write-only) - // 4 : kc / credit / aa1 (write-only) - // 5 : AIA, Application issuer area - //Then we can 'ship' back the 6 * 8 bytes of data, - // with 0xFF:s in block 3 and 4. - - LED_B_ON(); - //Send back to client, but don't bother if we already sent this - - // only useful if looping in arm (not try_once && not abort_after_read) - if (memcmp(last_csn, card_data, 8) != 0) { - // If caller requires that we get Conf, CC, AA, continue until we got it - if ( (result_status ^ FLAG_ICLASS_READER_CSN ^ flagReadConfig ^ flagReadCC ^ flagReadAA) == 0) { - cmd_send(CMD_ACK, result_status, 0, 0, card_data, sizeof(card_data)); - if (abort_after_read) { - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LED_A_OFF(); - LED_B_OFF(); - return; - } - //Save that we already sent this.... - memcpy(last_csn, card_data, 8); - } - - } - LED_B_OFF(); - userCancelled = BUTTON_PRESS() || usb_poll_validate_length(); } - if (userCancelled) { - cmd_send(CMD_ACK, 0xFF, 0, 0, card_data, 0); - } else { - cmd_send(CMD_ACK, 0, 0, 0, card_data, 0); + + //Read block 2, e-purse + if (flagReadCC) { + if (sendCmdGetResponseWithRetries(readEpurse, sizeof(readEpurse), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { + result_status |= FLAG_ICLASS_READER_CC; + memcpy(card_data + (8*2), resp, 8); + } else { + Dbprintf("Failed to read e-purse"); + } + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; } + + //Read block 5, AA + if (flagReadAA) { + if (sendCmdGetResponseWithRetries(readAA, sizeof(readAA), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { + result_status |= FLAG_ICLASS_READER_AA; + memcpy(card_data + (8*5), resp, 8); + } else { + Dbprintf("Failed to read AA block"); + } + } + + cmd_send(CMD_ACK, result_status, 0, 0, card_data, sizeof(card_data)); + LED_A_OFF(); } + void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { + LED_A_ON(); + + bool use_credit_key = false; uint8_t card_data[USB_CMD_DATA_SIZE]={0}; uint16_t block_crc_LUT[255] = {0}; @@ -1551,6 +1482,9 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { } //Dbprintf("Lookup table: %02x %02x %02x" ,block_crc_LUT[0],block_crc_LUT[1],block_crc_LUT[2]); + uint8_t readcheck_cc[] = { ICLASS_CMD_READCHECK_KD, 0x02 }; + if (use_credit_key) + readcheck_cc[0] = ICLASS_CMD_READCHECK_KC; uint8_t check[] = { 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t read[] = { 0x0c, 0x00, 0x00, 0x00 }; @@ -1575,7 +1509,7 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { StartCountSspClk(); uint32_t start_time = 0; uint32_t eof_time = 0; - + while (!BUTTON_PRESS()) { WDT_HIT(); @@ -1585,34 +1519,33 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { break; } - uint8_t read_status = handshakeIclassTag(card_data, &eof_time); + if (!selectIclassTag(card_data, &eof_time)) continue; + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + if (!sendCmdGetResponseWithRetries(readcheck_cc, sizeof(readcheck_cc), resp, sizeof(resp), 8, 3, start_time, &eof_time)) continue; - if (read_status < 2) continue; - - //for now replay captured auth (as cc not updated) + // replay captured auth (cc must not have been updated) memcpy(check+5, MAC, 4); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; if (!sendCmdGetResponseWithRetries(check, sizeof(check), resp, sizeof(resp), 4, 5, start_time, &eof_time)) { - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf("Error: Authentication Fail!"); continue; } - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; //first get configuration block (block 1) crc = block_crc_LUT[1]; read[1] = 1; read[2] = crc >> 8; read[3] = crc & 0xff; + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; if (!sendCmdGetResponseWithRetries(read, sizeof(read), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf("Dump config (block 1) failed"); continue; } - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; mem = resp[5]; memory.k16 = (mem & 0x80); memory.book = (mem & 0x20); @@ -1633,8 +1566,8 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { read[2] = crc >> 8; read[3] = crc & 0xff; + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; if (sendCmdGetResponseWithRetries(read, sizeof(read), resp, sizeof(resp), 10, 10, start_time, &eof_time)) { - start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; Dbprintf(" %02x: %02x %02x %02x %02x %02x %02x %02x %02x", block, resp[0], resp[1], resp[2], resp[3], resp[4], resp[5], @@ -1683,19 +1616,33 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) { 0); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_D_OFF(); LED_A_OFF(); } -void iClass_Authentication(uint8_t *MAC) { - uint8_t check[] = { ICLASS_CMD_CHECK_KD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - uint8_t resp[ICLASS_BUFFER_SIZE]; + +void iClass_Check(uint8_t *MAC) { + uint8_t check[9] = {ICLASS_CMD_CHECK_KD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t resp[4]; memcpy(check+5, MAC, 4); - bool isOK; uint32_t eof_time; - isOK = sendCmdGetResponseWithRetries(check, sizeof(check), resp, sizeof(resp), 4, 6, 0, &eof_time); - cmd_send(CMD_ACK,isOK, 0, 0, 0, 0); + bool isOK = sendCmdGetResponseWithRetries(check, sizeof(check), resp, sizeof(resp), 4, 6, 0, &eof_time); + cmd_send(CMD_ACK, isOK, 0, 0, resp, sizeof(resp)); } + +void iClass_Readcheck(uint8_t block, bool use_credit_key) { + uint8_t readcheck[2] = {ICLASS_CMD_READCHECK_KD, block}; + if (use_credit_key) { + readcheck[0] = ICLASS_CMD_READCHECK_KC; + } + uint8_t resp[8]; + uint32_t eof_time; + bool isOK = sendCmdGetResponseWithRetries(readcheck, sizeof(readcheck), resp, sizeof(resp), 8, 6, 0, &eof_time); + cmd_send(CMD_ACK, isOK, 0, 0, resp, sizeof(resp)); +} + + static bool iClass_ReadBlock(uint8_t blockNo, uint8_t *readdata) { uint8_t readcmd[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockNo, 0x00, 0x00}; //0x88, 0x00 // can i use 0C? char bl = blockNo; @@ -1705,23 +1652,32 @@ static bool iClass_ReadBlock(uint8_t blockNo, uint8_t *readdata) { uint8_t resp[10]; bool isOK = false; uint32_t eof_time; - - //readcmd[1] = blockNo; + isOK = sendCmdGetResponseWithRetries(readcmd, sizeof(readcmd), resp, sizeof(resp), 10, 10, 0, &eof_time); memcpy(readdata, resp, sizeof(resp)); return isOK; } + void iClass_ReadBlk(uint8_t blockno) { + + LED_A_ON(); + uint8_t readblockdata[] = {0,0,0,0,0,0,0,0,0,0}; bool isOK = false; isOK = iClass_ReadBlock(blockno, readblockdata); cmd_send(CMD_ACK, isOK, 0, 0, readblockdata, 8); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_D_OFF(); + + LED_A_OFF(); } void iClass_Dump(uint8_t blockno, uint8_t numblks) { + + LED_A_ON(); + uint8_t readblockdata[] = {0,0,0,0,0,0,0,0,0,0}; bool isOK = false; uint8_t blkCnt = 0; @@ -1751,12 +1707,19 @@ void iClass_Dump(uint8_t blockno, uint8_t numblks) { } //return pointer to dump memory in arg3 cmd_send(CMD_ACK, isOK, blkCnt, BigBuf_max_traceLen(), 0, 0); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); + LED_D_OFF(); BigBuf_free(); + + LED_A_OFF(); } + static bool iClass_WriteBlock_ext(uint8_t blockNo, uint8_t *data) { + + LED_A_ON(); + uint8_t write[] = { ICLASS_CMD_UPDATE, blockNo, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //uint8_t readblockdata[10]; //write[1] = blockNo; @@ -1768,7 +1731,7 @@ static bool iClass_WriteBlock_ext(uint8_t blockNo, uint8_t *data) { uint8_t resp[10]; bool isOK = false; uint32_t eof_time = 0; - + isOK = sendCmdGetResponseWithRetries(write, sizeof(write), resp, sizeof(resp), 10, 10, 0, &eof_time); uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; if (isOK) { //if reader responded correctly @@ -1780,10 +1743,17 @@ static bool iClass_WriteBlock_ext(uint8_t blockNo, uint8_t *data) { } } } + + LED_A_OFF(); + return isOK; } + void iClass_WriteBlock(uint8_t blockNo, uint8_t *data) { + + LED_A_ON(); + bool isOK = iClass_WriteBlock_ext(blockNo, data); if (isOK){ Dbprintf("Write block [%02x] successful", blockNo); @@ -1791,7 +1761,11 @@ void iClass_WriteBlock(uint8_t blockNo, uint8_t *data) { Dbprintf("Write block [%02x] failed", blockNo); } cmd_send(CMD_ACK, isOK, 0, 0, 0, 0); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_D_OFF(); + + LED_A_OFF(); } void iClass_Clone(uint8_t startblock, uint8_t endblock, uint8_t *data) { @@ -1819,5 +1793,6 @@ void iClass_Clone(uint8_t startblock, uint8_t endblock, uint8_t *data) { cmd_send(CMD_ACK, 1, 0, 0, 0, 0); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); + LED_D_OFF(); + LED_A_OFF(); } diff --git a/armsrc/iclass.h b/armsrc/iclass.h index 3cbe79fb..9666e888 100644 --- a/armsrc/iclass.h +++ b/armsrc/iclass.h @@ -15,6 +15,7 @@ #define ICLASS_H__ #include +#include #include "common.h" // for RAMFUNC extern void RAMFUNC SnoopIClass(void); @@ -22,7 +23,8 @@ extern void SimulateIClass(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t extern void ReaderIClass(uint8_t arg0); extern void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC); extern void IClass_iso14443A_GetPublic(uint8_t arg0); -extern void iClass_Authentication(uint8_t *MAC); +extern void iClass_Readcheck(uint8_t block, bool use_credit_key); +extern void iClass_Check(uint8_t *MAC); extern void iClass_WriteBlock(uint8_t blockNo, uint8_t *data); extern void iClass_ReadBlk(uint8_t blockNo); extern void iClass_Dump(uint8_t blockno, uint8_t numblks); diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index fff8b370..3b39d576 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -132,7 +132,7 @@ void CodeIso15693AsReader(uint8_t *cmd, int n) { // EOF ToSend[++ToSendMax] = 0x20; //0010 + 0000 padding - + ToSendMax++; } @@ -249,14 +249,18 @@ void CodeIso15693AsTag(uint8_t *cmd, size_t len) { void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t *start_time) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_FULL_MOD); - + + if (*start_time < DELAY_ARM_TO_TAG) { + *start_time = DELAY_ARM_TO_TAG; + } + *start_time = (*start_time - DELAY_ARM_TO_TAG) & 0xfffffff0; while (GetCountSspClk() > *start_time) { // we may miss the intended time *start_time += 16; // next possible time } - + while (GetCountSspClk() < *start_time) /* wait */ ; @@ -275,7 +279,7 @@ void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t *start_time) { WDT_HIT(); } LED_B_OFF(); - + *start_time = *start_time + DELAY_ARM_TO_TAG; } @@ -289,7 +293,7 @@ void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR | FPGA_HF_SIMULATOR_MODULATE_424K); uint32_t modulation_start_time = *start_time - DELAY_ARM_TO_READER + 3 * 8; // no need to transfer the unmodulated start of SOF - + while (GetCountSspClk() > (modulation_start_time & 0xfffffff8) + 3) { // we will miss the intended time if (slot_time) { modulation_start_time += slot_time; // use next available slot @@ -298,7 +302,7 @@ void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, } } - while (GetCountSspClk() < (modulation_start_time & 0xfffffff8)) + while (GetCountSspClk() < (modulation_start_time & 0xfffffff8)) /* wait */ ; uint8_t shift_delay = modulation_start_time & 0x00000007; @@ -414,7 +418,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 // DecodeTag->posCount = 2; DecodeTag->state = STATE_TAG_SOF_HIGH; break; - + case STATE_TAG_SOF_HIGH: // waiting for 10 times high. Take average over the last 8 if (amplitude > DecodeTag->threshold_sof) { @@ -428,7 +432,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 } } else { // high phase was too short DecodeTag->posCount = 1; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; } break; @@ -444,18 +448,18 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->sum2 = 0; DecodeTag->posCount = 2; DecodeTag->state = STATE_TAG_RECEIVING_DATA; - // FpgaDisableTracing(); // DEBUGGING - // Dbprintf("amplitude = %d, threshold_sof = %d, threshold_half/4 = %d, previous_amplitude = %d", - // amplitude, - // DecodeTag->threshold_sof, - // DecodeTag->threshold_half/4, - // DecodeTag->previous_amplitude); // DEBUGGING + FpgaDisableTracing(); // DEBUGGING + Dbprintf("amplitude = %d, threshold_sof = %d, threshold_half/4 = %d, previous_amplitude = %d", + amplitude, + DecodeTag->threshold_sof, + DecodeTag->threshold_half/4, + DecodeTag->previous_amplitude); // DEBUGGING LED_C_ON(); } else { DecodeTag->posCount++; if (DecodeTag->posCount > 13) { // high phase too long DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -478,7 +482,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->state = STATE_TAG_EOF; } else { DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -508,7 +512,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 // logic 0 if (DecodeTag->lastBit == SOF_PART1) { // incomplete SOF DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } else { @@ -522,7 +526,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 if (DecodeTag->len > DecodeTag->max_len) { // buffer overflow, give up DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -561,7 +565,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 DecodeTag->state = STATE_TAG_EOF_TAIL; } else { DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -585,7 +589,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 return true; } else { DecodeTag->posCount = 0; - DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; + DecodeTag->previous_amplitude = amplitude; DecodeTag->state = STATE_TAG_SOF_LOW; LED_C_OFF(); } @@ -598,8 +602,7 @@ static int inline __attribute__((always_inline)) Handle15693SamplesFromTag(uint1 } -static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data, uint16_t max_len) -{ +static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data, uint16_t max_len) { DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; DecodeTag->posCount = 0; DecodeTag->state = STATE_TAG_SOF_LOW; @@ -608,8 +611,7 @@ static void DecodeTagInit(DecodeTag_t *DecodeTag, uint8_t *data, uint16_t max_le } -static void DecodeTagReset(DecodeTag_t *DecodeTag) -{ +static void DecodeTagReset(DecodeTag_t *DecodeTag) { DecodeTag->posCount = 0; DecodeTag->state = STATE_TAG_SOF_LOW; DecodeTag->previous_amplitude = MAX_PREVIOUS_AMPLITUDE; @@ -649,10 +651,10 @@ int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, uint16_t timeo samples++; if (samples == 1) { - // DMA has transferred the very first data + // DMA has transferred the very first data dma_start_time = GetCountSspClk() & 0xfffffff0; } - + uint16_t tagdata = *upTo++; if(upTo >= dmaBuf + ISO15693_DMA_BUFFER_SIZE) { // we have read all of the DMA buffer content. @@ -680,7 +682,7 @@ int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, uint16_t timeo } if (samples > timeout && DecodeTag.state < STATE_TAG_RECEIVING_DATA) { - ret = -1; // timeout + ret = -1; // timeout break; } @@ -699,9 +701,9 @@ int GetIso15693AnswerFromTag(uint8_t* response, uint16_t max_len, uint16_t timeo - DecodeTag.len * 8 * 8 * 16 // time for byte transfers - 32 * 16 // time for SOF transfer - (DecodeTag.lastBit != SOF_PART2?32*16:0); // time for EOF transfer - + if (DEBUG) Dbprintf("timing: sof_time = %d, eof_time = %d", sof_time, *eof_time); - + LogTrace_ISO15693(DecodeTag.output, DecodeTag.len, sof_time*4, *eof_time*4, NULL, false); return DecodeTag.len; @@ -1089,20 +1091,19 @@ static void BuildIdentifyRequest(void) //----------------------------------------------------------------------------- void AcquireRawAdcSamplesIso15693(void) { - LEDsoff(); LED_A_ON(); uint8_t *dest = BigBuf_get_addr(); FpgaDownloadAndGo(FPGA_BITSTREAM_HF); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER); + LED_D_ON(); FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); BuildIdentifyRequest(); // Give the tags time to energize - LED_D_ON(); SpinDelay(100); // Now send the command @@ -1129,6 +1130,7 @@ void AcquireRawAdcSamplesIso15693(void) void SnoopIso15693(void) { LED_A_ON(); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); BigBuf_free(); @@ -1348,17 +1350,13 @@ static void BuildInventoryResponse(uint8_t *uid) // return: length of received data, or -1 for timeout int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint32_t *eof_time) { - LED_A_ON(); - LED_B_OFF(); - LED_C_OFF(); - if (init) { Iso15693InitReader(); StartCountSspClk(); } - + int answerLen = 0; - + if (!speed) { // low speed (1 out of 256) CodeIso15693AsReader256(send, sendlen); @@ -1367,18 +1365,13 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, int speed, uint8_t *recv, CodeIso15693AsReader(send, sendlen); } - if (start_time == 0) { - start_time = GetCountSspClk(); - } TransmitTo15693Tag(ToSend, ToSendMax, &start_time); // Now wait for a response if (recv != NULL) { - answerLen = GetIso15693AnswerFromTag(recv, max_recv_len, DELAY_ISO15693_VCD_TO_VICC_READER * 2, eof_time); + answerLen = GetIso15693AnswerFromTag(recv, max_recv_len, ISO15693_READER_TIMEOUT, eof_time); } - LED_A_OFF(); - return answerLen; } @@ -1462,9 +1455,8 @@ void SetDebugIso15693(uint32_t debug) { // Simulate an ISO15693 reader, perform anti-collision and then attempt to read a sector. // all demodulation performed in arm rather than host. - greg //--------------------------------------------------------------------------------------- -void ReaderIso15693(uint32_t parameter) -{ - LEDsoff(); +void ReaderIso15693(uint32_t parameter) { + LED_A_ON(); set_tracing(true); @@ -1564,9 +1556,8 @@ void ReaderIso15693(uint32_t parameter) // Simulate an ISO15693 TAG. // For Inventory command: print command and send Inventory Response with given UID // TODO: interpret other reader commands and send appropriate response -void SimTagIso15693(uint32_t parameter, uint8_t *uid) -{ - LEDsoff(); +void SimTagIso15693(uint32_t parameter, uint8_t *uid) { + LED_A_ON(); FpgaDownloadAndGo(FPGA_BITSTREAM_HF); @@ -1597,7 +1588,8 @@ void SimTagIso15693(uint32_t parameter, uint8_t *uid) } FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); + LED_D_OFF(); + LED_A_OFF(); } @@ -1605,7 +1597,6 @@ void SimTagIso15693(uint32_t parameter, uint8_t *uid) // (some manufactures offer a way to read the AFI, though) void BruteforceIso15693Afi(uint32_t speed) { - LEDsoff(); LED_A_ON(); uint8_t data[6]; @@ -1648,17 +1639,19 @@ void BruteforceIso15693Afi(uint32_t speed) Dbprintf("AFI Bruteforcing done."); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); + LED_D_OFF(); + LED_A_OFF(); + } // Allows to directly send commands to the tag via the client void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint8_t data[]) { + LED_A_ON(); + int recvlen = 0; uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; uint32_t eof_time; - - LED_A_ON(); if (DEBUG) { Dbprintf("SEND:"); @@ -1695,17 +1688,16 @@ void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint //----------------------------------------------------------------------------- // Set the UID to the tag (based on Iceman work). -void SetTag15693Uid(uint8_t *uid) -{ - uint8_t cmd[4][9] = {0x00}; +void SetTag15693Uid(uint8_t *uid) { + LED_A_ON(); + + uint8_t cmd[4][9] = {0x00}; uint16_t crc; int recvlen = 0; uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; uint32_t eof_time; - - LED_A_ON(); // Command 1 : 02213E00000000 cmd[0][0] = 0x02; @@ -1767,8 +1759,6 @@ void SetTag15693Uid(uint8_t *uid) cmd_send(CMD_ACK, recvlen>ISO15693_MAX_RESPONSE_LENGTH?ISO15693_MAX_RESPONSE_LENGTH:recvlen, 0, 0, recvbuf, ISO15693_MAX_RESPONSE_LENGTH); } - LED_D_OFF(); - LED_A_OFF(); } diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 96a2b39b..5cbe5ecc 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -21,6 +21,8 @@ //SSP_CLK runs at 13.56MHz / 4 = 3,39MHz when acting as reader. All values should be multiples of 16 #define DELAY_ISO15693_VCD_TO_VICC_READER 1056 // 1056/3,39MHz = 311.5us from end of command EOF to start of tag response #define DELAY_ISO15693_VICC_TO_VCD_READER 1024 // 1024/3.39MHz = 302.1us between end of tag response and next reader command +// times in samples @ 212kHz when acting as reader +#define ISO15693_READER_TIMEOUT 330 // 330/212kHz = 1558us, should be even enough for iClass tags responding to ACTALL void Iso15693InitReader(); void CodeIso15693AsReader(uint8_t *cmd, int n); diff --git a/client/cmdhficlass.c b/client/cmdhficlass.c index 81738686..195a282d 100644 --- a/client/cmdhficlass.c +++ b/client/cmdhficlass.c @@ -35,7 +35,6 @@ #include "util_posix.h" #include "cmdhf14a.h" // DropField() -static int CmdHelp(const char *Cmd); #define ICLASS_KEYS_MAX 8 static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][8] = { @@ -49,12 +48,14 @@ static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][8] = { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 } }; + typedef struct iclass_block { - uint8_t d[8]; + uint8_t d[8]; } iclass_block_t; -int usage_hf_iclass_chk(void) { - PrintAndLog("Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag"); + +static void usage_hf_iclass_chk(void) { + PrintAndLog("Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag"); PrintAndLog("Usage: hf iclass chk [h|e|r] "); PrintAndLog("Options:"); PrintAndLog("h Show this help"); @@ -62,31 +63,25 @@ int usage_hf_iclass_chk(void) { PrintAndLog(" e target Elite / High security key scheme"); PrintAndLog(" r interpret dictionary file as raw (diversified keys)"); PrintAndLog("Samples:"); - PrintAndLog(" hf iclass chk f default_iclass_keys.dic"); - PrintAndLog(" hf iclass chk f default_iclass_keys.dic e"); - return 0; + PrintAndLog(" hf iclass chk f default_iclass_keys.dic"); + PrintAndLog(" hf iclass chk f default_iclass_keys.dic e"); } -int xorbits_8(uint8_t val) { - uint8_t res = val ^ (val >> 1); //1st pass - res = res ^ (res >> 1); // 2nd pass - res = res ^ (res >> 2); // 3rd pass - res = res ^ (res >> 4); // 4th pass - return res & 1; -} -int CmdHFiClassList(const char *Cmd) { +static int CmdHFiClassList(const char *Cmd) { PrintAndLog("Deprecated command, use 'hf list iclass' instead"); return 0; } -int CmdHFiClassSnoop(const char *Cmd) { + +static int CmdHFiClassSnoop(const char *Cmd) { UsbCommand c = {CMD_SNOOP_ICLASS}; SendCommand(&c); return 0; } -int usage_hf_iclass_sim(void) { + +static void usage_hf_iclass_sim(void) { PrintAndLog("Usage: hf iclass sim