diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index d4c6e94a2..3f1d76946 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -1,34 +1,92 @@ +/* + * Electra intercom rfid protocol (Romania) + * + * Based on EM4100 protocol implementation from https://github.com/flipperdevices/flipperzero-firmware/blob/dev/lib/lfrfid/protocols/protocol_em4100.c + * + * Copyright 2024 Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * ------------------------------------------------------------------------------------------------------------------------------ + * PROTOCOL DESCRIPTION: + * ------------------------------------------------------------------------------------------------------------------------------ + * Electra intercom 125 kHz protocol based on 64-bit clock EM4100, but includes some extra data after base EM4100 data (epilogue) + * + * Epilogue size is 64 bits, but only first 16 bits matter. Rest 6 bytes - some filler data, + * that arbitrary change is not validated by the Electra intercoms + * + * There are curently three known types of epilogue: + * - 0x7E71AAAAAAAAAAAA (AA filler) + * - 0x7E71000000000000 (00 filler) + * - 0x0030AAAAAAAAAAAA + * + * First two epilogue bytes may be interpreted as EM4100 data continuation + * Nevertheless, these bytes have correct row parity bits, but have not correct collumn parity + + * For example: 0x7E71AAAAAAAAAAAA epilogue: + * + * In binary: | 0b01111110 | 01110001 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | + * In hex: | 0x7E | 71 | AA | AA | AA | AA | AA | AA | + * + * As EM4100 data: + * 0111 1 // 7 + * 1100 0 // C + * 0111 1 // 7 + * 1101 0 // here epilogue filler starts (from second bit) + * 1010 1 // there is no correct raw parity bits anymore + * 0101 0 + * 1010 1 + * 0101 0 + * 1010 // and no correct column parity + */ + +#include "bit_lib/bit_lib.h" #include +#include #include #include #include "lfrfid_protocols.h" +#define TAG "ELECTRA" + typedef uint64_t ElectraDecodedData; -#define ELECTRA_HEADER_POS (55) -#define ELECTRA_HEADER_MASK (0x1FFLLU << ELECTRA_HEADER_POS) +#define EM_HEADER_POS (55) +#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) -#define ELECTRA_FIRST_ROW_POS (50) +#define EM_FIRST_ROW_POS (50) -#define ELECTRA_ROW_COUNT (10) -#define ELECTRA_COLUMN_COUNT (4) -#define ELECTRA_BITS_PER_ROW_COUNT (ELECTRA_COLUMN_COUNT + 1) +#define EM_ROW_COUNT (10) +#define EM_COLUMN_COUNT (4) +#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) -#define ELECTRA_COLUMN_POS (4) +#define EM_COLUMN_POS (4) #define ELECTRA_STOP_POS (0) #define ELECTRA_STOP_MASK (0x1LLU << ELECTRA_STOP_POS) -#define ELECTRA_HEADER_AND_STOP_MASK (ELECTRA_HEADER_MASK | ELECTRA_STOP_MASK) -#define ELECTRA_HEADER_AND_STOP_DATA (ELECTRA_HEADER_MASK) +#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | ELECTRA_STOP_MASK) +#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) -#define ELECTRA_DECODED_DATA_SIZE (5) -#define ELECTRA_ENCODED_DATA_SIZE (sizeof(ElectraDecodedData)) +#define ELECTRA_DECODED_BASE_DATA_SIZE (5) +#define ELECTRA_ENCODED_BASE_DATA_SIZE (sizeof(ElectraDecodedData)) -#define ELECTRA_DECODED_EPILOGUE_SIZE (8) +#define ELECTRA_DECODED_EPILOGUE_SIZE (3) #define ELECTRA_ENCODED_EPILOGUE_SIZE (sizeof(ElectraDecodedData)) -#define ELECTRA_DECODED_SIZE (ELECTRA_DECODED_DATA_SIZE + ELECTRA_DECODED_EPILOGUE_SIZE) -#define ELECTRA_ENCODED_SIZE (ELECTRA_ENCODED_DATA_SIZE + ELECTRA_ENCODED_EPILOGUE_SIZE) +#define ELECTRA_DECODED_DATA_SIZE (ELECTRA_DECODED_BASE_DATA_SIZE + ELECTRA_DECODED_EPILOGUE_SIZE) +#define ELECTRA_ENCODED_DATA_SIZE (ELECTRA_ENCODED_BASE_DATA_SIZE + ELECTRA_ENCODED_EPILOGUE_SIZE) + +#define ELECTRA_DECODED_DATA_EPILOGUE_START_POS (ELECTRA_DECODED_BASE_DATA_SIZE) #define ELECTRA_CLOCK_PER_BIT (64) @@ -44,9 +102,9 @@ typedef uint64_t ElectraDecodedData; #define EM_ENCODED_DATA_HEADER (0xFF80000000000000ULL) typedef struct { - uint8_t data[ELECTRA_DECODED_SIZE]; + uint8_t data[ELECTRA_DECODED_DATA_SIZE]; - ElectraDecodedData encoded_data; + ElectraDecodedData encoded_base_data; ElectraDecodedData encoded_epilogue; uint8_t encoded_data_index; @@ -69,18 +127,19 @@ uint8_t* protocol_electra_get_data(ProtocolElectra* proto) { }; static void electra_decode( - const uint8_t* encoded_data, - const uint8_t encoded_data_size, + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, const uint8_t* encoded_epilogue, const uint8_t encoded_epilogue_size, uint8_t* decoded_data, const uint8_t decoded_data_size) { - furi_check(decoded_data_size >= ELECTRA_DECODED_SIZE); - furi_check(encoded_data_size >= ELECTRA_ENCODED_DATA_SIZE); + furi_check(decoded_data_size >= ELECTRA_DECODED_DATA_SIZE); + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); furi_check(encoded_epilogue_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); uint8_t decoded_data_index = 0; - ElectraDecodedData base_data = *((ElectraDecodedData*)(encoded_data)); + ElectraDecodedData base_data = *((ElectraDecodedData*)(encoded_base_data)); + //ElectraDecodedData epilogue = *((ElectraDecodedData*)(encoded_epilogue)); // clean result memset(decoded_data, 0, decoded_data_size); @@ -92,7 +151,7 @@ static void electra_decode( // nibbles uint8_t value = 0; - for(uint8_t r = 0; r < ELECTRA_ROW_COUNT; r++) { + for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { uint8_t nibble = 0; for(uint8_t i = 0; i < 5; i++) { if(i < 4) nibble = (nibble << 1) | (base_data & (1LLU << 63) ? 1 : 0); @@ -106,65 +165,76 @@ static void electra_decode( } } - memcpy( - decoded_data + ELECTRA_DECODED_DATA_SIZE, encoded_epilogue, ELECTRA_ENCODED_EPILOGUE_SIZE); + // copy first 3 bytes of encoded epilogue to decoded data + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 1]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 2]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 3]; } static bool electra_can_be_decoded( - const uint8_t* encoded_data, - const uint8_t encoded_data_size, - const uint8_t* epilogue_data) { - furi_check(encoded_data_size >= ELECTRA_ENCODED_DATA_SIZE); - const ElectraDecodedData* card_data = (ElectraDecodedData*)encoded_data; - const ElectraDecodedData* epilogue = (ElectraDecodedData*)epilogue_data; + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, + const uint8_t* encoded_epilogue_data, + const uint8_t encoded_epilogue_data_size) { + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); + furi_check(encoded_epilogue_data_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); + const ElectraDecodedData* base_data = (ElectraDecodedData*)encoded_base_data; + const ElectraDecodedData* epilogue = (ElectraDecodedData*)encoded_epilogue_data; - bool decoded = false; + // check electra epilogue. if em4100 header - break + if((*epilogue & EM_ENCODED_DATA_HEADER) == EM_ENCODED_DATA_HEADER) return false; - do { - // check electra epilogue. if em4100 header - break - if((*epilogue & EM_ENCODED_DATA_HEADER) == EM_ENCODED_DATA_HEADER) break; + // check header and stop bit + if((*base_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; - // check header and stop bit - if((*card_data & ELECTRA_HEADER_AND_STOP_MASK) != ELECTRA_HEADER_AND_STOP_DATA) break; + // check row parity + for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { + uint8_t parity_sum = 0; - // check row parity - for(uint8_t i = 0; i < ELECTRA_ROW_COUNT; i++) { - uint8_t parity_sum = 0; - - for(uint8_t j = 0; j < ELECTRA_BITS_PER_ROW_COUNT; j++) { - parity_sum += - (*card_data >> (ELECTRA_FIRST_ROW_POS - i * ELECTRA_BITS_PER_ROW_COUNT + j)) & - 1; - } - - if((parity_sum % 2)) { - break; - } + for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { + parity_sum += (*base_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; } - // check columns parity - for(uint8_t i = 0; i < ELECTRA_COLUMN_COUNT; i++) { - uint8_t parity_sum = 0; + if((parity_sum % 2)) { + return false; + } + } - for(uint8_t j = 0; j < ELECTRA_ROW_COUNT + 1; j++) { - parity_sum += - (*card_data >> (ELECTRA_COLUMN_POS - i + j * ELECTRA_BITS_PER_ROW_COUNT)) & 1; - } + // check columns parity + for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { + uint8_t parity_sum = 0; - if((parity_sum % 2)) { - break; - } + for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { + parity_sum += (*base_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; } - decoded = true; - } while(false); + if((parity_sum % 2)) { + FURI_LOG_D( + TAG, + "Unexpected column parity found. EM4100 data: %016llX", + bit_lib_bytes_to_num_be(encoded_base_data, encoded_base_data_size)); + return false; + } + } - return decoded; + // encoded_epilogue_data lsb encoded + uint8_t epilogue_filler = encoded_epilogue_data[(ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2]; + + for(uint8_t i = 0; i < ((ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2); i++) + if(encoded_epilogue_data[i] != epilogue_filler) { + FURI_LOG_D(TAG, "Unexpected epilogue filler found: %016llX", *epilogue); + return false; + } + + return true; } void protocol_electra_decoder_start(ProtocolElectra* proto) { memset(proto->data, 0, ELECTRA_DECODED_DATA_SIZE); - proto->encoded_data = 0; + proto->encoded_base_data = 0; proto->encoded_epilogue = 0; manchester_advance( @@ -199,22 +269,30 @@ bool protocol_electra_decoder_feed(ProtocolElectra* proto, bool level, uint32_t proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); if(data_ok) { + /* + EM 4100 BASE DATA (64 bit) ELECTRA EPILOGUE (64 bit) + _________________________________ _________________________________ + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | <- new data bit + --------------------------------- --------------------------------- + <- epilogue msb is carry bit to base data + */ bool carry = proto->encoded_epilogue >> 63 & 0b1; - proto->encoded_data = (proto->encoded_data << 1) | carry; + proto->encoded_base_data = (proto->encoded_base_data << 1) | carry; proto->encoded_epilogue = (proto->encoded_epilogue << 1) | data; if(electra_can_be_decoded( - (uint8_t*)&proto->encoded_data, - sizeof(ElectraDecodedData), - (uint8_t*)&proto->encoded_epilogue)) { + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, + (uint8_t*)&proto->encoded_epilogue, + ELECTRA_ENCODED_EPILOGUE_SIZE)) { electra_decode( - (uint8_t*)&proto->encoded_data, - sizeof(ElectraDecodedData), + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, (uint8_t*)&proto->encoded_epilogue, - sizeof(ElectraDecodedData), + ELECTRA_ENCODED_EPILOGUE_SIZE, proto->data, - ELECTRA_DECODED_SIZE); + ELECTRA_DECODED_DATA_SIZE); result = true; } } @@ -223,52 +301,57 @@ bool protocol_electra_decoder_feed(ProtocolElectra* proto, bool level, uint32_t return result; }; -static void electra_write_nibble(bool low_nibble, uint8_t data, ElectraDecodedData* encoded_data) { +static void em_write_nibble(bool low_nibble, uint8_t data, ElectraDecodedData* encoded_base_data) { uint8_t parity_sum = 0; uint8_t start = 0; if(!low_nibble) start = 4; for(int8_t i = (start + 3); i >= start; i--) { parity_sum += (data >> i) & 1; - *encoded_data = (*encoded_data << 1) | ((data >> i) & 1); + *encoded_base_data = (*encoded_base_data << 1) | ((data >> i) & 1); } - *encoded_data = (*encoded_data << 1) | ((parity_sum % 2) & 1); + *encoded_base_data = (*encoded_base_data << 1) | ((parity_sum % 2) & 1); } bool protocol_electra_encoder_start(ProtocolElectra* proto) { // header - proto->encoded_data = 0b111111111; + proto->encoded_base_data = 0b111111111; // data - for(uint8_t i = 0; i < ELECTRA_DECODED_DATA_SIZE; i++) { - electra_write_nibble(false, proto->data[i], &proto->encoded_data); - electra_write_nibble(true, proto->data[i], &proto->encoded_data); + for(uint8_t i = 0; i < ELECTRA_DECODED_BASE_DATA_SIZE; i++) { + em_write_nibble(false, proto->data[i], &proto->encoded_base_data); + em_write_nibble(true, proto->data[i], &proto->encoded_base_data); } // column parity and stop bit uint8_t parity_sum; - for(uint8_t c = 0; c < ELECTRA_COLUMN_COUNT; c++) { + for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { parity_sum = 0; - for(uint8_t i = 1; i <= ELECTRA_ROW_COUNT; i++) { - uint8_t parity_bit = (proto->encoded_data >> (i * ELECTRA_BITS_PER_ROW_COUNT - 1)) & 1; + for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { + uint8_t parity_bit = (proto->encoded_base_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; parity_sum += parity_bit; } - proto->encoded_data = (proto->encoded_data << 1) | ((parity_sum % 2) & 1); + proto->encoded_base_data = (proto->encoded_base_data << 1) | ((parity_sum % 2) & 1); } // stop bit - proto->encoded_data = (proto->encoded_data << 1) | 0; + proto->encoded_base_data = (proto->encoded_base_data << 1) | 0; proto->encoded_data_index = 0; proto->encoded_polarity = true; // epilogue - memcpy( - &proto->encoded_epilogue, - proto->data + ELECTRA_DECODED_DATA_SIZE, - ELECTRA_DECODED_EPILOGUE_SIZE); + proto->encoded_epilogue = (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS]); + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1]); + + //fill bytes 2-7 by epilogue filler + for(uint8_t i = 2; i < ELECTRA_ENCODED_EPILOGUE_SIZE; i++) { + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2]; + } return true; }; @@ -276,7 +359,7 @@ bool protocol_electra_encoder_start(ProtocolElectra* proto) { LevelDuration protocol_electra_encoder_yield(ProtocolElectra* proto) { bool level; if(proto->encoded_data_index < 64) - level = (proto->encoded_data >> (63 - proto->encoded_data_index)) & 1; + level = (proto->encoded_base_data >> (63 - proto->encoded_data_index)) & 1; else level = (proto->encoded_epilogue >> (63 - (proto->encoded_data_index - 64))) & 1; @@ -304,12 +387,12 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { // Correct protocol data by redecoding protocol_electra_encoder_start(protocol); electra_decode( - (uint8_t*)&protocol->encoded_data, + (uint8_t*)&protocol->encoded_base_data, sizeof(ElectraDecodedData), (uint8_t*)&protocol->encoded_epilogue, sizeof(ElectraDecodedData), protocol->data, - ELECTRA_DECODED_SIZE); + ELECTRA_DECODED_DATA_SIZE); protocol_electra_encoder_start(protocol); @@ -317,8 +400,8 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { request->t5577.block[0] = (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_64 | (4 << LFRFID_T5577_MAXBLOCK_SHIFT)); - request->t5577.block[1] = protocol->encoded_data >> 32; - request->t5577.block[2] = protocol->encoded_data & 0xFFFFFFFF; + request->t5577.block[1] = protocol->encoded_base_data >> 32; + request->t5577.block[2] = protocol->encoded_base_data & 0xFFFFFFFF; request->t5577.block[3] = protocol->encoded_epilogue >> 32; request->t5577.block[4] = protocol->encoded_epilogue & 0xFFFFFFFF; request->t5577.blocks_to_write = 5; @@ -333,8 +416,8 @@ void protocol_electra_render_data(ProtocolElectra* protocol, FuriString* result) const ProtocolBase protocol_electra = { .name = "Electra", - .manufacturer = "ELECTRA", - .data_size = ELECTRA_DECODED_SIZE, + .manufacturer = "EM41xx XL", + .data_size = ELECTRA_DECODED_DATA_SIZE, .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, .validate_count = 3, .alloc = (ProtocolAlloc)protocol_electra_alloc,