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