SubGhz: Сreating and delivering Security+ 2.0 (#1273)
* SubGhz: Security+ 2.0 "Add manually" option * SubGhz: fix message error * Unit_test: add Security+ 2.0 encoder * Applications: remove obsolete code * SubGhz: save menu position in "Add Manually" menu Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									5c45250dd2
								
							
						
					
					
						commit
						67fbefbe63
					
				| @ -420,14 +420,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SCENED | ||||
|     {.app = scened_app, | ||||
|      .name = "Templated Scene", | ||||
|      .stack_size = 1024, | ||||
|      .icon = NULL, | ||||
|      .flags = FlipperApplicationFlagDefault}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     {.app = lfrfid_debug_app, | ||||
|      .name = "LF-RFID Debug", | ||||
|  | ||||
| @ -1,35 +0,0 @@ | ||||
| #include "scened_app_scene_byte_input.h" | ||||
| 
 | ||||
| void ScenedAppSceneByteInput::on_enter(ScenedApp* app, bool /* need_restore */) { | ||||
|     ByteInputVM* byte_input = app->view_controller; | ||||
|     auto callback = cbc::obtain_connector(this, &ScenedAppSceneByteInput::result_callback); | ||||
| 
 | ||||
|     byte_input->set_result_callback(callback, NULL, app, data, 4); | ||||
|     byte_input->set_header_text("Enter the key"); | ||||
| 
 | ||||
|     app->view_controller.switch_to<ByteInputVM>(); | ||||
| } | ||||
| 
 | ||||
| bool ScenedAppSceneByteInput::on_event(ScenedApp* app, ScenedApp::Event* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == ScenedApp::EventType::ByteEditResult) { | ||||
|         app->scene_controller.switch_to_previous_scene(); | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void ScenedAppSceneByteInput::on_exit(ScenedApp* app) { | ||||
|     app->view_controller.get<ByteInputVM>()->clean(); | ||||
| } | ||||
| 
 | ||||
| void ScenedAppSceneByteInput::result_callback(void* context) { | ||||
|     ScenedApp* app = static_cast<ScenedApp*>(context); | ||||
|     ScenedApp::Event event; | ||||
| 
 | ||||
|     event.type = ScenedApp::EventType::ByteEditResult; | ||||
| 
 | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| #pragma once | ||||
| #include "../scened_app.h" | ||||
| 
 | ||||
| class ScenedAppSceneByteInput : public GenericScene<ScenedApp> { | ||||
| public: | ||||
|     void on_enter(ScenedApp* app, bool need_restore) final; | ||||
|     bool on_event(ScenedApp* app, ScenedApp::Event* event) final; | ||||
|     void on_exit(ScenedApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     void result_callback(void* context); | ||||
| 
 | ||||
|     uint8_t data[4] = { | ||||
|         0x01, | ||||
|         0xA2, | ||||
|         0xF4, | ||||
|         0xD3, | ||||
|     }; | ||||
| }; | ||||
| @ -1,47 +0,0 @@ | ||||
| #include "scened_app_scene_start.h" | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuByteInput, | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| void ScenedAppSceneStart::on_enter(ScenedApp* app, bool need_restore) { | ||||
|     auto submenu = app->view_controller.get<SubmenuVM>(); | ||||
|     auto callback = cbc::obtain_connector(this, &ScenedAppSceneStart::submenu_callback); | ||||
| 
 | ||||
|     submenu->add_item("Byte Input", SubmenuByteInput, callback, app); | ||||
| 
 | ||||
|     if(need_restore) { | ||||
|         submenu->set_selected_item(submenu_item_selected); | ||||
|     } | ||||
|     app->view_controller.switch_to<SubmenuVM>(); | ||||
| } | ||||
| 
 | ||||
| bool ScenedAppSceneStart::on_event(ScenedApp* app, ScenedApp::Event* event) { | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event->type == ScenedApp::EventType::MenuSelected) { | ||||
|         submenu_item_selected = event->payload.menu_index; | ||||
|         switch(event->payload.menu_index) { | ||||
|         case SubmenuByteInput: | ||||
|             app->scene_controller.switch_to_next_scene(ScenedApp::SceneType::ByteInputScene); | ||||
|             break; | ||||
|         } | ||||
|         consumed = true; | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void ScenedAppSceneStart::on_exit(ScenedApp* app) { | ||||
|     app->view_controller.get<SubmenuVM>()->clean(); | ||||
| } | ||||
| 
 | ||||
| void ScenedAppSceneStart::submenu_callback(void* context, uint32_t index) { | ||||
|     ScenedApp* app = static_cast<ScenedApp*>(context); | ||||
|     ScenedApp::Event event; | ||||
| 
 | ||||
|     event.type = ScenedApp::EventType::MenuSelected; | ||||
|     event.payload.menu_index = index; | ||||
| 
 | ||||
|     app->view_controller.send_event(&event); | ||||
| } | ||||
| @ -1,13 +0,0 @@ | ||||
| #pragma once | ||||
| #include "../scened_app.h" | ||||
| 
 | ||||
| class ScenedAppSceneStart : public GenericScene<ScenedApp> { | ||||
| public: | ||||
|     void on_enter(ScenedApp* app, bool need_restore) final; | ||||
|     bool on_event(ScenedApp* app, ScenedApp::Event* event) final; | ||||
|     void on_exit(ScenedApp* app) final; | ||||
| 
 | ||||
| private: | ||||
|     void submenu_callback(void* context, uint32_t index); | ||||
|     uint32_t submenu_item_selected = 0; | ||||
| }; | ||||
| @ -1,20 +0,0 @@ | ||||
| #include "scened_app.h" | ||||
| #include "scene/scened_app_scene_start.h" | ||||
| #include "scene/scened_app_scene_byte_input.h" | ||||
| 
 | ||||
| ScenedApp::ScenedApp() | ||||
|     : scene_controller{this} | ||||
|     , text_store{128} | ||||
|     , notification{"notification"} { | ||||
| } | ||||
| 
 | ||||
| ScenedApp::~ScenedApp() { | ||||
| } | ||||
| 
 | ||||
| void ScenedApp::run() { | ||||
|     scene_controller.add_scene(SceneType::Start, new ScenedAppSceneStart()); | ||||
|     scene_controller.add_scene(SceneType::ByteInputScene, new ScenedAppSceneByteInput()); | ||||
| 
 | ||||
|     notification_message(notification, &sequence_blink_green_10); | ||||
|     scene_controller.process(100); | ||||
| } | ||||
| @ -1,47 +0,0 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include <generic_scene.hpp> | ||||
| #include <scene_controller.hpp> | ||||
| #include <view_controller.hpp> | ||||
| #include <record_controller.hpp> | ||||
| #include <text_store.h> | ||||
| 
 | ||||
| #include <view_modules/submenu_vm.h> | ||||
| #include <view_modules/byte_input_vm.h> | ||||
| 
 | ||||
| #include <notification/notification_messages.h> | ||||
| 
 | ||||
| class ScenedApp { | ||||
| public: | ||||
|     enum class EventType : uint8_t { | ||||
|         GENERIC_EVENT_ENUM_VALUES, | ||||
|         MenuSelected, | ||||
|         ByteEditResult, | ||||
|     }; | ||||
| 
 | ||||
|     enum class SceneType : uint8_t { | ||||
|         GENERIC_SCENE_ENUM_VALUES, | ||||
|         ByteInputScene, | ||||
|     }; | ||||
| 
 | ||||
|     class Event { | ||||
|     public: | ||||
|         union { | ||||
|             int32_t menu_index; | ||||
|         } payload; | ||||
| 
 | ||||
|         EventType type; | ||||
|     }; | ||||
| 
 | ||||
|     SceneController<GenericScene<ScenedApp>, ScenedApp> scene_controller; | ||||
|     TextStore text_store; | ||||
|     ViewController<ScenedApp, SubmenuVM, ByteInputVM> view_controller; | ||||
|     RecordController<NotificationApp> notification; | ||||
| 
 | ||||
|     ~ScenedApp(); | ||||
|     ScenedApp(); | ||||
| 
 | ||||
|     void run(); | ||||
| }; | ||||
| @ -1,11 +0,0 @@ | ||||
| #include "scened_app.h" | ||||
| 
 | ||||
| // app enter function
 | ||||
| extern "C" int32_t scened_app(void* p) { | ||||
|     UNUSED(p); | ||||
|     ScenedApp* app = new ScenedApp(); | ||||
|     app->run(); | ||||
|     delete app; | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -1,6 +1,7 @@ | ||||
| #include "../subghz_i.h" | ||||
| #include <lib/subghz/protocols/keeloq.h> | ||||
| #include <lib/subghz/protocols/secplus_v1.h> | ||||
| #include <lib/subghz/protocols/secplus_v2.h> | ||||
| #include <lib/subghz/blocks/math.h> | ||||
| #include <dolphin/dolphin.h> | ||||
| #include <flipper_format/flipper_format_i.h> | ||||
| @ -24,6 +25,9 @@ enum SubmenuIndex { | ||||
|     SubmenuIndexFirefly_300_00, | ||||
|     SubmenuIndexLiftMaster_315_00, | ||||
|     SubmenuIndexLiftMaster_390_00, | ||||
|     SubmenuIndexSecPlus_v2_310_00, | ||||
|     SubmenuIndexSecPlus_v2_315_00, | ||||
|     SubmenuIndexSecPlus_v2_390_00, | ||||
| }; | ||||
| 
 | ||||
| bool subghz_scene_set_type_submenu_gen_data_protocol( | ||||
| @ -157,6 +161,24 @@ void subghz_scene_set_type_on_enter(void* context) { | ||||
|         SubmenuIndexLiftMaster_390_00, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "Security+2.0_310", | ||||
|         SubmenuIndexSecPlus_v2_310_00, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "Security+2.0_315", | ||||
|         SubmenuIndexSecPlus_v2_315_00, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
|     submenu_add_item( | ||||
|         subghz->submenu, | ||||
|         "Security+2.0_390", | ||||
|         SubmenuIndexSecPlus_v2_390_00, | ||||
|         subghz_scene_set_type_submenu_callback, | ||||
|         subghz); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType)); | ||||
| @ -330,7 +352,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|             while(!subghz_protocol_secplus_v1_check_fixed(key)) { | ||||
|                 key = subghz_random_serial(); | ||||
|             } | ||||
| 
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, | ||||
| @ -345,7 +366,6 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|             while(!subghz_protocol_secplus_v1_check_fixed(key)) { | ||||
|                 key = subghz_random_serial(); | ||||
|             } | ||||
| 
 | ||||
|             if(subghz_scene_set_type_submenu_gen_data_protocol( | ||||
|                    subghz, | ||||
|                    SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, | ||||
| @ -356,17 +376,70 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { | ||||
|                 generated_protocol = true; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case SubmenuIndexSecPlus_v2_310_00: | ||||
|             subghz->txrx->transmitter = subghz_transmitter_alloc_init( | ||||
|                 subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); | ||||
|             if(subghz->txrx->transmitter) { | ||||
|                 subghz_protocol_secplus_v2_create_data( | ||||
|                     subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), | ||||
|                     subghz->txrx->fff_data, | ||||
|                     key, | ||||
|                     0x68, | ||||
|                     0xE500000, | ||||
|                     310000000, | ||||
|                     FuriHalSubGhzPresetOok650Async); | ||||
|                 generated_protocol = true; | ||||
|             } else { | ||||
|                 generated_protocol = false; | ||||
|             } | ||||
|             subghz_transmitter_free(subghz->txrx->transmitter); | ||||
|             break; | ||||
|         case SubmenuIndexSecPlus_v2_315_00: | ||||
|             subghz->txrx->transmitter = subghz_transmitter_alloc_init( | ||||
|                 subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); | ||||
|             if(subghz->txrx->transmitter) { | ||||
|                 subghz_protocol_secplus_v2_create_data( | ||||
|                     subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), | ||||
|                     subghz->txrx->fff_data, | ||||
|                     key, | ||||
|                     0x68, | ||||
|                     0xE500000, | ||||
|                     315000000, | ||||
|                     FuriHalSubGhzPresetOok650Async); | ||||
|                 generated_protocol = true; | ||||
|             } else { | ||||
|                 generated_protocol = false; | ||||
|             } | ||||
|             subghz_transmitter_free(subghz->txrx->transmitter); | ||||
|             break; | ||||
|         case SubmenuIndexSecPlus_v2_390_00: | ||||
|             subghz->txrx->transmitter = subghz_transmitter_alloc_init( | ||||
|                 subghz->txrx->environment, SUBGHZ_PROTOCOL_SECPLUS_V2_NAME); | ||||
|             if(subghz->txrx->transmitter) { | ||||
|                 subghz_protocol_secplus_v2_create_data( | ||||
|                     subghz_transmitter_get_protocol_instance(subghz->txrx->transmitter), | ||||
|                     subghz->txrx->fff_data, | ||||
|                     key, | ||||
|                     0x68, | ||||
|                     0xE500000, | ||||
|                     390000000, | ||||
|                     FuriHalSubGhzPresetOok650Async); | ||||
|                 generated_protocol = true; | ||||
|             } else { | ||||
|                 generated_protocol = false; | ||||
|             } | ||||
|             subghz_transmitter_free(subghz->txrx->transmitter); | ||||
|             break; | ||||
|         default: | ||||
|             return false; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSetType, event.event); | ||||
| 
 | ||||
|         if(generated_protocol) { | ||||
|             subghz_file_name_clear(subghz); | ||||
|             DOLPHIN_DEED(DolphinDeedSubGhzAddManually); | ||||
|             scene_manager_set_scene_state( | ||||
|                 subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); | ||||
|             scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| @ -422,6 +422,12 @@ MU_TEST(subghz_encoder_secplus_v1_test) { | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V1_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_encoder_secplus_v2_test) { | ||||
|     mu_assert( | ||||
|         subghz_encoder_test("/ext/unit_tests/subghz/security_pls_2_0.sub"), | ||||
|         "Test encoder " SUBGHZ_PROTOCOL_SECPLUS_V2_NAME " error\r\n"); | ||||
| } | ||||
| 
 | ||||
| MU_TEST(subghz_random_test) { | ||||
|     mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n"); | ||||
| } | ||||
| @ -464,6 +470,7 @@ MU_TEST_SUITE(subghz) { | ||||
|     MU_RUN_TEST(subghz_encoder_megacode_test); | ||||
|     MU_RUN_TEST(subghz_encoder_holtek_test); | ||||
|     MU_RUN_TEST(subghz_encoder_secplus_v1_test); | ||||
|     MU_RUN_TEST(subghz_encoder_secplus_v2_test); | ||||
| 
 | ||||
|     MU_RUN_TEST(subghz_random_test); | ||||
|     subghz_test_deinit(); | ||||
|  | ||||
							
								
								
									
										8
									
								
								assets/unit_tests/subghz/security_pls_2_0.sub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								assets/unit_tests/subghz/security_pls_2_0.sub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| Filetype: Flipper SubGhz Key File | ||||
| Version: 1 | ||||
| Frequency: 315000000 | ||||
| Preset: FuriHalSubGhzPresetOok650Async | ||||
| Protocol: Security+ 2.0 | ||||
| Bit: 62 | ||||
| Key: 00 00 3D 29 0F B9 BE EE | ||||
| Secplus_packet_1: 00 00 3C 01 6B 19 DD 60 | ||||
| @ -145,8 +145,7 @@ static LevelDuration | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "DO CRASH HERE."); | ||||
|         furi_crash(NULL); | ||||
|         furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); | ||||
|         break; | ||||
|     } | ||||
|     return level_duration_make(data.level, data.duration); | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #include "secplus_v2.h" | ||||
| #include <lib/toolbox/manchester_decoder.h> | ||||
| #include <lib/toolbox/manchester_encoder.h> | ||||
| #include "../blocks/const.h" | ||||
| #include "../blocks/decoder.h" | ||||
| #include "../blocks/encoder.h" | ||||
| @ -42,6 +43,7 @@ struct SubGhzProtocolEncoderSecPlus_v2 { | ||||
| 
 | ||||
|     SubGhzProtocolBlockEncoder encoder; | ||||
|     SubGhzBlockGeneric generic; | ||||
|     uint64_t secplus_packet_1; | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
| @ -63,23 +65,555 @@ const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder = { | ||||
| }; | ||||
| 
 | ||||
| const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder = { | ||||
|     .alloc = NULL, | ||||
|     .free = NULL, | ||||
|     .alloc = subghz_protocol_encoder_secplus_v2_alloc, | ||||
|     .free = subghz_protocol_encoder_secplus_v2_free, | ||||
| 
 | ||||
|     .deserialize = NULL, | ||||
|     .stop = NULL, | ||||
|     .yield = NULL, | ||||
|     .deserialize = subghz_protocol_encoder_secplus_v2_deserialize, | ||||
|     .stop = subghz_protocol_encoder_secplus_v2_stop, | ||||
|     .yield = subghz_protocol_encoder_secplus_v2_yield, | ||||
| }; | ||||
| 
 | ||||
| const SubGhzProtocol subghz_protocol_secplus_v2 = { | ||||
|     .name = SUBGHZ_PROTOCOL_SECPLUS_V2_NAME, | ||||
|     .type = SubGhzProtocolTypeDynamic, | ||||
|     .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, | ||||
|     .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | | ||||
|             SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send, | ||||
| 
 | ||||
|     .decoder = &subghz_protocol_secplus_v2_decoder, | ||||
|     .encoder = &subghz_protocol_secplus_v2_encoder, | ||||
| }; | ||||
| 
 | ||||
| void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { | ||||
|     UNUSED(environment); | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolEncoderSecPlus_v2)); | ||||
| 
 | ||||
|     instance->base.protocol = &subghz_protocol_secplus_v2; | ||||
|     instance->generic.protocol_name = instance->base.protocol->name; | ||||
| 
 | ||||
|     instance->encoder.repeat = 10; | ||||
|     instance->encoder.size_upload = 256; | ||||
|     instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); | ||||
|     instance->encoder.is_runing = false; | ||||
|     return instance; | ||||
| } | ||||
| 
 | ||||
| void subghz_protocol_encoder_secplus_v2_free(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = context; | ||||
|     free(instance->encoder.upload); | ||||
|     free(instance); | ||||
| } | ||||
| 
 | ||||
| static bool subghz_protocol_secplus_v2_mix_invet(uint8_t invert, uint16_t p[]) { | ||||
|     // selectively invert buffers
 | ||||
|     switch(invert) { | ||||
|     case 0x00: // 0b0000 (True, True, False),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         break; | ||||
|     case 0x01: // 0b0001 (False, True, False),
 | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         break; | ||||
|     case 0x02: // 0b0010 (False, False, True),
 | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x04: // 0b0100 (True, True, True),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x05: // 0b0101 (True, False, True),
 | ||||
|     case 0x0a: // 0b1010 (True, False, True),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x06: // 0b0110 (False, True, True),
 | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x08: // 0b1000 (True, False, False),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         break; | ||||
|     case 0x09: // 0b1001 (False, False, False),
 | ||||
|         break; | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "Invert FAIL"); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool subghz_protocol_secplus_v2_mix_order_decode(uint8_t order, uint16_t p[]) { | ||||
|     uint16_t a = p[0], b = p[1], c = p[2]; | ||||
| 
 | ||||
|     // selectively reorder buffers
 | ||||
|     switch(order) { | ||||
|     case 0x06: // 0b0110  2, 1, 0],
 | ||||
|     case 0x09: // 0b1001  2, 1, 0],
 | ||||
|         p[2] = a; | ||||
|         p[1] = b; | ||||
|         p[0] = c; | ||||
|         break; | ||||
|     case 0x08: // 0b1000  1, 2, 0],
 | ||||
|     case 0x04: // 0b0100  1, 2, 0],
 | ||||
|         p[1] = a; | ||||
|         p[2] = b; | ||||
|         p[0] = c; | ||||
|         break; | ||||
|     case 0x01: // 0b0001 2, 0, 1],
 | ||||
|         p[2] = a; | ||||
|         p[0] = b; | ||||
|         p[1] = c; | ||||
|         break; | ||||
|     case 0x00: // 0b0000  0, 2, 1],
 | ||||
|         p[0] = a; | ||||
|         p[2] = b; | ||||
|         p[1] = c; | ||||
|         break; | ||||
|     case 0x05: // 0b0101 1, 0, 2],
 | ||||
|         p[1] = a; | ||||
|         p[0] = b; | ||||
|         p[2] = c; | ||||
|         break; | ||||
|     case 0x02: // 0b0010 0, 1, 2],
 | ||||
|     case 0x0A: // 0b1010 0, 1, 2],
 | ||||
|         p[0] = a; | ||||
|         p[1] = b; | ||||
|         p[2] = c; | ||||
|         break; | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "Order FAIL"); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool subghz_protocol_secplus_v2_mix_order_encode(uint8_t order, uint16_t p[]) { | ||||
|     uint16_t a, b, c; | ||||
| 
 | ||||
|     // selectively reorder buffers
 | ||||
|     switch(order) { | ||||
|     case 0x06: // 0b0110  2, 1, 0],
 | ||||
|     case 0x09: // 0b1001  2, 1, 0],
 | ||||
|         a = p[2]; | ||||
|         b = p[1]; | ||||
|         c = p[0]; | ||||
|         break; | ||||
|     case 0x08: // 0b1000  1, 2, 0],
 | ||||
|     case 0x04: // 0b0100  1, 2, 0],
 | ||||
|         a = p[1]; | ||||
|         b = p[2]; | ||||
|         c = p[0]; | ||||
|         break; | ||||
|     case 0x01: // 0b0001 2, 0, 1],
 | ||||
|         a = p[2]; | ||||
|         b = p[0]; | ||||
|         c = p[1]; | ||||
|         break; | ||||
|     case 0x00: // 0b0000  0, 2, 1],
 | ||||
|         a = p[0]; | ||||
|         b = p[2]; | ||||
|         c = p[1]; | ||||
|         break; | ||||
|     case 0x05: // 0b0101 1, 0, 2],
 | ||||
|         a = p[1]; | ||||
|         b = p[0]; | ||||
|         c = p[2]; | ||||
|         break; | ||||
|     case 0x02: // 0b0010 0, 1, 2],
 | ||||
|     case 0x0A: // 0b1010 0, 1, 2],
 | ||||
|         a = p[0]; | ||||
|         b = p[1]; | ||||
|         c = p[2]; | ||||
|         break; | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "Order FAIL"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     p[0] = a; | ||||
|     p[1] = b; | ||||
|     p[2] = c; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Security+ 2.0 half-message decoding | ||||
|  * @param data data  | ||||
|  * @param roll_array[] return roll_array part | ||||
|  * @param fixed[] return fixed part | ||||
|  * @return true On success | ||||
|  */ | ||||
| 
 | ||||
| static bool | ||||
|     subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { | ||||
|     uint8_t order = (data >> 34) & 0x0f; | ||||
|     uint8_t invert = (data >> 30) & 0x0f; | ||||
|     uint16_t p[3] = {0}; | ||||
| 
 | ||||
|     for(int i = 29; i >= 0; i -= 3) { | ||||
|         p[0] = p[0] << 1 | bit_read(data, i); | ||||
|         p[1] = p[1] << 1 | bit_read(data, i - 1); | ||||
|         p[2] = p[2] << 1 | bit_read(data, i - 2); | ||||
|     } | ||||
| 
 | ||||
|     if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return false; | ||||
|     if(!subghz_protocol_secplus_v2_mix_order_decode(order, p)) return false; | ||||
| 
 | ||||
|     data = order << 4 | invert; | ||||
|     int k = 0; | ||||
|     for(int i = 6; i >= 0; i -= 2) { | ||||
|         roll_array[k++] = (data >> i) & 0x03; | ||||
|         if(roll_array[k] == 3) { | ||||
|             FURI_LOG_E(TAG, "Roll_Array FAIL"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for(int i = 8; i >= 0; i -= 2) { | ||||
|         roll_array[k++] = (p[2] >> i) & 0x03; | ||||
|         if(roll_array[k] == 3) { | ||||
|             FURI_LOG_E(TAG, "Roll_Array FAIL"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fixed[0] = p[0] << 10 | p[1]; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Analysis of received data | ||||
|  * @param instance Pointer to a SubGhzBlockGeneric* instance | ||||
|  * @param packet_1 first part of the message | ||||
|  */ | ||||
| static void | ||||
|     subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { | ||||
|     uint32_t fixed_1[1]; | ||||
|     uint8_t roll_1[9] = {0}; | ||||
|     uint32_t fixed_2[1]; | ||||
|     uint8_t roll_2[9] = {0}; | ||||
|     uint8_t rolling_digits[18] = {0}; | ||||
| 
 | ||||
|     if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && | ||||
|        subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { | ||||
|         rolling_digits[0] = roll_2[8]; | ||||
|         rolling_digits[1] = roll_1[8]; | ||||
| 
 | ||||
|         rolling_digits[2] = roll_2[4]; | ||||
|         rolling_digits[3] = roll_2[5]; | ||||
|         rolling_digits[4] = roll_2[6]; | ||||
|         rolling_digits[5] = roll_2[7]; | ||||
| 
 | ||||
|         rolling_digits[6] = roll_1[4]; | ||||
|         rolling_digits[7] = roll_1[5]; | ||||
|         rolling_digits[8] = roll_1[6]; | ||||
|         rolling_digits[9] = roll_1[7]; | ||||
| 
 | ||||
|         rolling_digits[10] = roll_2[0]; | ||||
|         rolling_digits[11] = roll_2[1]; | ||||
|         rolling_digits[12] = roll_2[2]; | ||||
|         rolling_digits[13] = roll_2[3]; | ||||
| 
 | ||||
|         rolling_digits[14] = roll_1[0]; | ||||
|         rolling_digits[15] = roll_1[1]; | ||||
|         rolling_digits[16] = roll_1[2]; | ||||
|         rolling_digits[17] = roll_1[3]; | ||||
| 
 | ||||
|         uint32_t rolling = 0; | ||||
|         for(int i = 0; i < 18; i++) { | ||||
|             rolling = (rolling * 3) + rolling_digits[i]; | ||||
|         } | ||||
|         // Max value = 2^28 (268435456)
 | ||||
|         if(rolling >= 0x10000000) { | ||||
|             FURI_LOG_E(TAG, "Rolling FAIL"); | ||||
|             instance->cnt = 0; | ||||
|             instance->btn = 0; | ||||
|             instance->serial = 0; | ||||
|         } else { | ||||
|             instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); | ||||
|             instance->btn = fixed_1[0] >> 12; | ||||
|             instance->serial = fixed_1[0] << 20 | fixed_2[0]; | ||||
|         } | ||||
|     } else { | ||||
|         instance->cnt = 0; | ||||
|         instance->btn = 0; | ||||
|         instance->serial = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Security+ 2.0 half-message encoding | ||||
|  * @param roll_array[] roll_array part | ||||
|  * @param fixed[] fixed part | ||||
|  * @return return data  | ||||
|  */ | ||||
| 
 | ||||
| static uint64_t subghz_protocol_secplus_v2_encode_half(uint8_t roll_array[], uint32_t fixed) { | ||||
|     uint64_t data = 0; | ||||
|     uint16_t p[3] = {(fixed >> 10) & 0x3FF, fixed & 0x3FF, 0}; | ||||
|     uint8_t order = roll_array[0] << 2 | roll_array[1]; | ||||
|     uint8_t invert = roll_array[2] << 2 | roll_array[3]; | ||||
|     p[2] = (uint16_t)roll_array[4] << 8 | roll_array[5] << 6 | roll_array[6] << 4 | | ||||
|            roll_array[7] << 2 | roll_array[8]; | ||||
| 
 | ||||
|     if(!subghz_protocol_secplus_v2_mix_order_encode(order, p)) return 0; | ||||
|     if(!subghz_protocol_secplus_v2_mix_invet(invert, p)) return 0; | ||||
| 
 | ||||
|     for(int i = 0; i < 10; i++) { | ||||
|         data <<= 3; | ||||
|         data |= bit_read(p[0], 9 - i) << 2 | bit_read(p[1], 9 - i) << 1 | bit_read(p[2], 9 - i); | ||||
|     } | ||||
|     data |= ((uint64_t)order) << 34 | ((uint64_t)invert) << 30; | ||||
| 
 | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Security+ 2.0 message encoding | ||||
|  * @param instance SubGhzProtocolEncoderSecPlus_v2*  | ||||
|  */ | ||||
| 
 | ||||
| static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* instance) { | ||||
|     uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; | ||||
|     uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; | ||||
|     uint8_t rolling_digits[18] = {0}; | ||||
|     uint8_t roll_1[9] = {0}; | ||||
|     uint8_t roll_2[9] = {0}; | ||||
| 
 | ||||
|     instance->generic.cnt++; | ||||
|     //ToDo it is not known what value the counter starts
 | ||||
|     if(instance->generic.cnt > 0xFFFFFFF) instance->generic.cnt = 0xE500000; | ||||
|     uint32_t rolling = subghz_protocol_blocks_reverse_key(instance->generic.cnt, 28); | ||||
| 
 | ||||
|     for(int8_t i = 17; i > -1; i--) { | ||||
|         rolling_digits[i] = rolling % 3; | ||||
|         rolling /= 3; | ||||
|     } | ||||
| 
 | ||||
|     roll_2[8] = rolling_digits[0]; | ||||
|     roll_1[8] = rolling_digits[1]; | ||||
| 
 | ||||
|     roll_2[4] = rolling_digits[2]; | ||||
|     roll_2[5] = rolling_digits[3]; | ||||
|     roll_2[6] = rolling_digits[4]; | ||||
|     roll_2[7] = rolling_digits[5]; | ||||
| 
 | ||||
|     roll_1[4] = rolling_digits[6]; | ||||
|     roll_1[5] = rolling_digits[7]; | ||||
|     roll_1[6] = rolling_digits[8]; | ||||
|     roll_1[7] = rolling_digits[9]; | ||||
| 
 | ||||
|     roll_2[0] = rolling_digits[10]; | ||||
|     roll_2[1] = rolling_digits[11]; | ||||
|     roll_2[2] = rolling_digits[12]; | ||||
|     roll_2[3] = rolling_digits[13]; | ||||
| 
 | ||||
|     roll_1[0] = rolling_digits[14]; | ||||
|     roll_1[1] = rolling_digits[15]; | ||||
|     roll_1[2] = rolling_digits[16]; | ||||
|     roll_1[3] = rolling_digits[17]; | ||||
| 
 | ||||
|     instance->secplus_packet_1 = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_1 | | ||||
|                                  subghz_protocol_secplus_v2_encode_half(roll_1, fixed_1[0]); | ||||
|     instance->generic.data = SECPLUS_V2_HEADER | SECPLUS_V2_PACKET_2 | | ||||
|                              subghz_protocol_secplus_v2_encode_half(roll_2, fixed_2[0]); | ||||
| } | ||||
| 
 | ||||
| static LevelDuration | ||||
|     subghz_protocol_encoder_secplus_v2_add_duration_to_upload(ManchesterEncoderResult result) { | ||||
|     LevelDuration data = {.duration = 0, .level = 0}; | ||||
|     switch(result) { | ||||
|     case ManchesterEncoderResultShortLow: | ||||
|         data.duration = subghz_protocol_secplus_v2_const.te_short; | ||||
|         data.level = false; | ||||
|         break; | ||||
|     case ManchesterEncoderResultLongLow: | ||||
|         data.duration = subghz_protocol_secplus_v2_const.te_long; | ||||
|         data.level = false; | ||||
|         break; | ||||
|     case ManchesterEncoderResultLongHigh: | ||||
|         data.duration = subghz_protocol_secplus_v2_const.te_long; | ||||
|         data.level = true; | ||||
|         break; | ||||
|     case ManchesterEncoderResultShortHigh: | ||||
|         data.duration = subghz_protocol_secplus_v2_const.te_short; | ||||
|         data.level = true; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         furi_crash("SugGhz: ManchesterEncoderResult is incorrect."); | ||||
|         break; | ||||
|     } | ||||
|     return level_duration_make(data.level, data.duration); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Generating an upload from data. | ||||
|  * @param instance Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  */ | ||||
| static void | ||||
|     subghz_protocol_encoder_secplus_v2_get_upload(SubGhzProtocolEncoderSecPlus_v2* instance) { | ||||
|     furi_assert(instance); | ||||
|     size_t index = 0; | ||||
| 
 | ||||
|     ManchesterEncoderState enc_state; | ||||
|     manchester_encoder_reset(&enc_state); | ||||
|     ManchesterEncoderResult result; | ||||
| 
 | ||||
|     //Send data packet 1
 | ||||
|     for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { | ||||
|         if(!manchester_encoder_advance( | ||||
|                &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result)) { | ||||
|             instance->encoder.upload[index++] = | ||||
|                 subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); | ||||
|             manchester_encoder_advance( | ||||
|                 &enc_state, bit_read(instance->secplus_packet_1, i - 1), &result); | ||||
|         } | ||||
|         instance->encoder.upload[index++] = | ||||
|             subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); | ||||
|     } | ||||
|     instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( | ||||
|         manchester_encoder_finish(&enc_state)); | ||||
|     if(level_duration_get_level(instance->encoder.upload[index])) { | ||||
|         index++; | ||||
|     } | ||||
|     instance->encoder.upload[index++] = | ||||
|         level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); | ||||
| 
 | ||||
|     //Send data packet 2
 | ||||
|     manchester_encoder_reset(&enc_state); | ||||
|     for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { | ||||
|         if(!manchester_encoder_advance( | ||||
|                &enc_state, bit_read(instance->generic.data, i - 1), &result)) { | ||||
|             instance->encoder.upload[index++] = | ||||
|                 subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); | ||||
|             manchester_encoder_advance( | ||||
|                 &enc_state, bit_read(instance->generic.data, i - 1), &result); | ||||
|         } | ||||
|         instance->encoder.upload[index++] = | ||||
|             subghz_protocol_encoder_secplus_v2_add_duration_to_upload(result); | ||||
|     } | ||||
|     instance->encoder.upload[index] = subghz_protocol_encoder_secplus_v2_add_duration_to_upload( | ||||
|         manchester_encoder_finish(&enc_state)); | ||||
|     if(level_duration_get_level(instance->encoder.upload[index])) { | ||||
|         index++; | ||||
|     } | ||||
|     instance->encoder.upload[index++] = | ||||
|         level_duration_make(false, (uint32_t)subghz_protocol_secplus_v2_const.te_long * 136); | ||||
| 
 | ||||
|     instance->encoder.size_upload = index; | ||||
| } | ||||
| 
 | ||||
| bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { | ||||
|     furi_assert(context); | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = context; | ||||
|     bool res = false; | ||||
|     do { | ||||
|         if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { | ||||
|             FURI_LOG_E(TAG, "Deserialize error"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         uint8_t key_data[sizeof(uint64_t)] = {0}; | ||||
|         if(!flipper_format_read_hex( | ||||
|                flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { | ||||
|             FURI_LOG_E(TAG, "Secplus_packet_1"); | ||||
|             break; | ||||
|         } | ||||
|         for(uint8_t i = 0; i < sizeof(uint64_t); i++) { | ||||
|             instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; | ||||
|         } | ||||
| 
 | ||||
|         subghz_protocol_secplus_v2_remote_controller( | ||||
|             &instance->generic, instance->secplus_packet_1); | ||||
|         subghz_protocol_secplus_v2_encode(instance); | ||||
|         //optional parameter parameter
 | ||||
|         flipper_format_read_uint32( | ||||
|             flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); | ||||
|         subghz_protocol_encoder_secplus_v2_get_upload(instance); | ||||
| 
 | ||||
|         //update data
 | ||||
|         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 add Key"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         for(size_t i = 0; i < sizeof(uint64_t); i++) { | ||||
|             key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; | ||||
|         } | ||||
|         if(!flipper_format_update_hex( | ||||
|                flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { | ||||
|             FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         instance->encoder.is_runing = true; | ||||
| 
 | ||||
|         res = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| void subghz_protocol_encoder_secplus_v2_stop(void* context) { | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = context; | ||||
|     instance->encoder.is_runing = false; | ||||
| } | ||||
| 
 | ||||
| LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = context; | ||||
| 
 | ||||
|     if(instance->encoder.repeat == 0 || !instance->encoder.is_runing) { | ||||
|         instance->encoder.is_runing = 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; | ||||
| } | ||||
| 
 | ||||
| bool subghz_protocol_secplus_v2_create_data( | ||||
|     void* context, | ||||
|     FlipperFormat* flipper_format, | ||||
|     uint32_t serial, | ||||
|     uint8_t btn, | ||||
|     uint32_t cnt, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset) { | ||||
|     furi_assert(context); | ||||
|     SubGhzProtocolEncoderSecPlus_v2* instance = context; | ||||
|     instance->generic.serial = serial; | ||||
|     instance->generic.cnt = cnt; | ||||
|     instance->generic.btn = btn; | ||||
|     instance->generic.data_count_bit = | ||||
|         (uint8_t)subghz_protocol_secplus_v2_const.min_count_bit_for_found; | ||||
|     subghz_protocol_secplus_v2_encode(instance); | ||||
|     bool res = | ||||
|         subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, 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->secplus_packet_1 >> i * 8) & 0xFF; | ||||
|     } | ||||
| 
 | ||||
|     if(res && | ||||
|        !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { | ||||
|         FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); | ||||
|         res = false; | ||||
|     } | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { | ||||
|     UNUSED(environment); | ||||
|     SubGhzProtocolDecoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v2)); | ||||
| @ -213,186 +747,6 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Security+ 2.0 half-message decoding | ||||
|  * @param data data  | ||||
|  * @param roll_array[] return roll_array part | ||||
|  * @param fixed[] return fixed part | ||||
|  * @return true On success | ||||
|  */ | ||||
| 
 | ||||
| static bool | ||||
|     subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { | ||||
|     uint8_t order = (data >> 34) & 0x0f; | ||||
|     uint8_t invert = (data >> 30) & 0x0f; | ||||
|     uint16_t p[3] = {0}; | ||||
| 
 | ||||
|     for(int i = 29; i >= 0; i -= 3) { | ||||
|         p[0] = p[0] << 1 | bit_read(data, i); | ||||
|         p[1] = p[1] << 1 | bit_read(data, i - 1); | ||||
|         p[2] = p[2] << 1 | bit_read(data, i - 2); | ||||
|     } | ||||
| 
 | ||||
|     // selectively invert buffers
 | ||||
|     switch(invert) { | ||||
|     case 0x00: // 0b0000 (True, True, False),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         break; | ||||
|     case 0x01: // 0b0001 (False, True, False),
 | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         break; | ||||
|     case 0x02: // 0b0010 (False, False, True),
 | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x04: // 0b0100 (True, True, True),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x05: // 0b0101 (True, False, True),
 | ||||
|     case 0x0a: // 0b1010 (True, False, True),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x06: // 0b0110 (False, True, True),
 | ||||
|         p[1] = ~p[1] & 0x03FF; | ||||
|         p[2] = ~p[2] & 0x03FF; | ||||
|         break; | ||||
|     case 0x08: // 0b1000 (True, False, False),
 | ||||
|         p[0] = ~p[0] & 0x03FF; | ||||
|         break; | ||||
|     case 0x09: // 0b1001 (False, False, False),
 | ||||
|         break; | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "Invert FAIL"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     uint16_t a = p[0], b = p[1], c = p[2]; | ||||
| 
 | ||||
|     // selectively reorder buffers
 | ||||
|     switch(order) { | ||||
|     case 0x06: // 0b0110  2, 1, 0],
 | ||||
|     case 0x09: // 0b1001  2, 1, 0],
 | ||||
|         p[2] = a; | ||||
|         p[1] = b; | ||||
|         p[0] = c; | ||||
|         break; | ||||
|     case 0x08: // 0b1000  1, 2, 0],
 | ||||
|     case 0x04: // 0b0100  1, 2, 0],
 | ||||
|         p[1] = a; | ||||
|         p[2] = b; | ||||
|         p[0] = c; | ||||
|         break; | ||||
|     case 0x01: // 0b0001 2, 0, 1],
 | ||||
|         p[2] = a; | ||||
|         p[0] = b; | ||||
|         p[1] = c; | ||||
|         break; | ||||
|     case 0x00: // 0b0000  0, 2, 1],
 | ||||
|         p[0] = a; | ||||
|         p[2] = b; | ||||
|         p[1] = c; | ||||
|         break; | ||||
|     case 0x05: // 0b0101 1, 0, 2],
 | ||||
|         p[1] = a; | ||||
|         p[0] = b; | ||||
|         p[2] = c; | ||||
|         break; | ||||
|     case 0x02: // 0b0010 0, 1, 2],
 | ||||
|     case 0x0A: // 0b1010 0, 1, 2],
 | ||||
|         p[0] = a; | ||||
|         p[1] = b; | ||||
|         p[2] = c; | ||||
|         break; | ||||
|     default: | ||||
|         FURI_LOG_E(TAG, "Order FAIL"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     data = order << 4 | invert; | ||||
|     int k = 0; | ||||
|     for(int i = 6; i >= 0; i -= 2) { | ||||
|         roll_array[k++] = (data >> i) & 0x03; | ||||
|         if(roll_array[k] == 3) { | ||||
|             FURI_LOG_E(TAG, "Roll_Array FAIL"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for(int i = 8; i >= 0; i -= 2) { | ||||
|         roll_array[k++] = (p[2] >> i) & 0x03; | ||||
|         if(roll_array[k] == 3) { | ||||
|             FURI_LOG_E(TAG, "Roll_Array FAIL"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fixed[0] = p[0] << 10 | p[1]; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /** 
 | ||||
|  * Analysis of received data | ||||
|  * @param instance Pointer to a SubGhzBlockGeneric* instance | ||||
|  * @param packet_1 first part of the message | ||||
|  */ | ||||
| static void | ||||
|     subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { | ||||
|     uint32_t fixed_1[1]; | ||||
|     uint8_t roll_1[9] = {0}; | ||||
|     uint32_t fixed_2[1]; | ||||
|     uint8_t roll_2[9] = {0}; | ||||
|     uint8_t rolling_digits[18] = {0}; | ||||
| 
 | ||||
|     if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && | ||||
|        subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { | ||||
|         rolling_digits[0] = roll_2[8]; | ||||
|         rolling_digits[1] = roll_1[8]; | ||||
| 
 | ||||
|         rolling_digits[2] = roll_2[4]; | ||||
|         rolling_digits[3] = roll_2[5]; | ||||
|         rolling_digits[4] = roll_2[6]; | ||||
|         rolling_digits[5] = roll_2[7]; | ||||
| 
 | ||||
|         rolling_digits[6] = roll_1[4]; | ||||
|         rolling_digits[7] = roll_1[5]; | ||||
|         rolling_digits[8] = roll_1[6]; | ||||
|         rolling_digits[9] = roll_1[7]; | ||||
| 
 | ||||
|         rolling_digits[10] = roll_2[0]; | ||||
|         rolling_digits[11] = roll_2[1]; | ||||
|         rolling_digits[12] = roll_2[2]; | ||||
|         rolling_digits[13] = roll_2[3]; | ||||
| 
 | ||||
|         rolling_digits[14] = roll_1[0]; | ||||
|         rolling_digits[15] = roll_1[1]; | ||||
|         rolling_digits[16] = roll_1[2]; | ||||
|         rolling_digits[17] = roll_1[3]; | ||||
| 
 | ||||
|         uint32_t rolling = 0; | ||||
|         for(int i = 0; i < 18; i++) { | ||||
|             rolling = (rolling * 3) + rolling_digits[i]; | ||||
|         } | ||||
|         // Max value = 2^28 (268435456)
 | ||||
|         if(rolling >= 0x10000000) { | ||||
|             FURI_LOG_E(TAG, "Rolling FAIL"); | ||||
|             instance->cnt = 0; | ||||
|             instance->btn = 0; | ||||
|             instance->serial = 0; | ||||
|         } else { | ||||
|             instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); | ||||
|             instance->btn = fixed_1[0] >> 12; | ||||
|             instance->serial = fixed_1[0] << 20 | fixed_2[0]; | ||||
|         } | ||||
|     } else { | ||||
|         instance->cnt = 0; | ||||
|         instance->btn = 0; | ||||
|         instance->serial = 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { | ||||
|     furi_assert(context); | ||||
|     SubGhzProtocolDecoderSecPlus_v2* instance = context; | ||||
|  | ||||
| @ -10,6 +10,61 @@ extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; | ||||
| extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; | ||||
| extern const SubGhzProtocol subghz_protocol_secplus_v2; | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate SubGhzProtocolEncoderSecPlus_v2. | ||||
|  * @param environment Pointer to a SubGhzEnvironment instance | ||||
|  * @return SubGhzProtocolEncoderSecPlus_v2* pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  */ | ||||
| void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment); | ||||
| 
 | ||||
| /**
 | ||||
|  * Free SubGhzProtocolEncoderSecPlus_v2. | ||||
|  * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  */ | ||||
| void subghz_protocol_encoder_secplus_v2_free(void* context); | ||||
| 
 | ||||
| /**
 | ||||
|  * Deserialize and generating an upload to send. | ||||
|  * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  * @param flipper_format Pointer to a FlipperFormat instance | ||||
|  * @return true On success | ||||
|  */ | ||||
| bool subghz_protocol_encoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); | ||||
| 
 | ||||
| /**
 | ||||
|  * Forced transmission stop. | ||||
|  * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  */ | ||||
| void subghz_protocol_encoder_secplus_v2_stop(void* context); | ||||
| 
 | ||||
| /**
 | ||||
|  * Getting the level and duration of the upload to be loaded into DMA. | ||||
|  * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  * @return LevelDuration  | ||||
|  */ | ||||
| LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context); | ||||
| 
 | ||||
| /**
 | ||||
|  * Key generation from simple data. | ||||
|  * @param context Pointer to a SubGhzProtocolEncoderSecPlus_v2 instance | ||||
|  * @param flipper_format Pointer to a FlipperFormat instance | ||||
|  * @param serial Serial number, 32 bit | ||||
|  * @param btn Button number, 8 bit | ||||
|  * @param cnt Container value, 28 bit | ||||
|  * @param manufacture_name Name of manufacturer's key | ||||
|  * @param frequency Transmission frequency, Hz | ||||
|  * @param preset Modulation, FuriHalSubGhzPreset | ||||
|  * @return true On success | ||||
|  */ | ||||
| bool subghz_protocol_secplus_v2_create_data( | ||||
|     void* context, | ||||
|     FlipperFormat* flipper_format, | ||||
|     uint32_t serial, | ||||
|     uint8_t btn, | ||||
|     uint32_t cnt, | ||||
|     uint32_t frequency, | ||||
|     FuriHalSubGhzPreset preset); | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate SubGhzProtocolDecoderSecPlus_v2. | ||||
|  * @param environment Pointer to a SubGhzEnvironment instance | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Skorpionm
						Skorpionm