From 06521f83292d44445da23024bfa0e6d99699b9fc Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 11:07:35 +0100 Subject: [PATCH 01/30] return UNKNOWN if no preset name is given --- protocols/protocols_common.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/protocols/protocols_common.c b/protocols/protocols_common.c index 033d5cb..f7290a0 100644 --- a/protocols/protocols_common.c +++ b/protocols/protocols_common.c @@ -1,6 +1,9 @@ #include "protocols_common.h" const char* protopirate_get_short_preset_name(const char* preset_name) { + if(!preset_name) { + return "UNKNOWN"; + } if(!strcmp(preset_name, "FuriHalSubGhzPresetOok270Async")) { return "AM270"; } else if(!strcmp(preset_name, "FuriHalSubGhzPresetOok650Async")) { From 08d27354a57d0d26bf65bbb9bfae8674dc766115 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 11:19:51 +0100 Subject: [PATCH 02/30] fix risky/bon-portable 'inline' usage. Follow flipper zero codebase that mainly use static inline in headers for short functions, which guarantee that it will be inlined --- protocols/keeloq_common.c | 107 -------------------------------------- protocols/keeloq_common.h | 63 ++++++++++++++++++---- 2 files changed, 53 insertions(+), 117 deletions(-) delete mode 100644 protocols/keeloq_common.c diff --git a/protocols/keeloq_common.c b/protocols/keeloq_common.c deleted file mode 100644 index 0865a1f..0000000 --- a/protocols/keeloq_common.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "keeloq_common.h" - -#include - -#include - -#define bit(x, n) (((x) >> (n)) & 1) -#define g5(x, a, b, c, d, e) \ - (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) - -/** Simple Learning Encrypt - * @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter - * @param key - manufacture (64bit) - * @return keeloq encrypt data - */ -inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { - uint32_t x = data, r; - for(r = 0; r < 528; r++) - x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^ - bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31))) - << 31); - return x; -} - -/** Simple Learning Decrypt - * @param data - keeloq encrypt data - * @param key - manufacture (64bit) - * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter - */ -inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { - uint32_t x = data, r; - for(r = 0; r < 528; r++) - x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^ - bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30)); - return x; -} - -/** Normal Learning - * @param data - serial number (28bit) - * @param key - manufacture (64bit) - * @return manufacture for this serial number (64bit) - */ -inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { - uint32_t k1, k2; - - data &= 0x0FFFFFFF; - data |= 0x20000000; - k1 = subghz_protocol_keeloq_common_decrypt(data, key); - - data &= 0x0FFFFFFF; - data |= 0x60000000; - k2 = subghz_protocol_keeloq_common_decrypt(data, key); - - return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya -} - -/** Magic_xor_type1 Learning - * @param data - serial number (28bit) - * @param xor - magic xor (64bit) - * @return manufacture for this serial number (64bit) - */ - -inline uint64_t - subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { - data &= 0x0FFFFFFF; - return (((uint64_t)data << 32) | data) ^ xor; -} - -/** Magic_serial_type1 Learning - * @param data - serial number (28bit) - * @param man - magic man (64bit) - * @return manufacture for this serial number (64bit) - */ - -inline uint64_t - subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { - return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | - ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); -} - -/** Magic_serial_type2 Learning - * @param data - btn+serial number (32bit) - * @param man - magic man (64bit) - * @return manufacture for this serial number (64bit) - */ - -inline uint64_t - subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { - uint8_t* p = (uint8_t*)&data; - uint8_t* m = (uint8_t*)&man; - m[7] = p[0]; - m[6] = p[1]; - m[5] = p[2]; - m[4] = p[3]; - return man; -} - -/** Magic_serial_type3 Learning - * @param data - serial number (24bit) - * @param man - magic man (64bit) - * @return manufacture for this serial number (64bit) - */ - -inline uint64_t - subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { - return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); -} diff --git a/protocols/keeloq_common.h b/protocols/keeloq_common.h index c1570eeb..ca0cfbe 100644 --- a/protocols/keeloq_common.h +++ b/protocols/keeloq_common.h @@ -13,6 +13,11 @@ #include +#define bit(x, n) (((x) >> (n)) & 1) + +#define g5(x, a, b, c, d, e) \ + (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) + struct SubGhzKeystore { SubGhzKeyArray_t data; const char* mfname; @@ -47,7 +52,14 @@ struct SubGhzKeystore { * @param key - manufacture (64bit) * @return keeloq encrypt data */ -uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key); +static inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^ + bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31))) + << 31); + return x; +} /** * Simple Learning Decrypt @@ -55,7 +67,13 @@ uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64 * @param key - manufacture (64bit) * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter */ -uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key); +static inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { + uint32_t x = data, r; + for(r = 0; r < 528; r++) + x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^ + bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30)); + return x; +} /** * Normal Learning @@ -63,7 +81,19 @@ uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64 * @param key - manufacture (64bit) * @return manufacture for this serial number (64bit) */ -uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key); +static inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { + uint32_t k1, k2; + + data &= 0x0FFFFFFF; + data |= 0x20000000; + k1 = subghz_protocol_keeloq_common_decrypt(data, key); + + data &= 0x0FFFFFFF; + data |= 0x60000000; + k2 = subghz_protocol_keeloq_common_decrypt(data, key); + + return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya +} /** * Magic_xor_type1 Learning @@ -71,28 +101,41 @@ uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint * @param xor - magic xor (64bit) * @return manufacture for this serial number (64bit) */ -uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor); +static inline uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { + data &= 0x0FFFFFFF; + return (((uint64_t)data << 32) | data) ^ xor; +} /** Magic_serial_type1 Learning * @param data - serial number (28bit) * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ - -uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man); +static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | + ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); +} /** Magic_serial_type2 Learning * @param data - btn+serial number (32bit) * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ - -uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man); +static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { + uint8_t* p = (uint8_t*)&data; + uint8_t* m = (uint8_t*)&man; + m[7] = p[0]; + m[6] = p[1]; + m[5] = p[2]; + m[4] = p[3]; + return man; +} /** Magic_serial_type3 Learning * @param data - btn+serial number (32bit) * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ - -uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man); +static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { + return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); +} From 2b65d3e135aa8c71a16fb309ed167f195869c4af Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 11:21:28 +0100 Subject: [PATCH 03/30] format + define helpers description --- protocols/keeloq_common.h | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/protocols/keeloq_common.h b/protocols/keeloq_common.h index ca0cfbe..63e1bfc 100644 --- a/protocols/keeloq_common.h +++ b/protocols/keeloq_common.h @@ -13,8 +13,10 @@ #include +// Extracts bit number n from integer x #define bit(x, n) (((x) >> (n)) & 1) +// Builds a 5-bit number from five selected bit positions in x #define g5(x, a, b, c, d, e) \ (bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16) @@ -52,7 +54,8 @@ struct SubGhzKeystore { * @param key - manufacture (64bit) * @return keeloq encrypt data */ -static inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { +static inline uint32_t + subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) { uint32_t x = data, r; for(r = 0; r < 528; r++) x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^ @@ -67,7 +70,8 @@ static inline uint32_t subghz_protocol_keeloq_common_encrypt(const uint32_t data * @param key - manufacture (64bit) * @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter */ -static inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { +static inline uint32_t + subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) { uint32_t x = data, r; for(r = 0; r < 528; r++) x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^ @@ -81,7 +85,8 @@ static inline uint32_t subghz_protocol_keeloq_common_decrypt(const uint32_t data * @param key - manufacture (64bit) * @return manufacture for this serial number (64bit) */ -static inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { +static inline uint64_t + subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) { uint32_t k1, k2; data &= 0x0FFFFFFF; @@ -101,7 +106,8 @@ static inline uint64_t subghz_protocol_keeloq_common_normal_learning(uint32_t da * @param xor - magic xor (64bit) * @return manufacture for this serial number (64bit) */ -static inline uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { +static inline uint64_t + subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) { data &= 0x0FFFFFFF; return (((uint64_t)data << 32) | data) ^ xor; } @@ -111,7 +117,8 @@ static inline uint64_t subghz_protocol_keeloq_common_magic_xor_type1_learning(ui * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ -static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { +static inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) { return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) | ((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32); } @@ -121,7 +128,8 @@ static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type1_learning * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ -static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { +static inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) { uint8_t* p = (uint8_t*)&data; uint8_t* m = (uint8_t*)&man; m[7] = p[0]; @@ -136,6 +144,7 @@ static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning * @param man - magic man (64bit) * @return manufacture for this serial number (64bit) */ -static inline uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { +static inline uint64_t + subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); } From 695d9c3d46f4f59a2ed92fb46af8518cda130338 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 16:28:12 +0100 Subject: [PATCH 04/30] rename AUT64_KEY_STRUCT_PACKED_SIZE to AUT64_PACKED_KEY_SIZE as the size is not the struct size but the packed key size --- protocols/aut64.h | 2 +- protocols/vag.c | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/protocols/aut64.h b/protocols/aut64.h index b78db65..dde9c53 100644 --- a/protocols/aut64.h +++ b/protocols/aut64.h @@ -15,7 +15,7 @@ struct aut64_key { uint8_t sbox[AUT64_SBOX_SIZE]; }; -#define AUT64_KEY_STRUCT_PACKED_SIZE 16 +#define AUT64_PACKED_KEY_SIZE 16 void aut64_encrypt(const struct aut64_key key, uint8_t message[]); void aut64_decrypt(const struct aut64_key key, uint8_t message[]); diff --git a/protocols/vag.c b/protocols/vag.c index aaa2f2c..bd41226 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -32,13 +32,10 @@ static void protocol_vag_load_keys(const char* file_name) { protocol_vag_keys_loaded = 0; for(uint8_t i = 0; i < VAG_KEYS_COUNT; i++) { - uint8_t key_packed[AUT64_KEY_STRUCT_PACKED_SIZE]; + uint8_t key_packed[AUT64_PACKED_KEY_SIZE]; if(subghz_keystore_raw_get_data( - file_name, - i * AUT64_KEY_STRUCT_PACKED_SIZE, - key_packed, - AUT64_KEY_STRUCT_PACKED_SIZE)) { + file_name, i * AUT64_PACKED_KEY_SIZE, key_packed, AUT64_PACKED_KEY_SIZE)) { aut64_unpack(&protocol_vag_keys[i], key_packed); protocol_vag_keys_loaded++; } else { From 7196583200820e03077cb76712388bcde251e667 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 22:42:05 +0100 Subject: [PATCH 05/30] - API now returns status codes instead of void - aut64_encrypt/decrypt/pack/unpack now return int - API now uses pointers everywhere - Made 0xFF sentinel meaningful - Strict key validation added and enforced - aut64_encrypt, aut64_decrypt, and aut64_pack fail fast if the key is invalid - aut64_unpack() unpacks into dest, then validates the resulting key - Pointer validation for public API functions - Removed sizeof(...) loop bounds and replaced with defined sizes - Avoid reading uninitialized memory / overwriting incorrectly - Converted hardcoded message[7] to message[AUT64_BLOCK_SIZE - 1]. - Upgraded internal array dimensions to use defines --- protocols/aut64.c | 672 ++++++++++++++++++++++------------------------ protocols/aut64.h | 36 ++- 2 files changed, 341 insertions(+), 367 deletions(-) diff --git a/protocols/aut64.c b/protocols/aut64.c index a946d67..dca6283 100644 --- a/protocols/aut64.c +++ b/protocols/aut64.c @@ -7,261 +7,45 @@ * AUT64 algorithm, 12 rounds * 8 bytes block size, 8 bytes key size * 8 bytes pbox size, 16 bytes sbox size - * + * * Based on: Reference AUT64 implementation in JavaScript (aut64.js) * Vencislav Atanasov, 2025-09-13 - * + * * Based on: Reference AUT64 implementation in python * C Hicks, hkscy.org, 03-01-19 */ -static const uint8_t table_ln[AUT64_NUM_ROUNDS][8] = { - { - 0x4, - 0x5, - 0x6, - 0x7, - 0x0, - 0x1, - 0x2, - 0x3, - }, // Round 0 - { - 0x5, - 0x4, - 0x7, - 0x6, - 0x1, - 0x0, - 0x3, - 0x2, - }, // Round 1 - { - 0x6, - 0x7, - 0x4, - 0x5, - 0x2, - 0x3, - 0x0, - 0x1, - }, // Round 2 - { - 0x7, - 0x6, - 0x5, - 0x4, - 0x3, - 0x2, - 0x1, - 0x0, - }, // Round 3 - { - 0x0, - 0x1, - 0x2, - 0x3, - 0x4, - 0x5, - 0x6, - 0x7, - }, // Round 4 - { - 0x1, - 0x0, - 0x3, - 0x2, - 0x5, - 0x4, - 0x7, - 0x6, - }, // Round 5 - { - 0x2, - 0x3, - 0x0, - 0x1, - 0x6, - 0x7, - 0x4, - 0x5, - }, // Round 6 - { - 0x3, - 0x2, - 0x1, - 0x0, - 0x7, - 0x6, - 0x5, - 0x4, - }, // Round 7 - { - 0x5, - 0x4, - 0x7, - 0x6, - 0x1, - 0x0, - 0x3, - 0x2, - }, // Round 8 - { - 0x4, - 0x5, - 0x6, - 0x7, - 0x0, - 0x1, - 0x2, - 0x3, - }, // Round 9 - { - 0x7, - 0x6, - 0x5, - 0x4, - 0x3, - 0x2, - 0x1, - 0x0, - }, // Round 10 - { - 0x6, - 0x7, - 0x4, - 0x5, - 0x2, - 0x3, - 0x0, - 0x1, - }, // Round 11 +static const uint8_t table_ln[AUT64_NUM_ROUNDS][AUT64_BLOCK_SIZE] = { + {0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 0 + {0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 1 + {0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 2 + {0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 3 + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 4 + {0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 5 + {0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 6 + {0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 7 + {0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 8 + {0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 9 + {0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 10 + {0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 11 }; -static const uint8_t table_un[AUT64_NUM_ROUNDS][8] = { - { - 0x1, - 0x0, - 0x3, - 0x2, - 0x5, - 0x4, - 0x7, - 0x6, - }, // Round 0 - { - 0x0, - 0x1, - 0x2, - 0x3, - 0x4, - 0x5, - 0x6, - 0x7, - }, // Round 1 - { - 0x3, - 0x2, - 0x1, - 0x0, - 0x7, - 0x6, - 0x5, - 0x4, - }, // Round 2 - { - 0x2, - 0x3, - 0x0, - 0x1, - 0x6, - 0x7, - 0x4, - 0x5, - }, // Round 3 - { - 0x5, - 0x4, - 0x7, - 0x6, - 0x1, - 0x0, - 0x3, - 0x2, - }, // Round 4 - { - 0x4, - 0x5, - 0x6, - 0x7, - 0x0, - 0x1, - 0x2, - 0x3, - }, // Round 5 - { - 0x7, - 0x6, - 0x5, - 0x4, - 0x3, - 0x2, - 0x1, - 0x0, - }, // Round 6 - { - 0x6, - 0x7, - 0x4, - 0x5, - 0x2, - 0x3, - 0x0, - 0x1, - }, // Round 7 - { - 0x3, - 0x2, - 0x1, - 0x0, - 0x7, - 0x6, - 0x5, - 0x4, - }, // Round 8 - { - 0x2, - 0x3, - 0x0, - 0x1, - 0x6, - 0x7, - 0x4, - 0x5, - }, // Round 9 - { - 0x1, - 0x0, - 0x3, - 0x2, - 0x5, - 0x4, - 0x7, - 0x6, - }, // Round 10 - { - 0x0, - 0x1, - 0x2, - 0x3, - 0x4, - 0x5, - 0x6, - 0x7, - }, // Round 11 +static const uint8_t table_un[AUT64_NUM_ROUNDS][AUT64_BLOCK_SIZE] = { + {0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 0 + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 1 + {0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 2 + {0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 3 + {0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 4 + {0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 5 + {0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 6 + {0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 7 + {0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 8 + {0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 9 + {0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 10 + {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 11 }; -static const uint8_t table_offset[256] = { +static const uint8_t table_offset[AUT64_OFFSET_TABLE_SIZE] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1 @@ -281,7 +65,7 @@ static const uint8_t table_offset[256] = { 0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA // F }; -static const uint8_t table_sub[16] = { +static const uint8_t table_sub[AUT64_SBOX_SIZE] = { 0x0, 0x1, 0x9, @@ -300,186 +84,360 @@ static const uint8_t table_sub[16] = { 0x8, }; -static uint8_t key_nibble( - const struct aut64_key key, - const uint8_t nibble, - const uint8_t table[], - const uint8_t iteration) { - const uint8_t keyValue = key.key[table[iteration]]; - const uint8_t offset = (keyValue << 4) | nibble; - return table_offset[offset]; -} +// Build an inverse/permutation table. +// Sentinel 0xFF is used to detect missing entries and duplicates. +// Returns AUT64_OK on success, otherwise AUT64_ERR_INVALID_KEY. +static int reverse_box(uint8_t* reversed, const uint8_t* box, size_t len) { + size_t i; -static uint8_t round_key(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { - uint8_t result_hi = 0, result_lo = 0; - - for(int i = 0; i < AUT64_BLOCK_SIZE - 1; i++) { - result_hi ^= key_nibble(key, state[i] >> 4, table_un[roundN], i); - result_lo ^= key_nibble(key, state[i] & 0x0F, table_ln[roundN], i); + for(i = 0; i < len; i++) { + reversed[i] = 0xFF; } - return (result_hi << 4) | result_lo; -} + for(i = 0; i < len; i++) { + const uint8_t v = box[i]; + if(v >= len) { + return AUT64_ERR_INVALID_KEY; + } + if(reversed[v] != 0xFF) { + // Duplicate value means it is not a permutation. + return AUT64_ERR_INVALID_KEY; + } + reversed[v] = (uint8_t)i; + } -static uint8_t final_byte_nibble(const struct aut64_key key, const uint8_t table[]) { - const uint8_t keyValue = key.key[table[AUT64_BLOCK_SIZE - 1]]; - return table_sub[keyValue] << 4; -} - -static uint8_t encrypt_final_byte_nibble( - const struct aut64_key key, - const uint8_t nibble, - const uint8_t table[]) { - const uint8_t offset = final_byte_nibble(key, table); - - int i; - - for(i = 0; i < 16; i++) { - if(table_offset[offset + i] == nibble) { - break; + for(i = 0; i < len; i++) { + if(reversed[i] == 0xFF) { + // Missing mapping. + return AUT64_ERR_INVALID_KEY; } } - return i; + return AUT64_OK; } +// Validate that 'box' is a permutation of 0..len-1. +// Uses 0xFF sentinel logic to detect duplicates/missing values. +static int validate_box_is_permutation(const uint8_t* box, size_t len) { + uint8_t inv[32]; // enough for pbox (8) and sbox (16) + size_t i; + + if(len > sizeof(inv)) { + return AUT64_ERR_INVALID_KEY; + } + + for(i = 0; i < len; i++) { + inv[i] = 0xFF; + } + + for(i = 0; i < len; i++) { + const uint8_t v = box[i]; + if(v >= len) { + return AUT64_ERR_INVALID_KEY; + } + if(inv[v] != 0xFF) { + return AUT64_ERR_INVALID_KEY; + } + inv[v] = (uint8_t)i; + } + + for(i = 0; i < len; i++) { + if(inv[i] == 0xFF) { + return AUT64_ERR_INVALID_KEY; + } + } + + return AUT64_OK; +} + +// Validate that a key is structurally correct: +// - key nibbles are in range 0..15 +// - pbox is a permutation of 0..7 +// - sbox is a permutation of 0..15 +// return AUT64_OK or AUT64_ERR_INVALID_KEY/AUT64_ERR_NULL_POINTER +int aut64_validate_key(const struct aut64_key* key) { + uint8_t i; + int rc; + + if(!key) { + return AUT64_ERR_NULL_POINTER; + } + + // key->key[] is treated as nibbles in multiple places (table_sub indexing, offset building). + for(i = 0; i < AUT64_KEY_SIZE; i++) { + if(key->key[i] >= AUT64_SBOX_SIZE) { + return AUT64_ERR_INVALID_KEY; + } + } + + rc = validate_box_is_permutation(key->pbox, AUT64_PBOX_SIZE); + if(rc != AUT64_OK) { + return rc; + } + + rc = validate_box_is_permutation(key->sbox, AUT64_SBOX_SIZE); + if(rc != AUT64_OK) { + return rc; + } + + return AUT64_OK; +} + +// Compute one 4-bit contribution to the round key +static uint8_t key_nibble( + const struct aut64_key* key, + uint8_t nibble, + const uint8_t table[AUT64_BLOCK_SIZE], + uint8_t iteration) { + const uint8_t keyValue = key->key[table[iteration]]; + const uint8_t offset = (uint8_t)((keyValue << 4) | nibble); + return table_offset[offset]; +} + +// Compute the round compression byte derived from the current state and the key for a given round. +static uint8_t round_key(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) { + uint8_t result_hi = 0, result_lo = 0; + + for(int i = 0; i < AUT64_BLOCK_SIZE - 1; i++) { + result_hi ^= key_nibble(key, (uint8_t)(state[i] >> 4), table_un[roundN], (uint8_t)i); + result_lo ^= key_nibble(key, (uint8_t)(state[i] & 0x0F), table_ln[roundN], (uint8_t)i); + } + + return (uint8_t)((result_hi << 4) | result_lo); +} + +// Compute the transformed key nibble used as an offset for final-byte processing in a round. static uint8_t - encrypt_compress(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { - const uint8_t roundKey = round_key(key, state, roundN); - uint8_t result_hi = roundKey >> 4, result_lo = roundKey & 0x0F; - - result_hi ^= - encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] >> 4, table_un[roundN]); - result_lo ^= - encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, table_ln[roundN]); - - return (result_hi << 4) | result_lo; + final_byte_nibble(const struct aut64_key* key, const uint8_t table[AUT64_BLOCK_SIZE]) { + const uint8_t keyValue = key->key[table[AUT64_BLOCK_SIZE - 1]]; + return (uint8_t)(table_sub[keyValue] << 4); } -static uint8_t decrypt_final_byte_nibble( - const struct aut64_key key, - const uint8_t nibble, - const uint8_t table[], - const uint8_t result) { +// Compute the inverse lookup for a final-byte nibble during encryption. +static uint8_t encrypt_final_byte_nibble( + const struct aut64_key* key, + uint8_t nibble, + const uint8_t table[AUT64_BLOCK_SIZE]) { const uint8_t offset = final_byte_nibble(key, table); - return table_offset[(result ^ nibble) + offset]; + for(int i = 0; i < 16; i++) { + if(table_offset[(uint8_t)(offset + i)] == nibble) { + return (uint8_t)i; + } + } + // Should never happen for valid inputs; return 0 as a defined value. + return 0; } +// Perform the compression step for one encryption round, producing the new last byte. static uint8_t - decrypt_compress(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { + encrypt_compress(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) { const uint8_t roundKey = round_key(key, state, roundN); - uint8_t result_hi = roundKey >> 4, result_lo = roundKey & 0x0F; + uint8_t result_hi = (uint8_t)(roundKey >> 4), result_lo = (uint8_t)(roundKey & 0x0F); + + result_hi ^= encrypt_final_byte_nibble( + key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] >> 4), table_un[roundN]); + result_lo ^= encrypt_final_byte_nibble( + key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] & 0x0F), table_ln[roundN]); + + return (uint8_t)((result_hi << 4) | result_lo); +} + +// Reverse the final-byte nibble transformation during decryption. +static uint8_t decrypt_final_byte_nibble( + const struct aut64_key* key, + uint8_t nibble, + const uint8_t table[AUT64_BLOCK_SIZE], + uint8_t result) { + const uint8_t offset = final_byte_nibble(key, table); + return table_offset[(uint8_t)((result ^ nibble) + offset)]; +} + +// Perform the compression step for one decryption round, restoring the previous last byte. +static uint8_t + decrypt_compress(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) { + const uint8_t roundKey = round_key(key, state, roundN); + uint8_t result_hi = (uint8_t)(roundKey >> 4), result_lo = (uint8_t)(roundKey & 0x0F); result_hi = decrypt_final_byte_nibble( - key, state[AUT64_BLOCK_SIZE - 1] >> 4, table_un[roundN], result_hi); + key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] >> 4), table_un[roundN], result_hi); result_lo = decrypt_final_byte_nibble( - key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, table_ln[roundN], result_lo); + key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] & 0x0F), table_ln[roundN], result_lo); - return (result_hi << 4) | result_lo; + return (uint8_t)((result_hi << 4) | result_lo); } -static uint8_t substitute(const struct aut64_key key, const uint8_t byte) { - return (key.sbox[byte >> 4] << 4) | key.sbox[byte & 0x0F]; +// Apply the S-box substitution to a single byte. +static uint8_t substitute(const struct aut64_key* key, uint8_t byte) { + return (uint8_t)((key->sbox[byte >> 4] << 4) | key->sbox[byte & 0x0F]); } -static void permute_bytes(const struct aut64_key key, uint8_t state[]) { - uint8_t result[AUT64_PBOX_SIZE] = {0}; +// Apply the byte-level permutation (pbox) to the 8-byte state block. +static void permute_bytes(const struct aut64_key* key, uint8_t* state) { + // Key is validated up-front, so pbox[] is a correct permutation of 0..7. + uint8_t result[AUT64_PBOX_SIZE]; for(int i = 0; i < AUT64_PBOX_SIZE; i++) { - result[key.pbox[i]] = state[i]; + result[key->pbox[i]] = state[i]; } memcpy(state, result, AUT64_PBOX_SIZE); } -static uint8_t permute_bits(const struct aut64_key key, const uint8_t byte) { +// Apply bit-level permutation to a single byte using the pbox mapping. +static uint8_t permute_bits(const struct aut64_key* key, uint8_t byte) { + // Key is validated up-front, so pbox[] is a correct permutation of 0..7. uint8_t result = 0; for(int i = 0; i < 8; i++) { if(byte & (1 << i)) { - result |= (1 << key.pbox[i]); + result |= (uint8_t)(1 << key->pbox[i]); } } return result; } -static void reverse_box(uint8_t* reversed, const uint8_t* box, const size_t len) { - for(size_t i = 0; i < len; i++) { - for(size_t j = 0; j < len; j++) { - if(box[j] == i) { - reversed[i] = j; - break; - } - } - } -} +// Encrypt one 8-byte block in place using the provided validated key. +int aut64_encrypt(const struct aut64_key* key, uint8_t* message) { + int rc; -void aut64_encrypt(const struct aut64_key key, uint8_t message[]) { - struct aut64_key reverse_key; - memcpy(reverse_key.key, key.key, AUT64_KEY_SIZE); - reverse_box(reverse_key.pbox, key.pbox, AUT64_PBOX_SIZE); - reverse_box(reverse_key.sbox, key.sbox, AUT64_SBOX_SIZE); + if(!key || !message) { + return AUT64_ERR_NULL_POINTER; + } + + // Validate key before doing anything. This prevents silent, unsafe behavior. + rc = aut64_validate_key(key); + if(rc != AUT64_OK) { + return rc; + } + + // Build a reversed key (inverse pbox and sbox) ... + // Fully initialize to avoid any uninitialized fields/padding. + struct aut64_key reverse_key = (struct aut64_key){0}; + reverse_key.index = key->index; + memcpy(reverse_key.key, key->key, AUT64_KEY_SIZE); + + rc = reverse_box(reverse_key.pbox, key->pbox, AUT64_PBOX_SIZE); + if(rc != AUT64_OK) { + return rc; + } + + rc = reverse_box(reverse_key.sbox, key->sbox, AUT64_SBOX_SIZE); + if(rc != AUT64_OK) { + return rc; + } for(int i = 0; i < AUT64_NUM_ROUNDS; i++) { - permute_bytes(reverse_key, message); - message[7] = encrypt_compress(reverse_key, message, i); - message[7] = substitute(reverse_key, message[7]); - message[7] = permute_bits(reverse_key, message[7]); - message[7] = substitute(reverse_key, message[7]); + permute_bytes(&reverse_key, message); + message[AUT64_BLOCK_SIZE - 1] = encrypt_compress(&reverse_key, message, (uint8_t)i); + message[AUT64_BLOCK_SIZE - 1] = substitute(&reverse_key, message[AUT64_BLOCK_SIZE - 1]); + message[AUT64_BLOCK_SIZE - 1] = permute_bits(&reverse_key, message[AUT64_BLOCK_SIZE - 1]); + message[AUT64_BLOCK_SIZE - 1] = substitute(&reverse_key, message[AUT64_BLOCK_SIZE - 1]); } + + return AUT64_OK; } -void aut64_decrypt(const struct aut64_key key, uint8_t message[]) { +// Decrypt one 8-byte block in place using the provided validated key. +int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { + int rc; + + if(!key || !message) { + return AUT64_ERR_NULL_POINTER; + } + + rc = aut64_validate_key(key); + if(rc != AUT64_OK) { + return rc; + } + for(int i = AUT64_NUM_ROUNDS - 1; i >= 0; i--) { - message[7] = substitute(key, message[7]); - message[7] = permute_bits(key, message[7]); - message[7] = substitute(key, message[7]); - message[7] = decrypt_compress(key, message, i); + message[AUT64_BLOCK_SIZE - 1] = substitute(key, message[AUT64_BLOCK_SIZE - 1]); + message[AUT64_BLOCK_SIZE - 1] = permute_bits(key, message[AUT64_BLOCK_SIZE - 1]); + message[AUT64_BLOCK_SIZE - 1] = substitute(key, message[AUT64_BLOCK_SIZE - 1]); + message[AUT64_BLOCK_SIZE - 1] = decrypt_compress(key, message, (uint8_t)i); permute_bytes(key, message); } + + return AUT64_OK; } -void aut64_pack(uint8_t dest[], const struct aut64_key src) { - dest[0] = src.index; +// Serialize a validated key structure into its 16-byte packed format. +int aut64_pack(uint8_t* dest, const struct aut64_key* src) { + int rc; - for(uint8_t i = 0; i < sizeof(src.key) / 2; i++) { - dest[i + 1] = (src.key[i * 2] << 4) | src.key[i * 2 + 1]; + if(!dest || !src) { + return AUT64_ERR_NULL_POINTER; + } + + // Validate the key we are about to pack. This prevents producing garbage packed keys. + rc = aut64_validate_key(src); + if(rc != AUT64_OK) { + return rc; + } + + // Initialize the output so callers never observe stale bytes. + memset(dest, 0, AUT64_PACKED_KEY_SIZE); + + dest[0] = src->index; + + for(uint8_t i = 0; i < AUT64_KEY_SIZE / 2; i++) { + dest[i + 1] = (uint8_t)((src->key[i * 2] << 4) | src->key[i * 2 + 1]); } uint32_t pbox = 0; - - for(uint8_t i = 0; i < sizeof(src.pbox); i++) { - pbox = (pbox << 3) | src.pbox[i]; + for(uint8_t i = 0; i < AUT64_PBOX_SIZE; i++) { + pbox = (pbox << 3) | src->pbox[i]; } - dest[5] = pbox >> 16; - dest[6] = (pbox >> 8) & 0xFF; - dest[7] = pbox & 0xFF; + dest[5] = (uint8_t)(pbox >> 16); + dest[6] = (uint8_t)((pbox >> 8) & 0xFF); + dest[7] = (uint8_t)(pbox & 0xFF); - for(uint8_t i = 0; i < sizeof(src.sbox) / 2; i++) { - dest[i + 8] = (src.sbox[i * 2] << 4) | src.sbox[i * 2 + 1]; + for(uint8_t i = 0; i < AUT64_SBOX_SIZE / 2; i++) { + dest[i + 8] = (uint8_t)((src->sbox[i * 2] << 4) | src->sbox[i * 2 + 1]); } + + return AUT64_OK; } -void aut64_unpack(struct aut64_key* dest, const uint8_t src[]) { - dest->index = src[0]; - - for(uint8_t i = 0; i < sizeof(dest->key) / 2; i++) { - dest->key[i * 2] = src[i + 1] >> 4; - dest->key[i * 2 + 1] = src[i + 1] & 0xF; +// Deserialize a 16-byte packed key into a key structure and validate it. +int aut64_unpack(struct aut64_key* dest, const uint8_t* src) { + if(!dest || !src) { + return AUT64_ERR_NULL_POINTER; } - uint32_t pbox = (src[5] << 16) | (src[6] << 8) | src[7]; + // Clear the whole struct first, so all fields are in a defined state. + *dest = (struct aut64_key){0}; - for(int8_t i = sizeof(dest->pbox) - 1; i >= 0; i--) { - dest->pbox[i] = pbox & 0x7; + dest->index = src[0]; + + for(uint8_t i = 0; i < AUT64_KEY_SIZE / 2; i++) { + dest->key[i * 2] = (uint8_t)(src[i + 1] >> 4); + dest->key[i * 2 + 1] = (uint8_t)(src[i + 1] & 0xF); + } + + uint32_t pbox = ((uint32_t)src[5] << 16) | ((uint32_t)src[6] << 8) | (uint32_t)src[7]; + + for(int8_t i = AUT64_PBOX_SIZE - 1; i >= 0; i--) { + dest->pbox[i] = (uint8_t)(pbox & 0x7); pbox >>= 3; } - for(uint8_t i = 0; i < sizeof(dest->sbox) / 2; i++) { - dest->sbox[i * 2] = src[i + 8] >> 4; - dest->sbox[i * 2 + 1] = src[i + 8] & 0xF; + for(uint8_t i = 0; i < AUT64_SBOX_SIZE / 2; i++) { + dest->sbox[i * 2] = (uint8_t)(src[i + 8] >> 4); + dest->sbox[i * 2 + 1] = (uint8_t)(src[i + 8] & 0xF); } + + // Validate what we just unpacked. If invalid, return error. + // We do not fix up broken keys silently. + { + int rc = aut64_validate_key(dest); + if(rc != AUT64_OK) { + return AUT64_ERR_INVALID_PACKED; + } + } + + return AUT64_OK; } diff --git a/protocols/aut64.h b/protocols/aut64.h index dde9c53..d350666 100644 --- a/protocols/aut64.h +++ b/protocols/aut64.h @@ -1,12 +1,23 @@ #pragma once #include +#include -#define AUT64_NUM_ROUNDS 12 -#define AUT64_BLOCK_SIZE 8 -#define AUT64_KEY_SIZE 8 -#define AUT64_PBOX_SIZE 8 -#define AUT64_SBOX_SIZE 16 +#define AUT64_NUM_ROUNDS 12 +#define AUT64_BLOCK_SIZE 8 +#define AUT64_KEY_SIZE 8 +#define AUT64_PBOX_SIZE 8 +#define AUT64_SBOX_SIZE 16 +#define AUT64_PACKED_KEY_SIZE 16 + +// Internal helper table size (offset lookup table). +#define AUT64_OFFSET_TABLE_SIZE 256 + +// Status codes. Keep it simple and C-friendly. +#define AUT64_OK 0 +#define AUT64_ERR_INVALID_KEY (-1) +#define AUT64_ERR_INVALID_PACKED (-2) +#define AUT64_ERR_NULL_POINTER (-3) struct aut64_key { uint8_t index; @@ -15,9 +26,14 @@ struct aut64_key { uint8_t sbox[AUT64_SBOX_SIZE]; }; -#define AUT64_PACKED_KEY_SIZE 16 +// Optional helper if callers want to check keys up-front. +int aut64_validate_key(const struct aut64_key* key); -void aut64_encrypt(const struct aut64_key key, uint8_t message[]); -void aut64_decrypt(const struct aut64_key key, uint8_t message[]); -void aut64_pack(uint8_t dest[], const struct aut64_key src); -void aut64_unpack(struct aut64_key* dest, const uint8_t src[]); +// Pointers are used for both the key and the message to avoid implicit copies. +// The message buffer must be at least AUT64_BLOCK_SIZE bytes. +int aut64_encrypt(const struct aut64_key* key, uint8_t* message); +int aut64_decrypt(const struct aut64_key* key, uint8_t* message); + +// Packed key buffer must be at least AUT64_PACKED_KEY_SIZE bytes. +int aut64_pack(uint8_t* dest, const struct aut64_key* src); +int aut64_unpack(struct aut64_key* dest, const uint8_t* src); From 6e01796a951644868bccf8dcc6399286e84aa4c5 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 22:42:40 +0100 Subject: [PATCH 06/30] Use updated aut64 API, catch errors --- protocols/vag.c | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/protocols/vag.c b/protocols/vag.c index bd41226..f261d72 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -36,8 +36,17 @@ static void protocol_vag_load_keys(const char* file_name) { if(subghz_keystore_raw_get_data( file_name, i * AUT64_PACKED_KEY_SIZE, key_packed, AUT64_PACKED_KEY_SIZE)) { - aut64_unpack(&protocol_vag_keys[i], key_packed); - protocol_vag_keys_loaded++; + int rc = aut64_unpack(&protocol_vag_keys[i], key_packed); + if(rc == AUT64_ERR_INVALID_PACKED) { + FURI_LOG_E(TAG, "Invalid key: %u", i); + } else if(rc == AUT64_ERR_NULL_POINTER) { + FURI_LOG_E(TAG, "Key is NULL: %d", i); + } + if(rc == AUT64_OK) { + protocol_vag_keys_loaded++; + } else { + break; + } } else { FURI_LOG_E(TAG, "Unable to load key %u", i); break; @@ -188,8 +197,14 @@ static bool vag_aut64_decrypt(uint8_t* block, int key_index) { FURI_LOG_E(TAG, "Key not found: %d", key_index + 1); return false; } - aut64_decrypt(*key, block); - return true; + int rc = aut64_decrypt(key, block); + if(rc == AUT64_ERR_INVALID_KEY) { + FURI_LOG_E(TAG, "Invalid key: %d", key_index + 1); + } else if(rc == AUT64_ERR_NULL_POINTER) { + FURI_LOG_E(TAG, "key is NULL: %d", key_index + 1); + } + + return (rc == AUT64_OK) ? true : false; } static void vag_parse_data(SubGhzProtocolDecoderVAG* instance) { @@ -1067,8 +1082,14 @@ static bool vag_aut64_encrypt(uint8_t* block, int key_index) { FURI_LOG_E(TAG, "Key not found for encryption: %d", key_index + 1); return false; } - aut64_encrypt(*key, block); - return true; + int rc = aut64_encrypt(key, block); + if(rc == AUT64_ERR_INVALID_KEY) { + FURI_LOG_E(TAG, "Invalid key: %d", key_index + 1); + } else if(rc == AUT64_ERR_NULL_POINTER) { + FURI_LOG_E(TAG, "key is NULL"); + } + + return (rc == AUT64_OK) ? true : false; } static uint8_t vag_get_dispatch_byte(uint8_t btn, uint8_t vag_type) { From d2c153a5c6b38468f53ca4c52f5712ce97bb02b2 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 23:04:33 +0100 Subject: [PATCH 07/30] make aut64 validations optional via a define --- protocols/aut64.c | 32 +++++++++++++++++++++----------- protocols/aut64.h | 5 +++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/protocols/aut64.c b/protocols/aut64.c index dca6283..53430bf 100644 --- a/protocols/aut64.c +++ b/protocols/aut64.c @@ -96,6 +96,7 @@ static int reverse_box(uint8_t* reversed, const uint8_t* box, size_t len) { for(i = 0; i < len; i++) { const uint8_t v = box[i]; +#ifdef AUT64_ENABLE_VALIDATIONS if(v >= len) { return AUT64_ERR_INVALID_KEY; } @@ -103,19 +104,24 @@ static int reverse_box(uint8_t* reversed, const uint8_t* box, size_t len) { // Duplicate value means it is not a permutation. return AUT64_ERR_INVALID_KEY; } +#endif reversed[v] = (uint8_t)i; } +#ifdef AUT64_ENABLE_VALIDATIONS for(i = 0; i < len; i++) { if(reversed[i] == 0xFF) { // Missing mapping. return AUT64_ERR_INVALID_KEY; } } +#endif return AUT64_OK; } +#ifdef AUT64_ENABLE_VALIDATIONS + // Validate that 'box' is a permutation of 0..len-1. // Uses 0xFF sentinel logic to detect duplicates/missing values. static int validate_box_is_permutation(const uint8_t* box, size_t len) { @@ -183,6 +189,8 @@ int aut64_validate_key(const struct aut64_key* key) { return AUT64_OK; } +#endif // AUT64_ENABLE_VALIDATIONS + // Compute one 4-bit contribution to the round key static uint8_t key_nibble( const struct aut64_key* key, @@ -306,11 +314,13 @@ int aut64_encrypt(const struct aut64_key* key, uint8_t* message) { return AUT64_ERR_NULL_POINTER; } +#ifdef AUT64_ENABLE_VALIDATIONS // Validate key before doing anything. This prevents silent, unsafe behavior. rc = aut64_validate_key(key); if(rc != AUT64_OK) { return rc; } +#endif // Build a reversed key (inverse pbox and sbox) ... // Fully initialize to avoid any uninitialized fields/padding. @@ -341,16 +351,16 @@ int aut64_encrypt(const struct aut64_key* key, uint8_t* message) { // Decrypt one 8-byte block in place using the provided validated key. int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { - int rc; - if(!key || !message) { return AUT64_ERR_NULL_POINTER; } - rc = aut64_validate_key(key); +#ifdef AUT64_ENABLE_VALIDATIONS + int rc = aut64_validate_key(key); if(rc != AUT64_OK) { return rc; } +#endif for(int i = AUT64_NUM_ROUNDS - 1; i >= 0; i--) { message[AUT64_BLOCK_SIZE - 1] = substitute(key, message[AUT64_BLOCK_SIZE - 1]); @@ -365,17 +375,17 @@ int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { // Serialize a validated key structure into its 16-byte packed format. int aut64_pack(uint8_t* dest, const struct aut64_key* src) { - int rc; - if(!dest || !src) { return AUT64_ERR_NULL_POINTER; } +#ifdef AUT64_ENABLE_VALIDATIONS // Validate the key we are about to pack. This prevents producing garbage packed keys. - rc = aut64_validate_key(src); + int rc = aut64_validate_key(src); if(rc != AUT64_OK) { return rc; } +#endif // Initialize the output so callers never observe stale bytes. memset(dest, 0, AUT64_PACKED_KEY_SIZE); @@ -430,14 +440,14 @@ int aut64_unpack(struct aut64_key* dest, const uint8_t* src) { dest->sbox[i * 2 + 1] = (uint8_t)(src[i + 8] & 0xF); } +#ifdef AUT64_ENABLE_VALIDATIONS // Validate what we just unpacked. If invalid, return error. // We do not fix up broken keys silently. - { - int rc = aut64_validate_key(dest); - if(rc != AUT64_OK) { - return AUT64_ERR_INVALID_PACKED; - } + int rc = aut64_validate_key(dest); + if(rc != AUT64_OK) { + return AUT64_ERR_INVALID_PACKED; } +#endif return AUT64_OK; } diff --git a/protocols/aut64.h b/protocols/aut64.h index d350666..3f40a79 100644 --- a/protocols/aut64.h +++ b/protocols/aut64.h @@ -3,6 +3,9 @@ #include #include +// uncomment to activate key validation, index boundary verifications, ... +//#define AUT64_ENABLE_VALIDATIONS + #define AUT64_NUM_ROUNDS 12 #define AUT64_BLOCK_SIZE 8 #define AUT64_KEY_SIZE 8 @@ -26,8 +29,10 @@ struct aut64_key { uint8_t sbox[AUT64_SBOX_SIZE]; }; +#ifdef AUT64_ENABLE_VALIDATIONS // Optional helper if callers want to check keys up-front. int aut64_validate_key(const struct aut64_key* key); +#endif // Pointers are used for both the key and the message to avoid implicit copies. // The message buffer must be at least AUT64_BLOCK_SIZE bytes. From ecdee5cf1ee719da899c18bde372bc22565ddb5a Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 23:18:25 +0100 Subject: [PATCH 08/30] make validations optionnal --- protocols/aut64.c | 13 ++++++------- protocols/vag.c | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/protocols/aut64.c b/protocols/aut64.c index 53430bf..6f51b6c 100644 --- a/protocols/aut64.c +++ b/protocols/aut64.c @@ -1,5 +1,5 @@ -#include #include "aut64.h" +#include // https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf @@ -310,11 +310,10 @@ static uint8_t permute_bits(const struct aut64_key* key, uint8_t byte) { int aut64_encrypt(const struct aut64_key* key, uint8_t* message) { int rc; +#ifdef AUT64_ENABLE_VALIDATIONS if(!key || !message) { return AUT64_ERR_NULL_POINTER; } - -#ifdef AUT64_ENABLE_VALIDATIONS // Validate key before doing anything. This prevents silent, unsafe behavior. rc = aut64_validate_key(key); if(rc != AUT64_OK) { @@ -351,11 +350,10 @@ int aut64_encrypt(const struct aut64_key* key, uint8_t* message) { // Decrypt one 8-byte block in place using the provided validated key. int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { +#ifdef AUT64_ENABLE_VALIDATIONS if(!key || !message) { return AUT64_ERR_NULL_POINTER; } - -#ifdef AUT64_ENABLE_VALIDATIONS int rc = aut64_validate_key(key); if(rc != AUT64_OK) { return rc; @@ -375,11 +373,10 @@ int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { // Serialize a validated key structure into its 16-byte packed format. int aut64_pack(uint8_t* dest, const struct aut64_key* src) { +#ifdef AUT64_ENABLE_VALIDATIONS if(!dest || !src) { return AUT64_ERR_NULL_POINTER; } - -#ifdef AUT64_ENABLE_VALIDATIONS // Validate the key we are about to pack. This prevents producing garbage packed keys. int rc = aut64_validate_key(src); if(rc != AUT64_OK) { @@ -414,9 +411,11 @@ int aut64_pack(uint8_t* dest, const struct aut64_key* src) { // Deserialize a 16-byte packed key into a key structure and validate it. int aut64_unpack(struct aut64_key* dest, const uint8_t* src) { +#ifdef AUT64_ENABLE_VALIDATIONS if(!dest || !src) { return AUT64_ERR_NULL_POINTER; } +#endif // Clear the whole struct first, so all fields are in a defined state. *dest = (struct aut64_key){0}; diff --git a/protocols/vag.c b/protocols/vag.c index f261d72..2cd5ff0 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -37,6 +37,7 @@ static void protocol_vag_load_keys(const char* file_name) { if(subghz_keystore_raw_get_data( file_name, i * AUT64_PACKED_KEY_SIZE, key_packed, AUT64_PACKED_KEY_SIZE)) { int rc = aut64_unpack(&protocol_vag_keys[i], key_packed); +#ifdef AUT64_ENABLE_VALIDATIONS if(rc == AUT64_ERR_INVALID_PACKED) { FURI_LOG_E(TAG, "Invalid key: %u", i); } else if(rc == AUT64_ERR_NULL_POINTER) { @@ -47,6 +48,10 @@ static void protocol_vag_load_keys(const char* file_name) { } else { break; } +#else + (void)rc; + protocol_vag_keys_loaded++; +#endif } else { FURI_LOG_E(TAG, "Unable to load key %u", i); break; @@ -198,11 +203,13 @@ static bool vag_aut64_decrypt(uint8_t* block, int key_index) { return false; } int rc = aut64_decrypt(key, block); +#ifdef AUT64_ENABLE_VALIDATIONS if(rc == AUT64_ERR_INVALID_KEY) { FURI_LOG_E(TAG, "Invalid key: %d", key_index + 1); } else if(rc == AUT64_ERR_NULL_POINTER) { FURI_LOG_E(TAG, "key is NULL: %d", key_index + 1); } +#endif return (rc == AUT64_OK) ? true : false; } @@ -1083,11 +1090,13 @@ static bool vag_aut64_encrypt(uint8_t* block, int key_index) { return false; } int rc = aut64_encrypt(key, block); +#ifdef AUT64_ENABLE_VALIDATIONS if(rc == AUT64_ERR_INVALID_KEY) { FURI_LOG_E(TAG, "Invalid key: %d", key_index + 1); } else if(rc == AUT64_ERR_NULL_POINTER) { FURI_LOG_E(TAG, "key is NULL"); } +#endif return (rc == AUT64_OK) ? true : false; } From 20a1860395f302dd94a656f4931dab7b8ebc77e5 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Wed, 11 Feb 2026 23:38:04 +0100 Subject: [PATCH 09/30] make aut64_pack compilation optional, as it's not (yet?) used in the code --- protocols/aut64.c | 2 ++ protocols/aut64.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/protocols/aut64.c b/protocols/aut64.c index 6f51b6c..66c80c5 100644 --- a/protocols/aut64.c +++ b/protocols/aut64.c @@ -371,6 +371,7 @@ int aut64_decrypt(const struct aut64_key* key, uint8_t* message) { return AUT64_OK; } +#ifdef AUT64_PACK_SUPPORT // Serialize a validated key structure into its 16-byte packed format. int aut64_pack(uint8_t* dest, const struct aut64_key* src) { #ifdef AUT64_ENABLE_VALIDATIONS @@ -408,6 +409,7 @@ int aut64_pack(uint8_t* dest, const struct aut64_key* src) { return AUT64_OK; } +#endif // AUT64_PACK_SUPPORT // Deserialize a 16-byte packed key into a key structure and validate it. int aut64_unpack(struct aut64_key* dest, const uint8_t* src) { diff --git a/protocols/aut64.h b/protocols/aut64.h index 3f40a79..fd9572e 100644 --- a/protocols/aut64.h +++ b/protocols/aut64.h @@ -6,6 +6,9 @@ // uncomment to activate key validation, index boundary verifications, ... //#define AUT64_ENABLE_VALIDATIONS +// uncomment to add compilation of aut64_pack (currently unused in the code) +//#define AUT64_PACK_SUPPORT + #define AUT64_NUM_ROUNDS 12 #define AUT64_BLOCK_SIZE 8 #define AUT64_KEY_SIZE 8 @@ -40,5 +43,7 @@ int aut64_encrypt(const struct aut64_key* key, uint8_t* message); int aut64_decrypt(const struct aut64_key* key, uint8_t* message); // Packed key buffer must be at least AUT64_PACKED_KEY_SIZE bytes. +#ifdef AUT64_PACK_SUPPORT int aut64_pack(uint8_t* dest, const struct aut64_key* src); +#endif int aut64_unpack(struct aut64_key* dest, const uint8_t* src); From 0271feca445a34bd39061df5f9423e5c24e0ff9f Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 00:18:49 +0100 Subject: [PATCH 10/30] use calloc instead of malloc, avoid setting variables to zero after --- protocols/vag.c | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/protocols/vag.c b/protocols/vag.c index 2cd5ff0..46f7172 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -498,14 +498,11 @@ const SubGhzProtocol vag_protocol = { void* subghz_protocol_decoder_vag_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderVAG* instance = malloc(sizeof(SubGhzProtocolDecoderVAG)); + SubGhzProtocolDecoderVAG* instance = calloc(1, sizeof(SubGhzProtocolDecoderVAG)); + furi_check(instance); instance->base.protocol = &vag_protocol; instance->generic.protocol_name = instance->base.protocol->name; instance->decrypted = false; - instance->serial = 0; - instance->cnt = 0; - instance->btn = 0; - instance->check_byte = 0; instance->key_idx = 0xFF; protocol_vag_load_keys(APP_ASSETS_PATH("vag")); @@ -1729,25 +1726,15 @@ void* subghz_protocol_encoder_vag_alloc(SubGhzEnvironment* environment) { UNUSED(environment); FURI_LOG_I(TAG, "VAG encoder alloc"); - SubGhzProtocolEncoderVAG* instance = malloc(sizeof(SubGhzProtocolEncoderVAG)); + SubGhzProtocolEncoderVAG* instance = calloc(1, sizeof(SubGhzProtocolEncoderVAG)); + furi_check(instance); instance->base.protocol = &vag_protocol; instance->generic.protocol_name = instance->base.protocol->name; - instance->upload = malloc(VAG_ENCODER_UPLOAD_MAX_SIZE * sizeof(LevelDuration)); - instance->size_upload = 0; + instance->upload = calloc(VAG_ENCODER_UPLOAD_MAX_SIZE, sizeof(LevelDuration)); + furi_check(instance->upload); instance->repeat = 10; - instance->front = 0; instance->is_running = false; - - instance->key1_low = 0; - instance->key1_high = 0; - instance->key2_low = 0; - instance->key2_high = 0; - instance->serial = 0; - instance->cnt = 0; - instance->vag_type = 0; - instance->btn = 0; - instance->dispatch_byte = 0; instance->key_idx = 0xFF; protocol_vag_load_keys(APP_ASSETS_PATH("vag")); From 637092c27ec7a0fd0c4df0dd552ce36372e10659 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 09:32:28 +0100 Subject: [PATCH 11/30] - Harden Manchester timing/reset, shrink encoder emit code - Add compact timing helpers/macros for consistent TE tolerance checks - Reset decoder Manchester state + counters in reset() to avoid stale parses - Make Type2 Manchester decoder use tolerance-based checks (half-bit robustness) - Compact repeated encoder Manchester bit emission via macro - Add upload buffer overflow guards in encoder builders --- protocols/vag.c | 218 ++++++++++++++++++++---------------------------- 1 file changed, 92 insertions(+), 126 deletions(-) diff --git a/protocols/vag.c b/protocols/vag.c index 46f7172..908bdce 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -5,6 +5,46 @@ #define TAG "VAGProtocol" +#define VAG_ENCODER_UPLOAD_MAX_SIZE 680 +#define VAG_KEYS_COUNT 3 +// uncomment to enable +//#define VAG_CHECK_UPLOAD_OVERFLOW + +/* Timing helpers: keep decoder/encoder checks compact and consistent */ +static inline uint32_t vag_udiff_u32(uint32_t a, uint32_t b) { + return (a > b) ? (a - b) : (b - a); +} +#define VAG_NEAR(d, t, tol) (vag_udiff_u32((d), (t)) <= (uint32_t)(tol)) +#define VAG_TOL_300 79u +#define VAG_TOL_500 120u + +/* Manchester emit helper: one bit -> two half-bits */ +#define VAG_EMIT_MANCHESTER(u, idx, bit, te) \ + do { \ + if(bit) { \ + (u)[(idx)++] = level_duration_make(true, (te)); \ + (u)[(idx)++] = level_duration_make(false, (te)); \ + } else { \ + (u)[(idx)++] = level_duration_make(false, (te)); \ + (u)[(idx)++] = level_duration_make(true, (te)); \ + } \ + } while(0) + +#ifdef VAG_CHECK_UPLOAD_OVERFLOW +/* Guard against upload buffer overflow; set size_upload=0 on failure */ +#define VAG_UPLOAD_NEED(inst, idx, need) \ + do { \ + if(((idx) + (need)) > VAG_ENCODER_UPLOAD_MAX_SIZE) { \ + FURI_LOG_E( \ + TAG, "Upload overflow (need=%u idx=%zu)", (unsigned)(need), (size_t)(idx)); \ + (inst)->size_upload = 0; \ + return; \ + } \ + } while(0) +#else +#define VAG_UPLOAD_NEED(inst, idx, need) +#endif + static const SubGhzBlockConst subghz_protocol_vag_const = { .te_short = 500, .te_long = 1000, @@ -12,8 +52,6 @@ static const SubGhzBlockConst subghz_protocol_vag_const = { .min_count_bit_for_found = 80, }; -#define VAG_KEYS_COUNT 3 - static int8_t protocol_vag_keys_loaded = -1; static struct aut64_key protocol_vag_keys[VAG_KEYS_COUNT]; @@ -519,8 +557,22 @@ void subghz_protocol_decoder_vag_free(void* context) { void subghz_protocol_decoder_vag_reset(void* context) { furi_check(context); SubGhzProtocolDecoderVAG* instance = context; + instance->parser_step = VAGDecoderStepReset; instance->decrypted = false; + + /* Reset decoder state to avoid stale parsing after external resets */ + instance->te_last = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->data_count_bit = 0; + instance->vag_type = 0; + instance->header_count = 0; + instance->mid_count = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->serial = 0; instance->cnt = 0; instance->btn = 0; @@ -605,12 +657,7 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati if(duration > 79) { return; } - if(instance->te_last < 300) { - diff = 300 - instance->te_last; - } else { - diff = instance->te_last - 300; - } - if(diff > 79) { + if(!VAG_NEAR(instance->te_last, 300, VAG_TOL_300)) { return; } instance->parser_step = VAGDecoderStepData1; @@ -618,12 +665,7 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati } check_preamble1_prev: - if(instance->te_last < 300) { - diff = 300 - instance->te_last; - } else { - diff = instance->te_last - 300; - } - if(diff > 79) { + if(!VAG_NEAR(instance->te_last, 300, VAG_TOL_300)) { instance->parser_step = VAGDecoderStepReset; if(instance->header_count >= 201) { } @@ -773,86 +815,52 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati break; case VAGDecoderStepSync2A: - if(!level) { - if(duration < 500) { - diff = 500 - duration; - } else { - diff = duration - 500; - } - if(diff < 80) { - if(instance->te_last < 1000) { - diff = 1000 - instance->te_last; - } else { - diff = instance->te_last - 1000; - } - if(diff < 80) { - instance->te_last = duration; - instance->parser_step = VAGDecoderStepSync2B; - break; - } - } + if(!level && VAG_NEAR(duration, 500, 79) && VAG_NEAR(instance->te_last, 1000, 79)) { + instance->te_last = duration; + instance->parser_step = VAGDecoderStepSync2B; + break; } instance->parser_step = VAGDecoderStepReset; break; case VAGDecoderStepSync2B: - if(level) { - if(duration < 750) { - diff = 750 - duration; - } else { - diff = duration - 750; - } - if(diff < 80) { - instance->te_last = duration; - instance->parser_step = VAGDecoderStepSync2C; - break; - } + if(level && VAG_NEAR(duration, 750, 79)) { + instance->te_last = duration; + instance->parser_step = VAGDecoderStepSync2C; + break; } instance->parser_step = VAGDecoderStepReset; break; case VAGDecoderStepSync2C: - if(!level) { - if(duration < 750) { - diff = 750 - duration; - } else { - diff = duration - 750; - } - if(diff <= 79) { - if(instance->te_last < 750) { - diff = 750 - instance->te_last; - } else { - diff = instance->te_last - 750; - } - if(diff <= 79) { - instance->mid_count++; - instance->parser_step = VAGDecoderStepSync2B; + if(!level && VAG_NEAR(duration, 750, 79) && VAG_NEAR(instance->te_last, 750, 79)) { + instance->mid_count++; + instance->parser_step = VAGDecoderStepSync2B; - if(instance->mid_count == 3) { - instance->data_low = 1; - instance->data_high = 0; - instance->bit_count = 1; - manchester_advance( - instance->manchester_state, - ManchesterEventReset, - &instance->manchester_state, - NULL); - instance->parser_step = VAGDecoderStepData2; - } - break; - } + if(instance->mid_count == 3) { + instance->data_low = 1; + instance->data_high = 0; + instance->bit_count = 1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->parser_step = VAGDecoderStepData2; } + break; } instance->parser_step = VAGDecoderStepReset; break; case VAGDecoderStepData2: - if(duration >= 380 && duration <= 620) { + + if(VAG_NEAR(duration, 500, VAG_TOL_500)) { event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; goto process_manchester2; } - if(duration >= 880 && duration <= 1120) { + if(VAG_NEAR(duration, 1000, VAG_TOL_500)) { event = level ? ManchesterEventLongLow : ManchesterEventLongHigh; goto process_manchester2; } @@ -1152,6 +1160,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) { size_t index = 0; LevelDuration* upload = instance->upload; + VAG_UPLOAD_NEED(instance, index, 700); uint8_t btn_byte = vag_btn_to_byte(instance->btn, 1); uint8_t dispatch = vag_get_dispatch_byte(btn_byte, 1); @@ -1233,13 +1242,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 15; i >= 0; i--) { bool bit = (prefix >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Prefix 0x%04X: %zu pulses", prefix, index - prefix_start); @@ -1258,13 +1261,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 63; i >= 0; i--) { bool bit = (key1_inv >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Key1: %zu pulses (64 bits)", index - key1_start); @@ -1276,13 +1273,7 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 15; i >= 0; i--) { bool bit = (key2_inv >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Key2: %zu pulses (16 bits)", index - key2_start); @@ -1301,6 +1292,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) { size_t index = 0; LevelDuration* upload = instance->upload; + VAG_UPLOAD_NEED(instance, index, 700); uint8_t btn_byte = vag_btn_to_byte(instance->btn, 2); uint8_t dispatch = vag_get_dispatch_byte(btn_byte, 2); @@ -1393,13 +1385,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 15; i >= 0; i--) { bool bit = (prefix >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Prefix 0x%04X: %zu pulses", prefix, index - prefix_start); @@ -1417,13 +1403,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 63; i >= 0; i--) { bool bit = (key1_inv >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Key1: %zu pulses", index - key1_start); @@ -1435,13 +1415,7 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) { #endif for(int i = 15; i >= 0; i--) { bool bit = (key2_inv >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 300); - upload[index++] = level_duration_make(false, 300); - } else { - upload[index++] = level_duration_make(false, 300); - upload[index++] = level_duration_make(true, 300); - } + VAG_EMIT_MANCHESTER(upload, index, bit, 300); } FURI_LOG_D(TAG, "Key2: %zu pulses", index - key2_start); @@ -1456,6 +1430,7 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) { size_t index = 0; LevelDuration* upload = instance->upload; + VAG_UPLOAD_NEED(instance, index, 600); uint8_t btn_byte = vag_btn_to_byte(instance->btn, instance->vag_type); uint8_t dispatch = vag_get_dispatch_byte(btn_byte, instance->vag_type); @@ -1619,15 +1594,8 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) { bool last_level = false; for(int i = 15; i >= 0; i--) { bool bit = (key2 >> i) & 1; - if(bit) { - upload[index++] = level_duration_make(true, 500); - upload[index++] = level_duration_make(false, 500); - last_level = false; - } else { - upload[index++] = level_duration_make(false, 500); - upload[index++] = level_duration_make(true, 500); - last_level = true; - } + VAG_EMIT_MANCHESTER(upload, index, bit, 500); + last_level = !bit; } FURI_LOG_D( TAG, @@ -1719,8 +1687,6 @@ void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) { } } -#define VAG_ENCODER_UPLOAD_MAX_SIZE 680 - #ifdef ENABLE_EMULATE_FEATURE void* subghz_protocol_encoder_vag_alloc(SubGhzEnvironment* environment) { UNUSED(environment); From 783f5597f9c3650a2105b17b42c3d7ee9571df2d Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 11:20:31 +0100 Subject: [PATCH 12/30] use calloc instead of malloc, remove not needed anymore variable set to 0 as the calloc do them, furi_check the allocated memory --- protocols/fiat_v0.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 1969001..f259ae5 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -82,20 +82,17 @@ const SubGhzProtocol fiat_protocol_v0 = { void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolEncoderFiatV0* instance = malloc(sizeof(SubGhzProtocolEncoderFiatV0)); + SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0)); + furi_check(instance); instance->base.protocol = &fiat_protocol_v0; instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; instance->encoder.size_upload = 1024; - instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.upload = calloc(1, instance->encoder.size_upload * sizeof(LevelDuration)); + furi_check(instance->encoder.upload); instance->encoder.is_running = false; - instance->encoder.front = 0; - - instance->cnt = 0; - instance->serial = 0; - instance->btn = 0; return instance; } @@ -342,7 +339,8 @@ LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) { void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderFiatV0* instance = malloc(sizeof(SubGhzProtocolDecoderFiatV0)); + SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0)); + furi_check(instance); instance->base.protocol = &fiat_protocol_v0; instance->generic.protocol_name = instance->base.protocol->name; return instance; From c4c7b6baee8f5534e42b9ddf0e25a910bf200db9 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 11:39:11 +0100 Subject: [PATCH 13/30] removed unneeded headers --- protocols/fiat_v0.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index 0f4db08..4409f07 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include #include "../defines.h" From eb4a328c89064fedc45f48d0b2cd6dd60a634df4 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 11:47:27 +0100 Subject: [PATCH 14/30] do not free/realloc furi_string as flipper_format_read_string is doing a reset of the string, check for problems and return error --- protocols/fiat_v0.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index f259ae5..7e4ea86 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -226,18 +226,20 @@ SubGhzProtocolStatus do { FuriString* temp_str = furi_string_alloc(); + furi_check(temp_str); if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); furi_string_free(temp_str); + temp_str = NULL; break; } if(!furi_string_equal(temp_str, instance->base.protocol->name)) { FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str)); furi_string_free(temp_str); + temp_str = NULL; break; } - furi_string_free(temp_str); uint32_t bit_count_temp; if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { @@ -245,13 +247,18 @@ SubGhzProtocolStatus break; } - temp_str = furi_string_alloc(); if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { FURI_LOG_E(TAG, "Missing Key"); furi_string_free(temp_str); + temp_str = NULL; break; } + // if no string to decode return error + if(!temp_str) { + return ret; + } + const char* key_str = furi_string_get_cstr(temp_str); uint64_t key = 0; size_t str_len = strlen(key_str); From 62b311d96c7d7c60b038121da1c7339685b7a93e Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 12:30:26 +0100 Subject: [PATCH 15/30] added missing headers, some defines for caps and duration --- protocols/fiat_v0.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index 4409f07..e06a709 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include #include #include #include @@ -12,6 +14,18 @@ #define FIAT_PROTOCOL_V0_NAME "Fiat V0" +/* Worst-case encoder upload capacity (LevelDuration entries) + * Per burst: + * - preamble: FIAT_V0_PREAMBLE_PAIRS * 2 + * - data: max 141 entries (see encoder differential rules) + * Between bursts: one inter-burst gap entry + */ +#define FIAT_V0_DATA_MAX_EDGES ((63 + 6) * 2 + 3) +#define FIAT_V0_BURST_MAX_EDGES (FIAT_V0_PREAMBLE_PAIRS * 2 + FIAT_V0_DATA_MAX_EDGES) +#define FIAT_V0_UPLOAD_CAP \ + (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) +#define FIAT_V0_GAP_US 800 + typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0; typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0; From 7ab8f6837c774a1b1f1e1c4c9ed4af0f5e045af1 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 12:31:15 +0100 Subject: [PATCH 16/30] testing: - Harden timing decode, correct Manchester polarity, and size encoder buffer exactly - Derive exact encoder upload capacity (FIAT_V0_UPLOAD_CAP = 1325 with current constants) - Fix Manchester Short/Long High/Low event polarity (level maps to duration level) - Fix end-of-frame off-by-one for btn bits (>= 0x46) - Zero-init encoder/decoder allocations and reset decoder during alloc - Reset Manchester state at frame start and ignore very short glitch pulses seen in capture - Add overflow guards on upload building and yield stop conditions --- protocols/fiat_v0.c | 68 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 7e4ea86..6d748a2 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -89,7 +89,7 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; - instance->encoder.size_upload = 1024; + instance->encoder.size_upload = FIAT_V0_UPLOAD_CAP; instance->encoder.upload = calloc(1, instance->encoder.size_upload * sizeof(LevelDuration)); furi_check(instance->encoder.upload); instance->encoder.is_running = false; @@ -108,8 +108,10 @@ void subghz_protocol_encoder_fiat_v0_free(void* context) { static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) { furi_check(instance); - size_t index = 0; + /* Encoder buffer is pre-sized for the worst case */ + furi_check(instance->encoder.size_upload >= FIAT_V0_UPLOAD_CAP); + size_t index = 0; uint32_t te_short = subghz_protocol_fiat_v0_const.te_short; uint32_t te_long = subghz_protocol_fiat_v0_const.te_long; @@ -202,6 +204,8 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat instance->encoder.upload[index++] = level_duration_make(false, te_short * 8); } + /* Guard against overflow if constants or bit counts change */ + furi_check(index <= FIAT_V0_UPLOAD_CAP); instance->encoder.size_upload = index; instance->encoder.front = 0; @@ -325,7 +329,8 @@ LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) { furi_check(context); SubGhzProtocolEncoderFiatV0* instance = context; - if(!instance->encoder.is_running || instance->encoder.repeat == 0) { + if(!instance->encoder.is_running || !instance->encoder.size_upload || + !instance->encoder.repeat) { instance->encoder.is_running = false; return level_duration_reset(); } @@ -348,6 +353,7 @@ void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0)); furi_check(instance); + subghz_protocol_decoder_fiat_v0_reset(instance); instance->base.protocol = &fiat_protocol_v0; instance->generic.protocol_name = instance->base.protocol->name; return instance; @@ -363,7 +369,7 @@ void subghz_protocol_decoder_fiat_v0_reset(void* context) { furi_check(context); SubGhzProtocolDecoderFiatV0* instance = context; instance->decoder.parser_step = FiatV0DecoderStepReset; - instance->decoder_state = 0; + instance->decoder_state = FiatV0DecoderStepReset; instance->preamble_count = 0; instance->data_low = 0; instance->data_high = 0; @@ -381,7 +387,7 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short; uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long; uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta; - uint32_t gap_threshold = 800; + uint32_t gap_threshold = FIAT_V0_GAP_US; uint32_t diff; switch(instance->decoder_state) { @@ -401,6 +407,8 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->te_last = duration; instance->preamble_count = 0; instance->bit_count = 0; + /* Ensure a known Manchester phase before the first data edge */ + instance->manchester_state = ManchesterStateMid1; manchester_advance( instance->manchester_state, ManchesterEventReset, @@ -413,6 +421,10 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du if(level) { return; } + /* Ignore very short glitches */ + if(duration < (te_short - te_delta)) { + return; + } if(duration < te_short) { diff = te_short - duration; if(diff < te_delta) { @@ -431,6 +443,13 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->data_high = 0; instance->bit_count = 0; instance->te_last = duration; + /* Reset Manchester state at frame start after preamble gap */ + instance->manchester_state = ManchesterStateMid1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); return; } } @@ -449,6 +468,13 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->data_high = 0; instance->bit_count = 0; instance->te_last = duration; + /* Reset Manchester state at frame start after preamble gap */ + instance->manchester_state = ManchesterStateMid1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); return; } } @@ -474,6 +500,13 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->data_high = 0; instance->bit_count = 0; instance->te_last = duration; + /* Reset Manchester state at frame start after preamble gap */ + instance->manchester_state = ManchesterStateMid1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); return; } } @@ -482,15 +515,19 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du case FiatV0DecoderStepData: { ManchesterEvent event = ManchesterEventReset; + /* Ignore very short glitches */ + if(duration < (te_short - te_delta)) { + break; + } if(duration < te_short) { diff = te_short - duration; if(diff < te_delta) { - event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; + event = level ? ManchesterEventShortHigh : ManchesterEventShortLow; } } else { diff = duration - te_short; if(diff < te_delta) { - event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; + event = level ? ManchesterEventShortHigh : ManchesterEventShortLow; } else { if(duration < te_long) { diff = te_long - duration; @@ -498,11 +535,22 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du diff = duration - te_long; } if(diff < te_delta) { - event = level ? ManchesterEventLongLow : ManchesterEventLongHigh; + event = level ? ManchesterEventLongHigh : ManchesterEventLongLow; } } } - + if(event == ManchesterEventReset) { + /* Bad edge timing or a gap: reset frame decode state */ + if(duration > (FIAT_V0_INTER_BURST_GAP / 2)) { + instance->decoder_state = FiatV0DecoderStepReset; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->manchester_state = ManchesterStateMid1; + } + break; + } if(event != ManchesterEventReset) { bool data_bit_bool; if(manchester_advance( @@ -525,7 +573,7 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->data_high = 0; } - if(instance->bit_count > 0x46) { + if(instance->bit_count >= 0x46) { instance->btn = (uint8_t)((instance->data_low << 1) | 1); instance->generic.data = ((uint64_t)instance->cnt << 32) | instance->serial; From 88258a988dcd042ada406a4839fd5a8581db55cf Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 12:59:38 +0100 Subject: [PATCH 17/30] move all defines in private c file for clarity --- protocols/fiat_v0.c | 16 ++++++++++++++-- protocols/fiat_v0.h | 14 -------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 6d748a2..92c1439 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -2,8 +2,6 @@ #include "../protopirate_app_i.h" #include -#define TAG "FiatProtocolV0" - static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .te_short = 200, .te_long = 400, @@ -11,6 +9,20 @@ static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .min_count_bit_for_found = 64, }; +#define TAG "FiatProtocolV0" +#define FIAT_PROTOCOL_V0_NAME "Fiat V0" + +/* Worst-case encoder upload capacity (LevelDuration entries) + * Per burst: + * - preamble: FIAT_V0_PREAMBLE_PAIRS * 2 + * - data: max 141 entries (see encoder differential rules) + * Between bursts: one inter-burst gap entry + */ +#define FIAT_V0_DATA_MAX_EDGES ((63 + 6) * 2 + 3) +#define FIAT_V0_BURST_MAX_EDGES (FIAT_V0_PREAMBLE_PAIRS * 2 + FIAT_V0_DATA_MAX_EDGES) +#define FIAT_V0_UPLOAD_CAP \ + (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) + #define FIAT_V0_PREAMBLE_PAIRS 150 #define FIAT_V0_GAP_US 800 #define FIAT_V0_TOTAL_BURSTS 3 diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index e06a709..329a06f 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -12,20 +12,6 @@ #include "../defines.h" -#define FIAT_PROTOCOL_V0_NAME "Fiat V0" - -/* Worst-case encoder upload capacity (LevelDuration entries) - * Per burst: - * - preamble: FIAT_V0_PREAMBLE_PAIRS * 2 - * - data: max 141 entries (see encoder differential rules) - * Between bursts: one inter-burst gap entry - */ -#define FIAT_V0_DATA_MAX_EDGES ((63 + 6) * 2 + 3) -#define FIAT_V0_BURST_MAX_EDGES (FIAT_V0_PREAMBLE_PAIRS * 2 + FIAT_V0_DATA_MAX_EDGES) -#define FIAT_V0_UPLOAD_CAP \ - (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) -#define FIAT_V0_GAP_US 800 - typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0; typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0; From 970b544f4a529e9b7a0140c095cd6ca8367a72f6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:26:38 +0300 Subject: [PATCH 18/30] revert fiat to use working decoder --- protocols/fiat_v0.c | 376 +++++++++++++++++++++++++------------------- protocols/fiat_v0.h | 2 - 2 files changed, 214 insertions(+), 164 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 1969001..1cec254 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -4,6 +4,8 @@ #define TAG "FiatProtocolV0" +#define FIAT_PROTOCOL_V0_NAME "Fiat V0" + static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .te_short = 200, .te_long = 400, @@ -26,9 +28,10 @@ struct SubGhzProtocolDecoderFiatV0 { uint32_t data_low; uint32_t data_high; uint8_t bit_count; - uint32_t cnt; - uint32_t serial; - uint8_t btn; + uint32_t hop; + uint32_t fix; + uint8_t endbyte; + uint8_t final_count; uint32_t te_last; }; @@ -37,9 +40,9 @@ struct SubGhzProtocolEncoderFiatV0 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; - uint32_t cnt; - uint32_t serial; - uint8_t btn; + uint32_t hop; + uint32_t fix; + uint8_t endbyte; }; typedef enum { @@ -93,9 +96,9 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { instance->encoder.is_running = false; instance->encoder.front = 0; - instance->cnt = 0; - instance->serial = 0; - instance->btn = 0; + instance->hop = 0; + instance->fix = 0; + instance->endbyte = 0; return instance; } @@ -114,19 +117,13 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat size_t index = 0; uint32_t te_short = subghz_protocol_fiat_v0_const.te_short; - uint32_t te_long = subghz_protocol_fiat_v0_const.te_long; FURI_LOG_I( TAG, - "Building upload: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", - instance->cnt, - instance->serial, - instance->btn); - - uint64_t data = ((uint64_t)instance->cnt << 32) | instance->serial; - - // Reverse the decoder's btn fix: decoder does (x << 1) | 1 - uint8_t btn_to_send = instance->btn >> 1; + "Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X", + instance->hop, + instance->fix, + instance->endbyte); for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) { if(burst > 0) { @@ -134,85 +131,52 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat level_duration_make(false, FIAT_V0_INTER_BURST_GAP); } - // Preamble: HIGH-LOW pairs + // Preamble: alternating short pulses for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) { instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); } - // Replace last preamble LOW with gap + // Extend last LOW to create the gap (~800us) instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US); - // First bit (bit 63) - special handling after gap - bool first_bit = (data >> 63) & 1; - if(first_bit) { - // First bit is 1: LONG HIGH - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else { - // First bit is 0: SHORT HIGH + LONG LOW - instance->encoder.upload[index++] = level_duration_make(true, te_short); - instance->encoder.upload[index++] = level_duration_make(false, te_long); - } + // Combine hop and fix into 64-bit data + uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix; - bool prev_bit = first_bit; - - // Encode remaining 63 data bits (bits 62:0) using differential Manchester - for(int bit = 62; bit >= 0; bit--) { + // Manchester encode 64 bits of data + for(int bit = 63; bit >= 0; bit--) { bool curr_bit = (data >> bit) & 1; - if(!prev_bit && !curr_bit) { - // 0->0: SHORT HIGH + SHORT LOW + if(curr_bit) { instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); - } else if(!prev_bit && curr_bit) { - // 0->1: LONG HIGH - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else if(prev_bit && !curr_bit) { - // 1->0: LONG LOW - instance->encoder.upload[index++] = level_duration_make(false, te_long); - } else { - // 1->1: SHORT LOW + SHORT HIGH - instance->encoder.upload[index++] = level_duration_make(false, te_short); - instance->encoder.upload[index++] = level_duration_make(true, te_short); - } - - prev_bit = curr_bit; - } - - // Encode 6 btn bits using same differential pattern - for(int bit = 5; bit >= 0; bit--) { - bool curr_bit = (btn_to_send >> bit) & 1; - - if(!prev_bit && !curr_bit) { - instance->encoder.upload[index++] = level_duration_make(true, te_short); - instance->encoder.upload[index++] = level_duration_make(false, te_short); - } else if(!prev_bit && curr_bit) { - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else if(prev_bit && !curr_bit) { - instance->encoder.upload[index++] = level_duration_make(false, te_long); } else { instance->encoder.upload[index++] = level_duration_make(false, te_short); instance->encoder.upload[index++] = level_duration_make(true, te_short); } - - prev_bit = curr_bit; } - // End marker - ensure we end with LOW - if(prev_bit) { - instance->encoder.upload[index++] = level_duration_make(false, te_short); + // Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits + for(int bit = 6; bit >= 0; bit--) { + bool curr_bit = (instance->endbyte >> bit) & 1; + + if(curr_bit) { + instance->encoder.upload[index++] = level_duration_make(true, te_short); + instance->encoder.upload[index++] = level_duration_make(false, te_short); + } else { + instance->encoder.upload[index++] = level_duration_make(false, te_short); + instance->encoder.upload[index++] = level_duration_make(true, te_short); + } } - instance->encoder.upload[index++] = level_duration_make(false, te_short * 8); + + // End with extended LOW (will be followed by inter-burst gap or end) + instance->encoder.upload[index++] = level_duration_make(false, te_short * 4); } instance->encoder.size_upload = index; instance->encoder.front = 0; - FURI_LOG_I( - TAG, - "Upload built: %zu elements, btn_to_send=0x%02X", - instance->encoder.size_upload, - btn_to_send); + FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload); } SubGhzProtocolStatus @@ -247,6 +211,7 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Missing Bit"); break; } + instance->generic.data_count_bit = 64; temp_str = furi_string_alloc(); if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { @@ -280,15 +245,18 @@ SubGhzProtocolStatus furi_string_free(temp_str); instance->generic.data = key; - instance->cnt = (uint32_t)(key >> 32); - instance->serial = (uint32_t)(key & 0xFFFFFFFF); + instance->hop = (uint32_t)(key >> 32); + instance->fix = (uint32_t)(key & 0xFFFFFFFF); uint32_t btn_temp = 0; if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { - instance->btn = (uint8_t)btn_temp; + instance->endbyte = (uint8_t)btn_temp; } else { - instance->btn = 0; + instance->endbyte = 0; } + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; + instance->generic.serial = instance->fix; if(!flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { @@ -296,14 +264,10 @@ SubGhzProtocolStatus } subghz_protocol_encoder_fiat_v0_get_upload(instance); + instance->encoder.is_running = true; - FURI_LOG_I( - TAG, - "Encoder ready: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", - instance->cnt, - instance->serial, - instance->btn); + FURI_LOG_I(TAG, "Encoder ready: hop=0x%08lX, fix=0x%08lX", instance->hop, instance->fix); ret = SubGhzProtocolStatusOk; } while(false); @@ -363,13 +327,28 @@ void subghz_protocol_decoder_fiat_v0_reset(void* context) { instance->data_low = 0; instance->data_high = 0; instance->bit_count = 0; - instance->cnt = 0; - instance->serial = 0; - instance->btn = 0; + instance->hop = 0; + instance->fix = 0; + instance->endbyte = 0; + instance->final_count = 0; instance->te_last = 0; instance->manchester_state = ManchesterStateMid1; } +// Helper function to reset decoder to data state with proper Manchester initialization +static void + fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) { + instance->decoder_state = FiatV0DecoderStepData; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->te_last = duration; + // Critical: Reset Manchester state when entering data mode + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); +} + void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) { furi_check(context); SubGhzProtocolDecoderFiatV0* instance = context; @@ -406,76 +385,68 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du case FiatV0DecoderStepPreamble: if(level) { + // HIGH pulse during preamble - just track timing + if(duration < te_short) { + diff = te_short - duration; + } else { + diff = duration - te_short; + } + if(diff < te_delta) { + instance->preamble_count++; + instance->te_last = duration; + } else { + instance->decoder_state = FiatV0DecoderStepReset; + } return; } + + // LOW pulse - check if it's the gap after preamble if(duration < te_short) { diff = te_short - duration; - if(diff < te_delta) { - instance->preamble_count++; - instance->te_last = duration; - if(instance->preamble_count >= 0x96) { - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { - diff = duration - gap_threshold; - } - if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; - return; - } - } - } else { - instance->decoder_state = FiatV0DecoderStepReset; - if(instance->preamble_count >= 0x96) { - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { - diff = duration - gap_threshold; - } - if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; - return; - } - } - } } else { diff = duration - te_short; - if(diff < te_delta) { - instance->preamble_count++; - instance->te_last = duration; - } else { - instance->decoder_state = FiatV0DecoderStepReset; - } + } + + if(diff < te_delta) { + // Normal short LOW - continue preamble + instance->preamble_count++; + instance->te_last = duration; + } else { + // Not a short pulse - check if it's the gap if(instance->preamble_count >= 0x96) { - if(duration >= 799) { - diff = duration - gap_threshold; - } else { + // We have enough preamble, check for gap + if(duration < gap_threshold) { diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; } if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; + // Valid gap detected - transition to data state + fiat_v0_decoder_enter_data_state(instance, duration); return; } } + // Invalid timing or not enough preamble + instance->decoder_state = FiatV0DecoderStepReset; + } + + // Also check for gap even with valid short timing if we have enough preamble + if(instance->preamble_count >= 0x96 && + instance->decoder_state == FiatV0DecoderStepPreamble) { + if(duration < gap_threshold) { + diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; + } + if(diff < te_delta) { + // Valid gap detected - transition to data state + fiat_v0_decoder_enter_data_state(instance, duration); + return; + } } break; - case FiatV0DecoderStepData: { + case FiatV0DecoderStepData: ManchesterEvent event = ManchesterEventReset; if(duration < te_short) { diff = te_short - duration; @@ -514,20 +485,68 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->bit_count++; if(instance->bit_count == 0x40) { - instance->serial = instance->data_low; - instance->cnt = instance->data_high; + instance->fix = instance->data_low; + instance->hop = instance->data_high; + FURI_LOG_D( + TAG, + "Bit 64: fix=0x%08lX, hop=0x%08lX, clearing data_low/data_high", + instance->fix, + instance->hop); instance->data_low = 0; instance->data_high = 0; } - if(instance->bit_count > 0x46) { - instance->btn = (uint8_t)((instance->data_low << 1) | 1); +#ifndef REMOVE_LOGS + if(instance->bit_count > 0x40 && instance->bit_count <= 0x47) { + uint8_t endbyte_bit_num = instance->bit_count - 0x40; + uint8_t endbyte_bit_index = endbyte_bit_num - 1; + uint8_t data_low_byte = (uint8_t)instance->data_low; + char binary_str[9] = {0}; + for(int i = 7; i >= 0; i--) { + binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; + } + FURI_LOG_D( + TAG, + "Bit %d (endbyte bit %d/%d): new_bit=%lu, data_low=0x%08lX (0x%02X), binary=0b%s", + instance->bit_count, + endbyte_bit_index, + endbyte_bit_num - 1, + (unsigned long)new_bit, + instance->data_low, + data_low_byte, + binary_str); + } +#endif + if(instance->bit_count == 0x47) { +#ifndef REMOVE_LOGS + uint8_t data_low_byte = (uint8_t)instance->data_low; + char binary_str[9] = {0}; + for(int i = 7; i >= 0; i--) { + binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; + } + FURI_LOG_D( + TAG, + "EXTRACTING AT BIT 71: bit_count=%d, data_low=0x%08lX (0x%02X), binary=0b%s, accumulated %d bits after bit 64", + instance->bit_count, + instance->data_low, + data_low_byte, + binary_str, + instance->bit_count - 0x40); +#endif + instance->final_count = instance->bit_count; + instance->endbyte = (uint8_t)instance->data_low; - instance->generic.data = ((uint64_t)instance->cnt << 32) | instance->serial; + FURI_LOG_D( + TAG, + "EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)", + instance->endbyte, + instance->endbyte); + + instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; instance->generic.data_count_bit = 71; - instance->generic.serial = instance->serial; - instance->generic.btn = instance->btn; - instance->generic.cnt = instance->cnt; + instance->generic.serial = instance->fix; + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; if(instance->base.callback) { instance->base.callback(&instance->base, instance->base.context); @@ -539,11 +558,39 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->decoder_state = FiatV0DecoderStepReset; } } + } else { + if(instance->bit_count == 0x47) { + // We have exactly 71 bits - extract endbyte + uint8_t data_low_byte = (uint8_t)instance->data_low; + instance->endbyte = data_low_byte; + + FURI_LOG_D( + TAG, + "GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X", + instance->bit_count, + instance->endbyte); + + instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; + instance->generic.data_count_bit = 71; + instance->generic.serial = instance->fix; + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->decoder_state = FiatV0DecoderStepReset; + } else if(instance->bit_count < 0x40) { + instance->decoder_state = FiatV0DecoderStepReset; + } } instance->te_last = duration; break; } - } } uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) { @@ -566,24 +613,28 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize( do { if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) break; + if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(preset->name))) break; + if(!flipper_format_write_string_cstr( flipper_format, "Protocol", instance->generic.protocol_name)) break; - uint32_t bits = 71; + uint32_t bits = instance->generic.data_count_bit; if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) break; char key_str[20]; - snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->cnt, instance->serial); + snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix); if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) break; - if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->cnt, 1)) break; - if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1)) break; + uint32_t temp = instance->hop; + if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) break; - uint32_t temp = instance->btn; + if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->fix, 1)) break; + + temp = instance->endbyte; if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) break; ret = SubGhzProtocolStatusOk; @@ -608,13 +659,14 @@ void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* outpu output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Sn:%08lX Btn:%02X\r\n" - "Cnt:%08lX\r\n", + "Hop:%08lX\r\n" + "Sn:%08lX\r\n" + "EndByte:%02X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, - instance->cnt, - instance->serial, - instance->serial, - instance->btn, - instance->cnt); + instance->hop, + instance->fix, + instance->hop, + instance->fix, + instance->endbyte); } diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index 0f4db08..bce58af 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -12,8 +12,6 @@ #include "../defines.h" -#define FIAT_PROTOCOL_V0_NAME "Fiat V0" - typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0; typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0; From e483f1db26cf906e44e39ddfe2ae72697b14b6fd Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 17:44:38 +0100 Subject: [PATCH 19/30] restore values from working decoder --- protocols/fiat_v0.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 42e22b2..98efae0 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -23,9 +23,9 @@ static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { #define FIAT_V0_UPLOAD_CAP \ (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) -#define FIAT_V0_PREAMBLE_PAIRS 150 -#define FIAT_V0_GAP_US 800 -#define FIAT_V0_TOTAL_BURSTS 3 +#define FIAT_V0_PREAMBLE_PAIRS 4 +#define FIAT_V0_GAP_US 3500 +#define FIAT_V0_TOTAL_BURSTS 6 #define FIAT_V0_INTER_BURST_GAP 25000 struct SubGhzProtocolDecoderFiatV0 { From a35353fed136fc27b5cc66dd907788c9c50e393c Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 17:45:33 +0100 Subject: [PATCH 20/30] format --- protocols/fiat_v0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 98efae0..f286b10 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -2,7 +2,7 @@ #include "../protopirate_app_i.h" #include -#define TAG "FiatProtocolV0" +#define TAG "FiatProtocolV0" #define FIAT_PROTOCOL_V0_NAME "Fiat V0" static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { From 66973cf6dccc87e8006c56451913ebccd68ab133 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 17:48:41 +0100 Subject: [PATCH 21/30] format and correct defines from main --- protocols/fiat_v0.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index f286b10..1e4f384 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -23,9 +23,9 @@ static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { #define FIAT_V0_UPLOAD_CAP \ (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) -#define FIAT_V0_PREAMBLE_PAIRS 4 -#define FIAT_V0_GAP_US 3500 -#define FIAT_V0_TOTAL_BURSTS 6 +#define FIAT_V0_PREAMBLE_PAIRS 150 +#define FIAT_V0_GAP_US 800 +#define FIAT_V0_TOTAL_BURSTS 3 #define FIAT_V0_INTER_BURST_GAP 25000 struct SubGhzProtocolDecoderFiatV0 { From 784d106b8d1444db64803594d177be0876ec8782 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 22:21:17 +0100 Subject: [PATCH 22/30] restored from main, reapplied calloc instead of malloc and some furi_checks --- protocols/fiat_v0.c | 430 +++++++++++++++++--------------------------- protocols/fiat_v0.h | 3 +- 2 files changed, 169 insertions(+), 264 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 1e4f384..7ea6c4b 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -12,17 +12,6 @@ static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .min_count_bit_for_found = 64, }; -/* Worst-case encoder upload capacity (LevelDuration entries) - * Per burst: - * - preamble: FIAT_V0_PREAMBLE_PAIRS * 2 - * - data: max 141 entries (see encoder differential rules) - * Between bursts: one inter-burst gap entry - */ -#define FIAT_V0_DATA_MAX_EDGES ((63 + 6) * 2 + 3) -#define FIAT_V0_BURST_MAX_EDGES (FIAT_V0_PREAMBLE_PAIRS * 2 + FIAT_V0_DATA_MAX_EDGES) -#define FIAT_V0_UPLOAD_CAP \ - (FIAT_V0_TOTAL_BURSTS * FIAT_V0_BURST_MAX_EDGES + (FIAT_V0_TOTAL_BURSTS - 1)) - #define FIAT_V0_PREAMBLE_PAIRS 150 #define FIAT_V0_GAP_US 800 #define FIAT_V0_TOTAL_BURSTS 3 @@ -38,10 +27,9 @@ struct SubGhzProtocolDecoderFiatV0 { uint32_t data_low; uint32_t data_high; uint8_t bit_count; - uint32_t hop; - uint32_t fix; - uint8_t endbyte; - uint8_t final_count; + uint32_t cnt; + uint32_t serial; + uint8_t btn; uint32_t te_last; }; @@ -50,9 +38,9 @@ struct SubGhzProtocolEncoderFiatV0 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; - uint32_t hop; - uint32_t fix; - uint8_t endbyte; + uint32_t cnt; + uint32_t serial; + uint8_t btn; }; typedef enum { @@ -102,8 +90,8 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; - instance->encoder.size_upload = FIAT_V0_UPLOAD_CAP; - instance->encoder.upload = calloc(1, instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.size_upload = 1024; + instance->encoder.upload = calloc(instance->encoder.size_upload, sizeof(LevelDuration)); furi_check(instance->encoder.upload); instance->encoder.is_running = false; @@ -121,18 +109,22 @@ void subghz_protocol_encoder_fiat_v0_free(void* context) { static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) { furi_check(instance); - /* Encoder buffer is pre-sized for the worst case */ - furi_check(instance->encoder.size_upload >= FIAT_V0_UPLOAD_CAP); - size_t index = 0; + uint32_t te_short = subghz_protocol_fiat_v0_const.te_short; + uint32_t te_long = subghz_protocol_fiat_v0_const.te_long; FURI_LOG_I( TAG, - "Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X", - instance->hop, - instance->fix, - instance->endbyte); + "Building upload: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", + instance->cnt, + instance->serial, + instance->btn); + + uint64_t data = ((uint64_t)instance->cnt << 32) | instance->serial; + + // Reverse the decoder's btn fix: decoder does (x << 1) | 1 + uint8_t btn_to_send = instance->btn >> 1; for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) { if(burst > 0) { @@ -140,54 +132,85 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat level_duration_make(false, FIAT_V0_INTER_BURST_GAP); } - // Preamble: alternating short pulses + // Preamble: HIGH-LOW pairs for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) { instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); } - // Extend last LOW to create the gap (~800us) + // Replace last preamble LOW with gap instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US); - // Combine hop and fix into 64-bit data - uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix; + // First bit (bit 63) - special handling after gap + bool first_bit = (data >> 63) & 1; + if(first_bit) { + // First bit is 1: LONG HIGH + instance->encoder.upload[index++] = level_duration_make(true, te_long); + } else { + // First bit is 0: SHORT HIGH + LONG LOW + instance->encoder.upload[index++] = level_duration_make(true, te_short); + instance->encoder.upload[index++] = level_duration_make(false, te_long); + } - // Manchester encode 64 bits of data - for(int bit = 63; bit >= 0; bit--) { + bool prev_bit = first_bit; + + // Encode remaining 63 data bits (bits 62:0) using differential Manchester + for(int bit = 62; bit >= 0; bit--) { bool curr_bit = (data >> bit) & 1; - if(curr_bit) { + if(!prev_bit && !curr_bit) { + // 0->0: SHORT HIGH + SHORT LOW instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); + } else if(!prev_bit && curr_bit) { + // 0->1: LONG HIGH + instance->encoder.upload[index++] = level_duration_make(true, te_long); + } else if(prev_bit && !curr_bit) { + // 1->0: LONG LOW + instance->encoder.upload[index++] = level_duration_make(false, te_long); + } else { + // 1->1: SHORT LOW + SHORT HIGH + instance->encoder.upload[index++] = level_duration_make(false, te_short); + instance->encoder.upload[index++] = level_duration_make(true, te_short); + } + + prev_bit = curr_bit; + } + + // Encode 6 btn bits using same differential pattern + for(int bit = 5; bit >= 0; bit--) { + bool curr_bit = (btn_to_send >> bit) & 1; + + if(!prev_bit && !curr_bit) { + instance->encoder.upload[index++] = level_duration_make(true, te_short); + instance->encoder.upload[index++] = level_duration_make(false, te_short); + } else if(!prev_bit && curr_bit) { + instance->encoder.upload[index++] = level_duration_make(true, te_long); + } else if(prev_bit && !curr_bit) { + instance->encoder.upload[index++] = level_duration_make(false, te_long); } else { instance->encoder.upload[index++] = level_duration_make(false, te_short); instance->encoder.upload[index++] = level_duration_make(true, te_short); } + + prev_bit = curr_bit; } - // Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits - for(int bit = 6; bit >= 0; bit--) { - bool curr_bit = (instance->endbyte >> bit) & 1; - - if(curr_bit) { - instance->encoder.upload[index++] = level_duration_make(true, te_short); - instance->encoder.upload[index++] = level_duration_make(false, te_short); - } else { - instance->encoder.upload[index++] = level_duration_make(false, te_short); - instance->encoder.upload[index++] = level_duration_make(true, te_short); - } + // End marker - ensure we end with LOW + if(prev_bit) { + instance->encoder.upload[index++] = level_duration_make(false, te_short); } - - // End with extended LOW (will be followed by inter-burst gap or end) - instance->encoder.upload[index++] = level_duration_make(false, te_short * 4); + instance->encoder.upload[index++] = level_duration_make(false, te_short * 8); } - /* Guard against overflow if constants or bit counts change */ - furi_check(index <= FIAT_V0_UPLOAD_CAP); instance->encoder.size_upload = index; instance->encoder.front = 0; - FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload); + FURI_LOG_I( + TAG, + "Upload built: %zu elements, btn_to_send=0x%02X", + instance->encoder.size_upload, + btn_to_send); } SubGhzProtocolStatus @@ -204,40 +227,32 @@ SubGhzProtocolStatus do { FuriString* temp_str = furi_string_alloc(); - furi_check(temp_str); if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); furi_string_free(temp_str); - temp_str = NULL; break; } if(!furi_string_equal(temp_str, instance->base.protocol->name)) { FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str)); furi_string_free(temp_str); - temp_str = NULL; break; } + furi_string_free(temp_str); uint32_t bit_count_temp; if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { FURI_LOG_E(TAG, "Missing Bit"); break; } - instance->generic.data_count_bit = 64; + temp_str = furi_string_alloc(); if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { FURI_LOG_E(TAG, "Missing Key"); furi_string_free(temp_str); - temp_str = NULL; break; } - // if no string to decode return error - if(!temp_str) { - return ret; - } - const char* key_str = furi_string_get_cstr(temp_str); uint64_t key = 0; size_t str_len = strlen(key_str); @@ -263,18 +278,15 @@ SubGhzProtocolStatus furi_string_free(temp_str); instance->generic.data = key; - instance->hop = (uint32_t)(key >> 32); - instance->fix = (uint32_t)(key & 0xFFFFFFFF); + instance->cnt = (uint32_t)(key >> 32); + instance->serial = (uint32_t)(key & 0xFFFFFFFF); uint32_t btn_temp = 0; if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { - instance->endbyte = (uint8_t)btn_temp; + instance->btn = (uint8_t)btn_temp; } else { - instance->endbyte = 0; + instance->btn = 0; } - instance->generic.btn = instance->endbyte; - instance->generic.cnt = instance->hop; - instance->generic.serial = instance->fix; if(!flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { @@ -282,10 +294,14 @@ SubGhzProtocolStatus } subghz_protocol_encoder_fiat_v0_get_upload(instance); - instance->encoder.is_running = true; - FURI_LOG_I(TAG, "Encoder ready: hop=0x%08lX, fix=0x%08lX", instance->hop, instance->fix); + FURI_LOG_I( + TAG, + "Encoder ready: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", + instance->cnt, + instance->serial, + instance->btn); ret = SubGhzProtocolStatusOk; } while(false); @@ -303,8 +319,7 @@ LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) { furi_check(context); SubGhzProtocolEncoderFiatV0* instance = context; - if(!instance->encoder.is_running || !instance->encoder.size_upload || - !instance->encoder.repeat) { + if(!instance->encoder.is_running || instance->encoder.repeat == 0) { instance->encoder.is_running = false; return level_duration_reset(); } @@ -327,7 +342,6 @@ void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0)); furi_check(instance); - subghz_protocol_decoder_fiat_v0_reset(instance); instance->base.protocol = &fiat_protocol_v0; instance->generic.protocol_name = instance->base.protocol->name; return instance; @@ -343,33 +357,18 @@ void subghz_protocol_decoder_fiat_v0_reset(void* context) { furi_check(context); SubGhzProtocolDecoderFiatV0* instance = context; instance->decoder.parser_step = FiatV0DecoderStepReset; - instance->decoder_state = FiatV0DecoderStepReset; + instance->decoder_state = 0; instance->preamble_count = 0; instance->data_low = 0; instance->data_high = 0; instance->bit_count = 0; - instance->hop = 0; - instance->fix = 0; - instance->endbyte = 0; - instance->final_count = 0; + instance->cnt = 0; + instance->serial = 0; + instance->btn = 0; instance->te_last = 0; instance->manchester_state = ManchesterStateMid1; } -// Helper function to reset decoder to data state with proper Manchester initialization -static void - fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; - // Critical: Reset Manchester state when entering data mode - manchester_advance( - instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); -} - void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) { furi_check(context); SubGhzProtocolDecoderFiatV0* instance = context; @@ -396,8 +395,6 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->te_last = duration; instance->preamble_count = 0; instance->bit_count = 0; - /* Ensure a known Manchester phase before the first data edge */ - instance->manchester_state = ManchesterStateMid1; manchester_advance( instance->manchester_state, ManchesterEventReset, @@ -408,85 +405,86 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du case FiatV0DecoderStepPreamble: if(level) { - // HIGH pulse during preamble - just track timing - if(duration < te_short) { - diff = te_short - duration; + return; + } + if(duration < te_short) { + diff = te_short - duration; + if(diff < te_delta) { + instance->preamble_count++; + instance->te_last = duration; + if(instance->preamble_count >= 0x96) { + if(duration < gap_threshold) { + diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; + } + if(diff < te_delta) { + instance->decoder_state = FiatV0DecoderStepData; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->te_last = duration; + return; + } + } } else { - diff = duration - te_short; + instance->decoder_state = FiatV0DecoderStepReset; + if(instance->preamble_count >= 0x96) { + if(duration < gap_threshold) { + diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; + } + if(diff < te_delta) { + instance->decoder_state = FiatV0DecoderStepData; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->te_last = duration; + return; + } + } } + } else { + diff = duration - te_short; if(diff < te_delta) { instance->preamble_count++; instance->te_last = duration; } else { instance->decoder_state = FiatV0DecoderStepReset; } - return; - } - // Ignore very short glitches - if(duration < (te_short - te_delta)) { - return; - } - // LOW pulse - check if it's the gap after preamble - if(duration < te_short) { - diff = te_short - duration; - } else { - diff = duration - te_short; - } - - if(diff < te_delta) { - // Normal short LOW - continue preamble - instance->preamble_count++; - instance->te_last = duration; - } else { - // Not a short pulse - check if it's the gap if(instance->preamble_count >= 0x96) { - // We have enough preamble, check for gap - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { + if(duration >= 799) { diff = duration - gap_threshold; + } else { + diff = gap_threshold - duration; } if(diff < te_delta) { - // Valid gap detected - transition to data state - fiat_v0_decoder_enter_data_state(instance, duration); + instance->decoder_state = FiatV0DecoderStepData; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->te_last = duration; return; } } - // Invalid timing or not enough preamble - instance->decoder_state = FiatV0DecoderStepReset; - } - - // Also check for gap even with valid short timing if we have enough preamble - if(instance->preamble_count >= 0x96 && - instance->decoder_state == FiatV0DecoderStepPreamble) { - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { - diff = duration - gap_threshold; - } - if(diff < te_delta) { - // Valid gap detected - transition to data state - fiat_v0_decoder_enter_data_state(instance, duration); - return; - } } break; - case FiatV0DecoderStepData: + case FiatV0DecoderStepData: { ManchesterEvent event = ManchesterEventReset; - /* Ignore very short glitches */ - if(duration < (te_short - te_delta)) { - break; - } if(duration < te_short) { diff = te_short - duration; if(diff < te_delta) { - event = level ? ManchesterEventShortHigh : ManchesterEventShortLow; + event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; } } else { diff = duration - te_short; if(diff < te_delta) { - event = level ? ManchesterEventShortHigh : ManchesterEventShortLow; + event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; } else { if(duration < te_long) { diff = te_long - duration; @@ -494,22 +492,11 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du diff = duration - te_long; } if(diff < te_delta) { - event = level ? ManchesterEventLongHigh : ManchesterEventLongLow; + event = level ? ManchesterEventLongLow : ManchesterEventLongHigh; } } } - if(event == ManchesterEventReset) { - /* Bad edge timing or a gap: reset frame decode state */ - if(duration > (FIAT_V0_INTER_BURST_GAP / 2)) { - instance->decoder_state = FiatV0DecoderStepReset; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->manchester_state = ManchesterStateMid1; - } - break; - } + if(event != ManchesterEventReset) { bool data_bit_bool; if(manchester_advance( @@ -526,68 +513,20 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->bit_count++; if(instance->bit_count == 0x40) { - instance->fix = instance->data_low; - instance->hop = instance->data_high; - FURI_LOG_D( - TAG, - "Bit 64: fix=0x%08lX, hop=0x%08lX, clearing data_low/data_high", - instance->fix, - instance->hop); + instance->serial = instance->data_low; + instance->cnt = instance->data_high; instance->data_low = 0; instance->data_high = 0; } -#ifndef REMOVE_LOGS - if(instance->bit_count > 0x40 && instance->bit_count <= 0x47) { - uint8_t endbyte_bit_num = instance->bit_count - 0x40; - uint8_t endbyte_bit_index = endbyte_bit_num - 1; - uint8_t data_low_byte = (uint8_t)instance->data_low; - char binary_str[9] = {0}; - for(int i = 7; i >= 0; i--) { - binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; - } - FURI_LOG_D( - TAG, - "Bit %d (endbyte bit %d/%d): new_bit=%lu, data_low=0x%08lX (0x%02X), binary=0b%s", - instance->bit_count, - endbyte_bit_index, - endbyte_bit_num - 1, - (unsigned long)new_bit, - instance->data_low, - data_low_byte, - binary_str); - } -#endif - if(instance->bit_count == 0x47) { -#ifndef REMOVE_LOGS - uint8_t data_low_byte = (uint8_t)instance->data_low; - char binary_str[9] = {0}; - for(int i = 7; i >= 0; i--) { - binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; - } - FURI_LOG_D( - TAG, - "EXTRACTING AT BIT 71: bit_count=%d, data_low=0x%08lX (0x%02X), binary=0b%s, accumulated %d bits after bit 64", - instance->bit_count, - instance->data_low, - data_low_byte, - binary_str, - instance->bit_count - 0x40); -#endif - instance->final_count = instance->bit_count; - instance->endbyte = (uint8_t)instance->data_low; + if(instance->bit_count > 0x46) { + instance->btn = (uint8_t)((instance->data_low << 1) | 1); - FURI_LOG_D( - TAG, - "EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)", - instance->endbyte, - instance->endbyte); - - instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; + instance->generic.data = ((uint64_t)instance->cnt << 32) | instance->serial; instance->generic.data_count_bit = 71; - instance->generic.serial = instance->fix; - instance->generic.btn = instance->endbyte; - instance->generic.cnt = instance->hop; + instance->generic.serial = instance->serial; + instance->generic.btn = instance->btn; + instance->generic.cnt = instance->cnt; if(instance->base.callback) { instance->base.callback(&instance->base, instance->base.context); @@ -599,39 +538,11 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->decoder_state = FiatV0DecoderStepReset; } } - } else { - if(instance->bit_count == 0x47) { - // We have exactly 71 bits - extract endbyte - uint8_t data_low_byte = (uint8_t)instance->data_low; - instance->endbyte = data_low_byte; - - FURI_LOG_D( - TAG, - "GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X", - instance->bit_count, - instance->endbyte); - - instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; - instance->generic.data_count_bit = 71; - instance->generic.serial = instance->fix; - instance->generic.btn = instance->endbyte; - instance->generic.cnt = instance->hop; - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } - - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->decoder_state = FiatV0DecoderStepReset; - } else if(instance->bit_count < 0x40) { - instance->decoder_state = FiatV0DecoderStepReset; - } } instance->te_last = duration; break; } + } } uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) { @@ -654,28 +565,24 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize( do { if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) break; - if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(preset->name))) break; - if(!flipper_format_write_string_cstr( flipper_format, "Protocol", instance->generic.protocol_name)) break; - uint32_t bits = instance->generic.data_count_bit; + uint32_t bits = 71; if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) break; char key_str[20]; - snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix); + snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->cnt, instance->serial); if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) break; - uint32_t temp = instance->hop; - if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) break; + if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->cnt, 1)) break; + if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1)) break; - if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->fix, 1)) break; - - temp = instance->endbyte; + uint32_t temp = instance->btn; if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) break; ret = SubGhzProtocolStatusOk; @@ -700,14 +607,13 @@ void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* outpu output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Hop:%08lX\r\n" - "Sn:%08lX\r\n" - "EndByte:%02X\r\n", + "Sn:%08lX Btn:%02X\r\n" + "Cnt:%08lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, - instance->hop, - instance->fix, - instance->hop, - instance->fix, - instance->endbyte); + instance->cnt, + instance->serial, + instance->serial, + instance->btn, + instance->cnt); } diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index 329a06f..31edaf7 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -1,13 +1,12 @@ #pragma once #include -#include -#include #include #include #include #include #include #include +#include #include #include "../defines.h" From b49a964391c02de4a844216d0c1c7b5d7575afa9 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 22:24:41 +0100 Subject: [PATCH 23/30] reapplied temp_str furi_string --- protocols/fiat_v0.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 7ea6c4b..7fa3ae4 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -227,18 +227,17 @@ SubGhzProtocolStatus do { FuriString* temp_str = furi_string_alloc(); + furi_check( temp_str ); + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); - furi_string_free(temp_str); break; } if(!furi_string_equal(temp_str, instance->base.protocol->name)) { FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str)); - furi_string_free(temp_str); break; } - furi_string_free(temp_str); uint32_t bit_count_temp; if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { @@ -246,10 +245,8 @@ SubGhzProtocolStatus break; } - temp_str = furi_string_alloc(); if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { FURI_LOG_E(TAG, "Missing Key"); - furi_string_free(temp_str); break; } From 75f42836246cc6593c963d0392e5e81d78bf763e Mon Sep 17 00:00:00 2001 From: gullradriel Date: Thu, 12 Feb 2026 22:39:05 +0100 Subject: [PATCH 24/30] use files from file_decoder and not main, dumb me git status --- protocols/fiat_v0.c | 384 +++++++++++++++++++++++++------------------- protocols/fiat_v0.h | 1 + 2 files changed, 218 insertions(+), 167 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 7fa3ae4..48172ea 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -2,8 +2,12 @@ #include "../protopirate_app_i.h" #include -#define TAG "FiatProtocolV0" -#define FIAT_PROTOCOL_V0_NAME "Fiat V0" +#define TAG "FiatProtocolV0" +#define FIAT_PROTOCOL_V0_NAME "Fiat V0" +#define FIAT_V0_PREAMBLE_PAIRS 150 +#define FIAT_V0_GAP_US 800 +#define FIAT_V0_TOTAL_BURSTS 3 +#define FIAT_V0_INTER_BURST_GAP 25000 static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .te_short = 200, @@ -12,11 +16,6 @@ static const SubGhzBlockConst subghz_protocol_fiat_v0_const = { .min_count_bit_for_found = 64, }; -#define FIAT_V0_PREAMBLE_PAIRS 150 -#define FIAT_V0_GAP_US 800 -#define FIAT_V0_TOTAL_BURSTS 3 -#define FIAT_V0_INTER_BURST_GAP 25000 - struct SubGhzProtocolDecoderFiatV0 { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; @@ -27,9 +26,10 @@ struct SubGhzProtocolDecoderFiatV0 { uint32_t data_low; uint32_t data_high; uint8_t bit_count; - uint32_t cnt; - uint32_t serial; - uint8_t btn; + uint32_t hop; + uint32_t fix; + uint8_t endbyte; + uint8_t final_count; uint32_t te_last; }; @@ -38,9 +38,9 @@ struct SubGhzProtocolEncoderFiatV0 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; - uint32_t cnt; - uint32_t serial; - uint8_t btn; + uint32_t hop; + uint32_t fix; + uint8_t endbyte; }; typedef enum { @@ -112,19 +112,13 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat size_t index = 0; uint32_t te_short = subghz_protocol_fiat_v0_const.te_short; - uint32_t te_long = subghz_protocol_fiat_v0_const.te_long; FURI_LOG_I( TAG, - "Building upload: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", - instance->cnt, - instance->serial, - instance->btn); - - uint64_t data = ((uint64_t)instance->cnt << 32) | instance->serial; - - // Reverse the decoder's btn fix: decoder does (x << 1) | 1 - uint8_t btn_to_send = instance->btn >> 1; + "Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X", + instance->hop, + instance->fix, + instance->endbyte); for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) { if(burst > 0) { @@ -132,85 +126,52 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat level_duration_make(false, FIAT_V0_INTER_BURST_GAP); } - // Preamble: HIGH-LOW pairs + // Preamble: alternating short pulses for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) { instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); } - // Replace last preamble LOW with gap + // Extend last LOW to create the gap (~800us) instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US); - // First bit (bit 63) - special handling after gap - bool first_bit = (data >> 63) & 1; - if(first_bit) { - // First bit is 1: LONG HIGH - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else { - // First bit is 0: SHORT HIGH + LONG LOW - instance->encoder.upload[index++] = level_duration_make(true, te_short); - instance->encoder.upload[index++] = level_duration_make(false, te_long); - } + // Combine hop and fix into 64-bit data + uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix; - bool prev_bit = first_bit; - - // Encode remaining 63 data bits (bits 62:0) using differential Manchester - for(int bit = 62; bit >= 0; bit--) { + // Manchester encode 64 bits of data + for(int bit = 63; bit >= 0; bit--) { bool curr_bit = (data >> bit) & 1; - if(!prev_bit && !curr_bit) { - // 0->0: SHORT HIGH + SHORT LOW + if(curr_bit) { instance->encoder.upload[index++] = level_duration_make(true, te_short); instance->encoder.upload[index++] = level_duration_make(false, te_short); - } else if(!prev_bit && curr_bit) { - // 0->1: LONG HIGH - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else if(prev_bit && !curr_bit) { - // 1->0: LONG LOW - instance->encoder.upload[index++] = level_duration_make(false, te_long); - } else { - // 1->1: SHORT LOW + SHORT HIGH - instance->encoder.upload[index++] = level_duration_make(false, te_short); - instance->encoder.upload[index++] = level_duration_make(true, te_short); - } - - prev_bit = curr_bit; - } - - // Encode 6 btn bits using same differential pattern - for(int bit = 5; bit >= 0; bit--) { - bool curr_bit = (btn_to_send >> bit) & 1; - - if(!prev_bit && !curr_bit) { - instance->encoder.upload[index++] = level_duration_make(true, te_short); - instance->encoder.upload[index++] = level_duration_make(false, te_short); - } else if(!prev_bit && curr_bit) { - instance->encoder.upload[index++] = level_duration_make(true, te_long); - } else if(prev_bit && !curr_bit) { - instance->encoder.upload[index++] = level_duration_make(false, te_long); } else { instance->encoder.upload[index++] = level_duration_make(false, te_short); instance->encoder.upload[index++] = level_duration_make(true, te_short); } - - prev_bit = curr_bit; } - // End marker - ensure we end with LOW - if(prev_bit) { - instance->encoder.upload[index++] = level_duration_make(false, te_short); + // Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits + for(int bit = 6; bit >= 0; bit--) { + bool curr_bit = (instance->endbyte >> bit) & 1; + + if(curr_bit) { + instance->encoder.upload[index++] = level_duration_make(true, te_short); + instance->encoder.upload[index++] = level_duration_make(false, te_short); + } else { + instance->encoder.upload[index++] = level_duration_make(false, te_short); + instance->encoder.upload[index++] = level_duration_make(true, te_short); + } } - instance->encoder.upload[index++] = level_duration_make(false, te_short * 8); + + // End with extended LOW (will be followed by inter-burst gap or end) + instance->encoder.upload[index++] = level_duration_make(false, te_short * 4); } instance->encoder.size_upload = index; instance->encoder.front = 0; - FURI_LOG_I( - TAG, - "Upload built: %zu elements, btn_to_send=0x%02X", - instance->encoder.size_upload, - btn_to_send); + FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload); } SubGhzProtocolStatus @@ -227,7 +188,7 @@ SubGhzProtocolStatus do { FuriString* temp_str = furi_string_alloc(); - furi_check( temp_str ); + furi_check(temp_str); if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); @@ -238,12 +199,14 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str)); break; } + furi_string_free(temp_str); uint32_t bit_count_temp; if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { FURI_LOG_E(TAG, "Missing Bit"); break; } + instance->generic.data_count_bit = 64; if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { FURI_LOG_E(TAG, "Missing Key"); @@ -275,15 +238,18 @@ SubGhzProtocolStatus furi_string_free(temp_str); instance->generic.data = key; - instance->cnt = (uint32_t)(key >> 32); - instance->serial = (uint32_t)(key & 0xFFFFFFFF); + instance->hop = (uint32_t)(key >> 32); + instance->fix = (uint32_t)(key & 0xFFFFFFFF); uint32_t btn_temp = 0; if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { - instance->btn = (uint8_t)btn_temp; + instance->endbyte = (uint8_t)btn_temp; } else { - instance->btn = 0; + instance->endbyte = 0; } + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; + instance->generic.serial = instance->fix; if(!flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { @@ -291,14 +257,10 @@ SubGhzProtocolStatus } subghz_protocol_encoder_fiat_v0_get_upload(instance); + instance->encoder.is_running = true; - FURI_LOG_I( - TAG, - "Encoder ready: cnt=0x%08lX, serial=0x%08lX, btn=0x%02X", - instance->cnt, - instance->serial, - instance->btn); + FURI_LOG_I(TAG, "Encoder ready: hop=0x%08lX, fix=0x%08lX", instance->hop, instance->fix); ret = SubGhzProtocolStatusOk; } while(false); @@ -359,13 +321,28 @@ void subghz_protocol_decoder_fiat_v0_reset(void* context) { instance->data_low = 0; instance->data_high = 0; instance->bit_count = 0; - instance->cnt = 0; - instance->serial = 0; - instance->btn = 0; + instance->hop = 0; + instance->fix = 0; + instance->endbyte = 0; + instance->final_count = 0; instance->te_last = 0; instance->manchester_state = ManchesterStateMid1; } +// Helper function to reset decoder to data state with proper Manchester initialization +static void + fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) { + instance->decoder_state = FiatV0DecoderStepData; + instance->preamble_count = 0; + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->te_last = duration; + // Critical: Reset Manchester state when entering data mode + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); +} + void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) { furi_check(context); SubGhzProtocolDecoderFiatV0* instance = context; @@ -402,76 +379,68 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du case FiatV0DecoderStepPreamble: if(level) { + // HIGH pulse during preamble - just track timing + if(duration < te_short) { + diff = te_short - duration; + } else { + diff = duration - te_short; + } + if(diff < te_delta) { + instance->preamble_count++; + instance->te_last = duration; + } else { + instance->decoder_state = FiatV0DecoderStepReset; + } return; } + + // LOW pulse - check if it's the gap after preamble if(duration < te_short) { diff = te_short - duration; - if(diff < te_delta) { - instance->preamble_count++; - instance->te_last = duration; - if(instance->preamble_count >= 0x96) { - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { - diff = duration - gap_threshold; - } - if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; - return; - } - } - } else { - instance->decoder_state = FiatV0DecoderStepReset; - if(instance->preamble_count >= 0x96) { - if(duration < gap_threshold) { - diff = gap_threshold - duration; - } else { - diff = duration - gap_threshold; - } - if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; - return; - } - } - } } else { diff = duration - te_short; - if(diff < te_delta) { - instance->preamble_count++; - instance->te_last = duration; - } else { - instance->decoder_state = FiatV0DecoderStepReset; - } + } + + if(diff < te_delta) { + // Normal short LOW - continue preamble + instance->preamble_count++; + instance->te_last = duration; + } else { + // Not a short pulse - check if it's the gap if(instance->preamble_count >= 0x96) { - if(duration >= 799) { - diff = duration - gap_threshold; - } else { + // We have enough preamble, check for gap + if(duration < gap_threshold) { diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; } if(diff < te_delta) { - instance->decoder_state = FiatV0DecoderStepData; - instance->preamble_count = 0; - instance->data_low = 0; - instance->data_high = 0; - instance->bit_count = 0; - instance->te_last = duration; + // Valid gap detected - transition to data state + fiat_v0_decoder_enter_data_state(instance, duration); return; } } + // Invalid timing or not enough preamble + instance->decoder_state = FiatV0DecoderStepReset; + } + + // Also check for gap even with valid short timing if we have enough preamble + if(instance->preamble_count >= 0x96 && + instance->decoder_state == FiatV0DecoderStepPreamble) { + if(duration < gap_threshold) { + diff = gap_threshold - duration; + } else { + diff = duration - gap_threshold; + } + if(diff < te_delta) { + // Valid gap detected - transition to data state + fiat_v0_decoder_enter_data_state(instance, duration); + return; + } } break; - case FiatV0DecoderStepData: { + case FiatV0DecoderStepData: ManchesterEvent event = ManchesterEventReset; if(duration < te_short) { diff = te_short - duration; @@ -510,20 +479,68 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->bit_count++; if(instance->bit_count == 0x40) { - instance->serial = instance->data_low; - instance->cnt = instance->data_high; + instance->fix = instance->data_low; + instance->hop = instance->data_high; + FURI_LOG_D( + TAG, + "Bit 64: fix=0x%08lX, hop=0x%08lX, clearing data_low/data_high", + instance->fix, + instance->hop); instance->data_low = 0; instance->data_high = 0; } - if(instance->bit_count > 0x46) { - instance->btn = (uint8_t)((instance->data_low << 1) | 1); +#ifndef REMOVE_LOGS + if(instance->bit_count > 0x40 && instance->bit_count <= 0x47) { + uint8_t endbyte_bit_num = instance->bit_count - 0x40; + uint8_t endbyte_bit_index = endbyte_bit_num - 1; + uint8_t data_low_byte = (uint8_t)instance->data_low; + char binary_str[9] = {0}; + for(int i = 7; i >= 0; i--) { + binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; + } + FURI_LOG_D( + TAG, + "Bit %d (endbyte bit %d/%d): new_bit=%lu, data_low=0x%08lX (0x%02X), binary=0b%s", + instance->bit_count, + endbyte_bit_index, + endbyte_bit_num - 1, + (unsigned long)new_bit, + instance->data_low, + data_low_byte, + binary_str); + } +#endif + if(instance->bit_count == 0x47) { +#ifndef REMOVE_LOGS + uint8_t data_low_byte = (uint8_t)instance->data_low; + char binary_str[9] = {0}; + for(int i = 7; i >= 0; i--) { + binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0'; + } + FURI_LOG_D( + TAG, + "EXTRACTING AT BIT 71: bit_count=%d, data_low=0x%08lX (0x%02X), binary=0b%s, accumulated %d bits after bit 64", + instance->bit_count, + instance->data_low, + data_low_byte, + binary_str, + instance->bit_count - 0x40); +#endif + instance->final_count = instance->bit_count; + instance->endbyte = (uint8_t)instance->data_low; - instance->generic.data = ((uint64_t)instance->cnt << 32) | instance->serial; + FURI_LOG_D( + TAG, + "EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)", + instance->endbyte, + instance->endbyte); + + instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; instance->generic.data_count_bit = 71; - instance->generic.serial = instance->serial; - instance->generic.btn = instance->btn; - instance->generic.cnt = instance->cnt; + instance->generic.serial = instance->fix; + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; if(instance->base.callback) { instance->base.callback(&instance->base, instance->base.context); @@ -535,11 +552,39 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du instance->decoder_state = FiatV0DecoderStepReset; } } + } else { + if(instance->bit_count == 0x47) { + // We have exactly 71 bits - extract endbyte + uint8_t data_low_byte = (uint8_t)instance->data_low; + instance->endbyte = data_low_byte; + + FURI_LOG_D( + TAG, + "GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X", + instance->bit_count, + instance->endbyte); + + instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; + instance->generic.data_count_bit = 71; + instance->generic.serial = instance->fix; + instance->generic.btn = instance->endbyte; + instance->generic.cnt = instance->hop; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + + instance->data_low = 0; + instance->data_high = 0; + instance->bit_count = 0; + instance->decoder_state = FiatV0DecoderStepReset; + } else if(instance->bit_count < 0x40) { + instance->decoder_state = FiatV0DecoderStepReset; + } } instance->te_last = duration; break; } - } } uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) { @@ -562,24 +607,28 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize( do { if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) break; + if(!flipper_format_write_string_cstr( flipper_format, "Preset", furi_string_get_cstr(preset->name))) break; + if(!flipper_format_write_string_cstr( flipper_format, "Protocol", instance->generic.protocol_name)) break; - uint32_t bits = 71; + uint32_t bits = instance->generic.data_count_bit; if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) break; char key_str[20]; - snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->cnt, instance->serial); + snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix); if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) break; - if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->cnt, 1)) break; - if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1)) break; + uint32_t temp = instance->hop; + if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) break; - uint32_t temp = instance->btn; + if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->fix, 1)) break; + + temp = instance->endbyte; if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) break; ret = SubGhzProtocolStatusOk; @@ -604,13 +653,14 @@ void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* outpu output, "%s %dbit\r\n" "Key:%08lX%08lX\r\n" - "Sn:%08lX Btn:%02X\r\n" - "Cnt:%08lX\r\n", + "Hop:%08lX\r\n" + "Sn:%08lX\r\n" + "EndByte:%02X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, - instance->cnt, - instance->serial, - instance->serial, - instance->btn, - instance->cnt); + instance->hop, + instance->fix, + instance->hop, + instance->fix, + instance->endbyte); } diff --git a/protocols/fiat_v0.h b/protocols/fiat_v0.h index 31edaf7..bce58af 100644 --- a/protocols/fiat_v0.h +++ b/protocols/fiat_v0.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "../defines.h" From 7b9f951f42420c12c13c05339be0ba8ee44ebe86 Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:16:35 -0800 Subject: [PATCH 25/30] CI: rerun checks From a166eb623572ab0e468c32ac5a5cf2848061aca2 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Sun, 15 Feb 2026 22:14:20 +0100 Subject: [PATCH 26/30] try-fix encoder --- protocols/fiat_v0.c | 112 +++++++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 48172ea..4363781 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -41,6 +41,9 @@ struct SubGhzProtocolEncoderFiatV0 { uint32_t hop; uint32_t fix; uint8_t endbyte; + + // Capacity of encoder.upload in LevelDuration elements. + size_t upload_capacity; }; typedef enum { @@ -81,6 +84,30 @@ const SubGhzProtocol fiat_protocol_v0 = { // ENCODER IMPLEMENTATION // ============================================================================ +static size_t fiat_v0_encoder_calc_required_upload(void) { + // Per burst: + // - preamble: FIAT_V0_PREAMBLE_PAIRS pairs -> 2 elements each + // - data: 64 bits Manchester -> 2 elements per bit + // - endbyte: 7 bits Manchester -> 2 elements per bit + // - trailer: 1 element (extended low) + const size_t per_burst = (FIAT_V0_PREAMBLE_PAIRS * 2) + (64 * 2) + (7 * 2) + 1; + // Inter-burst gap is a single element between bursts. + return (FIAT_V0_TOTAL_BURSTS * per_burst) + + (FIAT_V0_TOTAL_BURSTS > 0 ? (FIAT_V0_TOTAL_BURSTS - 1) : 0); +} + +static void + fiat_v0_encoder_ensure_upload_capacity(SubGhzProtocolEncoderFiatV0* instance, size_t required) { + furi_check(instance); + furi_check(required <= instance->upload_capacity); + + LevelDuration* new_upload = + realloc(instance->encoder.upload, required * sizeof(LevelDuration)); + furi_check(new_upload); + instance->encoder.upload = new_upload; + instance->upload_capacity = required; +} + void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0)); @@ -90,8 +117,9 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 10; - instance->encoder.size_upload = 1024; - instance->encoder.upload = calloc(instance->encoder.size_upload, sizeof(LevelDuration)); + instance->encoder.size_upload = 0; + instance->upload_capacity = fiat_v0_encoder_calc_required_upload(); + instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration)); furi_check(instance->encoder.upload); instance->encoder.is_running = false; @@ -111,6 +139,9 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat furi_check(instance); size_t index = 0; + const size_t required = fiat_v0_encoder_calc_required_upload(); + fiat_v0_encoder_ensure_upload_capacity(instance, required); + uint32_t te_short = subghz_protocol_fiat_v0_const.te_short; FURI_LOG_I( @@ -118,7 +149,7 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat "Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X", instance->hop, instance->fix, - instance->endbyte); + instance->endbyte & 0x7F); for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) { if(burst > 0) { @@ -132,7 +163,7 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat instance->encoder.upload[index++] = level_duration_make(false, te_short); } - // Extend last LOW to create the gap (~800us) + // Extend last LOW to create the gap (~FIAT_V0_GAP_US) instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US); // Combine hop and fix into 64-bit data @@ -152,8 +183,9 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat } // Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits + uint8_t endbyte = (uint8_t)(instance->endbyte & 0x7F); for(int bit = 6; bit >= 0; bit--) { - bool curr_bit = (instance->endbyte >> bit) & 1; + bool curr_bit = (endbyte >> bit) & 1; if(curr_bit) { instance->encoder.upload[index++] = level_duration_make(true, te_short); @@ -168,6 +200,7 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat instance->encoder.upload[index++] = level_duration_make(false, te_short * 4); } + furi_check(index <= instance->upload_capacity); instance->encoder.size_upload = index; instance->encoder.front = 0; @@ -186,10 +219,10 @@ SubGhzProtocolStatus flipper_format_rewind(flipper_format); - do { - FuriString* temp_str = furi_string_alloc(); - furi_check(temp_str); + FuriString* temp_str = furi_string_alloc(); + furi_check(temp_str); + do { if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { FURI_LOG_E(TAG, "Missing Protocol"); break; @@ -199,14 +232,24 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str)); break; } - furi_string_free(temp_str); - uint32_t bit_count_temp; - if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { + uint32_t bit_count_temp = 0; + if(flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) { + // This protocol transmits 71 bits: 64-bit key + 7-bit endbyte. + // Let's do according to what's read, (64 or 71), else 71 + if(bit_count_temp == 64 || bit_count_temp == 71) { + instance->generic.data_count_bit = bit_count_temp; + } else { + FURI_LOG_E( + TAG, + "Inconsistent Bit value of %" PRIu32 " was defaulted to 71", + instance->generic.data_count_bit); + instance->generic.data_count_bit = 71; + } + } else { FURI_LOG_E(TAG, "Missing Bit"); break; } - instance->generic.data_count_bit = 64; if(!flipper_format_read_string(flipper_format, "Key", temp_str)) { FURI_LOG_E(TAG, "Missing Key"); @@ -223,19 +266,24 @@ SubGhzProtocolStatus if(c == ' ') continue; uint8_t nibble; - if(c >= '0' && c <= '9') - nibble = c - '0'; - else if(c >= 'A' && c <= 'F') - nibble = c - 'A' + 10; - else if(c >= 'a' && c <= 'f') - nibble = c - 'a' + 10; - else + if(c >= '0' && c <= '9') { + nibble = (uint8_t)(c - '0'); + } else if(c >= 'A' && c <= 'F') { + nibble = (uint8_t)(c - 'A' + 10); + } else if(c >= 'a' && c <= 'f') { + nibble = (uint8_t)(c - 'a' + 10); + } else { break; + } key = (key << 4) | nibble; hex_pos++; } - furi_string_free(temp_str); + + if(hex_pos != 16) { + FURI_LOG_E(TAG, "Key parse error: expected 16 hex nibbles, got %u", (unsigned)hex_pos); + break; + } instance->generic.data = key; instance->hop = (uint32_t)(key >> 32); @@ -243,28 +291,36 @@ SubGhzProtocolStatus uint32_t btn_temp = 0; if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) { - instance->endbyte = (uint8_t)btn_temp; + instance->endbyte = (uint8_t)(btn_temp & 0x7F); } else { instance->endbyte = 0; } + instance->generic.btn = instance->endbyte; instance->generic.cnt = instance->hop; instance->generic.serial = instance->fix; - if(!flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { + uint32_t repeat_temp = 0; + if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_temp, 1)) { + instance->encoder.repeat = repeat_temp; + } else { instance->encoder.repeat = 10; } subghz_protocol_encoder_fiat_v0_get_upload(instance); - instance->encoder.is_running = true; - FURI_LOG_I(TAG, "Encoder ready: hop=0x%08lX, fix=0x%08lX", instance->hop, instance->fix); + FURI_LOG_I( + TAG, + "Encoder ready: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X", + instance->hop, + instance->fix, + instance->endbyte); ret = SubGhzProtocolStatusOk; } while(false); + furi_string_free(temp_str); return ret; } @@ -534,7 +590,7 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du TAG, "EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)", instance->endbyte, - instance->endbyte); + instance->endbyte & 0x7F); instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; instance->generic.data_count_bit = 71; @@ -562,7 +618,7 @@ void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t du TAG, "GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X", instance->bit_count, - instance->endbyte); + instance->endbyte & 0x7F); instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix; instance->generic.data_count_bit = 71; @@ -662,5 +718,5 @@ void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* outpu instance->fix, instance->hop, instance->fix, - instance->endbyte); + instance->endbyte & 0x7F); } From 6969a09974e0e53a8be5a83b5c1614a0f1b078e6 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Tue, 17 Feb 2026 16:02:24 +0100 Subject: [PATCH 27/30] fix test on wrong variable --- scenes/protopirate_scene_receiver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/protopirate_scene_receiver.c b/scenes/protopirate_scene_receiver.c index 0dc6fa4..07e8a9f 100644 --- a/scenes/protopirate_scene_receiver.c +++ b/scenes/protopirate_scene_receiver.c @@ -129,7 +129,7 @@ static void protopirate_scene_receiver_callback( furi_string_replace_all(protocol, " ", "_"); FuriString* saved_path = furi_string_alloc(); - if(!protocol) { + if(!saved_path) { FURI_LOG_E(TAG, "saved_path allocation failed"); furi_string_free(protocol); furi_string_free(str_buff); From 8f00e382d9c316268867b4f13a0bf3184c7bfd05 Mon Sep 17 00:00:00 2001 From: gullradriel Date: Tue, 17 Feb 2026 16:45:00 +0100 Subject: [PATCH 28/30] typo on logged variable type --- protocols/fiat_v0.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/protocols/fiat_v0.c b/protocols/fiat_v0.c index 4363781..9b51074 100644 --- a/protocols/fiat_v0.c +++ b/protocols/fiat_v0.c @@ -1,6 +1,7 @@ #include "fiat_v0.h" #include "../protopirate_app_i.h" #include +#include #define TAG "FiatProtocolV0" #define FIAT_PROTOCOL_V0_NAME "Fiat V0" @@ -242,7 +243,7 @@ SubGhzProtocolStatus } else { FURI_LOG_E( TAG, - "Inconsistent Bit value of %" PRIu32 " was defaulted to 71", + "Inconsistent Bit value of %d was defaulted to 71", instance->generic.data_count_bit); instance->generic.data_count_bit = 71; } From 241ec6d75c50ec897b8815423b972d27ab48cb9f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:06:39 +0300 Subject: [PATCH 29/30] fix --- protocols/vag.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/protocols/vag.c b/protocols/vag.c index 4831a8d..4bd6a48 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -67,13 +67,13 @@ static const SubGhzBlockConst subghz_protocol_vag_const = { #define VAG_T34_PREAMBLE_MIN 31u #define VAG_T34_SYNC_PAIRS 3u -#define VAG_DATA_GAP_MIN 4001u -#define VAG_TOTAL_BITS 80u -#define VAG_KEY1_BITS 64u -#define VAG_PREFIX_BITS 15u -#define VAG_BIT_LIMIT 96u -#define VAG_FRAME_PREFIX_T1 0x2F3Fu -#define VAG_FRAME_PREFIX_T2 0x2F1Cu +#define VAG_DATA_GAP_MIN 4001u +#define VAG_TOTAL_BITS 80u +#define VAG_KEY1_BITS 64u +#define VAG_PREFIX_BITS 15u +#define VAG_BIT_LIMIT 96u +#define VAG_FRAME_PREFIX_T1 0x2F3Fu +#define VAG_FRAME_PREFIX_T2 0x2F1Cu #define VAG_KEYS_COUNT 3 @@ -590,7 +590,6 @@ void subghz_protocol_decoder_vag_reset(void* context) { instance->decrypted = false; /* Reset decoder state to avoid stale parsing after external resets */ - instance->te_last = 0; instance->data_low = 0; instance->data_high = 0; instance->bit_count = 0; @@ -685,8 +684,7 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati instance->data_high = 0; instance->bit_count = 0; instance->vag_type = 1; - } else if( - instance->data_low == VAG_FRAME_PREFIX_T2 && instance->data_high == 0) { + } else if(instance->data_low == VAG_FRAME_PREFIX_T2 && instance->data_high == 0) { instance->data_low = 0; instance->data_high = 0; instance->bit_count = 0; @@ -746,8 +744,7 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati break; case VAGDecoderStepSync2A: - if(!level && - DURATION_DIFF(duration, VAG_T34_TE_SHORT) < VAG_T34_TE_DELTA && + if(!level && DURATION_DIFF(duration, VAG_T34_TE_SHORT) < VAG_T34_TE_DELTA && DURATION_DIFF(instance->decoder.te_last, VAG_T34_TE_LONG) < VAG_T34_LONG_DELTA) { instance->decoder.te_last = duration; instance->decoder.parser_step = VAGDecoderStepSync2B; @@ -766,8 +763,7 @@ void subghz_protocol_decoder_vag_feed(void* context, bool level, uint32_t durati break; case VAGDecoderStepSync2C: - if(!level && - DURATION_DIFF(duration, VAG_T34_SYNC) < VAG_T34_SYNC_DELTA && + if(!level && DURATION_DIFF(duration, VAG_T34_SYNC) < VAG_T34_SYNC_DELTA && DURATION_DIFF(instance->decoder.te_last, VAG_T34_SYNC) < VAG_T34_SYNC_DELTA) { instance->mid_count++; instance->decoder.parser_step = VAGDecoderStepSync2B; From 7325675e043da381a8c9f42cfa0378694567ab5f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:14:10 +0300 Subject: [PATCH 30/30] fix merge bugs --- protocols/vag.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/protocols/vag.c b/protocols/vag.c index 4bd6a48..b267319 100644 --- a/protocols/vag.c +++ b/protocols/vag.c @@ -6,18 +6,9 @@ #define TAG "VAGProtocol" #define VAG_ENCODER_UPLOAD_MAX_SIZE 680 -#define VAG_KEYS_COUNT 3 // uncomment to enable //#define VAG_CHECK_UPLOAD_OVERFLOW -/* Timing helpers: keep decoder/encoder checks compact and consistent */ -static inline uint32_t vag_udiff_u32(uint32_t a, uint32_t b) { - return (a > b) ? (a - b) : (b - a); -} -#define VAG_NEAR(d, t, tol) (vag_udiff_u32((d), (t)) <= (uint32_t)(tol)) -#define VAG_TOL_300 79u -#define VAG_TOL_500 120u - /* Manchester emit helper: one bit -> two half-bits */ #define VAG_EMIT_MANCHESTER(u, idx, bit, te) \ do { \