From 1287d10fa0f314703575abd26ba7c491399979ad Mon Sep 17 00:00:00 2001 From: zero-mega <171701900+zero-mega@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:43:41 +0200 Subject: [PATCH] move newer Renault variants into Renault V1 add hitag2 seed bruteforce for serial derived keys (this is not Hitag2Hell on flipper) fix Fiat V1 encoder fix Kia V7 encoder remove untested porsche protocol merge Mitsubishi protocol into Kia V0 --- README.md | 5 +- application.fam | 12 +- protocols/fiat_v1.c | 265 +++- protocols/fiat_v2.c | 17 +- protocols/kia_v0.c | 118 +- protocols/kia_v7.c | 116 +- protocols/mitsubishi_v0.c | 261 --- protocols/mitsubishi_v0.h | 31 - protocols/plugins/protopirate_am_plugin.c | 4 +- protocols/plugins/protopirate_fm_plugin.c | 4 +- protocols/porsche_touareg.c | 378 ----- protocols/porsche_touareg.h | 32 - protocols/protocol_items.c | 22 +- protocols/protocol_items.h | 2 + protocols/protocol_timings.c | 16 +- protocols/protocols_common.c | 19 +- protocols/renault_v0.c | 1315 ++++------------ protocols/renault_v0.h | 2 - protocols/renault_v1.c | 1568 +++++++++++++++++++ protocols/renault_v1.h | 63 + scenes/plugins/protopirate_emulate_plugin.c | 257 ++- scenes/plugins/protopirate_psa_bf_plugin.c | 260 ++- scenes/plugins/protopirate_psa_bf_plugin.h | 2 +- scenes/protopirate_scene_receiver_info.c | 19 +- scenes/protopirate_scene_sub_decode.c | 7 +- 25 files changed, 2789 insertions(+), 2006 deletions(-) delete mode 100644 protocols/mitsubishi_v0.c delete mode 100644 protocols/mitsubishi_v0.h delete mode 100644 protocols/porsche_touareg.c delete mode 100644 protocols/porsche_touareg.h create mode 100644 protocols/renault_v1.c create mode 100644 protocols/renault_v1.h diff --git a/README.md b/README.md index 00d8259..ae939c8 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch | Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 | | Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 | | Mazda V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | Checksum | 315.00 / 433.92 | -| Porsche Touareg | ✅ | ❌ | PWM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 | | PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM650 | XTEA/XOR | CRC8 | 315.00 / 433.92 | | Renault V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code / Replay | Type/IC | 315.00 / 433.92 | +| Renault V1 | ✅ | ✅ | Manchester | AM650 | HITAG2 | XOR8 | 315.00 / 433.92 | | StarLine | ✅ | ✅ | PWM | AM650 | KeeLoq | ❌ | 315.00 / 433.92 | | Subaru | ✅ | ✅ | PPM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 | | VAG (VW/Audi/Seat/Skoda) | ✅ | ✅ | Manchester | AM650 | AUT64/XTEA | ❌ | 434.42 | @@ -53,7 +53,7 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch | Ford V2 | ✅ | ✅ | Manchester | F4 | Rolling Code (simple replay) | ❌ | 434.25 | | Ford V3 | ✅ | ❌ | Manchester | F4 | Rolling Code | ❌ | 434.25 | | Honda Static | ✅ | ✅ | PWM | Honda1 | Static Code | Checksum | 315.00 / 433.92 | -| Kia V0 / Suzuki V0 / Honda V0 | ✅ | ✅ | PWM | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 | +| Kia V0 / Suzuki V0 / Honda V0 / Mitsubishi V0 | ✅ | ✅ | PWM | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 | | Kia V2 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC4 | 315.00 / 433.92 | | Kia V3 / V4 | ✅ | ✅ | PWM | FM476 | KeeLoq | CRC4 (BF) | 315.00 / 433.92 | | Kia V5 | ✅ | ✅ | PWM | FM476 | Rolling Code | ✅ | 315.00 / 433.92 | @@ -61,7 +61,6 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch | Kia V7 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 | | Honda V2 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 | | Mazda V0 | ✅ | ✅ | Manchester | FM (F2?) | Rolling Code | Checksum | 315.00 / 433.92 | -| Mitsubishi V0 | ✅ | ❌ | PWM | FM476 | Rolling Code | ❌ | 315.00 / 433.92 | | PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | FM (F3?) | XTEA/XOR | CRC8 | 315.00 / 433.92 | | Scher-Khan | ✅ | ❌ | PWM | FM | Magic Code | ❌ | 315.00 / 433.92 | diff --git a/application.fam b/application.fam index c234564..b6e4ee9 100644 --- a/application.fam +++ b/application.fam @@ -77,10 +77,10 @@ App( "protocols/kia_v1.c", "protocols/kia_v2.c", "protocols/mazda_v0.c", - "protocols/porsche_touareg.c", "protocols/psa.c", "protocols/psa_crypto.c", "protocols/renault_v0.c", + "protocols/renault_v1.c", "protocols/subaru.c", "protocols/star_line.c", ], @@ -119,8 +119,8 @@ App( "protocols/kia_v6.c", "protocols/kia_v7.c", "protocols/mazda_v0.c", - "protocols/mitsubishi_v0.c", "protocols/psa.c", + "protocols/renault_v1.c", "protocols/psa_crypto.c", ], fal_embedded=True, @@ -326,6 +326,13 @@ ProtoPirateTxProtocolPlugin( "renault_v0_protocol", ["renault_v0.c"], ) +ProtoPirateTxProtocolPlugin( + "renault_v1", + "renault_v1.h", + "RENAULT_PROTOCOL_V1_NAME", + "renault_v1_protocol", + ["renault_v1.c"], +) ProtoPirateTxProtocolPlugin( "star_line", "star_line.h", @@ -406,6 +413,7 @@ App( "protocols/psa_crypto.c", "protocols/psa_crypto_bf.c", "protocols/psa_bf_core.c", + "protocols/renault_v1.c", "protocols/protocols_common.c", ], fap_icon_assets="images", diff --git a/protocols/fiat_v1.c b/protocols/fiat_v1.c index 8bab153..f3bd385 100644 --- a/protocols/fiat_v1.c +++ b/protocols/fiat_v1.c @@ -23,10 +23,15 @@ #define FIAT_V1_XOR_FIELD "XOR" #define FIAT_V1_HITAG2_KEY_FIELD "Hitag2 Key" #define FIAT_V1_HITAG2_EPOCH_FIELD "Hitag2 Epoch" -#define FIAT_V1_KNOWN_KEY_COUNT 8U +#define FIAT_V1_KNOWN_KEY_COUNT 9U -#define FIAT_V1_ENC_LEAD_US 2033U -#define FIAT_V1_ENC_GAP_US 3252U +#define FIAT_V1_TE_VARIANT_A 0U +#define FIAT_V1_TE_VARIANT_B 1U +#define FIAT_V1_DEFAULT_TE_VARIANT FIAT_V1_TE_VARIANT_B +#define FIAT_V1_ENC_LEAD_A_US 2033U +#define FIAT_V1_ENC_GAP_A_US 3252U +#define FIAT_V1_ENC_LEAD_B_US 850U +#define FIAT_V1_ENC_GAP_B_US 1230U #define FIAT_V1_ENC_DEFAULT_REPEAT 6U #define FIAT_V1_UPLOAD_CAPACITY 240U _Static_assert( @@ -48,9 +53,24 @@ static const SubGhzBlockConst subghz_protocol_fiat_v1_const_b = { }; static const SubGhzBlockConst* fiat_v1_variant_const(uint8_t variant) { - return (variant == 0U) ? &subghz_protocol_fiat_v1_const : &subghz_protocol_fiat_v1_const_b; + return (variant == FIAT_V1_TE_VARIANT_A) ? &subghz_protocol_fiat_v1_const : + &subghz_protocol_fiat_v1_const_b; } +#if PROTOPIRATE_WITH_ENCODER +static uint32_t fiat_v1_enc_lead_us(uint8_t variant) { + return (variant == FIAT_V1_TE_VARIANT_A) ? FIAT_V1_ENC_LEAD_A_US : FIAT_V1_ENC_LEAD_B_US; +} + +static uint32_t fiat_v1_enc_gap_us(uint8_t variant) { + return (variant == FIAT_V1_TE_VARIANT_A) ? FIAT_V1_ENC_GAP_A_US : FIAT_V1_ENC_GAP_B_US; +} + +static uint32_t fiat_v1_enc_te_us(uint8_t variant) { + return fiat_v1_variant_const(variant)->te_short; +} +#endif + typedef enum { FiatV1DecoderStepReset = 0, FiatV1DecoderStepData = 1, @@ -77,6 +97,7 @@ struct SubGhzProtocolDecoderFiatV1 { uint8_t hitag2_key[6]; uint32_t hitag2_epoch; bool hitag2_key_valid; + uint8_t te_variant; }; #if PROTOPIRATE_WITH_ENCODER @@ -91,6 +112,7 @@ struct SubGhzProtocolEncoderFiatV1 { uint32_t hop; uint8_t tail_bits; uint8_t frame_xor; + uint8_t te_variant; }; #endif @@ -280,7 +302,10 @@ static void fiat_v1_decode_fields(SubGhzProtocolDecoderFiatV1* instance) { } static bool - fiat_v1_commit(SubGhzProtocolDecoderFiatV1* instance, const uint8_t raw[FIAT_V1_WIRE_BYTES]) { + fiat_v1_commit( + SubGhzProtocolDecoderFiatV1* instance, + const uint8_t raw[FIAT_V1_WIRE_BYTES], + uint8_t te_variant) { if(!fiat_v1_frame_valid(raw)) { return false; } @@ -292,6 +317,8 @@ static bool memcpy(instance->raw_data, raw, FIAT_V1_WIRE_BYTES); memcpy(instance->last_raw_data, raw, FIAT_V1_WIRE_BYTES); instance->last_raw_valid = true; + instance->te_variant = (te_variant == FIAT_V1_TE_VARIANT_A) ? FIAT_V1_TE_VARIANT_A : + FIAT_V1_TE_VARIANT_B; fiat_v1_decode_fields(instance); FURI_LOG_D( @@ -333,7 +360,7 @@ static bool } } - return fiat_v1_commit(instance, raw); + return fiat_v1_commit(instance, raw, variant); } static void fiat_v1_try_decode(SubGhzProtocolDecoderFiatV1* instance, uint8_t variant) { @@ -525,6 +552,7 @@ static const uint8_t fiat_v1_known_keys[FIAT_V1_KNOWN_KEY_COUNT][6] = { {0x4DU, 0x49U, 0x4BU, 0x52U, 0x4FU, 0x4EU}, {0xCDU, 0x49U, 0x4BU, 0x52U, 0x4FU, 0x4EU}, {0x33U, 0xFAU, 0x2FU, 0xCDU, 0xC3U, 0x3BU}, + {0xF6U, 0x1AU, 0xEFU, 0x9CU, 0xD0U, 0x1BU}, }; static bool fiat_v1_key_matches( @@ -537,24 +565,73 @@ static bool fiat_v1_key_matches( return fiat_v1_bcm_generate_authenticator(uid, button, control, key, epoch) == hop; } +static bool fiat_v1_key_matches_any_button( + uint32_t uid, + uint16_t control, + uint32_t hop, + const uint8_t key[6], + uint32_t epoch) { + static const uint8_t buttons[] = {0x1U, 0x2U, 0x4U, 0x8U}; + for(size_t i = 0; i < COUNT_OF(buttons); i++) { + if(fiat_v1_key_matches(uid, buttons[i], control, hop, key, epoch)) { + return true; + } + } + return false; +} + +static bool fiat_v1_resolve_hitag2_key( + FlipperFormat* ff, + uint32_t uid, + uint8_t button, + uint16_t control, + uint32_t hop, + uint8_t key_out[6], + uint32_t* epoch_out) { + if(!key_out) return false; + + uint8_t key[6] = {0}; + uint32_t epoch = 0U; + if(ff) { + flipper_format_rewind(ff); + if(flipper_format_read_hex(ff, FIAT_V1_HITAG2_KEY_FIELD, key, sizeof(key))) { + flipper_format_rewind(ff); + if(flipper_format_read_uint32(ff, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1U)) { + epoch &= 0x3FFFFUL; + } + if(fiat_v1_key_matches(uid, button, control, hop, key, epoch) || + fiat_v1_key_matches_any_button(uid, control, hop, key, epoch)) { + memcpy(key_out, key, sizeof(key)); + if(epoch_out) *epoch_out = epoch; + return true; + } + } + } + + for(uint8_t i = 0U; i < FIAT_V1_KNOWN_KEY_COUNT; i++) { + if(fiat_v1_key_matches(uid, button, control, hop, fiat_v1_known_keys[i], 0U) || + fiat_v1_key_matches_any_button(uid, control, hop, fiat_v1_known_keys[i], 0U)) { + memcpy(key_out, fiat_v1_known_keys[i], sizeof(key)); + if(epoch_out) *epoch_out = 0U; + return true; + } + } + return false; +} + static void fiat_v1_verify_hitag2_key(SubGhzProtocolDecoderFiatV1* instance) { instance->hitag2_key_valid = false; instance->hitag2_epoch = 0U; memset(instance->hitag2_key, 0, sizeof(instance->hitag2_key)); - const uint32_t uid = instance->uid; - const uint8_t button = instance->generic.btn; - const uint16_t control = (uint16_t)(instance->generic.cnt & 0x03FFU); - const uint32_t hop = instance->hop; - - for(uint8_t i = 0U; i < FIAT_V1_KNOWN_KEY_COUNT; i++) { - if(fiat_v1_key_matches( - uid, button, control, hop, fiat_v1_known_keys[i], instance->hitag2_epoch)) { - memcpy(instance->hitag2_key, fiat_v1_known_keys[i], sizeof(instance->hitag2_key)); - instance->hitag2_key_valid = true; - return; - } - } + instance->hitag2_key_valid = fiat_v1_resolve_hitag2_key( + NULL, + instance->uid, + instance->generic.btn, + (uint16_t)(instance->generic.cnt & 0x03FFU), + instance->hop, + instance->hitag2_key, + &instance->hitag2_epoch); } #if PROTOPIRATE_WITH_ENCODER @@ -565,18 +642,20 @@ static bool fiat_v1_encoder_build_upload(SubGhzProtocolEncoderFiatV1* instance) return false; } + const uint8_t variant = instance->te_variant; + const uint32_t te = fiat_v1_enc_te_us(variant); size_t index = 0U; const size_t cap = FIAT_V1_UPLOAD_CAPACITY; - index = pp_emit_merge(upload, index, cap, true, FIAT_V1_ENC_LEAD_US); + index = pp_emit_merge(upload, index, cap, true, fiat_v1_enc_lead_us(variant)); for(uint8_t bit_index = 0U; bit_index < FIAT_V1_WIRE_BITS; bit_index++) { const bool bit = ((instance->raw_data[bit_index >> 3U] >> (7U - (bit_index & 7U))) & 1U) != 0U; - index = pp_emit_merge(upload, index, cap, bit, FIAT_V1_TE_SHORT); - index = pp_emit_merge(upload, index, cap, !bit, FIAT_V1_TE_SHORT); + index = pp_emit_merge(upload, index, cap, bit, te); + index = pp_emit_merge(upload, index, cap, !bit, te); } - index = pp_emit_merge(upload, index, cap, false, FIAT_V1_ENC_GAP_US); + index = pp_emit_merge(upload, index, cap, false, fiat_v1_enc_gap_us(variant)); furi_check(index <= cap); instance->encoder.size_upload = index; instance->encoder.front = 0U; @@ -591,6 +670,7 @@ void* subghz_protocol_encoder_fiat_v1_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &fiat_v1_protocol; instance->generic.protocol_name = instance->base.protocol->name; instance->tail_bits = FIAT_V1_DEFAULT_TAIL_BITS; + instance->te_variant = FIAT_V1_DEFAULT_TE_VARIANT; instance->encoder.repeat = FIAT_V1_ENC_DEFAULT_REPEAT; return instance; } @@ -618,18 +698,22 @@ SubGhzProtocolStatus } instance->generic.data_count_bit = bit_count; - uint32_t serial = 0U; - uint32_t button = 0U; - uint32_t control = 0U; + uint32_t key_serial = 0U; + uint32_t key_button = 0U; + uint32_t key_control = 0U; + uint32_t captured_hop = 0U; uint8_t raw_from_file[FIAT_V1_WIRE_BYTES] = {0}; + bool have_raw = false; flipper_format_rewind(flipper_format); if(flipper_format_read_hex( flipper_format, FIAT_V1_RAW_FIELD, raw_from_file, sizeof(raw_from_file)) && fiat_v1_frame_valid(raw_from_file)) { - serial = fiat_v1_uid(raw_from_file); - button = raw_from_file[6] >> 4U; - control = fiat_v1_counter(raw_from_file); + have_raw = true; + key_serial = fiat_v1_uid(raw_from_file); + key_button = raw_from_file[6] >> 4U; + key_control = fiat_v1_counter(raw_from_file); + captured_hop = fiat_v1_hop(raw_from_file); instance->tail_bits = raw_from_file[11] & 0x03U; } else { SubGhzBlockGeneric generic = {0}; @@ -637,29 +721,53 @@ SubGhzProtocolStatus if(subghz_block_generic_deserialize_check_count_bit( &generic, flipper_format, subghz_protocol_fiat_v1_const.min_count_bit_for_found) == SubGhzProtocolStatusOk) { - serial = (uint32_t)(generic.data >> 32U); - button = generic.btn; - control = generic.cnt; + key_serial = (uint32_t)(generic.data >> 32U); + key_button = generic.btn; + key_control = generic.cnt; + captured_hop = (uint32_t)generic.data; + } + flipper_format_rewind(flipper_format); + uint32_t hop_field = 0U; + if(flipper_format_read_uint32(flipper_format, FIAT_V1_HOP_FIELD, &hop_field, 1U)) { + captured_hop = hop_field; + } + } + + uint32_t serial = key_serial; + uint32_t button = key_button; + uint32_t control = key_control; + uint32_t type = FIAT_V1_DEFAULT_TE_VARIANT; + pp_encoder_read_fields(flipper_format, &serial, &button, &control, &type); + if(!have_raw) { + key_serial = serial; + key_control = control; + if(fiat_v1_button_valid((uint8_t)key_button) == false) { + key_button = button; } } - pp_encoder_read_fields(flipper_format, &serial, &button, &control, NULL); if(serial == 0U || serial == UINT32_MAX || !fiat_v1_button_valid((uint8_t)button)) { return SubGhzProtocolStatusErrorParserOthers; } flipper_format_rewind(flipper_format); - if(!flipper_format_read_hex( - flipper_format, FIAT_V1_HITAG2_KEY_FIELD, instance->hitag2_key, 6U)) { - return SubGhzProtocolStatusErrorParserOthers; + uint32_t tail_bits = instance->tail_bits; + if(flipper_format_read_uint32(flipper_format, FIAT_V1_TAIL_BITS_FIELD, &tail_bits, 1U)) { + instance->tail_bits = (uint8_t)(tail_bits & 0x03U); } - uint32_t epoch = 0U; - flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1U)) { - instance->epoch = epoch & 0x3FFFFUL; - } else { - instance->epoch = 0U; + instance->te_variant = (type == FIAT_V1_TE_VARIANT_A) ? FIAT_V1_TE_VARIANT_A : + FIAT_V1_TE_VARIANT_B; + + if(!fiat_v1_resolve_hitag2_key( + flipper_format, + key_serial ? key_serial : serial, + (uint8_t)(key_button ? key_button : button), + (uint16_t)(key_control & 0x03FFU), + captured_hop, + instance->hitag2_key, + &instance->epoch)) { + return SubGhzProtocolStatusErrorParserOthers; } control &= 0x03FFU; @@ -694,12 +802,13 @@ SubGhzProtocolStatus FURI_LOG_I( TAG, - "TX UID:%08lX Btn:%02lX Cnt:%03lX Auth:%08lX Epoch:%05lX XOR:%02X", + "TX UID:%08lX Btn:%02lX Cnt:%03lX Auth:%08lX Epoch:%05lX TE:%u XOR:%02X", (unsigned long)serial, (unsigned long)button, (unsigned long)control, (unsigned long)instance->hop, (unsigned long)instance->epoch, + instance->te_variant, instance->frame_xor); return SubGhzProtocolStatusOk; @@ -738,6 +847,7 @@ void subghz_protocol_decoder_fiat_v1_reset(void* context) { instance->frame_xor = 0U; instance->hitag2_key_valid = false; instance->hitag2_epoch = 0U; + instance->te_variant = FIAT_V1_DEFAULT_TE_VARIANT; memset(instance->hitag2_key, 0, sizeof(instance->hitag2_key)); fiat_v1_clear_all_cells(instance); } @@ -789,36 +899,28 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize( } flipper_format_rewind(flipper_format); - flipper_format_insert_or_update_hex( - flipper_format, FIAT_V1_RAW_FIELD, instance->raw_data, FIAT_V1_WIRE_BYTES); + flipper_format_delete_key(flipper_format, FIAT_V1_RAW_FIELD); - uint32_t hop = instance->hop; - uint32_t frame_xor = instance->frame_xor; - uint32_t tail_bits = instance->tail_bits; - if(!flipper_format_write_uint32(flipper_format, FIAT_V1_HOP_FIELD, &hop, 1) || - !flipper_format_write_uint32(flipper_format, FIAT_V1_XOR_FIELD, &frame_xor, 1) || - !flipper_format_write_uint32(flipper_format, FIAT_V1_TAIL_BITS_FIELD, &tail_bits, 1)) { + pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial); + pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn); + pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt); + pp_flipper_update_or_insert_u32(flipper_format, FF_TYPE, instance->te_variant); + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V1_HOP_FIELD, instance->hop); + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V1_XOR_FIELD, instance->frame_xor); + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V1_TAIL_BITS_FIELD, instance->tail_bits); + + if(!flipper_format_insert_or_update_hex( + flipper_format, FIAT_V1_RAW_FIELD, instance->raw_data, FIAT_V1_WIRE_BYTES)) { return SubGhzProtocolStatusErrorParserOthers; } - ret = pp_serialize_fields( - flipper_format, - PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT, - instance->generic.serial, - instance->generic.btn, - instance->generic.cnt, - 0); - if(ret != SubGhzProtocolStatusOk) { - return ret; - } - if(instance->hitag2_key_valid) { uint32_t epoch = instance->hitag2_epoch & 0x3FFFFUL; if(!flipper_format_insert_or_update_hex( - flipper_format, FIAT_V1_HITAG2_KEY_FIELD, instance->hitag2_key, 6U) || - !flipper_format_write_uint32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1)) { + flipper_format, FIAT_V1_HITAG2_KEY_FIELD, instance->hitag2_key, 6U)) { return SubGhzProtocolStatusErrorParserOthers; } + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, epoch); } return pp_write_display( @@ -829,29 +931,14 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize( static void fiat_v1_load_hitag2_key(SubGhzProtocolDecoderFiatV1* instance, FlipperFormat* flipper_format) { - uint8_t key[6] = {0}; - flipper_format_rewind(flipper_format); - if(!flipper_format_read_hex(flipper_format, FIAT_V1_HITAG2_KEY_FIELD, key, 6U)) { - return; - } - - uint32_t epoch = 0U; - flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1U)) { - epoch &= 0x3FFFFUL; - } else { - epoch = 0U; - } - - memcpy(instance->hitag2_key, key, sizeof(instance->hitag2_key)); - instance->hitag2_epoch = epoch; - instance->hitag2_key_valid = fiat_v1_key_matches( + instance->hitag2_key_valid = fiat_v1_resolve_hitag2_key( + flipper_format, instance->uid, instance->generic.btn, (uint16_t)(instance->generic.cnt & 0x03FFU), instance->hop, - key, - epoch); + instance->hitag2_key, + &instance->hitag2_epoch); } SubGhzProtocolStatus @@ -868,6 +955,14 @@ SubGhzProtocolStatus return SubGhzProtocolStatusErrorValueBitCount; } + flipper_format_rewind(flipper_format); + uint32_t value = 0U; + instance->te_variant = FIAT_V1_DEFAULT_TE_VARIANT; + if(flipper_format_read_uint32(flipper_format, FF_TYPE, &value, 1)) { + instance->te_variant = (value == FIAT_V1_TE_VARIANT_A) ? FIAT_V1_TE_VARIANT_A : + FIAT_V1_TE_VARIANT_B; + } + flipper_format_rewind(flipper_format); if(flipper_format_read_hex( flipper_format, FIAT_V1_RAW_FIELD, instance->raw_data, FIAT_V1_WIRE_BYTES)) { @@ -885,7 +980,6 @@ SubGhzProtocolStatus instance->uid = instance->generic.serial; instance->tail_bits = FIAT_V1_DEFAULT_TAIL_BITS; - uint32_t value = 0U; if(flipper_format_read_uint32(flipper_format, FF_SERIAL, &value, 1)) { instance->generic.serial = value; } @@ -924,7 +1018,7 @@ void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* outpu "%08lX %03lX%01X %08lX\r\n" "Sync:%02X UID:%08lX Auth:%08lX\r\n" "Btn:%02X [%s] Ctrl:%03lX\r\n" - "Tail:%u XOR:%02X\r\n", + "TE:%u Tail:%u XOR:%02X\r\n", instance->generic.protocol_name, FIAT_V1_LOGICAL_BITS, instance->hitag2_key_valid ? "KEY:OK" : "KEY:??", @@ -938,6 +1032,7 @@ void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* outpu instance->generic.btn, fiat_v1_button_name(instance->generic.btn), (unsigned long)instance->generic.cnt, + instance->te_variant, instance->tail_bits, instance->frame_xor); } diff --git a/protocols/fiat_v2.c b/protocols/fiat_v2.c index 4cbce89..4c23e0e 100644 --- a/protocols/fiat_v2.c +++ b/protocols/fiat_v2.c @@ -347,19 +347,18 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize( } flipper_format_rewind(flipper_format); - flipper_format_insert_or_update_hex( - flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES); - - uint32_t hop = instance->hop; - uint32_t button = instance->button; - if(!flipper_format_write_uint32(flipper_format, FIAT_V2_HOP_FIELD, &hop, 1) || - !flipper_format_write_uint32(flipper_format, FIAT_V2_BTN_FIELD, &button, 1)) { - return SubGhzProtocolStatusErrorParserOthers; - } + flipper_format_delete_key(flipper_format, FIAT_V2_RAW_FIELD); pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial); pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn); pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt); + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V2_HOP_FIELD, instance->hop); + pp_flipper_update_or_insert_u32(flipper_format, FIAT_V2_BTN_FIELD, instance->button); + + if(!flipper_format_insert_or_update_hex( + flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES)) { + return SubGhzProtocolStatusErrorParserOthers; + } return SubGhzProtocolStatusOk; } diff --git a/protocols/kia_v0.c b/protocols/kia_v0.c index 926daae..b00f937 100644 --- a/protocols/kia_v0.c +++ b/protocols/kia_v0.c @@ -16,6 +16,7 @@ static const SubGhzBlockConst kia_protocol_v0_const = { #define KIA_V0_TYPE_KIA 1U #define KIA_V0_TYPE_SUZUKI 2U #define KIA_V0_TYPE_HONDA 3U +#define KIA_V0_TYPE_MITSU 4U #define KIA_V0_BIT_COUNT_KIA 61U #define KIA_V0_BIT_COUNT_SUZUKI 64U @@ -29,17 +30,20 @@ static const SubGhzBlockConst kia_protocol_v0_const = { #define KIA_V0_SUZUKI_GAP_SPAN 500U #define KIA_V0_TYPE1_SYNC 750U -#define KIA_V0_TYPE1_PREAMBLE_PAIRS 0x13FU -#define KIA_V0_TYPE2_PREAMBLE_PAIRS 0x140U -#define KIA_V0_TAIL_PREAMBLE_PAIRS 0x0FU +#define KIA_V0_TYPE1_PREAMBLE_PAIRS 0x13FU +#define KIA_V0_TYPE2_PREAMBLE_PAIRS 0x140U +#define KIA_V0_TAIL_PREAMBLE_PAIRS 0x0FU +#define KIA_V0_MITSU_TAIL_PREAMBLE_PAIRS 0x50U +#define KIA_V0_MITSU_PREAMBLE_MIN 72U +#define KIA_V0_MITSU_PREAMBLE_MAX 88U -#define KIA_V0_UPLOAD_CAPACITY \ - ((KIA_V0_TYPE2_PREAMBLE_PAIRS * 2U) + (KIA_V0_BIT_COUNT_SUZUKI * 2U) + 3U + \ - (KIA_V0_TAIL_PREAMBLE_PAIRS * 2U) + (KIA_V0_BIT_COUNT_SUZUKI * 2U)) +#define KIA_V0_UPLOAD_CAPACITY \ + ((KIA_V0_TYPE2_PREAMBLE_PAIRS * 2U) + 2U + (KIA_V0_BIT_COUNT_SUZUKI * 2U) + 3U + \ + (KIA_V0_MITSU_TAIL_PREAMBLE_PAIRS * 2U) + (KIA_V0_BIT_COUNT_SUZUKI * 2U) + 4U) _Static_assert( KIA_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, "KIA_V0_UPLOAD_CAPACITY exceeds shared upload slab"); -#define KIA_V0_ENCODER_DEFAULT_REPEAT 10U +#define KIA_V0_ENCODER_DEFAULT_REPEAT 3U typedef enum { KiaV0DecoderStepReset = 0, @@ -64,6 +68,8 @@ struct SubGhzProtocolDecoderKIA { uint16_t packet_bit_count; uint16_t preamble_pairs; uint8_t type; + uint64_t last_kia_data; + bool have_last_kia; }; struct SubGhzProtocolEncoderKIA { @@ -328,6 +334,8 @@ static const char* kia_v0_protocol_name(uint8_t type) { return "Suzuki V0"; case KIA_V0_TYPE_HONDA: return "Honda V0"; + case KIA_V0_TYPE_MITSU: + return "Mitsubishi V0"; default: return KIA_PROTOCOL_V0_NAME; } @@ -378,7 +386,7 @@ static void kia_v0_parse_data( kia_v0_parse_family_raw(generic->data, type, fields); generic->data_count_bit = (type == KIA_V0_TYPE_SUZUKI) ? KIA_V0_BIT_COUNT_SUZUKI : KIA_V0_BIT_COUNT_KIA; - if(type == KIA_V0_TYPE_KIA) { + if((type == KIA_V0_TYPE_KIA) || (type == KIA_V0_TYPE_MITSU)) { fields->crc_valid = kia_v0_verify_crc_poly(generic->data); } } @@ -427,7 +435,15 @@ static void kia_v0_decoder_finish_kia_or_honda_at_gap(SubGhzProtocolDecoderKIA* const uint64_t data = instance->decoder.decode_data; if(kia_v0_verify_crc_poly(data)) { - kia_v0_decoder_commit(instance, data, KIA_V0_TYPE_KIA, KIA_V0_BIT_COUNT_KIA); + uint8_t type = KIA_V0_TYPE_KIA; + if(instance->have_last_kia && (instance->last_kia_data == data) && + (instance->preamble_pairs >= KIA_V0_MITSU_PREAMBLE_MIN) && + (instance->preamble_pairs <= KIA_V0_MITSU_PREAMBLE_MAX)) { + type = KIA_V0_TYPE_MITSU; + } + instance->last_kia_data = data; + instance->have_last_kia = true; + kia_v0_decoder_commit(instance, data, type, KIA_V0_BIT_COUNT_KIA); return; } @@ -527,6 +543,26 @@ static void kia_v0_build_kia_upload(SubGhzProtocolEncoderKIA* instance, uint64_t instance->encoder.size_upload = index; } +static void kia_v0_build_mitsu_upload(SubGhzProtocolEncoderKIA* instance, uint64_t raw) { + size_t index = 0; + + instance->encoder.upload[index++] = level_duration_make(true, KIA_V0_TYPE1_SYNC); + instance->encoder.upload[index++] = level_duration_make(false, KIA_V0_TYPE1_SYNC); + index = + kia_v0_append_short_pairs(instance->encoder.upload, index, KIA_V0_TYPE1_PREAMBLE_PAIRS); + index = kia_v0_append_data_pairs(instance->encoder.upload, index, raw, KIA_V0_BIT_COUNT_KIA); + instance->encoder.upload[index++] = level_duration_make(true, 1500); + instance->encoder.upload[index++] = level_duration_make(false, 1500); + index = kia_v0_append_short_pairs( + instance->encoder.upload, index, KIA_V0_MITSU_TAIL_PREAMBLE_PAIRS); + index = kia_v0_append_data_pairs(instance->encoder.upload, index, raw, KIA_V0_BIT_COUNT_KIA); + instance->encoder.upload[index++] = level_duration_make(true, 1500); + instance->encoder.upload[index++] = level_duration_make(false, 1500); + + instance->encoder.front = 0; + instance->encoder.size_upload = index; +} + #endif #if PROTOPIRATE_WITH_ENCODER @@ -557,6 +593,23 @@ static uint8_t kia_v0_infer_type_from_bits(uint32_t bits) { if(bits == KIA_V0_BIT_COUNT_HONDA) return KIA_V0_TYPE_HONDA; return KIA_V0_TYPE_KIA; } + +static uint8_t kia_v0_type_from_protocol_name(const char* name) { + if(!name) { + return 0; + } + if((strcmp(name, "Mitsu v0") == 0) || (strcmp(name, "Mitsu V0") == 0) || + (strcmp(name, "Mitsubishi v0") == 0) || (strcmp(name, "Mitsubishi V0") == 0)) { + return KIA_V0_TYPE_MITSU; + } + if((strcmp(name, "Suzuki") == 0) || (strcmp(name, "Suzuki V0") == 0)) { + return KIA_V0_TYPE_SUZUKI; + } + if((strcmp(name, "Honda v0") == 0) || (strcmp(name, "Honda V0") == 0)) { + return KIA_V0_TYPE_HONDA; + } + return 0; +} #if PROTOPIRATE_WITH_ENCODER static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) { @@ -605,7 +658,11 @@ static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) { instance->fields.crc); instance->generic.data_count_bit = KIA_V0_BIT_COUNT_KIA; kia_v0_parse_data(&instance->generic, instance->type, &instance->fields, NULL); - kia_v0_build_kia_upload(instance, instance->generic.data); + if(instance->type == KIA_V0_TYPE_MITSU) { + kia_v0_build_mitsu_upload(instance, instance->generic.data); + } else { + kia_v0_build_kia_upload(instance, instance->generic.data); + } } } } @@ -699,7 +756,16 @@ SubGhzProtocolStatus if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) != SubGhzProtocolStatusOk) { - return SubGhzProtocolStatusErrorParserProtocolName; + FuriString* proto = furi_string_alloc(); + bool named_ok = false; + flipper_format_rewind(flipper_format); + if(flipper_format_read_string(flipper_format, FF_PROTOCOL, proto)) { + named_ok = kia_v0_type_from_protocol_name(furi_string_get_cstr(proto)) != 0; + } + furi_string_free(proto); + if(!named_ok) { + return SubGhzProtocolStatusErrorParserProtocolName; + } } static const uint16_t allowed_bits[] = { @@ -709,8 +775,20 @@ SubGhzProtocolStatus if(bit_st != SubGhzProtocolStatusOk) return bit_st; uint32_t type_u32 = kia_v0_infer_type_from_bits(bits); - pp_encoder_read_fields(flipper_format, NULL, NULL, NULL, &type_u32); - if(type_u32 < KIA_V0_TYPE_KIA || type_u32 > KIA_V0_TYPE_HONDA) { + uint32_t type_from_file = 0; + pp_encoder_read_fields(flipper_format, NULL, NULL, NULL, &type_from_file); + if(type_from_file >= KIA_V0_TYPE_KIA && type_from_file <= KIA_V0_TYPE_MITSU) { + type_u32 = type_from_file; + } else { + FuriString* proto = furi_string_alloc(); + flipper_format_rewind(flipper_format); + if(flipper_format_read_string(flipper_format, FF_PROTOCOL, proto)) { + const uint8_t named = kia_v0_type_from_protocol_name(furi_string_get_cstr(proto)); + if(named) type_u32 = named; + } + furi_string_free(proto); + } + if(type_u32 < KIA_V0_TYPE_KIA || type_u32 > KIA_V0_TYPE_MITSU) { return SubGhzProtocolStatusErrorValueBitCount; } instance->type = (uint8_t)type_u32; @@ -834,6 +912,8 @@ void subghz_protocol_decoder_kia_reset(void* context) { SubGhzProtocolDecoderKIA* instance = context; kia_v0_decoder_state_clear(instance); instance->type = 0; + instance->have_last_kia = false; + instance->last_kia_data = 0; } void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration) { @@ -1030,10 +1110,18 @@ SubGhzProtocolStatus uint32_t type_u32 = kia_v0_infer_type_from_bits(bits); flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32(flipper_format, FF_TYPE, &type_u32, 1)) { + if(flipper_format_read_uint32(flipper_format, FF_TYPE, &type_u32, 1) && + (type_u32 >= KIA_V0_TYPE_KIA) && (type_u32 <= KIA_V0_TYPE_MITSU)) { instance->type = (uint8_t)type_u32; } else { - instance->type = (uint8_t)kia_v0_infer_type_from_bits(bits); + FuriString* proto = furi_string_alloc(); + uint8_t named = 0; + flipper_format_rewind(flipper_format); + if(flipper_format_read_string(flipper_format, FF_PROTOCOL, proto)) { + named = kia_v0_type_from_protocol_name(furi_string_get_cstr(proto)); + } + furi_string_free(proto); + instance->type = named ? named : (uint8_t)kia_v0_infer_type_from_bits(bits); } KiaV0Fields scratch; diff --git a/protocols/kia_v7.c b/protocols/kia_v7.c index 42e1a16..d6c6b6e 100644 --- a/protocols/kia_v7.c +++ b/protocols/kia_v7.c @@ -2,14 +2,16 @@ #include "protocols_common.h" #include +#define KIA_V7_PREAMBLE_PAIRS 0x13FU +#define KIA_V7_TAIL_PREAMBLE_PAIRS 0x0FU +#define KIA_V7_PREAMBLE_MIN_PAIRS 16 +#define KIA_V7_HEADER 0x4C +#define KIA_V7_TAIL_GAP_US 0x7D0 +#define KIA_V7_KEY_BITS 64U +#define KIA_V7_DEFAULT_TX_REPEAT 3U +#define KIA_V7_FRAME_SLOTS(pairs) (((pairs) * 2U) + 1U + (KIA_V7_KEY_BITS * 2U) + 2U) #define KIA_V7_UPLOAD_CAPACITY \ - (1U + (KIA_V7_PREAMBLE_PAIRS * 2U) + 1U + (KIA_V7_KEY_BITS * 2U) + 2U) -#define KIA_V7_PREAMBLE_PAIRS 0x13F -#define KIA_V7_PREAMBLE_MIN_PAIRS 16 -#define KIA_V7_HEADER 0x4C -#define KIA_V7_TAIL_GAP_US 0x7D0 -#define KIA_V7_KEY_BITS 64U -#define KIA_V7_DEFAULT_TX_REPEAT 10U + (KIA_V7_FRAME_SLOTS(KIA_V7_PREAMBLE_PAIRS) + KIA_V7_FRAME_SLOTS(KIA_V7_TAIL_PREAMBLE_PAIRS)) _Static_assert( KIA_V7_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, "KIA_V7_UPLOAD_CAPACITY exceeds shared upload slab"); @@ -166,58 +168,76 @@ static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) { &instance->crc_valid); } -static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) { - furi_check(instance); - +static bool kia_v7_encoder_append_frame( + LevelDuration* upload, + size_t* index, + size_t max_size, + uint64_t data, + uint8_t bit_count, + size_t preamble_pairs) { const LevelDuration high_short = level_duration_make(true, kia_protocol_v7_const.te_short); const LevelDuration low_short = level_duration_make(false, kia_protocol_v7_const.te_short); const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US); - const size_t max_size = KIA_V7_UPLOAD_CAPACITY; + + for(size_t i = 0; i < preamble_pairs; i++) { + if((*index + 2U) > max_size) { + return false; + } + upload[(*index)++] = high_short; + upload[(*index)++] = low_short; + } + + if((*index + 1U) > max_size) { + return false; + } + upload[(*index)++] = high_short; + + for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) { + if((*index + 2U) > max_size) { + return false; + } + const bool value = ((data >> bit) & 1ULL) != 0ULL; + upload[(*index)++] = value ? high_short : low_short; + upload[(*index)++] = value ? low_short : high_short; + } + + if((*index + 2U) > max_size) { + return false; + } + upload[(*index)++] = high_short; + upload[(*index)++] = low_tail; + return true; +} + +static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) { + furi_check(instance); const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ? instance->tx_bit_count : 64U; + size_t index = 0; - size_t final_size = 0; - - for(uint8_t pass = 0; pass < 2; pass++) { - size_t index = pass; - - for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) { - if((index + 2U) > max_size) { - return false; - } - - instance->encoder.upload[index++] = high_short; - instance->encoder.upload[index++] = low_short; - } - - if((index + 1U) > max_size) { - return false; - } - instance->encoder.upload[index++] = high_short; - - for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) { - if((index + 2U) > max_size) { - return false; - } - - const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL; - instance->encoder.upload[index++] = value ? high_short : low_short; - instance->encoder.upload[index++] = value ? low_short : high_short; - } - - if((index + 2U) > max_size) { - return false; - } - instance->encoder.upload[index++] = high_short; - instance->encoder.upload[index++] = low_tail; - - final_size = index; + if(!kia_v7_encoder_append_frame( + instance->encoder.upload, + &index, + KIA_V7_UPLOAD_CAPACITY, + instance->generic.data, + bit_count, + KIA_V7_PREAMBLE_PAIRS)) { + return false; + } + if(!kia_v7_encoder_append_frame( + instance->encoder.upload, + &index, + KIA_V7_UPLOAD_CAPACITY, + instance->generic.data, + bit_count, + KIA_V7_TAIL_PREAMBLE_PAIRS)) { + return false; } instance->encoder.front = 0; - instance->encoder.size_upload = final_size; + instance->encoder.size_upload = index; return true; } #endif diff --git a/protocols/mitsubishi_v0.c b/protocols/mitsubishi_v0.c deleted file mode 100644 index b8c138c..0000000 --- a/protocols/mitsubishi_v0.c +++ /dev/null @@ -1,261 +0,0 @@ -#include "mitsubishi_v0.h" -#include "protocols_common.h" -#include - -// Original implementation by @lupettohf - -#define MITSUBISHI_BIT_COUNT 96 -#define MITSUBISHI_DATA_BYTES 12 - -static const SubGhzBlockConst subghz_protocol_mitsubishi_const = { - .te_short = 250, - .te_long = 500, - .te_delta = 100, - .min_count_bit_for_found = 80, -}; - -typedef enum { - MitsubishiDecoderStepReset = 0, - MitsubishiDecoderStepDataSave, - MitsubishiDecoderStepDataCheck, -} MitsubishiDecoderStep; - -struct SubGhzProtocolDecoderMitsubishi { - SubGhzProtocolDecoderBase base; - SubGhzBlockDecoder decoder; - SubGhzBlockGeneric generic; - - uint8_t decoder_state; - uint16_t bit_count; - uint8_t decode_data[MITSUBISHI_DATA_BYTES]; -}; - -static void mitsubishi_unscramble_payload(uint8_t* payload) { - for(uint8_t i = 0; i < 8; i++) { - payload[i] = (uint8_t)~payload[i]; - } - - uint16_t counter = ((uint16_t)payload[4] << 8) | payload[5]; - uint8_t hi = (counter >> 8) & 0xFF; - uint8_t lo = counter & 0xFF; - uint8_t mask1 = (hi & 0xAAU) | (lo & 0x55U); - uint8_t mask2 = (lo & 0xAAU) | (hi & 0x55U); - uint8_t mask3 = mask1 ^ mask2; - - for(uint8_t i = 0; i < 5; i++) { - payload[i] ^= mask3; - } -} - -static void mitsubishi_reset_payload(SubGhzProtocolDecoderMitsubishi* instance) { - instance->bit_count = 0; - memset(instance->decode_data, 0, sizeof(instance->decode_data)); -} - -static bool mitsubishi_collect_pair( - SubGhzProtocolDecoderMitsubishi* instance, - uint32_t high, - uint32_t low) { - bool bit_value; - - if(pp_is_short(high, &subghz_protocol_mitsubishi_const) && - pp_is_long(low, &subghz_protocol_mitsubishi_const)) { - bit_value = true; - } else if( - pp_is_long(high, &subghz_protocol_mitsubishi_const) && - pp_is_short(low, &subghz_protocol_mitsubishi_const)) { - bit_value = false; - } else { - return false; - } - - uint16_t bit_index = instance->bit_count; - if(bit_index < MITSUBISHI_BIT_COUNT) { - if(bit_value) { - uint8_t byte_index = bit_index >> 3; - uint8_t bit_position = 7 - (bit_index & 0x07); - instance->decode_data[byte_index] |= (1U << bit_position); - } - instance->bit_count++; - } - - return true; -} - -static void mitsubishi_publish_frame(SubGhzProtocolDecoderMitsubishi* instance) { - uint8_t payload[MITSUBISHI_DATA_BYTES]; - memcpy(payload, instance->decode_data, sizeof(payload)); - mitsubishi_unscramble_payload(payload); - - instance->generic.data_count_bit = instance->bit_count; - instance->generic.serial = ((uint32_t)payload[0] << 24) | ((uint32_t)payload[1] << 16) | - ((uint32_t)payload[2] << 8) | payload[3]; - instance->generic.cnt = ((uint16_t)payload[4] << 8) | payload[5]; - instance->generic.btn = payload[6]; - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } -} - -const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder = { - .alloc = subghz_protocol_decoder_mitsubishi_alloc, - .free = pp_decoder_free_default, - .feed = subghz_protocol_decoder_mitsubishi_feed, - .reset = subghz_protocol_decoder_mitsubishi_reset, - .get_hash_data = subghz_protocol_decoder_mitsubishi_get_hash_data, - .serialize = subghz_protocol_decoder_mitsubishi_serialize, - .deserialize = subghz_protocol_decoder_mitsubishi_deserialize, - .get_string = subghz_protocol_decoder_mitsubishi_get_string, -}; - -const SubGhzProtocolEncoder subghz_protocol_mitsubishi_encoder = { - .alloc = NULL, - .free = NULL, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol mitsubishi_v0_protocol = { - .name = MITSUBISHI_PROTOCOL_NAME, - .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | - SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, -#if PROTOPIRATE_WITH_DECODER - .decoder = &subghz_protocol_mitsubishi_decoder, -#else - .decoder = NULL, -#endif -#if PROTOPIRATE_WITH_ENCODER - .encoder = &subghz_protocol_mitsubishi_encoder, -#else - .encoder = NULL, -#endif -}; - -void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi)); - furi_check(instance); - instance->base.protocol = &mitsubishi_v0_protocol; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void subghz_protocol_decoder_mitsubishi_reset(void* context) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - instance->decoder_state = MitsubishiDecoderStepReset; - instance->decoder.te_last = 0; - instance->generic.data_count_bit = 0; - mitsubishi_reset_payload(instance); -} - -void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - - switch(instance->decoder_state) { - case MitsubishiDecoderStepReset: - if(level) { - instance->decoder.te_last = duration; - instance->decoder_state = MitsubishiDecoderStepDataCheck; - } - break; - - case MitsubishiDecoderStepDataSave: - if(level) { - instance->decoder.te_last = duration; - instance->decoder_state = MitsubishiDecoderStepDataCheck; - } else { - instance->decoder_state = MitsubishiDecoderStepReset; - mitsubishi_reset_payload(instance); - } - break; - - case MitsubishiDecoderStepDataCheck: - if(!level) { - if(mitsubishi_collect_pair(instance, instance->decoder.te_last, duration)) { - if(instance->bit_count >= MITSUBISHI_BIT_COUNT) { - mitsubishi_publish_frame(instance); - mitsubishi_reset_payload(instance); - instance->decoder_state = MitsubishiDecoderStepReset; - } else { - instance->decoder_state = MitsubishiDecoderStepDataSave; - } - } else { - mitsubishi_reset_payload(instance); - instance->decoder_state = MitsubishiDecoderStepReset; - } - } else { - instance->decoder.te_last = duration; - } - break; - } -} - -uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - uint8_t hash = 0; - for(size_t i = 0; i < sizeof(instance->decode_data); i++) { - hash ^= instance->decode_data[i]; - } - return hash; -} - -SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - SubGhzProtocolStatus ret = - subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(ret == SubGhzProtocolStatusOk) { - pp_serialize_fields( - flipper_format, - PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT, - instance->generic.serial, - instance->generic.btn, - instance->generic.cnt, - 0); - } - return ret; -} - -SubGhzProtocolStatus - subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_mitsubishi_const.min_count_bit_for_found); - - if(ret == SubGhzProtocolStatusOk) { - uint32_t btn = instance->generic.btn; - pp_encoder_read_fields( - flipper_format, &instance->generic.serial, &btn, &instance->generic.cnt, NULL); - instance->generic.btn = (uint8_t)btn; - } - - return ret; -} - -void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output) { - furi_check(context); - SubGhzProtocolDecoderMitsubishi* instance = context; - - furi_string_cat_printf( - output, - "%s %dbit\r\n" - "Sn:%08lX Cnt:%04lX\r\n" - "Btn:%02X\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - instance->generic.serial, - instance->generic.cnt, - instance->generic.btn); -} diff --git a/protocols/mitsubishi_v0.h b/protocols/mitsubishi_v0.h deleted file mode 100644 index b07b0d1..0000000 --- a/protocols/mitsubishi_v0.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../defines.h" - -#define MITSUBISHI_PROTOCOL_NAME "Mitsubishi V0" - -typedef struct SubGhzProtocolDecoderMitsubishi SubGhzProtocolDecoderMitsubishi; - -extern const SubGhzProtocol mitsubishi_v0_protocol; - -void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment); -void subghz_protocol_decoder_mitsubishi_free(void* context); -void subghz_protocol_decoder_mitsubishi_reset(void* context); -void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration); -uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context); -SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); -SubGhzProtocolStatus - subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format); -void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output); diff --git a/protocols/plugins/protopirate_am_plugin.c b/protocols/plugins/protopirate_am_plugin.c index 97147c5..4404850 100644 --- a/protocols/plugins/protopirate_am_plugin.c +++ b/protocols/plugins/protopirate_am_plugin.c @@ -9,9 +9,9 @@ #include "../kia_v1.h" #include "../kia_v2.h" #include "../mazda_v0.h" -#include "../porsche_touareg.h" #include "../psa.h" #include "../renault_v0.h" +#include "../renault_v1.h" #include "../subaru.h" #include "../star_line.h" #include "../honda_v1.h" @@ -27,9 +27,9 @@ static const SubGhzProtocol* const protopirate_protocol_registry_am_items[] = { &kia_protocol_v1, &kia_protocol_v2, &mazda_v0_protocol, - &porsche_touareg_protocol, &psa_protocol, &renault_v0_protocol, + &renault_v1_protocol, &subaru_protocol, &subghz_protocol_star_line, }; diff --git a/protocols/plugins/protopirate_fm_plugin.c b/protocols/plugins/protopirate_fm_plugin.c index 72f23da..5f31bab 100644 --- a/protocols/plugins/protopirate_fm_plugin.c +++ b/protocols/plugins/protopirate_fm_plugin.c @@ -7,8 +7,8 @@ #include "../kia_v6.h" #include "../kia_v7.h" #include "../mazda_v0.h" -#include "../mitsubishi_v0.h" #include "../psa.h" +#include "../renault_v1.h" static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = { &subghz_protocol_scher_khan, @@ -17,9 +17,9 @@ static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = { &kia_protocol_v5, &kia_protocol_v6, &mazda_v0_protocol, - &mitsubishi_v0_protocol, &kia_protocol_v7, &psa_protocol, + &renault_v1_protocol, }; static const SubGhzProtocolRegistry protopirate_protocol_registry_fm = { diff --git a/protocols/porsche_touareg.c b/protocols/porsche_touareg.c deleted file mode 100644 index 679c9d3..0000000 --- a/protocols/porsche_touareg.c +++ /dev/null @@ -1,378 +0,0 @@ -#include "porsche_touareg.h" -#include "protocols_common.h" -#include - -// Original implementation by @lupettohf - -#define PORSCHE_CAYENNE_BIT_COUNT 64 -#define PC_TE_SYNC 3370U -#define PC_TE_GAP 5930U -#define PC_SYNC_MIN 15 - -static const SubGhzBlockConst subghz_protocol_porsche_cayenne_const = { - .te_short = 1680, - .te_long = 3370, - .te_delta = 500, - .min_count_bit_for_found = PORSCHE_CAYENNE_BIT_COUNT, -}; - -typedef enum { - PorscheCayenneDecoderStepReset = 0, - PorscheCayenneDecoderStepSync, - PorscheCayenneDecoderStepGapHigh, - PorscheCayenneDecoderStepGapLow, - PorscheCayenneDecoderStepData, -} PorscheCayenneDecoderStep; - -struct SubGhzProtocolDecoderPorscheCayenne { - SubGhzProtocolDecoderBase base; - SubGhzBlockDecoder decoder; - SubGhzBlockGeneric generic; - - uint16_t sync_count; - uint64_t raw_data; - uint8_t bit_count; -}; - -static void porsche_cayenne_compute_frame( - uint32_t serial24, - uint8_t btn, - uint16_t counter, - uint8_t frame_type, - uint8_t* pkt) { - uint8_t b0 = (uint8_t)((btn << 4) | (frame_type & 0x07)); - uint8_t b1 = (serial24 >> 16) & 0xFF; - uint8_t b2 = (serial24 >> 8) & 0xFF; - uint8_t b3 = serial24 & 0xFF; - - uint16_t cnt = counter + 1; - uint8_t cnt_lo = cnt & 0xFF; - uint8_t cnt_hi = (cnt >> 8) & 0xFF; - - uint8_t r_h = b3; - uint8_t r_m = b1; - uint8_t r_l = b2; - -#define ROTATE24(rh, rm, rl) \ - do { \ - uint8_t _ch = ((rh) >> 7) & 1U; \ - uint8_t _cm = ((rm) >> 7) & 1U; \ - uint8_t _cl = ((rl) >> 7) & 1U; \ - (rh) = (uint8_t)(((rh) << 1) | _cm); \ - (rm) = (uint8_t)(((rm) << 1) | _cl); \ - (rl) = (uint8_t)(((rl) << 1) | _ch); \ - } while(0) - - for(uint8_t i = 0; i < 4; i++) { - ROTATE24(r_h, r_m, r_l); - } - for(uint16_t i = 0; i < cnt_lo; i++) { - ROTATE24(r_h, r_m, r_l); - } - -#undef ROTATE24 - - uint8_t a9a = r_h ^ b0; - - uint8_t nb9b_p1 = (uint8_t)((~cnt_lo << 2) & 0xFC) ^ r_m; - uint8_t nb9b_p2 = (uint8_t)((~cnt_hi << 2) & 0xFC) ^ r_m; - uint8_t nb9b_p3 = (uint8_t)((~cnt_hi >> 6) & 0x03) ^ r_m; - uint8_t a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03); - - uint8_t nb9c_p1 = (uint8_t)((~cnt_lo >> 2) & 0x3F) ^ r_l; - uint8_t nb9c_p2 = (uint8_t)((~cnt_hi & 0x03) << 6) ^ r_l; - uint8_t nb9c_p3 = (uint8_t)((~cnt_hi >> 2) & 0x3F) ^ r_l; - uint8_t a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C); - - pkt[0] = b0; - pkt[1] = b1; - pkt[2] = b2; - pkt[3] = b3; - pkt[4] = (uint8_t)(((a9a >> 2) & 0x3F) | ((~cnt_lo & 0x03U) << 6)); - pkt[5] = (uint8_t)((~cnt_lo & 0xC0U) | ((a9a & 0x03U) << 4) | (a9b & 0x0CU) | - ((~cnt_lo >> 2) & 0x03U)); - pkt[6] = (uint8_t)(((a9b & 0x03U) << 6) | ((a9c >> 2) & 0x3CU) | ((~cnt_lo >> 4) & 0x03U)); - pkt[7] = (uint8_t)(((a9b >> 4) & 0x0FU) | ((a9c & 0x0FU) << 4)); -} - -static void porsche_cayenne_parse_data(SubGhzProtocolDecoderPorscheCayenne* instance) { - uint8_t pkt[8]; - uint64_t raw = instance->generic.data; - - for(int8_t i = 7; i >= 0; i--) { - pkt[i] = (uint8_t)(raw & 0xFF); - raw >>= 8; - } - - instance->generic.serial = ((uint32_t)pkt[1] << 16) | ((uint32_t)pkt[2] << 8) | pkt[3]; - instance->generic.btn = (uint8_t)(pkt[0] >> 4); - instance->generic.cnt = 0; - - uint8_t frame_type = pkt[0] & 0x07; - uint8_t try_pkt[8]; - for(uint16_t try_cnt = 1; try_cnt <= 256; try_cnt++) { - porsche_cayenne_compute_frame( - instance->generic.serial, - instance->generic.btn, - (uint16_t)(try_cnt - 1), - frame_type, - try_pkt); - if(try_pkt[4] == pkt[4] && try_pkt[5] == pkt[5] && try_pkt[6] == pkt[6] && - try_pkt[7] == pkt[7]) { - instance->generic.cnt = try_cnt; - break; - } - } -} - -static void porsche_cayenne_publish_frame(SubGhzProtocolDecoderPorscheCayenne* instance) { - instance->generic.data = instance->raw_data; - instance->generic.data_count_bit = PORSCHE_CAYENNE_BIT_COUNT; - porsche_cayenne_parse_data(instance); - - if(instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } -} - -const SubGhzProtocolDecoder subghz_protocol_porsche_cayenne_decoder = { - .alloc = subghz_protocol_decoder_porsche_cayenne_alloc, - .free = pp_decoder_free_default, - .feed = subghz_protocol_decoder_porsche_cayenne_feed, - .reset = subghz_protocol_decoder_porsche_cayenne_reset, - .get_hash_data = subghz_protocol_decoder_porsche_cayenne_get_hash_data, - .serialize = subghz_protocol_decoder_porsche_cayenne_serialize, - .deserialize = subghz_protocol_decoder_porsche_cayenne_deserialize, - .get_string = subghz_protocol_decoder_porsche_cayenne_get_string, -}; - -const SubGhzProtocolEncoder subghz_protocol_porsche_cayenne_encoder = { - .alloc = NULL, - .free = NULL, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; - -const SubGhzProtocol porsche_touareg_protocol = { - .name = PORSCHE_CAYENNE_PROTOCOL_NAME, - .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | - SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, -#if PROTOPIRATE_WITH_DECODER - .decoder = &subghz_protocol_porsche_cayenne_decoder, -#else - .decoder = NULL, -#endif -#if PROTOPIRATE_WITH_ENCODER - .encoder = &subghz_protocol_porsche_cayenne_encoder, -#else - .encoder = NULL, -#endif -}; - -void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolDecoderPorscheCayenne* instance = - calloc(1, sizeof(SubGhzProtocolDecoderPorscheCayenne)); - furi_check(instance); - instance->base.protocol = &porsche_touareg_protocol; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void subghz_protocol_decoder_porsche_cayenne_reset(void* context) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - instance->decoder.te_last = 0; - instance->sync_count = 0; - instance->raw_data = 0; - instance->bit_count = 0; - instance->generic.data = 0; - instance->generic.data_count_bit = 0; -} - -void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - - const uint32_t te_short = subghz_protocol_porsche_cayenne_const.te_short; - const uint32_t te_long = subghz_protocol_porsche_cayenne_const.te_long; - const uint32_t te_delta = subghz_protocol_porsche_cayenne_const.te_delta; - - switch(instance->decoder.parser_step) { - case PorscheCayenneDecoderStepReset: - if(!level && DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { - instance->sync_count = 1; - instance->decoder.parser_step = PorscheCayenneDecoderStepSync; - } - break; - - case PorscheCayenneDecoderStepSync: - if(level) { - if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { - // keep collecting sync pairs - } else if( - instance->sync_count >= PC_SYNC_MIN && - DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { - instance->decoder.parser_step = PorscheCayenneDecoderStepGapLow; - } else { - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - } - } else { - if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { - instance->sync_count++; - } else if( - instance->sync_count >= PC_SYNC_MIN && - DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { - instance->decoder.parser_step = PorscheCayenneDecoderStepGapHigh; - } else { - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - } - } - break; - - case PorscheCayenneDecoderStepGapHigh: - if(level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { - instance->raw_data = 0; - instance->bit_count = 0; - instance->decoder.parser_step = PorscheCayenneDecoderStepData; - } else { - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - } - break; - - case PorscheCayenneDecoderStepGapLow: - if(!level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { - instance->raw_data = 0; - instance->bit_count = 0; - instance->decoder.parser_step = PorscheCayenneDecoderStepData; - } else { - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - } - break; - - case PorscheCayenneDecoderStepData: - if(level) { - bool bit_value = false; - if(DURATION_DIFF(instance->decoder.te_last, te_short) < te_delta && - DURATION_DIFF(duration, te_long) < te_delta) { - bit_value = false; - } else if( - DURATION_DIFF(instance->decoder.te_last, te_long) < te_delta && - DURATION_DIFF(duration, te_short) < te_delta) { - bit_value = true; - } else { - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - break; - } - - instance->raw_data = (instance->raw_data << 1) | (bit_value ? 1U : 0U); - instance->bit_count++; - - if(instance->bit_count >= PORSCHE_CAYENNE_BIT_COUNT) { - porsche_cayenne_publish_frame(instance); - instance->decoder.parser_step = PorscheCayenneDecoderStepReset; - } - } else { - instance->decoder.te_last = duration; - } - break; - } -} - -uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - SubGhzBlockDecoder decoder = { - .decode_data = instance->generic.data, - .decode_count_bit = instance->generic.data_count_bit, - }; - return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - - SubGhzProtocolStatus ret = - subghz_block_generic_serialize(&instance->generic, flipper_format, preset); - if(ret == SubGhzProtocolStatusOk) { - pp_serialize_fields( - flipper_format, - PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT, - instance->generic.serial & 0xFFFFFF, - instance->generic.btn, - instance->generic.cnt, - 0); - } - - return ret; -} - -SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize( - void* context, - FlipperFormat* flipper_format) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - - SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_porsche_cayenne_const.min_count_bit_for_found); - - if(ret == SubGhzProtocolStatusOk) { - porsche_cayenne_parse_data(instance); - - uint32_t serial = 0; - if(flipper_format_read_uint32(flipper_format, FF_SERIAL, &serial, 1)) { - instance->generic.serial = serial & 0xFFFFFF; - } - - uint32_t cnt = 0; - if(flipper_format_read_uint32(flipper_format, FF_CNT, &cnt, 1)) { - instance->generic.cnt = cnt; - } - - uint32_t btn = 0; - if(flipper_format_read_uint32(flipper_format, FF_BTN, &btn, 1)) { - instance->generic.btn = (uint8_t)btn; - } - } - - return ret; -} - -void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output) { - furi_check(context); - SubGhzProtocolDecoderPorscheCayenne* instance = context; - - uint8_t frame_type = (uint8_t)((instance->generic.data >> 56) & 0x07); - const char* frame_type_name = "??"; - if(frame_type == 0x02) { - frame_type_name = "First"; - } else if(frame_type == 0x01) { - frame_type_name = "Cont"; - } else if(frame_type == 0x04) { - frame_type_name = "Final"; - } - - furi_string_cat_printf( - output, - "%s %dbit\r\n" - "Sn:%06lX Btn:%X\r\n" - "Cnt:%04lX FT:%s\r\n" - "Raw:%08lX%08lX\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - (unsigned long)(instance->generic.serial & 0xFFFFFF), - (unsigned int)instance->generic.btn, - (unsigned long)instance->generic.cnt, - frame_type_name, - (unsigned long)(instance->generic.data >> 32), - (unsigned long)(instance->generic.data & 0xFFFFFFFF)); -} diff --git a/protocols/porsche_touareg.h b/protocols/porsche_touareg.h deleted file mode 100644 index 156d677..0000000 --- a/protocols/porsche_touareg.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../defines.h" - -#define PORSCHE_CAYENNE_PROTOCOL_NAME "Porsche Touareg" - -typedef struct SubGhzProtocolDecoderPorscheCayenne SubGhzProtocolDecoderPorscheCayenne; - -extern const SubGhzProtocol porsche_touareg_protocol; - -void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment); -void subghz_protocol_decoder_porsche_cayenne_free(void* context); -void subghz_protocol_decoder_porsche_cayenne_reset(void* context); -void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration); -uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context); -SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); -SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize( - void* context, - FlipperFormat* flipper_format); -void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output); diff --git a/protocols/protocol_items.c b/protocols/protocol_items.c index e0e4edb..d72ae35 100644 --- a/protocols/protocol_items.c +++ b/protocols/protocol_items.c @@ -55,10 +55,9 @@ static const ProtoPirateProtocolCatalogEntry protopirate_protocol_catalog[] = { {"Kia V7", ProtoPirateProtocolCatalogRouteFMDefault, PROTOPIRATE_TX_KEY("kia_v7")}, {"Honda V2", ProtoPirateProtocolCatalogRouteFMF4, PROTOPIRATE_TX_KEY("honda_v2")}, {"Mazda V0", ProtoPirateProtocolCatalogRouteByModulation, PROTOPIRATE_TX_KEY("mazda_v0")}, - {"Mitsubishi V0", ProtoPirateProtocolCatalogRouteFMDefault, NULL}, - {"Porsche Touareg", ProtoPirateProtocolCatalogRouteAMDefault, NULL}, {"PSA", ProtoPirateProtocolCatalogRouteByModulation, PROTOPIRATE_TX_KEY("psa")}, {"Renault V0", ProtoPirateProtocolCatalogRouteAMDefault, PROTOPIRATE_TX_KEY("renault_v0")}, + {"Renault V1", ProtoPirateProtocolCatalogRouteByModulation, PROTOPIRATE_TX_KEY("renault_v1")}, {"Scher-Khan", ProtoPirateProtocolCatalogRouteFMDefault, NULL}, {"Star Line", ProtoPirateProtocolCatalogRouteAMDefault, PROTOPIRATE_TX_KEY("star_line")}, {"Subaru", ProtoPirateProtocolCatalogRouteAMDefault, PROTOPIRATE_TX_KEY("subaru")}, @@ -74,6 +73,10 @@ static const ProtoPirateProtocolCatalogAlias protopirate_protocol_catalog_aliase {"Suzuki", "Kia V0"}, {"Suzuki V0", "Kia V0"}, {"Honda V0", "Kia V0"}, + {"Mitsu V0", "Kia V0"}, + {"Mitsu v0", "Kia V0"}, + {"Mitsubishi V0", "Kia V0"}, + {"Mitsubishi v0", "Kia V0"}, {"Land Rover V0", "Honda V2"}, {"VW", "VAG"}, }; @@ -162,6 +165,12 @@ bool protopirate_protocol_catalog_can_tx(const char* protocol_name) { return protopirate_protocol_catalog_tx_key(protocol_name) != NULL; } +bool protopirate_protocol_catalog_offers_bruteforce(const char* protocol_name) { + const char* canonical = protopirate_protocol_catalog_canonical_name(protocol_name); + return protopirate_catalog_string_equal(canonical, "PSA") || + protopirate_catalog_string_equal(canonical, "Renault V1"); +} + const char* protopirate_protocol_catalog_tx_key(const char* protocol_name) { const ProtoPirateProtocolCatalogEntry* entry = protopirate_protocol_catalog_find(protocol_name); @@ -181,6 +190,12 @@ const char* if(protopirate_catalog_string_equal(protocol_name, "Honda V0")) { return "Honda V0"; } + if(protopirate_catalog_string_equal(protocol_name, "Mitsu V0") || + protopirate_catalog_string_equal(protocol_name, "Mitsu v0") || + protopirate_catalog_string_equal(protocol_name, "Mitsubishi V0") || + protopirate_catalog_string_equal(protocol_name, "Mitsubishi v0")) { + return "Mitsubishi V0"; + } const char* canonical_name = protopirate_protocol_catalog_canonical_name(protocol_name); if(protopirate_catalog_string_equal(canonical_name, "Kia V0")) { @@ -190,6 +205,9 @@ const char* if(protocol_type == 3U) { return "Honda V0"; } + if(protocol_type == 4U) { + return "Mitsubishi V0"; + } } return canonical_name; diff --git a/protocols/protocol_items.h b/protocols/protocol_items.h index 6222c68..ecfb6d8 100644 --- a/protocols/protocol_items.h +++ b/protocols/protocol_items.h @@ -34,6 +34,8 @@ const char* protopirate_protocol_catalog_canonical_name(const char* protocol_nam bool protopirate_protocol_catalog_can_tx(const char* protocol_name); +bool protopirate_protocol_catalog_offers_bruteforce(const char* protocol_name); + const char* protopirate_protocol_catalog_tx_key(const char* protocol_name); const char* diff --git a/protocols/protocol_timings.c b/protocols/protocol_timings.c index e99dbbb..c095ddf 100644 --- a/protocols/protocol_timings.c +++ b/protocols/protocol_timings.c @@ -130,6 +130,13 @@ static const ProtoPirateProtocolTiming protocol_timings[] = { .te_delta = 60, .min_count_bit = 82, }, + { + .name = "Renault V1", + .te_short = 125, + .te_long = 250, + .te_delta = 50, + .min_count_bit = 88, + }, { .name = "Mazda V0", .te_short = 250, @@ -144,13 +151,6 @@ static const ProtoPirateProtocolTiming protocol_timings[] = { .te_delta = 100, .min_count_bit = 81, }, - { - .name = "Porsche Touareg", - .te_short = 1680, - .te_long = 3370, - .te_delta = 500, - .min_count_bit = 64, - }, { .name = "Subaru", .te_short = 800, @@ -206,6 +206,8 @@ const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* pro } aliases[] = { {"Honda V0", "Kia V0"}, {"Land Rover V0", "Honda V2"}, + {"Mitsu", "Kia V0"}, + {"Mitsubishi V0", "Kia V0"}, {"Suzuki", "Kia V0"}, {"V3", "Kia V3/V4"}, {"V4", "Kia V3/V4"}, diff --git a/protocols/protocols_common.c b/protocols/protocols_common.c index 8231d77..f45c1e6 100644 --- a/protocols/protocols_common.c +++ b/protocols/protocols_common.c @@ -196,17 +196,17 @@ SubGhzProtocolStatus pp_serialize_fields( uint32_t type) { if(!ff) return SubGhzProtocolStatusError; - if((field_mask & PP_FIELD_SERIAL) && !flipper_format_write_uint32(ff, FF_SERIAL, &serial, 1)) { - return SubGhzProtocolStatusErrorParserOthers; + if(field_mask & PP_FIELD_SERIAL) { + pp_flipper_update_or_insert_u32(ff, FF_SERIAL, serial); } - if((field_mask & PP_FIELD_BTN) && !flipper_format_write_uint32(ff, FF_BTN, &btn, 1)) { - return SubGhzProtocolStatusErrorParserOthers; + if(field_mask & PP_FIELD_BTN) { + pp_flipper_update_or_insert_u32(ff, FF_BTN, btn); } - if((field_mask & PP_FIELD_CNT) && !flipper_format_write_uint32(ff, FF_CNT, &cnt, 1)) { - return SubGhzProtocolStatusErrorParserOthers; + if(field_mask & PP_FIELD_CNT) { + pp_flipper_update_or_insert_u32(ff, FF_CNT, cnt); } - if((field_mask & PP_FIELD_TYPE) && !flipper_format_write_uint32(ff, FF_TYPE, &type, 1)) { - return SubGhzProtocolStatusErrorParserOthers; + if(field_mask & PP_FIELD_TYPE) { + pp_flipper_update_or_insert_u32(ff, FF_TYPE, type); } return SubGhzProtocolStatusOk; } @@ -221,8 +221,9 @@ SubGhzProtocolStatus return SubGhzProtocolStatusError; } furi_string_printf(display, "%s - %s", protocol_name, suffix); + flipper_format_rewind(ff); SubGhzProtocolStatus status = - flipper_format_write_string_cstr(ff, "Disp", furi_string_get_cstr(display)) ? + flipper_format_insert_or_update_string_cstr(ff, "Disp", furi_string_get_cstr(display)) ? SubGhzProtocolStatusOk : SubGhzProtocolStatusErrorParserOthers; furi_string_free(display); diff --git a/protocols/renault_v0.c b/protocols/renault_v0.c index cefe1e3..2abb3f1 100644 --- a/protocols/renault_v0.c +++ b/protocols/renault_v0.c @@ -1,27 +1,29 @@ #include "renault_v0.h" #include "protocols_common.h" -#define RENAULT_V0_MIN_BITS 0x52U -#define RENAULT_V0_DECODER_BIT_LIMIT 0x6DU -#define RENAULT_V0_SYNC_MIN_US 0x320U -#define RENAULT_V0_GAP_RESET_US 5000U -#define RENAULT_V0_END_BURST_MIN_BITS 96U -#define RENAULT_V0_END_BURST_MIN_US 1200U -#define RENAULT_V0_END_BURST_MAX_US 2000U -#define RENAULT_V0_DECODED_BITS_MAX 0x70U -#define RENAULT_V0_UPLOAD_CAPACITY 0x258U -#define RENAULT_V0_TE_DEFAULT_US 125U -#define RENAULT_V0_TE_PREAMBLE_12_US 140U -#define RENAULT_V0_ROLLING_REPEAT 1U -#define RENAULT_V0_REPLAY_REPEAT 10U - -#define RENAULT_V0_KEY2_FIELD "Key2" -#define RENAULT_V0_PREAMBLE_FIELD "Preamble" -#define RENAULT_V0_ROLLING_FIELD "Rolling" - +#define RENAULT_V0_MIN_BITS 0x52U +#define RENAULT_V0_DECODER_BIT_LIMIT 0x6DU +#define RENAULT_V0_SYNC_MIN_US 0x320U +#define RENAULT_V0_DECODED_BITS_MAX 0x70U +#define RENAULT_V0_UPLOAD_CAPACITY 0x258U _Static_assert( RENAULT_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, "RENAULT_V0_UPLOAD_CAPACITY exceeds shared upload slab"); +#define RENAULT_V0_TE_SHORT_US 0x7DU +#define RENAULT_V0_TE_LONG_US 0xFAU +#define RENAULT_V0_TE_DELTA_US 0x45U +#define RENAULT_V0_PREAMBLE_PAIRS 16U +#define RENAULT_V0_BURST_COUNT 3U +#define RENAULT_V0_INTER_BURST_US 0x61A8U +#define RENAULT_V0_FINAL_LOW_US 250U +#define RENAULT_V0_SYNC_HIGH_US 1000U +#define RENAULT_V0_REPEAT 1U +#define RENAULT_V0_KEY2_FIELD "Key2" + +typedef enum { + RenaultV0DecoderStepReset = 0, + RenaultV0DecoderStepData = 1, +} RenaultV0DecoderStep; typedef struct { uint32_t low; @@ -47,71 +49,6 @@ static const RenaultV0MatrixRow renault_v0_matrix[42] = { static const uint8_t renault_v0_decoder_state_table[4] = {0x01, 0x91, 0x9B, 0xFB}; -typedef enum { - RenaultV0TypeUnknown = 0, - RenaultV0Type13, - RenaultV0Type04, - RenaultV0Type0C, - RenaultV0Type1A, - RenaultV0Type3B, - RenaultV0Type3F, - RenaultV0TypeDynamic, -} RenaultV0TypeId; - -typedef struct { - RenaultV0TypeId id; - uint8_t value; - const char* name; - uint8_t checksum_low6; - uint8_t checksum_high2_xor; -} RenaultV0TypeEntry; - -static const RenaultV0TypeEntry renault_v0_types[] = { - {RenaultV0Type13, 0x13U, "13", 0x13U, 0x00U}, - {RenaultV0Type04, 0x04U, "04", 0x04U, 0x00U}, - {RenaultV0Type0C, 0x0CU, "0C", 0x0CU, 0x00U}, - {RenaultV0Type1A, 0x1AU, "1A", 0x1AU, 0x03U}, - {RenaultV0Type3B, 0x3BU, "3B", 0x3BU, 0x00U}, - {RenaultV0Type3F, 0x3FU, "3F", 0x3FU, 0x00U}, -}; - -typedef struct { - uint32_t te_short; - uint32_t te_long; - uint32_t te_delta; -} RenaultV0TeProfile; - -static const RenaultV0TeProfile renault_v0_te_profiles[] = { - {0x7DU, 0xFAU, 0x45U}, - {124U, 248U, 60U}, - {108U, 250U, 55U}, -}; - -typedef enum { - RenaultV0TeProfile125 = 0, - RenaultV0TeProfile124 = 1, - RenaultV0TeProfile108 = 2, -} RenaultV0TeProfileId; - -typedef enum { - RenaultV0DecoderStepReset = 0, - RenaultV0DecoderStepData = 1, -} RenaultV0DecoderStep; - -typedef struct { - uint64_t data; - uint32_t key2; - uint32_t serial; - uint8_t button; - uint8_t counter; - RenaultV0TypeId type_id; - uint8_t type_tag; - uint8_t preamble_bits; - bool c1_ok; - bool c2_ok; - bool ic_ok; -} RenaultV0DecodeAttempt; - typedef struct SubGhzProtocolDecoderRenaultV0 { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; @@ -121,17 +58,10 @@ typedef struct SubGhzProtocolDecoderRenaultV0 { uint8_t check_c1; uint8_t check_c2; uint8_t check_ic; - uint32_t key2; - uint8_t manchester_state; uint8_t decoded_bits[RENAULT_V0_DECODED_BITS_MAX]; uint8_t decoded_bit_count; - RenaultV0TypeId type_id; - uint8_t type_tag; - uint8_t preamble_bits; - bool pending_attempt_valid; - RenaultV0DecodeAttempt pending_attempt; } SubGhzProtocolDecoderRenaultV0; #if PROTOPIRATE_WITH_ENCODER @@ -139,148 +69,58 @@ typedef struct SubGhzProtocolEncoderRenaultV0 { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; - - uint16_t packet_bit_count; - uint8_t tx_button; - uint8_t preamble_bits; uint32_t key2; } SubGhzProtocolEncoderRenaultV0; - -typedef struct { - uint8_t preamble_pairs; - uint8_t burst_count; - uint32_t te_short; - uint32_t inter_burst_low; - uint32_t final_low; -} RenaultV0UploadShape; #endif -static void renault_v0_set_split_bit(uint32_t* low, uint32_t* high, uint8_t bit); -static uint8_t renault_v0_parity32(uint32_t value); -static bool renault_v0_type_button_valid(RenaultV0TypeId type_id, uint8_t button); -static const RenaultV0TypeEntry* - renault_v0_find_type_by_checks(uint8_t checksum, uint32_t key2, bool* c1_ok, bool* c2_ok); -static bool renault_v0_checksum_hi2xor_valid(uint8_t hi2xor); -static bool renault_v0_button_valid_generic(uint8_t button); -static bool renault_v0_preamble_bits_valid(uint8_t preamble_bits); -static uint8_t renault_v0_default_preamble_bits(RenaultV0TypeId type_id); -static bool renault_v0_type_preamble_bits_valid(RenaultV0TypeId type_id, uint8_t preamble_bits); -static const char* renault_v0_get_button_name(RenaultV0TypeId type_id, uint8_t button); +static void renault_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) { + for(size_t j = 0; j < 8; j++) { + bytes[j] = (uint8_t)((data >> ((7U - j) * 8U)) & 0xFFU); + } +} + static void - renault_v0_parse_fields(uint64_t data, uint32_t* serial, uint8_t* button, uint8_t* counter); -static void renault_v0_build_key( - uint32_t serial, - uint8_t button, - uint8_t counter, - uint64_t* out_data, - uint32_t* out_key2); -static bool renault_v0_classify_event_profile( - const RenaultV0TeProfile* profile, - uint32_t duration, - bool level, - uint8_t* event_code); -static bool renault_v0_classify_event(uint32_t duration, bool level, uint8_t* event_code); -static bool renault_v0_is_end_burst(bool level, uint32_t duration, uint8_t bit_count); -#if PROTOPIRATE_WITH_ENCODER -static RenaultV0TypeId renault_v0_detect_type(uint8_t checksum, uint32_t key2, uint8_t button); -#endif -static bool - renault_v0_classify_frame(uint64_t data, uint32_t key2, RenaultV0DecodeAttempt* attempt); -static uint8_t renault_v0_checksum(uint64_t data, uint32_t key2); -static bool renault_v0_model_matches( - uint64_t data, - uint32_t key2, - uint32_t serial, - uint8_t button, - uint8_t counter); -static bool renault_v0_attempt_at_offset( - const SubGhzProtocolDecoderRenaultV0* instance, - uint8_t offset, - RenaultV0DecodeAttempt* attempt); -static bool renault_v0_confirm_attempt( - SubGhzProtocolDecoderRenaultV0* instance, - const RenaultV0DecodeAttempt* attempt); -#if !defined(PROTOPIRATE_PROTOCOL_RX_ONLY) || defined(PROTOPIRATE_PROTOCOL_TX_ONLY) -static bool renault_v0_get_bit_msb82(uint64_t data, uint32_t key2, uint8_t bit_index); -#endif -static void renault_v0_apply_attempt( - SubGhzProtocolDecoderRenaultV0* instance, - const RenaultV0DecodeAttempt* attempt); -static void renault_v0_decode_candidate(SubGhzProtocolDecoderRenaultV0* instance); -static SubGhzProtocolStatus renault_v0_write_display( - FlipperFormat* flipper_format, - const char* protocol_name, - RenaultV0TypeId type_id, - uint8_t button); -#if PROTOPIRATE_WITH_ENCODER -static bool renault_v0_upload_shape_for_type(RenaultV0TypeId type_id, RenaultV0UploadShape* shape); -static bool - renault_v0_upload_shape_for_preamble(uint8_t preamble_bits, RenaultV0UploadShape* shape); -static uint32_t renault_v0_upload_te_for_preamble(uint8_t preamble_bits); -static bool renault_v0_emit( - SubGhzProtocolEncoderRenaultV0* instance, - size_t* index, - bool level, - uint32_t duration); -static bool renault_v0_emit_decoded_bit( - SubGhzProtocolEncoderRenaultV0* instance, - size_t* index, - uint8_t* state, - uint32_t te_short, - bool bit); -static bool renault_v0_build_upload( - SubGhzProtocolEncoderRenaultV0* instance, - RenaultV0TypeId type_id, - uint8_t preamble_bits); -#endif + renault_v0_parse_fields(uint64_t data, uint32_t* serial, uint8_t* button, uint8_t* counter) { + if(serial) { + *serial = (uint32_t)(data >> 40U); + } + if(button) { + *button = (uint8_t)(data >> 32U); + } + if(counter) { + *counter = (uint8_t)(((uint32_t)data >> 24U) & 0xFFU); + } +} -const SubGhzProtocolDecoder subghz_protocol_renault_v0_decoder = { - .alloc = subghz_protocol_decoder_renault_v0_alloc, - .free = pp_decoder_free_default, - .feed = subghz_protocol_decoder_renault_v0_feed, - .reset = subghz_protocol_decoder_renault_v0_reset, - .get_hash_data = subghz_protocol_decoder_renault_v0_get_hash_data, - .get_string = subghz_protocol_decoder_renault_v0_get_string, - .serialize = subghz_protocol_decoder_renault_v0_serialize, - .deserialize = subghz_protocol_decoder_renault_v0_deserialize, -}; +static bool renault_v0_button_valid(uint8_t button) { + return (button == 0x05U) || (button == 0x06U) || (button == 0x0AU); +} -#if PROTOPIRATE_WITH_ENCODER -const SubGhzProtocolEncoder subghz_protocol_renault_v0_encoder = { - .alloc = subghz_protocol_encoder_renault_v0_alloc, - .free = pp_encoder_free, - .deserialize = subghz_protocol_encoder_renault_v0_deserialize, - .stop = pp_encoder_stop, - .yield = pp_encoder_yield, -}; -#else -const SubGhzProtocolEncoder subghz_protocol_renault_v0_encoder = { - .alloc = NULL, - .free = NULL, - .deserialize = NULL, - .stop = NULL, - .yield = NULL, -}; -#endif +static const char* renault_v0_get_button_name(uint8_t button) { + switch(button) { + case 0x05: + return "Trunk"; + case 0x06: + return "Lock"; + case 0x0A: + return "Unlock"; + default: + return "?"; + } +} -const SubGhzProtocol renault_v0_protocol = { - .name = RENAULT_PROTOCOL_V0_NAME, - .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | - SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, +static uint8_t renault_v0_checksum(uint64_t data, uint32_t key2) { + uint8_t bytes[10]; + renault_v0_u64_to_bytes_be(data, bytes); + bytes[8] = (uint8_t)((key2 >> 10U) & 0xFFU); + bytes[9] = (uint8_t)((key2 >> 2U) & 0xFFU); -#if PROTOPIRATE_WITH_ENCODER - .encoder = &subghz_protocol_renault_v0_encoder, -#else - .encoder = NULL, -#endif -#if PROTOPIRATE_WITH_DECODER - .decoder = &subghz_protocol_renault_v0_decoder, -#else - .decoder = NULL, -#endif -}; + uint8_t checksum = 0U; + for(size_t i = 0; i < COUNT_OF(bytes); i++) { + checksum ^= bytes[i]; + } + return checksum; +} static void renault_v0_set_split_bit(uint32_t* low, uint32_t* high, uint8_t bit) { if(bit < 32U) { @@ -299,152 +139,6 @@ static uint8_t renault_v0_parity32(uint32_t value) { return (uint8_t)(value & 1U); } -static bool renault_v0_type_button_valid(RenaultV0TypeId type_id, uint8_t button) { - switch(type_id) { - case RenaultV0Type13: - return (button == 0x06U) || (button == 0x0AU); - case RenaultV0Type04: - return (button >= 0x04U) && (button <= 0x0BU); - case RenaultV0Type0C: - return (button >= 0xC4U) && (button <= 0xCBU); - case RenaultV0Type1A: - case RenaultV0Type3B: - return (button >= 0x44U) && (button <= 0x4BU); - case RenaultV0Type3F: - return (button >= 0xC4U) && (button <= 0xCBU); - case RenaultV0TypeDynamic: - return false; - default: - return false; - } -} - -static const RenaultV0TypeEntry* - renault_v0_find_type_by_checks(uint8_t checksum, uint32_t key2, bool* c1_ok, bool* c2_ok) { - const uint8_t checksum_low6 = checksum & 0x3FU; - const uint8_t checksum_high2_xor = (uint8_t)(((checksum >> 6U) & 0x03U) ^ (key2 & 0x03U)); - - if(c1_ok) { - *c1_ok = false; - } - if(c2_ok) { - *c2_ok = false; - } - - for(size_t i = 0; i < COUNT_OF(renault_v0_types); i++) { - const RenaultV0TypeEntry* type = &renault_v0_types[i]; - if(checksum_low6 != type->checksum_low6) { - continue; - } - - if(c1_ok) { - *c1_ok = true; - } - if(checksum_high2_xor != type->checksum_high2_xor) { - return NULL; - } - - if(c2_ok) { - *c2_ok = true; - } - return type; - } - - return NULL; -} - -static bool renault_v0_checksum_hi2xor_valid(uint8_t hi2xor) { - return (hi2xor == 0x00U) || (hi2xor == 0x03U); -} - -static bool renault_v0_button_valid_generic(uint8_t button) { - if((button == 0x06U) || (button == 0x0AU)) { - return true; - } - if((button >= 0x04U) && (button <= 0x0BU)) { - return true; - } - if((button >= 0x44U) && (button <= 0x4BU)) { - return true; - } - if((button >= 0xC4U) && (button <= 0xCBU)) { - return true; - } - return false; -} - -static bool renault_v0_preamble_bits_valid(uint8_t preamble_bits) { - switch(preamble_bits) { - case 12U: - case 16U: - case 18U: - case 20U: - return true; - default: - return false; - } -} - -static uint8_t renault_v0_default_preamble_bits(RenaultV0TypeId type_id) { - switch(type_id) { - case RenaultV0Type13: - case RenaultV0Type04: - return 16U; - case RenaultV0Type0C: - return 18U; - case RenaultV0Type1A: - return 12U; - case RenaultV0Type3B: - case RenaultV0Type3F: - return 20U; - default: - return 0U; - } -} - -static bool renault_v0_type_preamble_bits_valid(RenaultV0TypeId type_id, uint8_t preamble_bits) { - if(type_id == RenaultV0TypeDynamic) { - return renault_v0_preamble_bits_valid(preamble_bits); - } - - return preamble_bits == renault_v0_default_preamble_bits(type_id); -} - -static const char* renault_v0_get_button_name(RenaultV0TypeId type_id, uint8_t button) { - if(type_id == RenaultV0Type13) { - switch(button) { - case 0x06: - return "Lock"; - case 0x0A: - return "Unlock"; - default: - return "??"; - } - } - - const uint8_t low_nibble = button & 0x0FU; - if((low_nibble >= 0x04U) && (low_nibble <= 0x07U)) { - return "Lock"; - } - if((low_nibble >= 0x08U) && (low_nibble <= 0x0BU)) { - return "Unlock"; - } - return "??"; -} - -static void - renault_v0_parse_fields(uint64_t data, uint32_t* serial, uint8_t* button, uint8_t* counter) { - if(serial) { - *serial = (uint32_t)(data >> 40U); - } - if(button) { - *button = (uint8_t)(data >> 32U); - } - if(counter) { - *counter = (uint8_t)(((uint32_t)data >> 24U) & 0xFFU); - } -} - static void renault_v0_build_key( uint32_t serial, uint8_t button, @@ -524,142 +218,6 @@ static void renault_v0_build_key( } } -static bool renault_v0_classify_event_profile( - const RenaultV0TeProfile* profile, - uint32_t duration, - bool level, - uint8_t* event_code) { - furi_check(event_code); - furi_check(profile); - - const uint32_t te_short = profile->te_short; - const uint32_t te_long = profile->te_long; - const uint32_t te_delta = profile->te_delta; - - if(duration <= (te_short - 1U)) { - if((te_short - duration) > te_delta) { - return false; - } - *event_code = (uint8_t)(((level ? 1U : 0U) ^ 1U) << 1U); - return true; - } - - if(duration <= (te_long - 1U)) { - const uint32_t short_delta = duration - te_short; - const uint32_t long_inv_delta = te_long - duration; - - if(short_delta <= te_delta) { - if(long_inv_delta > te_delta) { - *event_code = (uint8_t)(((level ? 1U : 0U) ^ 1U) << 1U); - } else { - *event_code = level ? 4U : 6U; - } - return true; - } - - if(long_inv_delta <= te_delta) { - *event_code = level ? 4U : 6U; - return true; - } - - return false; - } - - if((duration - te_long) > te_delta) { - return false; - } - - *event_code = level ? 4U : 6U; - return true; -} - -static bool renault_v0_classify_event(uint32_t duration, bool level, uint8_t* event_code) { - for(size_t i = 0; i < COUNT_OF(renault_v0_te_profiles); i++) { - if(renault_v0_classify_event_profile( - &renault_v0_te_profiles[i], duration, level, event_code)) { - return true; - } - } - return false; -} - -static bool renault_v0_is_end_burst(bool level, uint32_t duration, uint8_t bit_count) { - return (!level) && (bit_count >= RENAULT_V0_END_BURST_MIN_BITS) && - (duration >= RENAULT_V0_END_BURST_MIN_US) && (duration <= RENAULT_V0_END_BURST_MAX_US); -} - -#if PROTOPIRATE_WITH_ENCODER -static RenaultV0TypeId renault_v0_detect_type(uint8_t checksum, uint32_t key2, uint8_t button) { - const RenaultV0TypeEntry* type = renault_v0_find_type_by_checks(checksum, key2, NULL, NULL); - if(type && renault_v0_type_button_valid(type->id, button)) { - return type->id; - } - - return RenaultV0TypeUnknown; -} -#endif - -static bool - renault_v0_classify_frame(uint64_t data, uint32_t key2, RenaultV0DecodeAttempt* attempt) { - furi_check(attempt); - - uint32_t serial = 0U; - uint8_t button = 0U; - uint8_t counter = 0U; - renault_v0_parse_fields(data, &serial, &button, &counter); - - const uint8_t checksum = renault_v0_checksum(data, key2); - const uint8_t checksum_low6 = checksum & 0x3FU; - const uint8_t checksum_high2_xor = (uint8_t)(((checksum >> 6U) & 0x03U) ^ (key2 & 0x03U)); - bool c1_ok = false; - bool c2_ok = false; - const RenaultV0TypeEntry* type = - renault_v0_find_type_by_checks(checksum, key2, &c1_ok, &c2_ok); - const bool ic_ok = renault_v0_model_matches(data, key2, serial, button, counter); - - attempt->data = data; - attempt->key2 = key2; - attempt->serial = serial; - attempt->button = button; - attempt->counter = counter; - attempt->c1_ok = c1_ok; - attempt->c2_ok = c2_ok; - attempt->ic_ok = ic_ok; - - if(type && c1_ok && c2_ok && renault_v0_type_button_valid(type->id, button)) { - attempt->type_id = type->id; - attempt->type_tag = type->value; - return true; - } - - const bool dynamic_c1_ok = renault_v0_button_valid_generic(button) && (serial != 0U) && - (serial <= 0xFFFFFFU); - const bool dynamic_c2_ok = renault_v0_checksum_hi2xor_valid(checksum_high2_xor); - attempt->c1_ok = dynamic_c1_ok; - attempt->c2_ok = dynamic_c2_ok; - - if(dynamic_c1_ok && dynamic_c2_ok) { - attempt->type_id = RenaultV0TypeDynamic; - attempt->type_tag = checksum_low6; - return true; - } - - return false; -} - -static uint8_t renault_v0_checksum(uint64_t data, uint32_t key2) { - uint8_t bytes[10]; - pp_u64_to_bytes_be(data, bytes); - bytes[8] = (uint8_t)((key2 >> 10U) & 0xFFU); - bytes[9] = (uint8_t)((key2 >> 2U) & 0xFFU); - - uint8_t checksum = 0U; - for(size_t i = 0; i < COUNT_OF(bytes); i++) { - checksum ^= bytes[i]; - } - return checksum; -} - static bool renault_v0_model_matches( uint64_t data, uint32_t key2, @@ -672,267 +230,92 @@ static bool renault_v0_model_matches( return (rebuilt_data == data) && (rebuilt_key2 == key2); } -static bool renault_v0_attempt_at_offset( - const SubGhzProtocolDecoderRenaultV0* instance, - uint8_t offset, - RenaultV0DecodeAttempt* attempt) { - furi_check(attempt); +static void + renault_v0_update_checks(uint64_t data, uint32_t key2, uint8_t* c1, uint8_t* c2, uint8_t* ic) { + uint32_t serial = 0U; + uint8_t button = 0U; + uint8_t counter = 0U; + renault_v0_parse_fields(data, &serial, &button, &counter); - if(((uint32_t)offset + 82U) > instance->decoded_bit_count) { + const uint8_t checksum = renault_v0_checksum(data, key2); + if(c1) { + *c1 = ((checksum & 0x3FU) == 0x13U) ? 0U : 1U; + } + if(c2) { + *c2 = (((key2 & 0x03U) == (uint32_t)(checksum >> 6U))) ? 0U : 1U; + } + if(ic) { + *ic = renault_v0_model_matches(data, key2, serial, button, counter) ? 0U : 1U; + } +} + +static bool renault_v0_type13_valid(uint64_t data, uint32_t key2) { + uint8_t button = (uint8_t)(data >> 32U); + const uint8_t checksum = renault_v0_checksum(data, key2); + if((checksum & 0x3FU) != 0x13U) { return false; } - - uint64_t data = 0ULL; - for(uint8_t i = 0; i < 64U; i++) { - data = (data << 1U) | (uint64_t)(instance->decoded_bits[offset + i] & 1U); - } - - uint32_t key2 = 0U; - for(uint8_t i = 0; i < 18U; i++) { - key2 = (key2 << 1U) | (uint32_t)(instance->decoded_bits[offset + 64U + i] & 1U); - } - - if(!renault_v0_classify_frame(data, key2, attempt)) { + if((key2 & 0x03U) != (uint32_t)(checksum >> 6U)) { return false; } - - attempt->preamble_bits = offset; - if(!renault_v0_type_preamble_bits_valid(attempt->type_id, attempt->preamble_bits)) { - return false; - } - - return true; -} - -static bool renault_v0_confirm_attempt( - SubGhzProtocolDecoderRenaultV0* instance, - const RenaultV0DecodeAttempt* attempt) { - if(attempt->type_id != RenaultV0TypeDynamic) { - instance->pending_attempt_valid = false; - return true; - } - - if(instance->pending_attempt_valid && (instance->pending_attempt.data == attempt->data) && - (instance->pending_attempt.key2 == attempt->key2) && - (instance->pending_attempt.type_id == attempt->type_id) && - (instance->pending_attempt.type_tag == attempt->type_tag) && - (instance->pending_attempt.preamble_bits == attempt->preamble_bits)) { - instance->pending_attempt_valid = false; - return true; - } - - instance->pending_attempt = *attempt; - instance->pending_attempt_valid = true; - return false; -} - -#if !defined(PROTOPIRATE_PROTOCOL_RX_ONLY) || defined(PROTOPIRATE_PROTOCOL_TX_ONLY) -static bool renault_v0_get_bit_msb82(uint64_t data, uint32_t key2, uint8_t bit_index) { - if(bit_index <= 0x3FU) { - return ((data >> (63U - bit_index)) & 1ULL) != 0ULL; - } - - return ((key2 >> (0x51U - bit_index)) & 1U) != 0U; -} -#endif - -static void renault_v0_apply_attempt( - SubGhzProtocolDecoderRenaultV0* instance, - const RenaultV0DecodeAttempt* attempt) { - instance->generic.data = attempt->data; - instance->decoder.decode_data = attempt->data; - instance->packet_bit_count = RENAULT_V0_MIN_BITS; - instance->decoder.decode_count_bit = RENAULT_V0_MIN_BITS; - instance->key2 = attempt->key2; - instance->generic.data_count_bit = RENAULT_V0_MIN_BITS; - instance->generic.serial = attempt->serial; - instance->generic.cnt = attempt->counter; - instance->generic.btn = attempt->button; - instance->type_id = attempt->type_id; - instance->type_tag = attempt->type_tag; - instance->preamble_bits = attempt->preamble_bits; - instance->check_c1 = !attempt->c1_ok; - instance->check_c2 = !attempt->c2_ok; - instance->check_ic = !attempt->ic_ok; -} - -static void renault_v0_decode_candidate(SubGhzProtocolDecoderRenaultV0* instance) { - if(instance->decoded_bit_count <= 0x51U) { - return; - } - - const uint8_t tail_offset = instance->decoded_bit_count - RENAULT_V0_MIN_BITS; - if(!renault_v0_preamble_bits_valid(tail_offset)) { - instance->pending_attempt_valid = false; - instance->packet_bit_count = 0U; - instance->generic.data_count_bit = 0U; - return; - } - - RenaultV0DecodeAttempt attempt = {0}; - if(!renault_v0_attempt_at_offset(instance, tail_offset, &attempt)) { - instance->pending_attempt_valid = false; - instance->packet_bit_count = 0U; - instance->generic.data_count_bit = 0U; - return; - } - - if(!renault_v0_confirm_attempt(instance, &attempt)) { - instance->decoded_bit_count = 0U; - return; - } - - if((instance->packet_bit_count != 0U) && (instance->generic.data == attempt.data) && - (instance->key2 == attempt.key2)) { - instance->decoded_bit_count = 0U; - return; - } - - renault_v0_apply_attempt(instance, &attempt); - instance->decoded_bit_count = 0U; - - if(instance->packet_bit_count && instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); - } -} - -static SubGhzProtocolStatus renault_v0_write_display( - FlipperFormat* flipper_format, - const char* protocol_name, - RenaultV0TypeId type_id, - uint8_t button) { - return pp_write_display( - flipper_format, protocol_name, renault_v0_get_button_name(type_id, button)); + return renault_v0_button_valid(button); } #if PROTOPIRATE_WITH_ENCODER - -static bool - renault_v0_upload_shape_for_type(RenaultV0TypeId type_id, RenaultV0UploadShape* shape) { - furi_check(shape); - - switch(type_id) { - case RenaultV0Type13: - shape->preamble_pairs = 16U; - shape->burst_count = 3U; - shape->te_short = RENAULT_V0_TE_DEFAULT_US; - shape->inter_burst_low = 0x61A8U; - shape->final_low = 250U; - return true; - case RenaultV0Type04: - case RenaultV0Type0C: - case RenaultV0Type1A: - case RenaultV0Type3B: - case RenaultV0Type3F: - return renault_v0_upload_shape_for_preamble( - renault_v0_default_preamble_bits(type_id), shape); - default: - return false; - } -} - -static bool - renault_v0_upload_shape_for_preamble(uint8_t preamble_bits, RenaultV0UploadShape* shape) { - furi_check(shape); - - if(!renault_v0_preamble_bits_valid(preamble_bits)) { - return false; - } - - shape->preamble_pairs = preamble_bits; - shape->burst_count = 3U; - shape->te_short = renault_v0_upload_te_for_preamble(preamble_bits); - shape->inter_burst_low = 1500U; - shape->final_low = 1500U; - return true; -} - -static uint32_t renault_v0_upload_te_for_preamble(uint8_t preamble_bits) { - if(preamble_bits == 12U) { - return RENAULT_V0_TE_PREAMBLE_12_US; - } - return RENAULT_V0_TE_DEFAULT_US; -} - static bool renault_v0_emit( SubGhzProtocolEncoderRenaultV0* instance, size_t* index, bool level, uint32_t duration) { - furi_check(instance); - furi_check(index); - const size_t prev = *index; - *index = - pp_emit_merge(instance->encoder.upload, prev, RENAULT_V0_UPLOAD_CAPACITY, level, duration); + *index = pp_emit_merge( + instance->encoder.upload, prev, RENAULT_V0_UPLOAD_CAPACITY, level, duration); if(*index > prev) { return true; } - if(prev > 0U && level_duration_get_level(instance->encoder.upload[prev - 1U]) == level) { - return true; - } - return false; + return (prev > 0U) && (level_duration_get_level(instance->encoder.upload[prev - 1U]) == level); } static bool renault_v0_emit_decoded_bit( SubGhzProtocolEncoderRenaultV0* instance, size_t* index, uint8_t* state, - uint32_t te_short, bool bit) { - furi_check(state); - - const uint32_t te_long = te_short * 2U; - if(*state == 1U) { if(bit) { - return renault_v0_emit(instance, index, false, te_short) && - renault_v0_emit(instance, index, true, te_short); + return renault_v0_emit(instance, index, false, RENAULT_V0_TE_SHORT_US) && + renault_v0_emit(instance, index, true, RENAULT_V0_TE_SHORT_US); } - *state = 2U; - return renault_v0_emit(instance, index, false, te_long); + return renault_v0_emit(instance, index, false, RENAULT_V0_TE_LONG_US); } if(bit) { *state = 1U; - return renault_v0_emit(instance, index, true, te_long); + return renault_v0_emit(instance, index, true, RENAULT_V0_TE_LONG_US); } - return renault_v0_emit(instance, index, true, te_short) && - renault_v0_emit(instance, index, false, te_short); + return renault_v0_emit(instance, index, true, RENAULT_V0_TE_SHORT_US) && + renault_v0_emit(instance, index, false, RENAULT_V0_TE_SHORT_US); } -static bool renault_v0_build_upload( - SubGhzProtocolEncoderRenaultV0* instance, - RenaultV0TypeId type_id, - uint8_t preamble_bits) { - furi_check(instance); - - RenaultV0UploadShape shape; - if(type_id == RenaultV0Type13) { - if(!renault_v0_upload_shape_for_type(type_id, &shape)) { - return false; - } - } else if(renault_v0_preamble_bits_valid(preamble_bits)) { - if(!renault_v0_upload_shape_for_preamble(preamble_bits, &shape)) { - return false; - } - } else { - if(!renault_v0_upload_shape_for_type(type_id, &shape)) { - return false; - } +static bool renault_v0_get_bit_msb82(uint64_t data, uint32_t key2, uint8_t bit_index) { + if(bit_index <= 0x3FU) { + return ((data >> (63U - bit_index)) & 1ULL) != 0ULL; } + return ((key2 >> (0x51U - bit_index)) & 1U) != 0U; +} +static bool renault_v0_build_upload(SubGhzProtocolEncoderRenaultV0* instance) { size_t write_index = 0U; - for(uint8_t burst = 0U; burst < shape.burst_count; burst++) { - if(!renault_v0_emit(instance, &write_index, true, 1000U)) { + for(uint8_t burst = 0U; burst < RENAULT_V0_BURST_COUNT; burst++) { + if(!renault_v0_emit(instance, &write_index, true, RENAULT_V0_SYNC_HIGH_US)) { return false; } uint8_t state = 1U; - for(uint8_t pair = 0U; pair < shape.preamble_pairs; pair++) { - if(!renault_v0_emit_decoded_bit(instance, &write_index, &state, shape.te_short, true)) { + for(uint8_t pair = 0U; pair < RENAULT_V0_PREAMBLE_PAIRS; pair++) { + if(!renault_v0_emit_decoded_bit(instance, &write_index, &state, true)) { return false; } } @@ -940,19 +323,20 @@ static bool renault_v0_build_upload( for(uint8_t bit_index = 0U; bit_index < RENAULT_V0_MIN_BITS; bit_index++) { const bool bit = renault_v0_get_bit_msb82(instance->generic.data, instance->key2, bit_index); - if(!renault_v0_emit_decoded_bit(instance, &write_index, &state, shape.te_short, bit)) { + if(!renault_v0_emit_decoded_bit(instance, &write_index, &state, bit)) { return false; } } if(state == 2U) { - if(!renault_v0_emit(instance, &write_index, true, shape.te_short)) { + if(!renault_v0_emit(instance, &write_index, true, RENAULT_V0_TE_SHORT_US)) { return false; } } - const uint32_t trailing_low = (burst + 1U < shape.burst_count) ? shape.inter_burst_low : - shape.final_low; + const uint32_t trailing_low = (burst + 1U < RENAULT_V0_BURST_COUNT) ? + RENAULT_V0_INTER_BURST_US : + RENAULT_V0_FINAL_LOW_US; if(!renault_v0_emit(instance, &write_index, false, trailing_low)) { return false; } @@ -960,29 +344,176 @@ static bool renault_v0_build_upload( instance->encoder.size_upload = write_index; instance->encoder.front = 0U; + return write_index > 0U; +} +#endif + +static bool renault_v0_classify_event(uint32_t duration, bool level, uint8_t* event_code) { + if(duration <= (RENAULT_V0_TE_SHORT_US - 1U)) { + if((RENAULT_V0_TE_SHORT_US - duration) > RENAULT_V0_TE_DELTA_US) { + return false; + } + *event_code = (uint8_t)(((level ? 1U : 0U) ^ 1U) << 1U); + return true; + } + + if(duration <= (RENAULT_V0_TE_LONG_US - 1U)) { + const uint32_t short_delta = duration - RENAULT_V0_TE_SHORT_US; + const uint32_t long_inv_delta = RENAULT_V0_TE_LONG_US - duration; + if(short_delta <= RENAULT_V0_TE_DELTA_US) { + if(long_inv_delta > RENAULT_V0_TE_DELTA_US) { + *event_code = (uint8_t)(((level ? 1U : 0U) ^ 1U) << 1U); + } else { + *event_code = level ? 4U : 6U; + } + return true; + } + if(long_inv_delta <= RENAULT_V0_TE_DELTA_US) { + *event_code = level ? 4U : 6U; + return true; + } + return false; + } + + if((duration - RENAULT_V0_TE_LONG_US) > RENAULT_V0_TE_DELTA_US) { + return false; + } + *event_code = level ? 4U : 6U; return true; } +static void renault_v0_decode_candidate(SubGhzProtocolDecoderRenaultV0* instance) { + const uint8_t bit_count = instance->decoded_bit_count; + if(bit_count <= 0x51U) { + return; + } + + uint8_t preamble = 0U; + while((preamble < bit_count) && (instance->decoded_bits[preamble] == 1U)) { + preamble++; + } + if(preamble <= 9U) { + return; + } + if((uint8_t)(bit_count - preamble) <= 0x51U) { + return; + } + + uint64_t data = 0ULL; + for(uint8_t i = 0; i < 64U; i++) { + data = (data << 1U) | (uint64_t)(instance->decoded_bits[preamble + i] & 1U); + } + + uint32_t key2 = 0U; + for(uint8_t i = 0; i < 18U; i++) { + key2 = (key2 << 1U) | (uint32_t)(instance->decoded_bits[preamble + 64U + i] & 1U); + } + + if(!renault_v0_type13_valid(data, key2)) { + instance->packet_bit_count = 0U; + instance->generic.data_count_bit = 0U; + return; + } + + uint32_t serial = 0U; + uint8_t button = 0U; + uint8_t counter = 0U; + renault_v0_parse_fields(data, &serial, &button, &counter); + + instance->generic.data = data; + instance->decoder.decode_data = data; + instance->decoder.decode_count_bit = RENAULT_V0_MIN_BITS; + instance->packet_bit_count = RENAULT_V0_MIN_BITS; + instance->generic.data_count_bit = RENAULT_V0_MIN_BITS; + instance->key2 = key2; + instance->generic.serial = serial; + instance->generic.btn = button; + instance->generic.cnt = counter; + renault_v0_update_checks( + data, key2, &instance->check_c1, &instance->check_c2, &instance->check_ic); + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } +} + +static uint32_t renault_v0_arm_lsl(uint32_t value, uint32_t shift) { + shift &= 0xFFU; + if(shift >= 32U) { + return 0U; + } + return value << shift; +} + +static uint32_t renault_v0_arm_lsr(uint32_t value, uint32_t shift) { + shift &= 0xFFU; + if(shift >= 32U) { + return 0U; + } + return value >> shift; +} + +const SubGhzProtocolDecoder subghz_protocol_renault_v0_decoder = { + .alloc = subghz_protocol_decoder_renault_v0_alloc, + .free = pp_decoder_free_default, + .feed = subghz_protocol_decoder_renault_v0_feed, + .reset = subghz_protocol_decoder_renault_v0_reset, + .get_hash_data = subghz_protocol_decoder_renault_v0_get_hash_data, + .get_string = subghz_protocol_decoder_renault_v0_get_string, + .serialize = subghz_protocol_decoder_renault_v0_serialize, + .deserialize = subghz_protocol_decoder_renault_v0_deserialize, +}; + +#if PROTOPIRATE_WITH_ENCODER +const SubGhzProtocolEncoder subghz_protocol_renault_v0_encoder = { + .alloc = subghz_protocol_encoder_renault_v0_alloc, + .free = pp_encoder_free, + .deserialize = subghz_protocol_encoder_renault_v0_deserialize, + .stop = pp_encoder_stop, + .yield = pp_encoder_yield, +}; +#else +const SubGhzProtocolEncoder subghz_protocol_renault_v0_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; +#endif + +const SubGhzProtocol renault_v0_protocol = { + .name = RENAULT_PROTOCOL_V0_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | + SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, +#if PROTOPIRATE_WITH_DECODER + .decoder = &subghz_protocol_renault_v0_decoder, +#else + .decoder = NULL, +#endif +#if PROTOPIRATE_WITH_ENCODER + .encoder = &subghz_protocol_renault_v0_encoder, +#else + .encoder = NULL, +#endif +}; + +#if PROTOPIRATE_WITH_ENCODER void* subghz_protocol_encoder_renault_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolEncoderRenaultV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderRenaultV0)); furi_check(instance); - instance->base.protocol = &renault_v0_protocol; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 1U; - instance->encoder.front = 0; + instance->encoder.repeat = RENAULT_V0_REPEAT; instance->encoder.is_running = false; - pp_encoder_buffer_ensure(instance, RENAULT_V0_UPLOAD_CAPACITY); - return instance; } SubGhzProtocolStatus subghz_protocol_encoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolEncoderRenaultV0* instance = context; SubGhzProtocolStatus ret = SubGhzProtocolStatusError; @@ -990,138 +521,87 @@ SubGhzProtocolStatus instance->encoder.front = 0; do { - if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) != + flipper_format_rewind(flipper_format); + if(subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, RENAULT_V0_MIN_BITS) != SubGhzProtocolStatusOk) { break; } - flipper_format_rewind(flipper_format); - SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, RENAULT_V0_MIN_BITS); - if(load_st != SubGhzProtocolStatusOk) { - break; - } - - if(!flipper_format_rewind(flipper_format)) { - break; - } - uint32_t key2 = 0U; + flipper_format_rewind(flipper_format); if(!flipper_format_read_uint32(flipper_format, RENAULT_V0_KEY2_FIELD, &key2, 1)) { break; } instance->key2 = key2; - const uint64_t captured_data = instance->generic.data; - const uint32_t captured_key2 = instance->key2; - - uint32_t preamble_bits = 0U; - flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32( - flipper_format, RENAULT_V0_PREAMBLE_FIELD, &preamble_bits, 1) && - renault_v0_preamble_bits_valid((uint8_t)preamble_bits)) { - instance->preamble_bits = (uint8_t)preamble_bits; - } else { - instance->preamble_bits = 0U; + if(!renault_v0_type13_valid(instance->generic.data, instance->key2)) { + break; } - uint32_t serial = 0; - uint8_t button = 0; - uint8_t counter = 0; + uint32_t serial = 0U; + uint8_t button = 0U; + uint8_t counter = 0U; renault_v0_parse_fields(instance->generic.data, &serial, &button, &counter); - const uint32_t captured_serial = serial; - const uint8_t captured_button = button; - const uint8_t captured_counter = counter; - RenaultV0DecodeAttempt captured_attempt = {0}; - if(!renault_v0_classify_frame(captured_data, captured_key2, &captured_attempt)) { - break; - } - const RenaultV0TypeId captured_type = captured_attempt.type_id; - if(instance->preamble_bits == 0U) { - instance->preamble_bits = renault_v0_default_preamble_bits(captured_type); - } - captured_attempt.preamble_bits = instance->preamble_bits; - if((captured_type == RenaultV0TypeDynamic) && - !renault_v0_preamble_bits_valid(instance->preamble_bits)) { - break; - } - const bool rolling_supported = (captured_type == RenaultV0Type13) && - captured_attempt.ic_ok; + const bool rolling = renault_v0_model_matches( + instance->generic.data, instance->key2, serial, button, counter); - if(rolling_supported) { - uint32_t serial_u32 = serial; - uint32_t btn_u32 = button; - uint32_t cnt_u32 = counter; - pp_encoder_read_fields(flipper_format, &serial_u32, &btn_u32, &cnt_u32, NULL); + uint32_t serial_u32 = serial; + uint32_t btn_u32 = button; + uint32_t cnt_u32 = counter; + pp_encoder_read_fields(flipper_format, &serial_u32, &btn_u32, &cnt_u32, NULL); + serial = serial_u32; + button = (uint8_t)btn_u32; + counter = (uint8_t)(cnt_u32 & 0xFFU); - instance->tx_button = (uint8_t)btn_u32; - if(!renault_v0_type_button_valid(captured_type, instance->tx_button)) { + if(rolling) { + if(!renault_v0_button_valid(button)) { break; } - counter = (uint8_t)(cnt_u32 & 0xFFU); - serial = serial_u32 & 0x00FFFFFFU; - renault_v0_build_key( - serial, instance->tx_button, counter, &instance->generic.data, &instance->key2); - const uint8_t generated_checksum = - renault_v0_checksum(instance->generic.data, instance->key2); - if(renault_v0_detect_type(generated_checksum, instance->key2, instance->tx_button) != - RenaultV0Type13) { + serial, button, counter, &instance->generic.data, &instance->key2); + if(!renault_v0_type13_valid(instance->generic.data, instance->key2)) { break; } - } else { - instance->tx_button = captured_button; - serial = captured_serial; - counter = captured_counter; - instance->generic.data = captured_data; - instance->key2 = captured_key2; } - instance->packet_bit_count = RENAULT_V0_MIN_BITS; - instance->generic.data_count_bit = RENAULT_V0_MIN_BITS; instance->generic.serial = serial; - instance->generic.btn = instance->tx_button; + instance->generic.btn = button; instance->generic.cnt = counter; + instance->generic.data_count_bit = RENAULT_V0_MIN_BITS; + instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, RENAULT_V0_REPEAT); + if(instance->encoder.repeat == 0U) { + instance->encoder.repeat = RENAULT_V0_REPEAT; + } - const uint32_t default_repeat = rolling_supported ? RENAULT_V0_ROLLING_REPEAT : - RENAULT_V0_REPLAY_REPEAT; - uint32_t tx_repeat = pp_encoder_read_repeat(flipper_format, default_repeat); - if(tx_repeat == 0U) { - tx_repeat = default_repeat; - } - if(!rolling_supported && (tx_repeat < RENAULT_V0_REPLAY_REPEAT)) { - tx_repeat = RENAULT_V0_REPLAY_REPEAT; - } - instance->encoder.repeat = tx_repeat; - - if(!renault_v0_build_upload(instance, captured_type, instance->preamble_bits)) { - break; - } - if(instance->encoder.size_upload == 0) { + pp_encoder_buffer_ensure(instance, RENAULT_V0_UPLOAD_CAPACITY); + if(!renault_v0_build_upload(instance)) { break; } - if(rolling_supported) { - flipper_format_rewind(flipper_format); + if(rolling) { uint8_t key_data[8]; - pp_u64_to_bytes_be(instance->generic.data, key_data); + renault_v0_u64_to_bytes_be(instance->generic.data, key_data); + flipper_format_rewind(flipper_format); if(!flipper_format_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) { flipper_format_rewind(flipper_format); - if(!flipper_format_insert_or_update_hex( - flipper_format, FF_KEY, key_data, sizeof(key_data))) { - break; - } + flipper_format_insert_or_update_hex( + flipper_format, FF_KEY, key_data, sizeof(key_data)); } - flipper_format_rewind(flipper_format); if(!flipper_format_update_uint32( flipper_format, RENAULT_V0_KEY2_FIELD, &instance->key2, 1)) { flipper_format_rewind(flipper_format); - if(!flipper_format_insert_or_update_uint32( - flipper_format, RENAULT_V0_KEY2_FIELD, &instance->key2, 1)) { - break; - } + flipper_format_insert_or_update_uint32( + flipper_format, RENAULT_V0_KEY2_FIELD, &instance->key2, 1); } + pp_serialize_fields( + flipper_format, + PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt, + 0); } instance->encoder.is_running = true; @@ -1130,39 +610,29 @@ SubGhzProtocolStatus return ret; } - #endif void* subghz_protocol_decoder_renault_v0_alloc(SubGhzEnvironment* environment) { UNUSED(environment); - SubGhzProtocolDecoderRenaultV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderRenaultV0)); furi_check(instance); - instance->base.protocol = &renault_v0_protocol; instance->generic.protocol_name = instance->base.protocol->name; instance->manchester_state = 1U; - return instance; } void subghz_protocol_decoder_renault_v0_reset(void* context) { furi_assert(context); - SubGhzProtocolDecoderRenaultV0* instance = context; instance->decoder.parser_step = RenaultV0DecoderStepReset; instance->manchester_state = 1U; instance->decoded_bit_count = 0U; instance->key2 = 0U; - instance->type_id = RenaultV0TypeUnknown; - instance->type_tag = 0U; - instance->preamble_bits = 0U; - instance->pending_attempt_valid = false; } void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t duration) { furi_assert(context); - SubGhzProtocolDecoderRenaultV0* instance = context; uint8_t event_code = 0U; @@ -1175,23 +645,6 @@ void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t return; } - if(renault_v0_is_end_burst(level, duration, instance->decoded_bit_count)) { - renault_v0_decode_candidate(instance); - instance->decoder.parser_step = RenaultV0DecoderStepReset; - return; - } - - if(duration >= RENAULT_V0_GAP_RESET_US) { - renault_v0_decode_candidate(instance); - instance->decoder.parser_step = RenaultV0DecoderStepReset; - if(level && (duration >= RENAULT_V0_SYNC_MIN_US)) { - instance->decoder.parser_step = RenaultV0DecoderStepData; - instance->decoded_bit_count = 0U; - instance->manchester_state = 1U; - } - return; - } - if(instance->decoded_bit_count > RENAULT_V0_DECODER_BIT_LIMIT) { renault_v0_decode_candidate(instance); instance->decoder.parser_step = RenaultV0DecoderStepReset; @@ -1199,9 +652,8 @@ void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t } if(!renault_v0_classify_event(duration, level, &event_code)) { - const bool starts_next_burst = level && (duration >= RENAULT_V0_SYNC_MIN_US); renault_v0_decode_candidate(instance); - if(starts_next_burst) { + if(level && (duration >= RENAULT_V0_SYNC_MIN_US)) { instance->decoder.parser_step = RenaultV0DecoderStepData; instance->decoded_bit_count = 0U; instance->manchester_state = 1U; @@ -1226,25 +678,8 @@ void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t } } -static uint32_t renault_v0_arm_lsl(uint32_t value, uint32_t shift) { - shift &= 0xFFU; - if(shift >= 32U) { - return 0U; - } - return value << shift; -} - -static uint32_t renault_v0_arm_lsr(uint32_t value, uint32_t shift) { - shift &= 0xFFU; - if(shift >= 32U) { - return 0U; - } - return value >> shift; -} - uint8_t subghz_protocol_decoder_renault_v0_get_hash_data(void* context) { furi_assert(context); - SubGhzProtocolDecoderRenaultV0* instance = context; const uint32_t low = (uint32_t)instance->decoder.decode_data; const uint32_t high = (uint32_t)(instance->decoder.decode_data >> 32U); @@ -1254,7 +689,6 @@ uint8_t subghz_protocol_decoder_renault_v0_get_hash_data(void* context) { uint32_t mixed = renault_v0_arm_lsr(low, shift); mixed |= renault_v0_arm_lsl(high, 32U - shift); mixed |= renault_v0_arm_lsr(high, shift - 32U); - hash ^= mixed; hash = ((hash << 1U) & 0xFEU) | ((hash >> 7U) & 1U); } @@ -1264,61 +698,22 @@ uint8_t subghz_protocol_decoder_renault_v0_get_hash_data(void* context) { } void subghz_protocol_decoder_renault_v0_get_string(void* context, FuriString* output) { - furi_assert(context); - + furi_check(context); SubGhzProtocolDecoderRenaultV0* instance = context; - + furi_string_printf( + output, "%s %ubit\r\n", instance->generic.protocol_name, instance->packet_bit_count); furi_string_cat_printf( output, - "%s %dbit\r\n" - "Key:%016llX\r\n" - "Key2:%05lX Sn:%06lX\r\n" - "Btn:%01X [%s] Cnt:%02lX\r\n" - "C1:[%s] C2:[%s]\r\n" - "IC:[%s]", - instance->generic.protocol_name, - instance->packet_bit_count, - instance->generic.data, - instance->key2, - instance->generic.serial, + "Key:%016llX\r\nKey2:%05lX Sn:%06lX\r\nBtn:%01X [%s] Cnt:%02lX\r\nC1:[%s] C2:[%s] IC:[%s]", + (unsigned long long)instance->generic.data, + (unsigned long)instance->key2, + (unsigned long)instance->generic.serial, instance->generic.btn, - renault_v0_get_button_name(instance->type_id, instance->generic.btn), - instance->generic.cnt, + renault_v0_get_button_name(instance->generic.btn), + (unsigned long)instance->generic.cnt, instance->check_c1 ? "ERR" : "OK", instance->check_c2 ? "ERR" : "OK", - instance->check_ic ? "MISS" : "MATCH"); -} - -bool renault_v0_flipper_is_rolling(FlipperFormat* flipper_format) { - if(!flipper_format) { - return false; - } - - uint32_t rolling = 0U; - flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32(flipper_format, RENAULT_V0_ROLLING_FIELD, &rolling, 1)) { - return rolling != 0U; - } - - SubGhzBlockGeneric generic = {0}; - flipper_format_rewind(flipper_format); - if(subghz_block_generic_deserialize_check_count_bit( - &generic, flipper_format, RENAULT_V0_MIN_BITS) != SubGhzProtocolStatusOk) { - return false; - } - - uint32_t key2 = 0U; - flipper_format_rewind(flipper_format); - if(!flipper_format_read_uint32(flipper_format, RENAULT_V0_KEY2_FIELD, &key2, 1)) { - return false; - } - - RenaultV0DecodeAttempt attempt = {0}; - if(!renault_v0_classify_frame(generic.data, key2, &attempt)) { - return false; - } - - return (attempt.type_id == RenaultV0Type13) && attempt.ic_ok; + instance->check_ic ? "ERR" : "OK"); } SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_serialize( @@ -1326,7 +721,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_serialize( FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); - SubGhzProtocolDecoderRenaultV0* instance = context; instance->generic.data_count_bit = instance->packet_bit_count; @@ -1336,45 +730,29 @@ SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_serialize( return status; } - status = renault_v0_write_display( - flipper_format, instance->generic.protocol_name, instance->type_id, instance->generic.btn); - if(status != SubGhzProtocolStatusOk) { - return status; - } - - if(!flipper_format_write_uint32(flipper_format, RENAULT_V0_KEY2_FIELD, &instance->key2, 1)) { - return SubGhzProtocolStatusErrorParserOthers; - } - - const uint32_t preamble_bits = instance->preamble_bits; - if(!flipper_format_write_uint32(flipper_format, RENAULT_V0_PREAMBLE_FIELD, &preamble_bits, 1)) { - return SubGhzProtocolStatusErrorParserOthers; - } - - status = pp_serialize_fields( + SubGhzProtocolStatus fields = pp_serialize_fields( flipper_format, PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT, instance->generic.serial, instance->generic.btn, instance->generic.cnt, - 0U); - if(status != SubGhzProtocolStatusOk) { - return status; + 0); + if(fields != SubGhzProtocolStatusOk) { + return fields; } - const uint32_t rolling = ((instance->type_id == RenaultV0Type13) && !instance->check_ic) ? 1U : - 0U; - if(!flipper_format_write_uint32(flipper_format, RENAULT_V0_ROLLING_FIELD, &rolling, 1)) { + if(!flipper_format_write_uint32(flipper_format, RENAULT_V0_KEY2_FIELD, &instance->key2, 1)) { return SubGhzProtocolStatusErrorParserOthers; } - - return SubGhzProtocolStatusOk; + return pp_write_display( + flipper_format, + instance->generic.protocol_name, + renault_v0_get_button_name(instance->generic.btn)); } SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); - SubGhzProtocolDecoderRenaultV0* instance = context; SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit( &instance->generic, flipper_format, RENAULT_V0_MIN_BITS); @@ -1388,15 +766,9 @@ SubGhzProtocolStatus } instance->key2 = key2; - uint32_t preamble_bits = 0U; - flipper_format_rewind(flipper_format); - if(flipper_format_read_uint32(flipper_format, RENAULT_V0_PREAMBLE_FIELD, &preamble_bits, 1) && - renault_v0_preamble_bits_valid((uint8_t)preamble_bits)) { - instance->preamble_bits = (uint8_t)preamble_bits; - } else { - instance->preamble_bits = 0U; + if(!renault_v0_type13_valid(instance->generic.data, instance->key2)) { + return SubGhzProtocolStatusError; } - instance->packet_bit_count = RENAULT_V0_MIN_BITS; instance->generic.data_count_bit = RENAULT_V0_MIN_BITS; instance->decoder.decode_data = instance->generic.data; @@ -1409,24 +781,11 @@ SubGhzProtocolStatus instance->generic.serial = serial; instance->generic.btn = button; instance->generic.cnt = counter; - - RenaultV0DecodeAttempt attempt = {0}; - if(renault_v0_classify_frame(instance->generic.data, instance->key2, &attempt)) { - instance->type_id = attempt.type_id; - instance->type_tag = attempt.type_tag; - if(instance->preamble_bits == 0U) { - instance->preamble_bits = renault_v0_default_preamble_bits(attempt.type_id); - } - instance->check_c1 = !attempt.c1_ok; - instance->check_c2 = !attempt.c2_ok; - instance->check_ic = !attempt.ic_ok; - } else { - instance->type_id = RenaultV0TypeUnknown; - instance->type_tag = 0U; - instance->check_c1 = true; - instance->check_c2 = true; - instance->check_ic = true; - } - + renault_v0_update_checks( + instance->generic.data, + instance->key2, + &instance->check_c1, + &instance->check_c2, + &instance->check_ic); return status; } diff --git a/protocols/renault_v0.h b/protocols/renault_v0.h index bde7339..9e01412 100644 --- a/protocols/renault_v0.h +++ b/protocols/renault_v0.h @@ -16,8 +16,6 @@ extern const SubGhzProtocol renault_v0_protocol; -bool renault_v0_flipper_is_rolling(FlipperFormat* flipper_format); - void* subghz_protocol_decoder_renault_v0_alloc(SubGhzEnvironment* environment); void subghz_protocol_decoder_renault_v0_reset(void* context); void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t duration); diff --git a/protocols/renault_v1.c b/protocols/renault_v1.c new file mode 100644 index 0000000..1e03587 --- /dev/null +++ b/protocols/renault_v1.c @@ -0,0 +1,1568 @@ +#include "renault_v1.h" +#include "protocols_common.h" + +#include +#include +#include + +#define TAG "RenaultV1Protocol" + +#define HITAG2_TE_US 125U +#define HITAG2_HEADER_LOW_US 1500U +#define HITAG2_HEADER_HIGH_US 1000U +#define HITAG2_SHORT_GAP_US 21500U +#define HITAG2_PREAMBLE_PAIRS 250U +#define HITAG2_LONG_FRAMES 3U +#define HITAG2_SHORT_FRAMES 3U +#define HITAG2_UPLOAD_CAPACITY 1295U +_Static_assert( + HITAG2_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, + "HITAG2_UPLOAD_CAPACITY exceeds shared upload slab"); +#define HITAG2_MIN_COUNT_BIT 88U +#define HITAG2_HEADER_BITS 16U +#define HITAG2_KEY_BITS 64U +#define HITAG2_KEY2_BITS 24U +#define HITAG2_SHORT_KEY_BITS 10U +#define HITAG2_KEY_END_BITS (HITAG2_HEADER_BITS + HITAG2_KEY_BITS) +#define HITAG2_LONG_FRAME_BITS (HITAG2_KEY_END_BITS + HITAG2_KEY2_BITS) +#define HITAG2_RECOVERED_YES 1U +#define HITAG2_RECOVERED_BF_MISS 2U +#define HITAG2_KEY_FIELD "Hitag2 Key" +#define HITAG2_EPOCH_FIELD "Hitag2 Epoch" + +#define HITAG2_HEADER_LOW_MIN_US 1150U +#define HITAG2_HEADER_LOW_MAX_US 2200U +#define HITAG2_HEADER_HIGH_MIN_US 800U +#define HITAG2_HEADER_HIGH_MAX_US 1150U +#define HITAG2_DATA_IGNORE_US 49U +#define HITAG2_DATA_RESET_US 520U +#define HITAG2_TE_HIGH_INIT_US 120U +#define HITAG2_TE_LOW_INIT_US 150U +#define HITAG2_HOP_FIELD "Hop" + +static const SubGhzBlockConst renault_v1_const = { + .te_short = HITAG2_TE_US, + .te_long = HITAG2_TE_US * 2U, + .te_delta = 50, + .min_count_bit_for_found = HITAG2_MIN_COUNT_BIT, +}; + +struct SubGhzProtocolDecoderRenaultV1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_state; + uint16_t te_high; + uint16_t te_low; + uint16_t header; + uint8_t recovered; + uint8_t hitag2_key[6]; + uint32_t hop; + uint8_t tail_bits; + bool hitag2_key_valid; + uint64_t last_data; + uint64_t last_data_2; + bool last_frame_valid; +}; + +#if PROTOPIRATE_WITH_ENCODER +struct SubGhzProtocolEncoderRenaultV1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint8_t recovered; + uint8_t hitag2_key[6]; + uint8_t tail_bits; + bool hitag2_key_valid; +}; +#endif + +typedef enum { + RenaultV1DecoderStepReset = 0, + RenaultV1DecoderStepCheckSync = 2, + RenaultV1DecoderStepData = 3, +} RenaultV1DecoderStep; + + +const SubGhzProtocolDecoder renault_v1_decoder = { + .alloc = subghz_protocol_decoder_renault_v1_alloc, + .free = pp_decoder_free_default, + + .feed = subghz_protocol_decoder_renault_v1_feed, + .reset = subghz_protocol_decoder_renault_v1_reset, + + .get_hash_data = subghz_protocol_decoder_renault_v1_get_hash_data, + .serialize = subghz_protocol_decoder_renault_v1_serialize, + .deserialize = subghz_protocol_decoder_renault_v1_deserialize, + .get_string = subghz_protocol_decoder_renault_v1_get_string, +}; + +#if PROTOPIRATE_WITH_ENCODER +const SubGhzProtocolEncoder renault_v1_encoder = { + .alloc = subghz_protocol_encoder_renault_v1_alloc, + .free = pp_encoder_free, + .deserialize = subghz_protocol_encoder_renault_v1_deserialize, + .stop = pp_encoder_stop, + .yield = pp_encoder_yield, +}; +#else +const SubGhzProtocolEncoder renault_v1_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; +#endif + +const SubGhzProtocol renault_v1_protocol = { + .name = RENAULT_PROTOCOL_V1_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, +#if PROTOPIRATE_WITH_DECODER + .decoder = &renault_v1_decoder, +#else + .decoder = NULL, +#endif +#if PROTOPIRATE_WITH_ENCODER + .encoder = &renault_v1_encoder, +#else + .encoder = NULL, +#endif +}; + +static const char* hitag2_get_button_name(uint8_t btn) { + static const char* const names[] = { + "Sync", + "Lock", + "Unlock", + "??", + "Trunk", + "??", + "??", + "??", + "Panic", + }; + return (btn < COUNT_OF(names)) ? names[btn] : "??"; +} + +static void hitag2_u64_to_bytes_be(uint64_t value, uint8_t* out, size_t nbytes) { + for(size_t i = 0; i < nbytes; i++) { + out[i] = (uint8_t)(value >> (8U * (nbytes - 1U - i))); + } +} + +static uint64_t hitag2_bytes_to_u64_be(const uint8_t* data, size_t nbytes) { + uint64_t value = 0; + for(size_t i = 0; i < nbytes; i++) { + value = (value << 8U) | data[i]; + } + return value; +} + +static void hitag2_pack_key_bytes(uint64_t key, uint64_t key_2, uint8_t raw[11]) { + hitag2_u64_to_bytes_be(key, raw, 8); + raw[8] = (uint8_t)(key_2 >> 16U); + raw[9] = (uint8_t)(key_2 >> 8U); + raw[10] = (uint8_t)key_2; +} + +static uint8_t hitag2_frame_xor(const uint8_t raw[11]) { + uint8_t value = 0; + for(size_t i = 0; i < 10; i++) { + value ^= raw[i]; + } + return value; +} + +static uint8_t hitag2_i4(uint64_t x, uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return (uint8_t)((((x >> a) & 1U) << 3U) | (((x >> b) & 1U) << 2U) | (((x >> c) & 1U) << 1U) | + ((x >> d) & 1U)); +} + +static uint8_t hitag2_f20(uint64_t state) { + const uint8_t s0 = (uint8_t)((0x3C65U >> hitag2_i4(state, 2U, 3U, 5U, 6U)) & 1U); + const uint8_t s1 = (uint8_t)((0x0EE5U >> hitag2_i4(state, 8U, 12U, 14U, 15U)) & 1U); + const uint8_t s2 = (uint8_t)((0x0EE5U >> hitag2_i4(state, 17U, 21U, 23U, 26U)) & 1U); + const uint8_t s3 = (uint8_t)((0x0EE5U >> hitag2_i4(state, 28U, 29U, 31U, 33U)) & 1U); + const uint8_t s4 = (uint8_t)((0x3C65U >> hitag2_i4(state, 34U, 43U, 44U, 46U)) & 1U); + return (uint8_t)((0x0DD3929BUL >> ((s0 << 4U) | (s1 << 3U) | (s2 << 2U) | (s3 << 1U) | s4)) & + 1U); +} + +static uint64_t hitag2_lfsr(uint64_t state) { + const uint64_t fb = + (state ^ (state >> 2U) ^ (state >> 3U) ^ (state >> 6U) ^ (state >> 7U) ^ (state >> 8U) ^ + (state >> 16U) ^ (state >> 22U) ^ (state >> 23U) ^ (state >> 26U) ^ (state >> 30U) ^ + (state >> 41U) ^ (state >> 42U) ^ (state >> 43U) ^ (state >> 46U) ^ (state >> 47U)) & + 1ULL; + return (state >> 1U) | (fb << 47U); +} + +static uint64_t hitag2_key_to_u64(const uint8_t key[6]) { + uint64_t key64 = 0ULL; + for(size_t i = 0; i < 6U; i++) { + key64 = (key64 << 8U) | key[i]; + } + return key64; +} + +static uint32_t hitag2_authenticator( + uint32_t uid, + uint8_t button, + uint32_t counter, + const uint8_t key[6]) { + const uint64_t key64 = hitag2_key_to_u64(key); + const uint32_t nonce = (counter << 4U) | ((uint32_t)button & 0x0FU); + uint64_t state = 0ULL; + for(uint8_t i = 32U; i < 48U; i++) { + state = (state << 1U) | ((key64 >> i) & 1ULL); + } + for(uint8_t i = 0U; i < 32U; i++) { + state = (state << 1U) | ((uint64_t)((uid >> i) & 1U)); + } + for(uint8_t i = 0U; i < 32U; i++) { + const uint64_t nonce_bit = (uint64_t)hitag2_f20(state) ^ ((nonce >> (31U - i)) & 1U); + state = (state >> 1U) | (((nonce_bit ^ ((key64 >> (31U - i)) & 1ULL)) & 1ULL) << 47U); + } + uint32_t hop = 0U; + for(uint8_t i = 0U; i < 32U; i++) { + hop = (hop << 1U) | hitag2_f20(state); + state = hitag2_lfsr(state); + } + return hop; +} + +static uint16_t hitag2_frame_cnt10(const uint8_t raw[11]) { + return (uint16_t)(((uint16_t)(raw[4] & 0x0FU) << 6U) | (raw[5] >> 2U)); +} + +static uint32_t hitag2_frame_hop(const uint8_t raw[11]) { + return ((uint32_t)(raw[5] & 3U) << 30U) | ((uint32_t)raw[6] << 22U) | + ((uint32_t)raw[7] << 14U) | ((uint32_t)raw[8] << 6U) | (raw[9] >> 2U); +} + +static uint8_t hitag2_frame_tail(const uint8_t raw[11]) { + return (uint8_t)(raw[9] & 3U); +} + +#if PROTOPIRATE_WITH_ENCODER +static void hitag2_pack_auth_frame( + uint32_t uid, + uint8_t btn, + uint16_t cnt10, + uint32_t hop, + uint8_t tail, + uint8_t raw[11]) { + raw[0] = (uint8_t)(uid >> 24U); + raw[1] = (uint8_t)(uid >> 16U); + raw[2] = (uint8_t)(uid >> 8U); + raw[3] = (uint8_t)uid; + raw[4] = (uint8_t)(((btn & 0x0FU) << 4U) | ((cnt10 >> 6U) & 0x0FU)); + raw[5] = (uint8_t)(((cnt10 & 0x3FU) << 2U) | ((hop >> 30U) & 3U)); + raw[6] = (uint8_t)(hop >> 22U); + raw[7] = (uint8_t)(hop >> 14U); + raw[8] = (uint8_t)(hop >> 6U); + raw[9] = (uint8_t)(((hop << 2U) & 0xFCU) | (tail & 3U)); + raw[10] = hitag2_frame_xor(raw); +} + +static void hitag2_apply_raw(uint8_t raw[11], uint64_t* data, uint64_t* data_2) { + *data = hitag2_bytes_to_u64_be(raw, 8); + *data_2 = ((uint64_t)raw[8] << 16U) | ((uint64_t)raw[9] << 8U) | raw[10]; +} +#endif + +static bool hitag2_key_nonzero(const uint8_t key[6]) { + for(size_t i = 0; i < 6U; i++) { + if(key[i]) return true; + } + return false; +} + +static void hitag2_flipper_u32(FlipperFormat* ff, const char* key, uint32_t value) { + flipper_format_rewind(ff); + if(!flipper_format_update_uint32(ff, key, &value, 1)) { + flipper_format_rewind(ff); + flipper_format_insert_or_update_uint32(ff, key, &value, 1); + } +} + +static bool hitag2_read_hex_be( + FlipperFormat* flipper_format, + const char* name, + uint8_t* data, + size_t nbytes); + +static bool hitag2_read_key(FlipperFormat* ff, uint8_t key[6]) { + memset(key, 0, 6U); + if(!hitag2_read_hex_be(ff, HITAG2_KEY_FIELD, key, 6U)) { + return false; + } + return hitag2_key_nonzero(key); +} + +static bool hitag2_key_matches_hop( + const uint8_t key[6], + uint32_t uid, + uint8_t btn, + uint16_t cnt10, + uint32_t hop) { + return hitag2_authenticator(uid, btn, cnt10 & 0x3FFU, key) == hop; +} + +static void hitag2_unpack_frame( + uint64_t data, + uint64_t data_2, + uint32_t* serial, + uint8_t* btn, + uint16_t* cnt10, + uint32_t* hop, + uint8_t* tail) { + uint8_t raw[11]; + hitag2_pack_key_bytes(data, data_2, raw); + if(serial) { + *serial = (uint32_t)(data >> 32U); + } + if(btn) { + *btn = (uint8_t)((raw[4] >> 4U) & 0x0FU); + } + if(cnt10) { + *cnt10 = hitag2_frame_cnt10(raw); + } + if(hop) { + *hop = hitag2_frame_hop(raw); + } + if(tail) { + *tail = hitag2_frame_tail(raw); + } +} + +static bool hitag2_read_hex_be( + FlipperFormat* flipper_format, + const char* name, + uint8_t* data, + size_t nbytes) { + if(!flipper_format_rewind(flipper_format)) { + return false; + } + return flipper_format_read_hex(flipper_format, name, data, nbytes); +} + +static bool hitag2_write_hex_be( + FlipperFormat* flipper_format, + const char* name, + uint64_t value, + size_t nbytes) { + uint8_t data[8] = {0}; + furi_check(nbytes <= sizeof(data)); + hitag2_u64_to_bytes_be(value, data, nbytes); + return flipper_format_insert_or_update_hex(flipper_format, name, data, nbytes); +} + +static uint8_t hitag2_extract_bits(uint32_t value, uint8_t lsb, uint8_t width) { + return (uint8_t)((value >> lsb) & ((1U << width) - 1U)); +} + +static void hitag2_serial_permute(const uint8_t serial[4], uint8_t perm[6]) { + const uint8_t sn0 = serial[0]; + const uint8_t sn1 = serial[1]; + const uint8_t sn2 = serial[2]; + const uint8_t sn3 = serial[3]; + + uint8_t acc = + (uint8_t)(((sn0 >> 6) & 2U) | ((sn1 >> 4) & 8U) | hitag2_extract_bits(sn0 ^ 0x10U, 4, 1)); + acc |= (uint8_t)((~(uint32_t)(sn0 << 2)) & 0x20U); + acc |= (uint8_t)((sn0 << 5) & 0x40U); + acc |= (uint8_t)((sn2 << 1) & 0x80U); + acc |= (uint8_t)((~(uint32_t)(sn2 >> 5)) & 4U); + acc |= (uint8_t)((~(uint32_t)(sn0 >> 2)) & 0x10U); + perm[0] = acc; + + const uint8_t sn1_inv_shr3 = (uint8_t) ~(sn1 >> 3); + const uint8_t sn3_inv_shl3 = (uint8_t) ~(sn3 << 3); + acc = (uint8_t)(hitag2_extract_bits(sn1, 5, 1) | (sn1_inv_shr3 & 4U) | (sn0 & 0x20U)); + acc |= (uint8_t)(sn3_inv_shl3 & 8U); + acc |= (uint8_t)((~(uint32_t)(sn2 << 2)) & 0x10U); + acc |= (uint8_t)((~(uint32_t)(sn2 << 3)) & 0x40U); + acc |= 0x80U; + perm[1] = acc; + + const uint8_t sn0_shr3 = (uint8_t)(sn0 >> 3); + acc = (uint8_t)(hitag2_extract_bits(sn0, 2, 1) | (sn0_shr3 & 2U) | ((~(uint32_t)(sn3 >> 2)) & 4U) | + (sn1 & 0x10U)); + acc |= (uint8_t)((~(uint32_t)(sn1 << 4)) & 0x20U); + acc |= (uint8_t)(sn3_inv_shl3 & 0x40U); + acc |= 0x80U; + perm[2] = acc; + + const uint8_t sn2_inv_shl6 = (uint8_t) ~(sn2 << 6); + acc = (uint8_t)(((sn0 >> 2) & 2U) | ((sn1 >> 3) & 8U) | hitag2_extract_bits(sn2 ^ 0x20U, 5, 1)); + acc |= (uint8_t)((sn1 << 5) & 0x20U); + acc |= (uint8_t)((sn3 << 1) & 0x40U); + acc |= (uint8_t)(((uint8_t)~sn0_shr3) & 4U); + acc |= (uint8_t)(sn1_inv_shr3 & 0x10U); + acc |= (uint8_t)(sn2_inv_shl6 & 0x80U); + perm[3] = acc; + + uint8_t perm4_lo = + (uint8_t)(((sn3 << 2) & 8U) | ((sn0 << 4) & 0x10U) | hitag2_extract_bits(sn0 ^ 4U, 2, 1)); + perm4_lo |= (uint8_t)((~(uint32_t)(sn3 >> 1)) & 2U); + perm4_lo |= (uint8_t)((~(uint32_t)(sn1 >> 4)) & 4U); + uint8_t perm4_hi = (uint8_t)((~(uint32_t)(sn0 << 4)) & 0x20U); + perm4_hi |= (uint8_t)(sn2_inv_shl6 & 0x40U); + const uint8_t sn1_inv_shl3 = (uint8_t) ~(sn1 << 3); + perm4_hi |= (uint8_t)(sn1_inv_shl3 & 0x80U); + perm[4] = (uint8_t)(perm4_lo | perm4_hi); + + uint8_t perm5 = + (uint8_t)(((sn3 >> 2) & 0x10U) | ((sn2 >> 3) & 2U) | (((uint8_t)~sn0) & 0x80U)); + perm5 |= (uint8_t)((~(uint32_t)(sn0 << 3)) & 8U); + perm5 |= (uint8_t)((sn0 >> 2) & 0x10U); + perm5 |= (uint8_t)(sn1_inv_shl3 & 0x20U); + perm5 |= (uint8_t)((sn3 >> 1) & 0x40U); + perm5 |= (uint8_t)((~(uint32_t)(sn1 >> 1)) & 4U); + perm[5] = perm5; +} + +static uint8_t hitag2_truth(uint32_t table, uint8_t index) { + return (uint8_t)((table >> index) & 1U); +} + +static uint8_t hitag2_filter_index(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return (uint8_t)((a << 3U) | (b << 2U) | (c << 1U) | d); +} + +static uint8_t hitag2_byte_bit(uint8_t byte, uint8_t bit) { + return (uint8_t)((byte >> bit) & 1U); +} + +static uint8_t hitag2_filter(const uint8_t state[6]) { + uint8_t group = 0; + group |= hitag2_truth( + 0x2C79U, + hitag2_filter_index( + hitag2_byte_bit(state[0], 1), + hitag2_byte_bit(state[0], 2), + hitag2_byte_bit(state[0], 4), + hitag2_byte_bit(state[0], 5))); + group |= (uint8_t)(hitag2_truth( + 0x6671U, + hitag2_filter_index( + hitag2_byte_bit(state[1], 0), + hitag2_byte_bit(state[1], 1), + hitag2_byte_bit(state[1], 3), + hitag2_byte_bit(state[1], 7))) + << 1U); + group |= (uint8_t)(hitag2_truth( + 0x6671U, + hitag2_filter_index( + hitag2_byte_bit(state[3], 5), + hitag2_byte_bit(state[2], 0), + hitag2_byte_bit(state[2], 2), + hitag2_byte_bit(state[2], 6))) + << 2U); + group |= (uint8_t)(hitag2_truth( + 0x6671U, + hitag2_filter_index( + hitag2_byte_bit(state[4], 6), + hitag2_byte_bit(state[3], 0), + hitag2_byte_bit(state[3], 2), + hitag2_byte_bit(state[3], 3))) + << 3U); + group |= (uint8_t)(hitag2_truth( + 0x2C79U, + hitag2_filter_index( + hitag2_byte_bit(state[5], 1), + hitag2_byte_bit(state[5], 3), + hitag2_byte_bit(state[5], 4), + hitag2_byte_bit(state[4], 5))) + << 4U); + return hitag2_truth(0x7907287BUL, group); +} + +static uint8_t hitag2_parity8(uint8_t value) { + value ^= (uint8_t)(value >> 4U); + value ^= (uint8_t)(value >> 2U); + value ^= (uint8_t)(value >> 1U); + return (uint8_t)(value & 1U); +} + +static uint8_t hitag2_feedback(const uint8_t state[6]) { + static const uint8_t masks[6] = {0xB3U, 0x80U, 0x83U, 0x22U, 0x00U, 0x73U}; + uint8_t feedback = 0; + for(uint8_t i = 0; i < 6; i++) { + feedback ^= hitag2_parity8((uint8_t)(state[i] & masks[i])); + } + return (uint8_t)(feedback & 1U); +} + +static void hitag2_shift_state(uint8_t state[6], uint8_t input) { + for(uint8_t i = 0; i < 5; i++) { + state[i] = (uint8_t)((state[i] << 1U) | (state[i + 1U] >> 7U)); + } + state[5] = (uint8_t)((state[5] << 1U) | (input & 1U)); +} + +static void hitag2_shift_u32(uint8_t buf[4], uint8_t inject) { + const uint8_t b0 = buf[0]; + const uint8_t b1 = buf[1]; + const uint8_t b2 = buf[2]; + const uint8_t b3 = buf[3]; + buf[3] = (uint8_t)((b2 >> 7U) | (b3 << 1U)); + buf[2] = (uint8_t)((b1 >> 7U) | (b2 << 1U)); + buf[1] = (uint8_t)((b0 >> 7U) | (b1 << 1U)); + buf[0] = (uint8_t)((b0 << 1U) | (inject & 1U)); +} + +static void hitag2_clock_cipher(uint8_t state[6], uint8_t iv_work[4], uint8_t iv_orig[4]) { + for(uint8_t i = 0; i < 32; i++) { + const uint8_t filter = hitag2_filter(state); + uint8_t mix = (iv_work[3] & 0x80U) ? (filter ? 0U : 1U) : (filter ? 1U : 0U); + if(iv_orig[3] & 0x80U) { + mix ^= 5U; + } + hitag2_shift_state(state, (uint8_t)(mix & 1U)); + hitag2_shift_u32(iv_work, 0); + hitag2_shift_u32(iv_orig, (uint8_t)((mix >> 2U) & 1U)); + } + + for(uint8_t i = 0; i < 32; i++) { + hitag2_shift_u32(iv_work, 0); + if(hitag2_filter(state)) { + iv_work[0] |= 1U; + } + hitag2_shift_state(state, hitag2_feedback(state)); + } +} + +#if PROTOPIRATE_WITH_ENCODER +static void hitag2_build_iv(uint32_t cnt, uint8_t btn, uint32_t seed, uint8_t iv[4]) { + iv[0] = (uint8_t)(((cnt << 4U) & 0xF0U) | (btn & 0x0FU)); + iv[1] = (uint8_t)((cnt >> 4U) & 0xFFU); + iv[2] = (uint8_t)(((seed >> 8U) & 0xF0U) | ((cnt >> 12U) & 0x0FU)); + iv[3] = (uint8_t)(seed & 0xFFU); +} + +static void + hitag2_encrypt_from_iv(const uint8_t serial_be[4], const uint8_t iv[4], uint8_t out[11]) { + uint8_t perm[6]; + uint8_t state[6]; + uint8_t iv_work[4]; + uint8_t iv_orig[4]; + + hitag2_serial_permute(serial_be, perm); + state[0] = serial_be[0]; + state[1] = serial_be[1]; + state[2] = serial_be[2]; + state[3] = serial_be[3]; + state[4] = perm[4]; + state[5] = perm[5]; + iv_work[0] = perm[0]; + iv_work[1] = perm[1]; + iv_work[2] = perm[2]; + iv_work[3] = perm[3]; + iv_orig[0] = iv[0]; + iv_orig[1] = iv[1]; + iv_orig[2] = iv[2]; + iv_orig[3] = iv[3]; + hitag2_clock_cipher(state, iv_work, iv_orig); + + const uint8_t hop0 = iv_work[0]; + uint8_t hop1 = (uint8_t)((hop0 >> 7U) | (iv_work[1] << 1U)); + uint8_t hop2 = (uint8_t)((iv_work[1] >> 7U) | (iv_work[2] << 1U)); + const uint8_t hop_ext = (uint8_t)(((hop0 >> 6U) & 1U) | (hop1 << 1U)); + uint8_t hop3 = (uint8_t)((iv_work[2] >> 7U) | (iv_work[3] << 1U)); + uint8_t hop4 = (uint8_t)((iv_work[3] >> 7U) | (state[5] << 1U)); + hop1 = (uint8_t)((hop1 >> 7U) | (hop2 << 1U)); + hop2 = (uint8_t)((hop2 >> 7U) | (hop3 << 1U)); + hop3 = (uint8_t)((hop3 >> 7U) | (hop4 << 1U)); + + uint32_t mix = ((uint32_t)iv[1] << 4U) | ((uint32_t)iv[0] >> 4U); + mix = (mix | (((uint32_t)iv[2] << 12U) & 0xFFFFU)) & 0xFFFFU; + + out[0] = serial_be[0]; + out[1] = serial_be[1]; + out[2] = serial_be[2]; + out[3] = serial_be[3]; + out[4] = (uint8_t)(((mix >> 6U) & 0x0FU) | ((iv[0] << 4U) & 0xF0U)); + out[5] = (uint8_t)((hop3 & 3U) | ((mix << 2U) & 0xFCU)); + out[6] = hop2; + out[7] = hop1; + out[8] = hop_ext; + out[9] = (uint8_t)((hop0 << 2U) | 2U); + out[10] = hitag2_frame_xor(out); +} + +static void hitag2_encrypt_frame( + uint32_t serial, + uint32_t cnt, + uint8_t btn, + uint32_t seed, + uint8_t out[11], + uint8_t iv[4]) { + const uint8_t serial_be[4] = { + (uint8_t)(serial >> 24U), + (uint8_t)(serial >> 16U), + (uint8_t)(serial >> 8U), + (uint8_t)serial, + }; + hitag2_build_iv(cnt, btn, seed, iv); + hitag2_encrypt_from_iv(serial_be, iv, out); +} +#endif + +static void hitag2_apply_check_remote( + uint64_t data, + uint64_t data_2, + uint8_t recovered, + uint32_t seed, + uint32_t* serial, + uint8_t* btn, + uint32_t* cnt, + uint32_t* hop, + uint8_t* tail) { + uint16_t cnt10 = 0; + hitag2_unpack_frame(data, data_2, serial, btn, &cnt10, hop, tail); + if(recovered == HITAG2_RECOVERED_YES) { + *cnt = ((seed >> 12U) & 0xFF0U) | ((seed << 4U) & 0xF000U) | (seed >> 28U); + } else { + *cnt = cnt10; + } +} + +static void renault_v1_check_remote_controller(SubGhzProtocolDecoderRenaultV1* instance) { + hitag2_apply_check_remote( + instance->generic.data, + instance->generic.data_2, + instance->recovered, + instance->generic.seed, + &instance->generic.serial, + &instance->generic.btn, + &instance->generic.cnt, + &instance->hop, + &instance->tail_bits); +} + +#if PROTOPIRATE_WITH_ENCODER +static bool hitag2_encoder_next_frame( + SubGhzProtocolEncoderRenaultV1* instance, + uint32_t orig_uid, + uint8_t orig_btn, + uint16_t orig_cnt10, + uint32_t orig_hop, + uint8_t tail) { + uint8_t raw[11]; + if(!hitag2_key_matches_hop( + instance->hitag2_key, orig_uid, orig_btn, orig_cnt10, orig_hop)) { + return false; + } + + uint8_t tx_btn = instance->generic.btn; + if(tx_btn == 0U) { + tx_btn = orig_btn; + } + uint32_t uid = instance->generic.serial ? instance->generic.serial : orig_uid; + uint16_t cnt10 = (uint16_t)(instance->generic.cnt & 0x3FFU); + uint32_t hop = hitag2_authenticator(uid, tx_btn, cnt10, instance->hitag2_key); + + hitag2_pack_auth_frame(uid, tx_btn, cnt10, hop, tail, raw); + hitag2_apply_raw(raw, &instance->generic.data, &instance->generic.data_2); + instance->generic.serial = uid; + instance->generic.btn = tx_btn; + instance->generic.cnt = cnt10; + instance->tail_bits = tail; + instance->hitag2_key_valid = true; + return true; +} +#endif + +static void hitag2_write_named_fields( + FlipperFormat* ff, + uint32_t serial, + uint8_t btn, + uint32_t cnt, + uint32_t hop) { + hitag2_flipper_u32(ff, FF_SERIAL, serial); + hitag2_flipper_u32(ff, FF_BTN, btn); + hitag2_flipper_u32(ff, FF_CNT, cnt); + hitag2_flipper_u32(ff, HITAG2_HOP_FIELD, hop); +} + +static void hitag2_read_recovered_and_seed( + FlipperFormat* flipper_format, + uint8_t* recovered, + uint32_t* seed) { + *recovered = 0; + *seed = 0; + + uint8_t recovered_hex = 0; + if(hitag2_read_hex_be(flipper_format, "Recovered", &recovered_hex, 1)) { + *recovered = recovered_hex; + } else { + uint32_t recovered_u32 = 0; + if(flipper_format_rewind(flipper_format) && + flipper_format_read_uint32(flipper_format, "Recovered", &recovered_u32, 1)) { + *recovered = (uint8_t)recovered_u32; + } + } + + uint8_t seed_be[4] = {0}; + if(hitag2_read_hex_be(flipper_format, "Seed", seed_be, 4)) { + *seed = (uint32_t)hitag2_bytes_to_u64_be(seed_be, 4); + return; + } + + if(flipper_format_rewind(flipper_format)) { + flipper_format_read_uint32(flipper_format, "Seed", seed, 1); + } +} + +#if PROTOPIRATE_WITH_ENCODER +void* subghz_protocol_encoder_renault_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRenaultV1* instance = calloc(1, sizeof(SubGhzProtocolEncoderRenaultV1)); + furi_check(instance); + + instance->base.protocol = &renault_v1_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + instance->encoder.repeat = 1; + instance->encoder.is_running = false; + + return instance; +} + +static bool + hitag2_encoder_add_level(LevelDuration* upload, size_t* index, bool level, uint32_t duration) { + if(*index >= HITAG2_UPLOAD_CAPACITY) { + return false; + } + upload[(*index)++] = level_duration_make(level, duration); + return true; +} + +static bool hitag2_encoder_add_bits( + LevelDuration* upload, + size_t* index, + uint64_t value, + uint8_t bit_count) { + for(uint8_t i = bit_count; i > 0; i--) { + const bool one = ((value >> (i - 1U)) & 1ULL) != 0; + if(one) { + if(!hitag2_encoder_add_level(upload, index, true, HITAG2_TE_US)) return false; + if(!hitag2_encoder_add_level(upload, index, false, HITAG2_TE_US)) return false; + } else { + if(!hitag2_encoder_add_level(upload, index, false, HITAG2_TE_US)) return false; + if(!hitag2_encoder_add_level(upload, index, true, HITAG2_TE_US)) return false; + } + } + return true; +} + +static bool renault_v1_encoder_get_upload(SubGhzProtocolEncoderRenaultV1* instance) { + furi_check(instance); + + size_t index = 0; + LevelDuration* upload = instance->encoder.upload; + const uint64_t key = instance->generic.data; + const uint64_t key_2 = instance->generic.data_2 & 0xFFFFFFULL; + + for(size_t i = 0; i < HITAG2_PREAMBLE_PAIRS; i++) { + if(!hitag2_encoder_add_level(upload, &index, true, HITAG2_TE_US)) return false; + if(!hitag2_encoder_add_level(upload, &index, false, HITAG2_TE_US)) return false; + } + + for(uint8_t frame = 0; frame < HITAG2_LONG_FRAMES; frame++) { + if(!hitag2_encoder_add_level(upload, &index, false, HITAG2_HEADER_LOW_US)) return false; + if(!hitag2_encoder_add_level(upload, &index, true, HITAG2_HEADER_HIGH_US)) return false; + if(!hitag2_encoder_add_bits(upload, &index, 1, HITAG2_HEADER_BITS)) return false; + if(!hitag2_encoder_add_bits(upload, &index, key, HITAG2_KEY_BITS)) return false; + if(!hitag2_encoder_add_bits(upload, &index, key_2, HITAG2_KEY2_BITS)) return false; + } + + const uint64_t short_key = (key >> 18U) & 0x3FFULL; + for(uint8_t frame = 0; frame < HITAG2_SHORT_FRAMES; frame++) { + if(!hitag2_encoder_add_level(upload, &index, false, HITAG2_HEADER_LOW_US)) return false; + if(!hitag2_encoder_add_level(upload, &index, true, HITAG2_HEADER_HIGH_US)) return false; + if(!hitag2_encoder_add_bits(upload, &index, 1, HITAG2_HEADER_BITS)) return false; + if(!hitag2_encoder_add_bits(upload, &index, short_key, HITAG2_SHORT_KEY_BITS)) + return false; + if(!hitag2_encoder_add_level(upload, &index, false, HITAG2_SHORT_GAP_US)) return false; + } + + instance->encoder.size_upload = index; + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_renault_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolEncoderRenaultV1* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + renault_v1_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + + uint8_t key2[8] = {0}; + if(!hitag2_read_hex_be(flipper_format, "Key_2", key2, sizeof(key2))) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->generic.data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); + + hitag2_read_recovered_and_seed( + flipper_format, &instance->recovered, &instance->generic.seed); + + if(!flipper_format_rewind(flipper_format)) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + + uint32_t hop = 0; + uint8_t tail = 0; + uint32_t wire_serial = 0; + uint8_t wire_btn = 0; + uint16_t wire_cnt10 = 0; + hitag2_unpack_frame( + instance->generic.data, + instance->generic.data_2, + &wire_serial, + &wire_btn, + &wire_cnt10, + &hop, + &tail); + hitag2_apply_check_remote( + instance->generic.data, + instance->generic.data_2, + instance->recovered, + instance->generic.seed, + &instance->generic.serial, + &instance->generic.btn, + &instance->generic.cnt, + &hop, + &tail); + instance->tail_bits = tail; + + const uint32_t orig_uid = wire_serial; + const uint8_t orig_btn = wire_btn; + const uint16_t orig_cnt10 = wire_cnt10; + const uint32_t orig_hop = hop; + + uint32_t serial = instance->generic.serial; + uint32_t btn = instance->generic.btn; + uint32_t cnt = instance->generic.cnt; + pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL); + instance->generic.serial = serial; + instance->generic.btn = (uint8_t)btn; + instance->generic.cnt = cnt; + + instance->hitag2_key_valid = false; + memset(instance->hitag2_key, 0, 6U); + const bool have_hitag2_key = hitag2_read_key(flipper_format, instance->hitag2_key); + if(have_hitag2_key) { + if(!hitag2_encoder_next_frame( + instance, orig_uid, orig_btn, orig_cnt10, orig_hop, tail) && + instance->recovered != HITAG2_RECOVERED_YES) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + } + + instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 1U); + if(instance->encoder.repeat == 0U) { + instance->encoder.repeat = 1U; + } + + if(!instance->hitag2_key_valid && instance->recovered == HITAG2_RECOVERED_YES) { + uint8_t out[11]; + uint8_t iv[4]; + hitag2_encrypt_frame( + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn, + instance->generic.seed, + out, + iv); + instance->generic.data = hitag2_bytes_to_u64_be(out, 8); + instance->generic.data_2 = ((uint64_t)out[8] << 16U) | ((uint64_t)out[9] << 8U) | + out[10]; + instance->generic.seed = ((uint32_t)iv[0] << 24U) | ((uint32_t)iv[1] << 16U) | + ((uint32_t)iv[2] << 8U) | iv[3]; + } else if(!instance->hitag2_key_valid && instance->recovered != HITAG2_RECOVERED_YES) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + + pp_encoder_buffer_ensure(instance, HITAG2_UPLOAD_CAPACITY); + + if(!renault_v1_encoder_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + + if(!flipper_format_rewind(flipper_format)) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + uint8_t key_data[8]; + hitag2_u64_to_bytes_be(instance->generic.data, key_data, 8); + if(!flipper_format_insert_or_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + hitag2_u64_to_bytes_be(instance->generic.data_2, key_data, 8); + if(!flipper_format_insert_or_update_hex(flipper_format, "Key_2", key_data, sizeof(key_data))) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(instance->recovered == HITAG2_RECOVERED_YES) { + const uint8_t recovered_hex = HITAG2_RECOVERED_YES; + if(!flipper_format_insert_or_update_hex( + flipper_format, "Recovered", &recovered_hex, 1)) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(!hitag2_write_hex_be(flipper_format, "Seed", instance->generic.seed, 4)) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + } + + { + uint32_t hop = 0; + hitag2_unpack_frame( + instance->generic.data, instance->generic.data_2, NULL, NULL, NULL, &hop, NULL); + hitag2_write_named_fields( + flipper_format, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt, + hop); + } + if(instance->hitag2_key_valid) { + flipper_format_rewind(flipper_format); + flipper_format_insert_or_update_hex( + flipper_format, HITAG2_KEY_FIELD, instance->hitag2_key, 6U); + hitag2_flipper_u32(flipper_format, HITAG2_EPOCH_FIELD, 0U); + } + + instance->encoder.front = 0; + instance->encoder.is_running = true; + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; +} + +#endif + +void* subghz_protocol_decoder_renault_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRenaultV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderRenaultV1)); + furi_check(instance); + instance->base.protocol = &renault_v1_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_renault_v1_reset(void* context) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + instance->decoder.parser_step = RenaultV1DecoderStepReset; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); +} + +static bool hitag2_duration_is_header_low(uint32_t duration) { + return (duration >= HITAG2_HEADER_LOW_MIN_US) && (duration <= HITAG2_HEADER_LOW_MAX_US); +} + +static uint32_t hitag2_data_threshold(uint16_t te) { + const uint32_t triple = (uint32_t)te * 3U; + if(triple < 300U) { + return 150U; + } + if(triple >= 422U) { + return 210U; + } + return triple / 2U; +} + +static uint16_t hitag2_adapt_te(uint16_t te, uint32_t duration) { + const uint32_t mixed = ((uint32_t)te * 7U) + duration; + if(mixed < 560U) { + return 70; + } + if(mixed >= 1488U) { + return 185; + } + return (uint16_t)(mixed / 8U); +} + +static bool hitag2_accept_frame(SubGhzProtocolDecoderRenaultV1* instance, uint64_t key_2) { + if(instance->header != 1U) { + return false; + } + + uint8_t raw[11]; + hitag2_pack_key_bytes(instance->generic.data, key_2, raw); + if(hitag2_frame_xor(raw) != raw[10]) { + return false; + } + + if(instance->last_frame_valid && instance->last_data == instance->generic.data && + instance->last_data_2 == key_2) { + return false; + } + + instance->generic.data_2 = key_2; + instance->generic.data_count_bit = HITAG2_MIN_COUNT_BIT; + instance->recovered = 0; + instance->generic.seed = 0; + uint16_t cnt10 = 0; + hitag2_unpack_frame( + instance->generic.data, + instance->generic.data_2, + &instance->generic.serial, + &instance->generic.btn, + &cnt10, + &instance->hop, + &instance->tail_bits); + instance->generic.cnt = cnt10; + instance->last_data = instance->generic.data; + instance->last_data_2 = key_2; + instance->last_frame_valid = true; + return true; +} + +void subghz_protocol_decoder_renault_v1_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + + while(true) { + switch(instance->decoder.parser_step) { + case RenaultV1DecoderStepReset: + if((!level) && hitag2_duration_is_header_low(duration)) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RenaultV1DecoderStepCheckSync; + } + return; + + case RenaultV1DecoderStepCheckSync: + if(level) { + if((duration < HITAG2_HEADER_HIGH_MIN_US) || + (duration > HITAG2_HEADER_HIGH_MAX_US) || + (instance->decoder.te_last < HITAG2_HEADER_LOW_MIN_US) || + (instance->decoder.te_last > HITAG2_HEADER_LOW_MAX_US)) { + instance->decoder.parser_step = RenaultV1DecoderStepReset; + return; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->manchester_state = ManchesterStateStart1; + instance->te_high = HITAG2_TE_HIGH_INIT_US; + instance->te_low = HITAG2_TE_LOW_INIT_US; + instance->decoder.parser_step = RenaultV1DecoderStepData; + return; + } + instance->decoder.parser_step = RenaultV1DecoderStepReset; + if(hitag2_duration_is_header_low(duration)) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RenaultV1DecoderStepCheckSync; + } + return; + + case RenaultV1DecoderStepData: + if(duration <= HITAG2_DATA_IGNORE_US) { + return; + } + if(duration > HITAG2_DATA_RESET_US) { + instance->decoder.parser_step = RenaultV1DecoderStepReset; + continue; + } + + uint16_t* te = level ? &instance->te_high : &instance->te_low; + ManchesterEvent event; + if(duration > hitag2_data_threshold(*te)) { + event = level ? ManchesterEventLongHigh : ManchesterEventLongLow; + } else { + event = level ? ManchesterEventShortHigh : ManchesterEventShortLow; + *te = hitag2_adapt_te(*te, duration); + } + + bool bit = false; + if(!manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &bit)) { + return; + } + + instance->decoder.decode_data = (instance->decoder.decode_data << 1U) | + (bit ? 1ULL : 0ULL); + instance->decoder.decode_count_bit++; + + if(instance->decoder.decode_count_bit == HITAG2_HEADER_BITS) { + instance->header = (uint16_t)~instance->decoder.decode_data; + instance->decoder.decode_data = 0; + return; + } + if(instance->decoder.decode_count_bit == HITAG2_KEY_END_BITS) { + instance->generic.data = ~instance->decoder.decode_data; + instance->decoder.decode_data = 0; + return; + } + if(instance->decoder.decode_count_bit == HITAG2_LONG_FRAME_BITS) { + const uint64_t key_2 = (~instance->decoder.decode_data) & 0xFFFFFFULL; + if(hitag2_accept_frame(instance, key_2) && instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RenaultV1DecoderStepData; + } + return; + + default: + instance->decoder.parser_step = RenaultV1DecoderStepReset; + return; + } + } +} + +uint8_t subghz_protocol_decoder_renault_v1_get_hash_data(void* context) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + return (uint8_t)instance->generic.serial ^ (uint8_t)(instance->generic.serial >> 8) ^ + (uint8_t)(instance->generic.serial >> 16) ^ (uint8_t)(instance->generic.serial >> 24) ^ + instance->generic.btn ^ (uint8_t)instance->generic.cnt ^ + (uint8_t)(instance->generic.cnt >> 8); +} + +static SubGhzProtocolStatus hitag2_write_extra_fields( + FlipperFormat* flipper_format, + uint64_t key_2, + uint8_t recovered, + uint32_t seed) { + if(!hitag2_write_hex_be(flipper_format, "Key_2", key_2, 8)) { + return SubGhzProtocolStatusErrorParserOthers; + } + uint8_t recovered_hex = 0; + if(recovered == HITAG2_RECOVERED_YES) { + recovered_hex = HITAG2_RECOVERED_YES; + } else if(recovered == HITAG2_RECOVERED_BF_MISS) { + recovered_hex = HITAG2_RECOVERED_BF_MISS; + } + if(!flipper_format_insert_or_update_hex(flipper_format, "Recovered", &recovered_hex, 1)) { + return SubGhzProtocolStatusErrorParserOthers; + } + if(!hitag2_write_hex_be(flipper_format, "Seed", seed, 4)) { + return SubGhzProtocolStatusErrorParserOthers; + } + return SubGhzProtocolStatusOk; +} + +SubGhzProtocolStatus subghz_protocol_decoder_renault_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + renault_v1_check_remote_controller(instance); + + const uint32_t serial = instance->generic.serial; + const uint8_t btn = instance->generic.btn; + const uint32_t cnt = instance->generic.cnt; + const uint32_t seed = instance->generic.seed; + const uint64_t data_2 = instance->generic.data_2; + instance->generic.serial = 0; + instance->generic.btn = 0; + instance->generic.cnt = 0; + instance->generic.seed = 0; + instance->generic.data_2 = 0; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + instance->generic.serial = serial; + instance->generic.btn = btn; + instance->generic.cnt = cnt; + instance->generic.seed = seed; + instance->generic.data_2 = data_2; + if(ret != SubGhzProtocolStatusOk) { + return ret; + } + + ret = hitag2_write_extra_fields( + flipper_format, instance->generic.data_2, instance->recovered, instance->generic.seed); + if(ret != SubGhzProtocolStatusOk) { + return ret; + } + hitag2_write_named_fields(flipper_format, serial, btn, cnt, instance->hop); + if(instance->hitag2_key_valid) { + flipper_format_rewind(flipper_format); + flipper_format_insert_or_update_hex( + flipper_format, HITAG2_KEY_FIELD, instance->hitag2_key, 6U); + hitag2_flipper_u32(flipper_format, HITAG2_EPOCH_FIELD, 0U); + } + return SubGhzProtocolStatusOk; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_renault_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + renault_v1_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + + uint8_t key2[8] = {0}; + if(!hitag2_read_hex_be(flipper_format, "Key_2", key2, sizeof(key2))) { + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->generic.data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); + + hitag2_read_recovered_and_seed( + flipper_format, &instance->recovered, &instance->generic.seed); + + renault_v1_check_remote_controller(instance); + instance->hitag2_key_valid = false; + memset(instance->hitag2_key, 0, 6U); + if(hitag2_read_key(flipper_format, instance->hitag2_key)) { + instance->hitag2_key_valid = hitag2_key_matches_hop( + instance->hitag2_key, + instance->generic.serial, + instance->generic.btn, + (uint16_t)(instance->generic.cnt & 0x3FFU), + instance->hop); + } + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; +} + +void subghz_protocol_decoder_renault_v1_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderRenaultV1* instance = context; + + renault_v1_check_remote_controller(instance); + + furi_string_printf( + output, + "%s\r\nK1:%016llX\r\nK2:%06llX Sn:%08lX\r\nBtn:%02X [%s] %db", + instance->generic.protocol_name, + (unsigned long long)instance->generic.data, + (unsigned long long)(instance->generic.data_2 & 0xFFFFFFULL), + (unsigned long)instance->generic.serial, + instance->generic.btn, + hitag2_get_button_name(instance->generic.btn), + instance->generic.data_count_bit); + + if(instance->recovered == HITAG2_RECOVERED_YES) { + furi_string_cat_printf( + output, + "\r\nIV:%08lX Cnt:%04lX", + (unsigned long)instance->generic.seed, + (unsigned long)(instance->generic.cnt & 0xFFFFU)); + } else if(instance->recovered == HITAG2_RECOVERED_BF_MISS) { + furi_string_cat_printf(output, "\r\nBF not found"); + } else { + furi_string_cat_printf( + output, + "\r\nCnt:%03lX Hop:%08lX", + (unsigned long)(instance->generic.cnt & 0x3FFU), + (unsigned long)instance->hop); + if(instance->hitag2_key_valid) { + furi_string_cat_printf(output, "\r\nKEY:OK"); + } else { + furi_string_cat_printf(output, "\r\nKEY:??"); + } + } +} + +#define HITAG2_BF_PROGRESS_INTERVAL 0x400U +#define HITAG2_BF_CANDIDATES 0x40000U + +static uint32_t hitag2_seed_from_iv(const uint8_t iv[4]) { + return ((uint32_t)iv[0] << 24U) | ((uint32_t)iv[1] << 16U) | ((uint32_t)iv[2] << 8U) | iv[3]; +} + +static void hitag2_bf_rearrange_dest(uint8_t dest[11]) { + const uint8_t frame4 = dest[6]; + uint8_t frame5 = dest[5]; + uint8_t frame6 = dest[4]; + uint8_t frame7 = dest[3]; + const uint8_t frame8 = dest[2]; + const uint8_t frame9 = dest[1]; + + uint8_t hop0 = (uint8_t)(((frame4 & 1U) << 7U) | (frame5 >> 1U)); + frame5 = (uint8_t)(((frame5 & 1U) << 7U) | (frame6 >> 1U)); + const uint8_t hop0_hi = (uint8_t)(hop0 >> 1U); + hop0 = (uint8_t)(hop0 & 1U); + hop0 = (uint8_t)((hop0 << 7U) | (frame5 >> 1U)); + frame6 = (uint8_t)(((frame6 & 1U) << 7U) | (frame7 >> 1U)); + frame5 = (uint8_t)(((frame5 & 1U) << 7U) | (frame6 >> 1U)); + dest[3] = frame5; + frame7 = (uint8_t)(((frame7 & 1U) << 7U) | (frame8 >> 1U)); + const uint8_t hop2 = (uint8_t)(((frame6 & 1U) << 7U) | (frame7 >> 1U)); + frame6 = (uint8_t)(((frame8 & 1U) << 7U) | (frame9 >> 1U)); + dest[6] = (uint8_t)(((frame9 & 1U) << 7U) | (frame4 >> 2U)); + dest[1] = (uint8_t)(((frame7 & 1U) << 7U) | (frame6 >> 1U)); + dest[5] = (uint8_t)(hop0_hi | (((frame4 >> 1U) & 1U) << 7U)); + dest[2] = hop2; + dest[4] = hop0; +} + +static void hitag2_bf_prepare( + const uint8_t frame[11], + uint8_t serial_be[4], + uint8_t perm[6], + uint8_t hop_target[4], + uint8_t* iv0, + uint8_t* fp) { + uint8_t dest[11]; + for(size_t i = 0; i < 11; i++) { + dest[i] = frame[10U - i]; + } + hitag2_bf_rearrange_dest(dest); + + hop_target[0] = dest[1]; + hop_target[1] = dest[2]; + hop_target[2] = dest[3]; + hop_target[3] = dest[4]; + *iv0 = (uint8_t)((frame[4] >> 4U) | (dest[5] << 4U)); + *fp = (uint8_t)((dest[5] >> 4U) | ((dest[6] & 3U) << 4U)); + + serial_be[0] = frame[0]; + serial_be[1] = frame[1]; + serial_be[2] = frame[2]; + serial_be[3] = frame[3]; + hitag2_serial_permute(serial_be, perm); +} + +static bool hitag2_bf_hop_matches( + const uint8_t serial_be[4], + const uint8_t perm[6], + const uint8_t iv[4], + const uint8_t hop_target[4]) { + uint8_t state[6]; + uint8_t iv_work[4]; + uint8_t iv_orig[4]; + + state[0] = serial_be[0]; + state[1] = serial_be[1]; + state[2] = serial_be[2]; + state[3] = serial_be[3]; + state[4] = perm[4]; + state[5] = perm[5]; + iv_work[0] = perm[0]; + iv_work[1] = perm[1]; + iv_work[2] = perm[2]; + iv_work[3] = perm[3]; + iv_orig[0] = iv[0]; + iv_orig[1] = iv[1]; + iv_orig[2] = iv[2]; + iv_orig[3] = iv[3]; + hitag2_clock_cipher(state, iv_work, iv_orig); + + return (iv_work[0] == hop_target[0]) && (iv_work[1] == hop_target[1]) && + (iv_work[2] == hop_target[2]) && (iv_work[3] == hop_target[3]); +} + +static void hitag2_brute_force_run(Hitag2BfState* state) { + furi_check(state); + + uint8_t serial_be[4]; + uint8_t perm[6]; + uint8_t hop_target[4]; + uint8_t iv0 = 0; + uint8_t fp = 0; + hitag2_bf_prepare(state->frame, serial_be, perm, hop_target, &iv0, &fp); + + state->progress_current = 0; + state->progress_total = HITAG2_BF_CANDIDATES; + state->status = HITAG2_BF_STATUS_RUNNING; + + uint8_t iv[4]; + for(uint32_t cand = 0; cand < HITAG2_BF_CANDIDATES; cand++) { + if((cand & (HITAG2_BF_PROGRESS_INTERVAL - 1U)) == 0U) { + state->progress_current = cand; + if(state->cancel) { + state->status = HITAG2_BF_STATUS_CANCELLED; + return; + } + } + + iv[0] = iv0; + iv[1] = (uint8_t)(fp | ((cand & 3U) << 6U)); + iv[2] = (uint8_t)(cand >> 2U); + iv[3] = (uint8_t)(cand >> 10U); + if(hitag2_bf_hop_matches(serial_be, perm, iv, hop_target)) { + state->iv[0] = iv[0]; + state->iv[1] = iv[1]; + state->iv[2] = iv[2]; + state->iv[3] = iv[3]; + state->progress_current = HITAG2_BF_CANDIDATES; + state->status = HITAG2_BF_STATUS_FOUND; + return; + } + } + + state->progress_current = HITAG2_BF_CANDIDATES; + state->status = state->cancel ? HITAG2_BF_STATUS_CANCELLED : HITAG2_BF_STATUS_NOT_FOUND; +} + +int32_t hitag2_brute_force_thread_entry(void* arg) { + Hitag2BfState* state = arg; + hitag2_brute_force_run(state); + if(state->on_done) { + state->on_done(state->on_done_ctx); + } + return 0; +} + +bool hitag2_bf_state_from_flipper_format(Hitag2BfState* state, FlipperFormat* ff) { + furi_check(state); + furi_check(ff); + + uint8_t key1[8] = {0}; + uint8_t key2[8] = {0}; + if(!hitag2_read_hex_be(ff, FF_KEY, key1, sizeof(key1))) { + return false; + } + if(!hitag2_read_hex_be(ff, "Key_2", key2, sizeof(key2))) { + return false; + } + + for(size_t i = 0; i < 8; i++) { + state->frame[i] = key1[i]; + } + state->frame[8] = key2[5]; + state->frame[9] = key2[6]; + state->frame[10] = key2[7]; + + state->cancel = 0; + state->progress_current = 0; + state->progress_total = HITAG2_BF_CANDIDATES; + state->status = HITAG2_BF_STATUS_IDLE; + state->on_done = NULL; + state->on_done_ctx = NULL; + state->iv[0] = 0; + state->iv[1] = 0; + state->iv[2] = 0; + state->iv[3] = 0; + return true; +} + +bool hitag2_bf_needs_bruteforce(FlipperFormat* ff, bool require_renault_v1) { + if(!ff) { + return false; + } + + FuriString* s = furi_string_alloc(); + flipper_format_rewind(ff); + if(require_renault_v1) { + if(!flipper_format_read_string(ff, FF_PROTOCOL, s) || + furi_string_cmp_str(s, RENAULT_PROTOCOL_V1_NAME) != 0) { + furi_string_free(s); + return false; + } + flipper_format_rewind(ff); + } + + bool has_key = flipper_format_read_string(ff, FF_KEY, s); + furi_string_free(s); + if(!has_key) { + return false; + } + + uint8_t recovered = 0; + uint32_t seed = 0; + hitag2_read_recovered_and_seed(ff, &recovered, &seed); + UNUSED(seed); + if(recovered != 0) { + return false; + } + uint8_t stored_key[6]; + if(hitag2_read_key(ff, stored_key)) { + return false; + } + return true; +} + +bool hitag2_bf_patch_flipper_format_on_success(FlipperFormat* ff, const Hitag2BfState* state) { + if(!ff || !state) { + return false; + } + + const uint32_t seed = hitag2_seed_from_iv(state->iv); + const uint64_t data = hitag2_bytes_to_u64_be(state->frame, 8); + const uint64_t data_2 = ((uint64_t)state->frame[8] << 16U) | ((uint64_t)state->frame[9] << 8U) | + state->frame[10]; + uint32_t serial = 0; + uint32_t cnt = 0; + uint32_t hop = 0; + uint8_t btn = 0; + uint8_t tail = 0; + hitag2_apply_check_remote( + data, data_2, HITAG2_RECOVERED_YES, seed, &serial, &btn, &cnt, &hop, &tail); + + flipper_format_rewind(ff); + const uint8_t recovered_hex = HITAG2_RECOVERED_YES; + if(!flipper_format_insert_or_update_hex(ff, "Recovered", &recovered_hex, 1)) { + return false; + } + if(!hitag2_write_hex_be(ff, "Seed", seed, 4)) { + return false; + } + hitag2_write_named_fields(ff, serial, btn, cnt, hop); + return true; +} + +bool hitag2_bf_patch_flipper_format_on_miss(FlipperFormat* ff) { + if(!ff) { + return false; + } + + flipper_format_rewind(ff); + const uint8_t recovered_hex = HITAG2_RECOVERED_BF_MISS; + return flipper_format_insert_or_update_hex(ff, "Recovered", &recovered_hex, 1); +} + +bool hitag2_flipper_format_get_string(FlipperFormat* ff, FuriString* output) { + if(!ff || !output) { + return false; + } + + SubGhzProtocolDecoderRenaultV1* decoder = subghz_protocol_decoder_renault_v1_alloc(NULL); + if(!decoder) { + return false; + } + + const bool ok = + subghz_protocol_decoder_renault_v1_deserialize(decoder, ff) == SubGhzProtocolStatusOk; + if(ok) { + furi_string_reset(output); + subghz_protocol_decoder_renault_v1_get_string(decoder, output); + } + pp_decoder_free_default(decoder); + return ok; +} diff --git a/protocols/renault_v1.h b/protocols/renault_v1.h new file mode 100644 index 0000000..4d77e8a --- /dev/null +++ b/protocols/renault_v1.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../defines.h" + +#define RENAULT_PROTOCOL_V1_NAME "Renault V1" + +typedef struct SubGhzProtocolDecoderRenaultV1 SubGhzProtocolDecoderRenaultV1; +typedef struct SubGhzProtocolEncoderRenaultV1 SubGhzProtocolEncoderRenaultV1; + +#define HITAG2_BF_STATUS_IDLE 0 +#define HITAG2_BF_STATUS_RUNNING 1 +#define HITAG2_BF_STATUS_FOUND 2 +#define HITAG2_BF_STATUS_NOT_FOUND 3 +#define HITAG2_BF_STATUS_CANCELLED 4 + +typedef struct Hitag2BfState Hitag2BfState; +struct Hitag2BfState { + volatile uint8_t cancel; + volatile uint32_t progress_current; + volatile uint8_t status; + volatile uint32_t progress_total; + uint8_t frame[11]; + uint8_t iv[4]; + void (*on_done)(void* context); + void* on_done_ctx; +}; + +int32_t hitag2_brute_force_thread_entry(void* arg); +bool hitag2_bf_state_from_flipper_format(Hitag2BfState* state, FlipperFormat* ff); +bool hitag2_bf_needs_bruteforce(FlipperFormat* ff, bool require_renault_v1); +bool hitag2_bf_patch_flipper_format_on_success(FlipperFormat* ff, const Hitag2BfState* state); +bool hitag2_bf_patch_flipper_format_on_miss(FlipperFormat* ff); +bool hitag2_flipper_format_get_string(FlipperFormat* ff, FuriString* output); + +extern const SubGhzProtocol renault_v1_protocol; + +void* subghz_protocol_decoder_renault_v1_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_renault_v1_reset(void* context); +void subghz_protocol_decoder_renault_v1_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_renault_v1_get_hash_data(void* context); +SubGhzProtocolStatus subghz_protocol_decoder_renault_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); +SubGhzProtocolStatus + subghz_protocol_decoder_renault_v1_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_renault_v1_get_string(void* context, FuriString* output); + +#if PROTOPIRATE_WITH_ENCODER +void* subghz_protocol_encoder_renault_v1_alloc(SubGhzEnvironment* environment); +SubGhzProtocolStatus + subghz_protocol_encoder_renault_v1_deserialize(void* context, FlipperFormat* flipper_format); +#endif diff --git a/scenes/plugins/protopirate_emulate_plugin.c b/scenes/plugins/protopirate_emulate_plugin.c index ea383d2..76a014d 100644 --- a/scenes/plugins/protopirate_emulate_plugin.c +++ b/scenes/plugins/protopirate_emulate_plugin.c @@ -15,6 +15,7 @@ #include "../../protocols/kia_v7.h" #include "../../protocols/psa.h" #include "../../protocols/renault_v0.h" +#include "../../protocols/renault_v1.h" #include #include @@ -46,7 +47,8 @@ #define EMU_PRESET_KEY_HITAG2_KEY "Hitag2 Key" #define EMU_PRESET_KEY_HITAG2_EPOCH "Hitag2 Epoch" #define EMU_CUSTOM_PRESET_KEY "Custom_preset_data" -#define EMU_FIAT_V1_KEY_TEXT_LEN 12U +#define EMU_HITAG2_KEY_TEXT_LEN 12U +#define EMU_HITAG2_RECOVERED_YES 1U typedef struct { uint32_t original_counter; @@ -63,7 +65,7 @@ typedef struct { bool flag_stop_called; bool replay_only; Storage* storage; - char hitag2_key_text[EMU_FIAT_V1_KEY_TEXT_LEN + 1U]; + char hitag2_key_text[EMU_HITAG2_KEY_TEXT_LEN + 1U]; } EmulateContext; typedef struct { @@ -75,32 +77,57 @@ typedef struct { static EmulateContext* emulate_context = NULL; static const ProtoPirateEmulateHostApi* g_host_api = NULL; -static bool emulate_hex_nibble(char c, uint8_t* nibble) { - if(c >= '0' && c <= '9') { - *nibble = (uint8_t)(c - '0'); - return true; - } - if(c >= 'A' && c <= 'F') { - *nibble = (uint8_t)(c - 'A' + 10); - return true; - } - if(c >= 'a' && c <= 'f') { - *nibble = (uint8_t)(c - 'a' + 10); - return true; +static bool emulate_hitag2_key_nonzero(const uint8_t key[6]) { + for(size_t i = 0; i < 6U; i++) { + if(key[i]) { + return true; + } } return false; } +static bool emulate_has_hitag2_key(FlipperFormat* flipper_format) { + if(!flipper_format) return false; + uint8_t key[6] = {0}; + flipper_format_rewind(flipper_format); + return flipper_format_read_hex(flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key)); +} + +static bool emulate_has_nonzero_hitag2_key(FlipperFormat* flipper_format) { + if(!flipper_format) return false; + uint8_t key[6] = {0}; + flipper_format_rewind(flipper_format); + if(!flipper_format_read_hex(flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key))) { + return false; + } + return emulate_hitag2_key_nonzero(key); +} + static bool emulate_parse_hitag2_key_text(const char* text, uint8_t key[6]) { - if(!text || !key) return false; + if(!text || !key) { + return false; + } + uint8_t hex_count = 0U; uint8_t high_nibble = 0U; - for(size_t i = 0U; text[i] != '\0'; i++) { - if(text[i] == ' ') continue; + const char c = text[i]; + if(c == ' ') { + continue; + } uint8_t nibble = 0U; - if(!emulate_hex_nibble(text[i], &nibble) || hex_count >= EMU_FIAT_V1_KEY_TEXT_LEN) { + 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 { + return false; + } + + if(hex_count >= 12U) { return false; } if((hex_count & 1U) == 0U) { @@ -110,15 +137,46 @@ static bool emulate_parse_hitag2_key_text(const char* text, uint8_t key[6]) { } hex_count++; } - - return hex_count == EMU_FIAT_V1_KEY_TEXT_LEN; + return hex_count == 12U; } -static bool emulate_has_hitag2_key(FlipperFormat* flipper_format) { - if(!flipper_format) return false; - uint8_t key[6] = {0}; +static bool emulate_write_hitag2_key(FlipperFormat* flipper_format, const uint8_t key[6]) { + if(!flipper_format || !key) { + return false; + } flipper_format_rewind(flipper_format); - return flipper_format_read_hex(flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key)); + if(!flipper_format_insert_or_update_hex( + flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, 6U)) { + return false; + } + uint32_t epoch = 0U; + flipper_format_rewind(flipper_format); + if(!flipper_format_read_uint32(flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U)) { + flipper_format_rewind(flipper_format); + flipper_format_insert_or_update_uint32( + flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U); + } + flipper_format_rewind(flipper_format); + return true; +} + +static bool emulate_hitag2_recovered_yes(FlipperFormat* flipper_format) { + if(!flipper_format) { + return false; + } + + uint8_t recovered_hex = 0; + flipper_format_rewind(flipper_format); + if(flipper_format_read_hex(flipper_format, "Recovered", &recovered_hex, 1)) { + return recovered_hex == EMU_HITAG2_RECOVERED_YES; + } + + uint32_t recovered_u32 = 0; + flipper_format_rewind(flipper_format); + if(flipper_format_read_uint32(flipper_format, "Recovered", &recovered_u32, 1)) { + return (uint8_t)recovered_u32 == EMU_HITAG2_RECOVERED_YES; + } + return false; } static void emulate_request_nav_pop(ProtoPirateApp* app) { @@ -133,9 +191,9 @@ static void emulate_request_nav_after_exit(ProtoPirateApp* app) { } } -static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx); +static bool emulate_prompt_hitag2_key(ProtoPirateApp* app, EmulateContext* ctx); -static void emulate_fiat_v1_key_input_callback(void* context) { +static void emulate_hitag2_key_input_callback(void* context) { ProtoPirateApp* app = context; EmulateContext* ctx = emulate_context; uint8_t key[6] = {0}; @@ -146,34 +204,24 @@ static void emulate_fiat_v1_key_input_callback(void* context) { notification_message(app->notifications, &sequence_error); } if(app && ctx) { - (void)emulate_prompt_fiat_v1_key(app, ctx); + (void)emulate_prompt_hitag2_key(app, ctx); } return; } - flipper_format_rewind(ctx->flipper_format); - if(!flipper_format_insert_or_update_hex( - ctx->flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, sizeof(key))) { + if(!emulate_write_hitag2_key(ctx->flipper_format, key)) { notification_message(app->notifications, &sequence_error); emulate_request_nav_pop(app); return; } - uint32_t epoch = 0U; - flipper_format_rewind(ctx->flipper_format); - if(!flipper_format_read_uint32(ctx->flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U)) { - flipper_format_rewind(ctx->flipper_format); - flipper_format_insert_or_update_uint32( - ctx->flipper_format, EMU_PRESET_KEY_HITAG2_EPOCH, &epoch, 1U); - } - view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout); if(app->view_about) { view_commit_model(app->view_about, true); } } -static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx) { +static bool emulate_prompt_hitag2_key(ProtoPirateApp* app, EmulateContext* ctx) { furi_check(app); furi_check(ctx); @@ -186,7 +234,7 @@ static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx) text_input_set_header_text(app->text_input, "HITAG2 key (12 hex):"); text_input_set_result_callback( app->text_input, - emulate_fiat_v1_key_input_callback, + emulate_hitag2_key_input_callback, app, ctx->hitag2_key_text, sizeof(ctx->hitag2_key_text), @@ -195,6 +243,22 @@ static bool emulate_prompt_fiat_v1_key(ProtoPirateApp* app, EmulateContext* ctx) return true; } +static bool emulate_needs_hitag2_prompt(EmulateContext* ctx) { + if(!ctx || !ctx->flipper_format) { + return false; + } + if(furi_string_equal(ctx->protocol_name, FIAT_V1_PROTOCOL_NAME)) { + return !emulate_has_hitag2_key(ctx->flipper_format); + } + if(furi_string_equal(ctx->protocol_name, RENAULT_PROTOCOL_V1_NAME)) { + if(emulate_hitag2_recovered_yes(ctx->flipper_format)) { + return false; + } + return !emulate_has_nonzero_hitag2_key(ctx->flipper_format); + } + return false; +} + static bool emu_preset_name_is_custom_marker(const char* preset_name) { return preset_name && (!strcmp(preset_name, "Custom") || !strcmp(preset_name, "CUSTOM") || !strcmp(preset_name, "FuriHalSubGhzPresetCustom") || @@ -784,10 +848,34 @@ static uint8_t emu_button_for_protocol( default: return original; } + } else if(strstr(protocol, RENAULT_PROTOCOL_V1_NAME)) { + switch(key) { + case InputKeyUp: + return 0x1; // Lock + case InputKeyOk: + return 0x2; // Unlock + case InputKeyDown: + return 0x2; // Unlock + case InputKeyLeft: + return 0x4; // Trunk + case InputKeyRight: + return 0x8; // Panic + default: + return original; + } + } else if(strstr(protocol, RENAULT_PROTOCOL_V0_NAME)) { + switch(key) { + case InputKeyOk: + return 0x06; // Lock + case InputKeyUp: + return 0x0A; // Unlock + case InputKeyDown: + return 0x05; // Trunk + default: + return original; + } } else if(strstr(protocol, "Fiat")) { return original; - } else if(strstr(protocol, "Porsche")) { - return original; } else if(strstr(protocol, "Scher")) { return original; } else if(strstr(protocol, "Star Line")) { @@ -796,19 +884,6 @@ static uint8_t emu_button_for_protocol( return original; } -static bool emulate_renault_is_rolling(FlipperFormat* ff) { - if(!ff) { - return false; - } - - uint32_t rolling = 0U; - flipper_format_rewind(ff); - if(flipper_format_read_uint32(ff, "Rolling", &rolling, 1)) { - return rolling != 0U; - } - return false; -} - static bool emulate_update_data(EmulateContext* ctx, uint8_t button) { if(!ctx || !ctx->flipper_format) return false; @@ -1115,14 +1190,24 @@ static void plugin_on_enter(void* context) { furi_string_set(ctx->protocol_name, "Unknown"); } - ctx->replay_only = furi_string_equal(ctx->protocol_name, RENAULT_PROTOCOL_V0_NAME) && - !emulate_renault_is_rolling(ctx->flipper_format); + ctx->replay_only = false; - // Standalone Suzuki/Honda V0 captures: merged into Kia V0 + // Standalone Suzuki/Honda/Mitsubishi V0 captures: merged into Kia V0 if(furi_string_equal(ctx->protocol_name, "Suzuki") || furi_string_equal(ctx->protocol_name, "Suzuki V0") || - furi_string_equal(ctx->protocol_name, "Honda V0")) { - uint32_t kia_v0_type = furi_string_equal(ctx->protocol_name, "Honda V0") ? 3U : 2U; + furi_string_equal(ctx->protocol_name, "Honda V0") || + furi_string_equal(ctx->protocol_name, "Mitsu V0") || + furi_string_equal(ctx->protocol_name, "Mitsu v0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi V0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi v0")) { + uint32_t kia_v0_type = + furi_string_equal(ctx->protocol_name, "Honda V0") ? 3U : + (furi_string_equal(ctx->protocol_name, "Mitsu V0") || + furi_string_equal(ctx->protocol_name, "Mitsu v0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi V0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi v0")) ? + 4U : + 2U; furi_string_set(ctx->protocol_name, KIA_PROTOCOL_V0_NAME); flipper_format_rewind(ctx->flipper_format); flipper_format_insert_or_update_string_cstr( @@ -1166,15 +1251,59 @@ static void plugin_on_enter(void* context) { ctx->current_counter = ctx->original_counter; } + if(furi_string_equal(ctx->protocol_name, FIAT_V1_PROTOCOL_NAME)) { + uint8_t raw[13] = {0}; + bool have_raw = false; + flipper_format_rewind(ctx->flipper_format); + if(flipper_format_read_hex(ctx->flipper_format, "Raw", raw, sizeof(raw)) && raw[0] == 0x00U && + raw[1] == 0x01U) { + have_raw = true; + } + + if(ctx->serial == 0U) { + if(have_raw) { + ctx->serial = ((uint32_t)raw[2] << 24U) | ((uint32_t)raw[3] << 16U) | + ((uint32_t)raw[4] << 8U) | raw[5]; + } else { + uint8_t key_bytes[8] = {0}; + flipper_format_rewind(ctx->flipper_format); + if(flipper_format_read_hex(ctx->flipper_format, "Key", key_bytes, sizeof(key_bytes))) { + ctx->serial = ((uint32_t)key_bytes[0] << 24U) | ((uint32_t)key_bytes[1] << 16U) | + ((uint32_t)key_bytes[2] << 8U) | key_bytes[3]; + } + } + if(ctx->serial != 0U) { + flipper_format_rewind(ctx->flipper_format); + flipper_format_insert_or_update_uint32( + ctx->flipper_format, EMU_PRESET_KEY_SERIAL, &ctx->serial, 1); + } + } + if(have_raw && (ctx->current_counter > 0x3FFU || ctx->original_counter > 0x3FFU)) { + const uint32_t ctrl = + ((uint32_t)(raw[6] & 0x0FU) << 6U) | ((uint32_t)raw[7] >> 2U); + ctx->original_counter = ctrl; + ctx->current_counter = ctrl; + flipper_format_rewind(ctx->flipper_format); + flipper_format_insert_or_update_uint32( + ctx->flipper_format, EMU_PRESET_KEY_CNT, &ctrl, 1); + if(ctx->original_button == 0U) { + ctx->original_button = (uint8_t)(raw[6] >> 4U); + uint32_t btn = ctx->original_button; + flipper_format_rewind(ctx->flipper_format); + flipper_format_insert_or_update_uint32( + ctx->flipper_format, EMU_PRESET_KEY_BTN, &btn, 1); + } + } + } + view_set_draw_callback(app->view_about, emulate_draw_callback); view_set_input_callback(app->view_about, emulate_input_callback); view_set_context(app->view_about, app); view_set_previous_callback(app->view_about, NULL); - if(furi_string_equal(ctx->protocol_name, FIAT_V1_PROTOCOL_NAME) && - !emulate_has_hitag2_key(ctx->flipper_format)) { - if(!emulate_prompt_fiat_v1_key(app, ctx)) { - FURI_LOG_E(TAG, "Failed to show Fiat V1 HITAG2 key input"); + if(emulate_needs_hitag2_prompt(ctx)) { + if(!emulate_prompt_hitag2_key(app, ctx)) { + FURI_LOG_E(TAG, "Failed to show HITAG2 key input"); notification_message(app->notifications, &sequence_error); emulate_context_free(); emulate_request_nav_pop(app); diff --git a/scenes/plugins/protopirate_psa_bf_plugin.c b/scenes/plugins/protopirate_psa_bf_plugin.c index 0de22fa..e1586cf 100644 --- a/scenes/plugins/protopirate_psa_bf_plugin.c +++ b/scenes/plugins/protopirate_psa_bf_plugin.c @@ -2,6 +2,7 @@ #include "../../defines.h" #include "../../protocols/psa_bf_core.h" +#include "../../protocols/renault_v1.h" #include "../../protocols/protocols_common.h" #include "../../helpers/protopirate_types.h" @@ -18,15 +19,69 @@ #define PSA_BF_PROGRESS_BAR_Y 24 #define PSA_BF_PROGRESS_BAR_H 8 +typedef enum { + ProtoPirateBfKindNone = 0, + ProtoPirateBfKindPsa, + ProtoPirateBfKindHitag2, +} ProtoPirateBfKind; + static const ProtoPiratePsaBfHostApi* g_host_api = NULL; +static ProtoPirateBfKind g_bf_kind = ProtoPirateBfKindNone; static PsaBfState* g_bf_state = NULL; +static Hitag2BfState* g_hitag2_state = NULL; static FuriThread* g_bf_thread = NULL; static ProtoPiratePsaBfContext g_active_ctx = ProtoPiratePsaBfContextReceiverInfo; static void show_bf_result(void* app, uint8_t status, ButtonCallback callback); static void bf_finish_and_show_result(void* app, ButtonCallback result_callback); +static uint8_t bf_status(void) { + if(g_bf_kind == ProtoPirateBfKindHitag2 && g_hitag2_state) { + return g_hitag2_state->status; + } + if(g_bf_state) { + return g_bf_state->status; + } + return PSA_BF_STATUS_IDLE; +} + +static void bf_progress_values(uint32_t* cur, uint32_t* total) { + if(g_bf_kind == ProtoPirateBfKindHitag2 && g_hitag2_state) { + *cur = g_hitag2_state->progress_current; + *total = g_hitag2_state->progress_total; + return; + } + if(g_bf_state) { + *cur = g_bf_state->progress_current; + *total = g_bf_state->progress_total; + return; + } + *cur = 0; + *total = 0; +} + +static void bf_set_cancel(void) { + if(g_hitag2_state) { + g_hitag2_state->cancel = 1; + } + if(g_bf_state) { + g_bf_state->cancel = 1; + } +} + +static void bf_free_states(void) { + if(g_bf_state) { + free(g_bf_state); + g_bf_state = NULL; + } + if(g_hitag2_state) { + free(g_hitag2_state); + g_hitag2_state = NULL; + } + g_bf_kind = ProtoPirateBfKindNone; +} + static bool item_needs_bruteforce_from_ff(FlipperFormat* ff, bool require_psa_protocol) { if(!ff) return false; FuriString* s = furi_string_alloc(); @@ -52,46 +107,76 @@ static bool item_needs_bruteforce_from_ff(FlipperFormat* ff, bool require_psa_pr static void show_bf_progress(void* app) { Widget* widget = g_host_api->get_widget(app); - if(!widget || !g_bf_state) return; + if(!widget || (!g_bf_state && !g_hitag2_state)) return; widget_reset(widget); widget_add_icon_element(widget, 0, 5, &I_DolphinWait_59x54); - widget_add_string_element(widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing..."); - uint32_t cur = g_bf_state->progress_current; - uint32_t total = g_bf_state->progress_total; + uint32_t cur = 0; + uint32_t total = 0; + bf_progress_values(&cur, &total); uint32_t pct_tenths = total ? (uint32_t)((uint64_t)cur * 1000 / total) : 0; if(pct_tenths > 1000) pct_tenths = 1000; + if(g_bf_kind == ProtoPirateBfKindHitag2) { + widget_add_string_element(widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Recover Key"); + widget_add_string_element(widget, 62, 12, AlignLeft, AlignTop, FontSecondary, "Max ETA:"); + widget_add_string_element(widget, 62, 22, AlignLeft, AlignTop, FontSecondary, "60 seconds"); + } else { + widget_add_string_element(widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing..."); + } + FuriString* pct_str = furi_string_alloc_printf("%lu.%u%%", pct_tenths / 10, (unsigned)(pct_tenths % 10)); widget_add_string_element( - widget, 62, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(pct_str)); + widget, + 62, + (g_bf_kind == ProtoPirateBfKindHitag2) ? 34 : 12, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(pct_str)); furi_string_free(pct_str); + const uint8_t bar_y = (g_bf_kind == ProtoPirateBfKindHitag2) ? 46 : PSA_BF_PROGRESS_BAR_Y; widget_add_rect_element( widget, PSA_BF_PROGRESS_BAR_X, - PSA_BF_PROGRESS_BAR_Y, + bar_y, PSA_BF_PROGRESS_BAR_W, PSA_BF_PROGRESS_BAR_H, 2, false); - static uint16_t bf_frame = 0; - bf_frame++; + uint8_t inner_w = PSA_BF_PROGRESS_BAR_W - 4; - uint8_t block_w = 16; - uint8_t travel = inner_w - block_w; - uint16_t phase = (bf_frame * 2) % (uint16_t)(2 * travel); - uint8_t block_x = (phase <= travel) ? (uint8_t)phase : (uint8_t)(2 * travel - phase); - widget_add_rect_element( - widget, - PSA_BF_PROGRESS_BAR_X + 2 + block_x, - PSA_BF_PROGRESS_BAR_Y + 2, - block_w, - PSA_BF_PROGRESS_BAR_H - 4, - 0, - true); + if(g_bf_kind == ProtoPirateBfKindHitag2) { + uint8_t fill_w = total ? (uint8_t)(((uint64_t)cur * inner_w) / total) : 0; + if(fill_w > 0) { + widget_add_rect_element( + widget, + PSA_BF_PROGRESS_BAR_X + 2, + bar_y + 2, + fill_w, + PSA_BF_PROGRESS_BAR_H - 4, + 0, + true); + } + } else { + static uint16_t bf_frame = 0; + bf_frame++; + uint8_t block_w = 16; + uint8_t travel = inner_w - block_w; + uint16_t phase = (bf_frame * 2) % (uint16_t)(2 * travel); + uint8_t block_x = (phase <= travel) ? (uint8_t)phase : (uint8_t)(2 * travel - phase); + widget_add_rect_element( + widget, + PSA_BF_PROGRESS_BAR_X + 2 + block_x, + bar_y + 2, + block_w, + PSA_BF_PROGRESS_BAR_H - 4, + 0, + true); + } } static void bf_result_ok_callback(GuiButtonType result, InputType type, void* context) { @@ -125,9 +210,32 @@ static void show_bf_result(void* app, uint8_t status, ButtonCallback callback) { } } -static void apply_success_to_history(void* app, PsaBfState* s) { +static void hitag2_refresh_history_text(void* app, FlipperFormat* ff) { + if(!app || !ff || !g_host_api || !g_host_api->history_set_item_str) { + return; + } + FuriString* text = furi_string_alloc(); + if(hitag2_flipper_format_get_string(ff, text)) { + g_host_api->history_set_item_str( + app, g_host_api->get_history_index(app), furi_string_get_cstr(text)); + } + furi_string_free(text); +} + +static void apply_success_to_history(void* app) { FlipperFormat* ff = g_host_api->get_history_flipper_format(app); uint16_t idx = g_host_api->get_history_index(app); + if(g_bf_kind == ProtoPirateBfKindHitag2 && g_hitag2_state) { + if(ff) { + hitag2_bf_patch_flipper_format_on_success(ff, g_hitag2_state); + hitag2_refresh_history_text(app, ff); + } + return; + } + if(!g_bf_state) { + return; + } + PsaBfState* s = g_bf_state; if(ff) { g_host_api->patch_flipper_format_on_success(ff, s); } @@ -153,10 +261,9 @@ static void apply_success_to_history(void* app, PsaBfState* s) { } static void bf_finish_and_show_result(void* app, ButtonCallback result_callback) { - if(!g_bf_state) return; + if(!g_bf_state && !g_hitag2_state) return; - PsaBfState* s = g_bf_state; - uint8_t status = s->status; + uint8_t status = bf_status(); if(g_bf_thread) { furi_thread_join(g_bf_thread); @@ -165,7 +272,7 @@ static void bf_finish_and_show_result(void* app, ButtonCallback result_callback) } if(status == PSA_BF_STATUS_FOUND) { - apply_success_to_history(app, s); + apply_success_to_history(app); if(g_active_ctx == ProtoPiratePsaBfContextSubDecode) { g_host_api->notification_success(app); } @@ -175,28 +282,32 @@ static void bf_finish_and_show_result(void* app, ButtonCallback result_callback) } show_bf_result(app, status, ok_cb); } else { - free(g_bf_state); - g_bf_state = NULL; + if(status == PSA_BF_STATUS_NOT_FOUND && g_bf_kind == ProtoPirateBfKindHitag2) { + FlipperFormat* ff = g_host_api->get_history_flipper_format(app); + if(ff) { + hitag2_bf_patch_flipper_format_on_miss(ff); + hitag2_refresh_history_text(app, ff); + } + } + bf_free_states(); show_bf_result(app, status, NULL); } } static void bf_cancel_thread(void) { if(g_bf_thread) { - if(g_bf_state) g_bf_state->cancel = 1; + bf_set_cancel(); furi_thread_join(g_bf_thread); furi_thread_free(g_bf_thread); g_bf_thread = NULL; } - if(g_bf_state) { - free(g_bf_state); - g_bf_state = NULL; - } + bf_free_states(); } static bool plugin_needs_bruteforce(void* app, ProtoPiratePsaBfContext ctx) { FlipperFormat* ff = g_host_api->get_history_flipper_format(app); - return item_needs_bruteforce_from_ff(ff, ctx == ProtoPiratePsaBfContextReceiverInfo); + const bool require = ctx == ProtoPiratePsaBfContextReceiverInfo; + return item_needs_bruteforce_from_ff(ff, require) || hitag2_bf_needs_bruteforce(ff, require); } static bool plugin_is_running(void* app) { @@ -206,11 +317,11 @@ static bool plugin_is_running(void* app) { static void plugin_on_scene_enter(void* app, ProtoPiratePsaBfContext ctx) { g_active_ctx = ctx; - if(g_bf_thread && g_bf_state) { - if(g_bf_state->status == PSA_BF_STATUS_RUNNING) { + if(g_bf_thread && (g_bf_state || g_hitag2_state)) { + if(bf_status() == PSA_BF_STATUS_RUNNING) { show_bf_progress(app); } else { - show_bf_result(app, g_bf_state->status, NULL); + show_bf_result(app, bf_status(), NULL); } } } @@ -221,23 +332,46 @@ static bool start_bruteforce(void* app) { FlipperFormat* ff = g_host_api->get_history_flipper_format(app); if(!ff || !plugin_needs_bruteforce(app, g_active_ctx)) return false; - PsaBfState* state = malloc(sizeof(PsaBfState)); - if(!state) { - g_host_api->notification_error(app); + const bool require = g_active_ctx == ProtoPiratePsaBfContextReceiverInfo; + if(item_needs_bruteforce_from_ff(ff, require)) { + PsaBfState* state = malloc(sizeof(PsaBfState)); + if(!state) { + g_host_api->notification_error(app); + return false; + } + if(!psa_bf_state_from_flipper_format(state, ff)) { + free(state); + g_host_api->notification_error(app); + return false; + } + state->on_done = NULL; + state->on_done_ctx = NULL; + g_bf_state = state; + g_bf_kind = ProtoPirateBfKindPsa; + g_bf_thread = furi_thread_alloc_ex("PsaBf", 2048, psa_brute_force_thread_entry, state); + } else if(hitag2_bf_needs_bruteforce(ff, require)) { + Hitag2BfState* state = malloc(sizeof(Hitag2BfState)); + if(!state) { + g_host_api->notification_error(app); + return false; + } + if(!hitag2_bf_state_from_flipper_format(state, ff)) { + free(state); + g_host_api->notification_error(app); + return false; + } + state->on_done = NULL; + state->on_done_ctx = NULL; + g_hitag2_state = state; + g_bf_kind = ProtoPirateBfKindHitag2; + g_bf_thread = + furi_thread_alloc_ex("Hitag2Bf", 2048, hitag2_brute_force_thread_entry, state); + } else { return false; } - if(!psa_bf_state_from_flipper_format(state, ff)) { - free(state); - g_host_api->notification_error(app); - return false; - } - state->on_done = NULL; - state->on_done_ctx = NULL; - g_bf_state = state; - g_bf_thread = furi_thread_alloc_ex("PsaBf", 2048, psa_brute_force_thread_entry, state); + if(!g_bf_thread) { - free(state); - g_bf_state = NULL; + bf_free_states(); g_host_api->notification_error(app); return false; } @@ -251,24 +385,23 @@ static bool g_active_ctx = ctx; if(event.type == SceneManagerEventTypeBack) { - if(g_bf_state && g_bf_state->status == PSA_BF_STATUS_FOUND) { + if(bf_status() == PSA_BF_STATUS_FOUND) { if(ctx == ProtoPiratePsaBfContextReceiverInfo) { g_host_api->receiver_info_rebuild_widget(app); } - free(g_bf_state); - g_bf_state = NULL; + bf_free_states(); return true; } - if(g_bf_thread && g_bf_state && g_bf_state->status == PSA_BF_STATUS_RUNNING) { - g_bf_state->cancel = 1; + if(g_bf_thread && bf_status() == PSA_BF_STATUS_RUNNING) { + bf_set_cancel(); return true; } return false; } if(event.type == SceneManagerEventTypeTick) { - if(g_bf_thread && g_bf_state) { - uint8_t bfst = g_bf_state->status; + if(g_bf_thread && (g_bf_state || g_hitag2_state)) { + uint8_t bfst = bf_status(); if(bfst == PSA_BF_STATUS_IDLE || bfst == PSA_BF_STATUS_RUNNING) { show_bf_progress(app); } else { @@ -289,7 +422,7 @@ static bool } if(event.event == ProtoPirateCustomEventPsaBruteforceComplete) { - if(g_bf_state) { + if(g_bf_state || g_hitag2_state) { bf_finish_and_show_result(app, NULL); if(g_active_ctx == ProtoPiratePsaBfContextSubDecode) { g_host_api->subdecode_signal_info_refresh(app); @@ -303,14 +436,13 @@ static bool return start_bruteforce(app); } if(event.event == ProtoPirateCustomEventReceiverInfoBruteforceCancel) { - if(g_bf_state && g_bf_state->status == PSA_BF_STATUS_FOUND) { + if(bf_status() == PSA_BF_STATUS_FOUND) { g_host_api->receiver_info_rebuild_widget(app); - free(g_bf_state); - g_bf_state = NULL; - } else if(g_bf_state && g_bf_state->status == PSA_BF_STATUS_RUNNING) { - g_bf_state->cancel = 1; + bf_free_states(); + } else if(bf_status() == PSA_BF_STATUS_RUNNING) { + bf_set_cancel(); } else { - if(g_bf_state) { + if(g_bf_state || g_hitag2_state) { bf_finish_and_show_result(app, NULL); } g_host_api->scene_previous(app); diff --git a/scenes/plugins/protopirate_psa_bf_plugin.h b/scenes/plugins/protopirate_psa_bf_plugin.h index 641150b..232c57b 100644 --- a/scenes/plugins/protopirate_psa_bf_plugin.h +++ b/scenes/plugins/protopirate_psa_bf_plugin.h @@ -9,7 +9,7 @@ #include "../../protocols/psa_bf_types.h" #define PROTOPIRATE_PSA_BF_PLUGIN_APP_ID "protopirate_psa_bf_plugin" -#define PROTOPIRATE_PSA_BF_PLUGIN_API_VERSION 1U +#define PROTOPIRATE_PSA_BF_PLUGIN_API_VERSION 2U typedef struct ProtoPirateApp ProtoPirateApp; typedef struct ProtoPirateHistory ProtoPirateHistory; diff --git a/scenes/protopirate_scene_receiver_info.c b/scenes/protopirate_scene_receiver_info.c index 04f2e12..15d29cf 100644 --- a/scenes/protopirate_scene_receiver_info.c +++ b/scenes/protopirate_scene_receiver_info.c @@ -52,6 +52,7 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { app->txrx->history, app->txrx->idx_menu_chosen, text, app->txrx->environment); bool is_psa = false; + bool offers_bf = false; FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); if(ff) { @@ -59,8 +60,9 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { flipper_format_rewind(ff); if(flipper_format_read_string(ff, FF_PROTOCOL, protocol)) { const char* protocol_name = furi_string_get_cstr(protocol); - if(strcmp(protopirate_protocol_catalog_canonical_name(protocol_name), "PSA") == 0) - is_psa = true; + const char* canonical = protopirate_protocol_catalog_canonical_name(protocol_name); + if(strcmp(canonical, "PSA") == 0) is_psa = true; + offers_bf = protopirate_protocol_catalog_offers_bruteforce(protocol_name); app->emulate_disabled_for_loaded = !protopirate_protocol_catalog_can_tx(protocol_name); } furi_string_free(protocol); @@ -126,16 +128,16 @@ static void protopirate_receiver_info_build_normal_widget(ProtoPirateApp* app) { app->widget, 0, 11, AlignLeft, AlignTop, FontSecondary, text_str); } - bool psa_needs_bf = false; - if(is_psa && protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) { - psa_needs_bf = app->psa_bf_plugin->widget_left_should_bruteforce( + bool needs_bf = false; + if(offers_bf && protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) { + needs_bf = app->psa_bf_plugin->widget_left_should_bruteforce( app, ProtoPiratePsaBfContextReceiverInfo); } - if(psa_needs_bf) { + if(needs_bf) { widget_add_button_element( app->widget, GuiButtonTypeLeft, - "Brute force", + "BF", protopirate_scene_receiver_info_widget_callback, app); } else @@ -180,7 +182,8 @@ static void protopirate_scene_receiver_info_widget_callback( has_match ? ProtoPirateCustomEventReceiverInfoUpdate : ProtoPirateCustomEventReceiverInfoSave); } else if(result == GuiButtonTypeLeft) { - if(protopirate_receiver_info_selected_protocol_is(app, "PSA") && + if((protopirate_receiver_info_selected_protocol_is(app, "PSA") || + protopirate_receiver_info_selected_protocol_is(app, "Renault V1")) && protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin && app->psa_bf_plugin->widget_left_should_bruteforce( app, ProtoPiratePsaBfContextReceiverInfo)) { diff --git a/scenes/protopirate_scene_sub_decode.c b/scenes/protopirate_scene_sub_decode.c index 6d1cab8..6822b6d 100644 --- a/scenes/protopirate_scene_sub_decode.c +++ b/scenes/protopirate_scene_sub_decode.c @@ -1212,7 +1212,8 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even FuriString* proto_str = furi_string_alloc(); flipper_format_rewind(ff); bool have_proto = flipper_format_read_string(ff, FF_PROTOCOL, proto_str); - bool is_psa = have_proto && furi_string_cmp_str(proto_str, "PSA") == 0; + bool offers_bf = have_proto && protopirate_protocol_catalog_offers_bruteforce( + furi_string_get_cstr(proto_str)); if(have_proto) { const char* protocol_name = furi_string_get_cstr(proto_str); @@ -1222,7 +1223,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even app->emulate_disabled_for_loaded = true; } furi_string_free(proto_str); - if(is_psa) { + if(offers_bf) { app->txrx->idx_menu_chosen = ctx->selected_history_index; bool needs_bf = false; if(protopirate_psa_bf_plugin_ensure_loaded(app) && app->psa_bf_plugin) { @@ -1233,7 +1234,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even widget_add_button_element( app->widget, GuiButtonTypeLeft, - "Brute force", + "BF", protopirate_scene_sub_decode_widget_callback, app); #ifdef ENABLE_EMULATE_FEATURE