#include "beninca_arc.h" #include "../blocks/const.h" #include "../blocks/decoder.h" #include "../blocks/encoder.h" #include "../blocks/generic.h" #include "../blocks/math.h" #include "core/log.h" #include #include #include "aes_common.h" #include "../blocks/custom_btn_i.h" #define TAG "BenincaARC" #define BENINCA_ARC_KEY_TYPE 9u static const SubGhzBlockConst subghz_protocol_beninca_arc_const = { .te_short = 300, .te_long = 600, .te_delta = 155, .min_count_bit_for_found = 128, }; typedef enum { BenincaARCDecoderStart = 0, BenincaARCDecoderHighLevel, BenincaARCDecoderLowLevel, } BenincaARCDecoderState; struct SubGhzProtocolDecoderBenincaARC { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; SubGhzKeystore* keystore; }; struct SubGhzProtocolEncoderBenincaARC { SubGhzProtocolEncoderBase base; SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; SubGhzKeystore* keystore; }; const SubGhzProtocolDecoder subghz_protocol_beninca_arc_decoder = { .alloc = subghz_protocol_decoder_beninca_arc_alloc, .free = subghz_protocol_decoder_beninca_arc_free, .feed = subghz_protocol_decoder_beninca_arc_feed, .reset = subghz_protocol_decoder_beninca_arc_reset, .get_hash_data = NULL, .get_hash_data_long = subghz_protocol_decoder_beninca_arc_get_hash_data, .serialize = subghz_protocol_decoder_beninca_arc_serialize, .deserialize = subghz_protocol_decoder_beninca_arc_deserialize, .get_string = subghz_protocol_decoder_beninca_arc_get_string, .get_string_brief = NULL, }; const SubGhzProtocolEncoder subghz_protocol_beninca_arc_encoder = { .alloc = subghz_protocol_encoder_beninca_arc_alloc, .free = subghz_protocol_encoder_beninca_arc_free, .deserialize = subghz_protocol_encoder_beninca_arc_deserialize, .stop = subghz_protocol_encoder_beninca_arc_stop, .yield = subghz_protocol_encoder_beninca_arc_yield, }; const SubGhzProtocol subghz_protocol_beninca_arc = { .name = SUBGHZ_PROTOCOL_BENINCA_ARC_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_beninca_arc_decoder, .encoder = &subghz_protocol_beninca_arc_encoder, }; // Get custom button code static uint8_t subghz_protocol_beninca_arc_get_btn_code(void) { uint8_t custom_btn_id = subghz_custom_btn_get(); uint8_t original_btn_code = subghz_custom_btn_get_original(); uint8_t btn = original_btn_code; // Set custom button if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { // Restore original button code btn = original_btn_code; } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { switch(original_btn_code) { case 0x02: btn = 0x04; break; case 0x04: btn = 0x02; break; case 0x00: btn = 0x04; break; default: break; } } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { switch(original_btn_code) { case 0x02: btn = 0x00; break; case 0x04: btn = 0x00; break; case 0x00: btn = 0x02; break; default: break; } } return btn; } static void get_subghz_protocol_beninca_arc_aes_key(SubGhzKeystore* keystore, uint8_t* aes_key) { uint64_t mfkey = 0; for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { if(manufacture_code->type == BENINCA_ARC_KEY_TYPE) { mfkey = manufacture_code->key; break; } } uint32_t derived_lo = (uint32_t)(mfkey & 0xFFFFFFFF); uint32_t derived_hi = (uint32_t)((mfkey >> 32) & 0xFFFFFFFF); uint64_t val64_a = ((uint64_t)derived_hi << 32) | derived_lo; for(uint8_t i = 0; i < 8; i++) { aes_key[i] = (val64_a >> (56 - i * 8)) & 0xFF; } uint32_t new_lo = ((derived_hi >> 24) & 0xFF) | ((derived_hi >> 8) & 0xFF00) | ((derived_hi << 8) & 0xFF0000) | ((derived_hi << 24) & 0xFF000000); uint32_t new_hi = ((derived_lo >> 24) & 0xFF) | ((derived_lo >> 8) & 0xFF00) | ((derived_lo << 8) & 0xFF0000) | ((derived_lo << 24) & 0xFF000000); uint64_t val64_b = ((uint64_t)new_hi << 32) | new_lo; for(uint8_t i = 0; i < 8; i++) { aes_key[i + 8] = (val64_b >> (56 - i * 8)) & 0xFF; } } static uint64_t subghz_protocol_beninca_arc_decrypt(SubGhzBlockGeneric* generic, SubGhzKeystore* keystore) { // Beninca ARC Decoder // 01.2026 - @xMasterX (MMX) & @zero-mega // Decrypt data uint8_t encrypted_data[16]; for(uint8_t i = 0; i < 8; i++) { encrypted_data[i] = (generic->data >> (56 - i * 8)) & 0xFF; encrypted_data[i + 8] = (generic->data_2 >> (56 - i * 8)) & 0xFF; } reverse_bits_in_bytes(encrypted_data, 16); uint8_t aes_key[16]; get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key); uint8_t expanded_key[176]; aes_key_expansion(aes_key, expanded_key); aes128_decrypt(expanded_key, encrypted_data); // Serial number of remote generic->serial = ((uint32_t)encrypted_data[0] << 24) | ((uint32_t)encrypted_data[1] << 16) | ((uint32_t)encrypted_data[2] << 8) | encrypted_data[3]; // Button code generic->btn = encrypted_data[4]; // Middle bytes contains mini counter that is increased while button is held // its value mostly stored in encrypted_data[9] but might be in other bytes as well // In order to support all variants we read all middle bytes as uint64_t // In case you have the remote with ARC rolling code please share RAW recording where you hold button for 15+ sec with us to improve this part! uint64_t middle_bytes = 0; middle_bytes = ((uint64_t)encrypted_data[5] << 32) | ((uint64_t)encrypted_data[6] << 24) | ((uint64_t)encrypted_data[7] << 16) | ((uint64_t)encrypted_data[8] << 8) | encrypted_data[9]; // 32-bit counter generic->cnt = ((uint32_t)encrypted_data[10] << 24) | ((uint32_t)encrypted_data[11] << 16) | ((uint32_t)encrypted_data[12] << 8) | encrypted_data[13]; // Fixed constant value AA 55 generic->seed = ((uint16_t)encrypted_data[14] << 8) | encrypted_data[15]; // Save original button for later use if(subghz_custom_btn_get_original() == 0) { subghz_custom_btn_set_original(generic->btn); } subghz_custom_btn_set_max(2); return middle_bytes; } static void subghz_protocol_beninca_arc_encrypt( SubGhzBlockGeneric* generic, SubGhzKeystore* keystore, uint64_t middle_bytes) { // Beninca ARC Encoder // 01.2026 - @xMasterX (MMX) & @zero-mega // Encrypt data uint8_t plaintext[16]; plaintext[0] = (generic->serial >> 24) & 0xFF; plaintext[1] = (generic->serial >> 16) & 0xFF; plaintext[2] = (generic->serial >> 8) & 0xFF; plaintext[3] = generic->serial & 0xFF; plaintext[4] = generic->btn; plaintext[5] = (middle_bytes >> 32) & 0xFF; plaintext[6] = (middle_bytes >> 24) & 0xFF; plaintext[7] = (middle_bytes >> 16) & 0xFF; plaintext[8] = (middle_bytes >> 8) & 0xFF; plaintext[9] = middle_bytes & 0xFF; plaintext[10] = (generic->cnt >> 24) & 0xFF; plaintext[11] = (generic->cnt >> 16) & 0xFF; plaintext[12] = (generic->cnt >> 8) & 0xFF; plaintext[13] = generic->cnt & 0xFF; plaintext[14] = (generic->seed >> 8) & 0xFF; plaintext[15] = generic->seed & 0xFF; uint8_t aes_key[16]; get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key); uint8_t expanded_key[176]; aes_key_expansion(aes_key, expanded_key); aes128_encrypt(expanded_key, plaintext); reverse_bits_in_bytes(plaintext, 16); for(uint8_t i = 0; i < 8; i++) { generic->data = (generic->data << 8) | plaintext[i]; generic->data_2 = (generic->data_2 << 8) | plaintext[i + 8]; } return; } void* subghz_protocol_encoder_beninca_arc_alloc(SubGhzEnvironment* environment) { SubGhzProtocolEncoderBenincaARC* instance = malloc(sizeof(SubGhzProtocolEncoderBenincaARC)); instance->base.protocol = &subghz_protocol_beninca_arc; instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); instance->encoder.repeat = 10; instance->encoder.size_upload = 800; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; } void subghz_protocol_encoder_beninca_arc_free(void* context) { furi_assert(context); SubGhzProtocolEncoderBenincaARC* instance = context; free(instance->encoder.upload); free(instance); } void subghz_protocol_encoder_beninca_arc_stop(void* context) { furi_assert(context); SubGhzProtocolEncoderBenincaARC* instance = context; instance->encoder.is_running = false; } static void subghz_protocol_beninca_arc_encoder_get_upload( SubGhzProtocolEncoderBenincaARC* instance, size_t* index) { furi_assert(instance); size_t index_local = *index; // First part of data 64 bits for(uint8_t i = 64; i > 0; i--) { if(bit_read(instance->generic.data, i - 1)) { // Send bit 1 instance->encoder.upload[index_local++] = level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short); instance->encoder.upload[index_local++] = level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long); } else { // Send bit 0 instance->encoder.upload[index_local++] = level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_long); instance->encoder.upload[index_local++] = level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_short); } } // Second part of data 64 bits - total 128bits data for(uint8_t i = 64; i > 0; i--) { if(bit_read(instance->generic.data_2, i - 1)) { // Send bit 1 instance->encoder.upload[index_local++] = level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short); instance->encoder.upload[index_local++] = level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long); } else { // Send bit 0 instance->encoder.upload[index_local++] = level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_long); instance->encoder.upload[index_local++] = level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_short); } } // Add stop bit instance->encoder.upload[index_local++] = level_duration_make(true, (uint32_t)subghz_protocol_beninca_arc_const.te_short); // Add gap between packets instance->encoder.upload[index_local++] = level_duration_make(false, (uint32_t)subghz_protocol_beninca_arc_const.te_long * 15); *index = index_local; } static void subghz_protocol_beninca_arc_encoder_prepare_packets( SubGhzProtocolEncoderBenincaARC* instance) { furi_assert(instance); // Counter increment // check OFEX mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { // standart counter mode. PULL data from subghz_block_generic_global variables if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFFFFF) { instance->generic.cnt = 0; } else { instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } } } else { // TODO: OFEX mode instance->generic.cnt += 1; } // Index for upload array size_t index = 0; // Generate new key using custom or default button instance->generic.btn = subghz_protocol_beninca_arc_get_btn_code(); // Make 3 packets with different mini counter values - 2, 4, 6 for(uint8_t i = 0; i < 3; i++) { subghz_protocol_beninca_arc_encrypt( &instance->generic, instance->keystore, (uint64_t)((i + 1) * 2)); subghz_protocol_beninca_arc_encoder_get_upload(instance, &index); } // Set final size of upload array instance->encoder.size_upload = index; } bool subghz_protocol_beninca_arc_create_data( void* context, FlipperFormat* flipper_format, uint32_t serial, uint8_t btn, uint32_t cnt, SubGhzRadioPreset* preset) { furi_assert(context); // UwU SubGhzProtocolEncoderBenincaARC* instance = context; instance->generic.serial = serial; instance->generic.btn = btn; // 02 / 04 instance->generic.cnt = cnt; instance->generic.seed = 0xAA55; // Fixed value constant instance->generic.data_count_bit = 128; subghz_protocol_beninca_arc_encrypt(&instance->generic, instance->keystore, 0x1); SubGhzProtocolStatus res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); res = SubGhzProtocolStatusErrorParserOthers; } if((res == SubGhzProtocolStatusOk) && !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Data2"); res = SubGhzProtocolStatusErrorParserOthers; } return res == SubGhzProtocolStatusOk; } SubGhzProtocolStatus subghz_protocol_encoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolEncoderBenincaARC* instance = context; SubGhzProtocolStatus res = SubGhzProtocolStatusError; do { if(SubGhzProtocolStatusOk != subghz_block_generic_deserialize(&instance->generic, flipper_format)) { FURI_LOG_E(TAG, "Deserialize error"); break; } // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); break; } uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Missing Data"); break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; } // TODO: if minicounter having larger value use it instead of fixed values subghz_protocol_beninca_arc_decrypt(&instance->generic, instance->keystore); subghz_protocol_beninca_arc_encoder_prepare_packets(instance); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); break; } for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to update Key"); break; } for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF; } if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to update Data"); break; } instance->encoder.is_running = true; res = SubGhzProtocolStatusOk; } while(false); return res; } LevelDuration subghz_protocol_encoder_beninca_arc_yield(void* context) { furi_assert(context); SubGhzProtocolEncoderBenincaARC* instance = context; if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { instance->encoder.is_running = false; return level_duration_reset(); } LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { instance->encoder.repeat--; instance->encoder.front = 0; } return ret; } void* subghz_protocol_decoder_beninca_arc_alloc(SubGhzEnvironment* environment) { SubGhzProtocolDecoderBenincaARC* instance = malloc(sizeof(SubGhzProtocolDecoderBenincaARC)); instance->base.protocol = &subghz_protocol_beninca_arc; instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); instance->decoder.parser_step = BenincaARCDecoderStart; return instance; } void subghz_protocol_decoder_beninca_arc_free(void* context) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; free(instance); } void subghz_protocol_decoder_beninca_arc_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; instance->decoder.parser_step = BenincaARCDecoderStart; } void subghz_protocol_decoder_beninca_arc_feed(void* context, bool level, uint32_t duration) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; switch(instance->decoder.parser_step) { case BenincaARCDecoderStart: if((!level) && (DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_long * 16) < subghz_protocol_beninca_arc_const.te_delta * 15)) { // GAP (9300 +- 2325 us) found switch to next state instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; instance->decoder.parser_step = BenincaARCDecoderHighLevel; break; } // No GAP so stay in current state break; case BenincaARCDecoderHighLevel: if(level) { instance->decoder.te_last = duration; instance->decoder.parser_step = BenincaARCDecoderLowLevel; // Check if we have collected enough bits if((instance->decoder.decode_count_bit == (subghz_protocol_beninca_arc_const.min_count_bit_for_found / 2)) && (instance->decoder.decode_data != 0)) { // Half data captured 64 bits instance->generic.data = instance->decoder.decode_data; instance->decoder.decode_data = 0; } else if( instance->decoder.decode_count_bit == subghz_protocol_beninca_arc_const.min_count_bit_for_found) { // Full data captured 128 bits instance->generic.data_2 = instance->decoder.decode_data; instance->generic.data_count_bit = instance->decoder.decode_count_bit; instance->decoder.parser_step = BenincaARCDecoderStart; if(instance->base.callback) { instance->base.callback(&instance->base, instance->base.context); } break; } } else { instance->decoder.parser_step = BenincaARCDecoderStart; } break; case BenincaARCDecoderLowLevel: if(!level) { // Bit 1 is short and long timing = 300us HIGH (te_last) and 600us LOW if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_beninca_arc_const.te_short) < subghz_protocol_beninca_arc_const.te_delta) && (DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_long) < subghz_protocol_beninca_arc_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); instance->decoder.parser_step = BenincaARCDecoderHighLevel; // Bit 0 is long and short timing = 600us HIGH (te_last) and 300us LOW } else if( (DURATION_DIFF( instance->decoder.te_last, subghz_protocol_beninca_arc_const.te_long) < subghz_protocol_beninca_arc_const.te_delta) && (DURATION_DIFF(duration, subghz_protocol_beninca_arc_const.te_short) < subghz_protocol_beninca_arc_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); instance->decoder.parser_step = BenincaARCDecoderHighLevel; } else { instance->decoder.parser_step = BenincaARCDecoderStart; } break; } else { instance->decoder.parser_step = BenincaARCDecoderStart; break; } } } uint32_t subghz_protocol_decoder_beninca_arc_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } SubGhzProtocolStatus subghz_protocol_decoder_beninca_arc_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); ret = SubGhzProtocolStatusErrorParserOthers; } if((ret == SubGhzProtocolStatusOk) && !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Unable to add Data"); ret = SubGhzProtocolStatusErrorParserOthers; } return ret; } SubGhzProtocolStatus subghz_protocol_decoder_beninca_arc_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; SubGhzProtocolStatus ret = SubGhzProtocolStatusError; do { ret = subghz_block_generic_deserialize_check_count_bit( &instance->generic, flipper_format, subghz_protocol_beninca_arc_const.min_count_bit_for_found); if(ret != SubGhzProtocolStatusOk) { break; } if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); ret = SubGhzProtocolStatusErrorParserOthers; break; } uint8_t key_data[sizeof(uint64_t)] = {0}; if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { FURI_LOG_E(TAG, "Missing Data"); ret = SubGhzProtocolStatusErrorParserOthers; break; } for(uint8_t i = 0; i < sizeof(uint64_t); i++) { instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; } } while(false); return ret; } void subghz_protocol_decoder_beninca_arc_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderBenincaARC* instance = context; uint64_t middle_bytes_dec = subghz_protocol_beninca_arc_decrypt(&instance->generic, instance->keystore); // push protocol data to global variable subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 32; subghz_block_generic_global.current_cnt = instance->generic.cnt; furi_string_printf( output, "%s %db\r\n" "Key1:%08llX\r\n" "Key2:%08llX\r\n" "Sn:%08lX Btn:%02X\r\n" "Mc:%0lX Cnt:%0lX\r\n" "Fx:%04lX", instance->base.protocol->name, instance->generic.data_count_bit, instance->generic.data, instance->generic.data_2, instance->generic.serial, instance->generic.btn, (uint32_t)(middle_bytes_dec & 0xFFFFFFFF), instance->generic.cnt, instance->generic.seed & 0xFFFF); }