esubghz_chat.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <gui/modules/text_box.h>
  5. #include <gui/modules/text_input.h>
  6. #include <gui/view_dispatcher.h>
  7. #include <gui/scene_manager.h>
  8. #include <toolbox/sha256.h>
  9. #include <notification/notification_messages.h>
  10. #include "crypto/gcm.h"
  11. #define APPLICATION_NAME "ESubGhzChat"
  12. #define DEFAULT_FREQ 433920000
  13. #define KEY_BITS 256
  14. #define IV_BYTES 12
  15. #define TAG_BYTES 16
  16. #define RX_TX_BUFFER_SIZE 1024
  17. #define CHAT_BOX_STORE_SIZE 4096
  18. #define TEXT_INPUT_STORE_SIZE 512
  19. #define TICK_INTERVAL 50
  20. #define MESSAGE_COMPLETION_TIMEOUT 200
  21. #define TIMEOUT_BETWEEN_MESSAGES 500
  22. typedef struct {
  23. SceneManager *scene_manager;
  24. ViewDispatcher *view_dispatcher;
  25. NotificationApp *notification;
  26. TextBox *chat_box;
  27. FuriString *chat_box_store;
  28. TextInput *text_input;
  29. char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
  30. FuriString *name_prefix;
  31. FuriString *msg_input;
  32. bool encrypted;
  33. uint32_t frequency;
  34. gcm_context gcm_ctx;
  35. uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
  36. uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
  37. char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
  38. FuriStreamBuffer *rx_collection_buffer;
  39. uint32_t last_time_rx_data;
  40. } ESubGhzChatState;
  41. typedef enum {
  42. ESubGhzChatScene_FreqInput,
  43. ESubGhzChatScene_PassInput,
  44. ESubGhzChatScene_ChatInput,
  45. ESubGhzChatScene_ChatBox,
  46. ESubGhzChatScene_MAX
  47. } ESubGhzChatScene;
  48. typedef enum {
  49. ESubGhzChatView_Input,
  50. ESubGhzChatView_ChatBox,
  51. } ESubGhzChatView;
  52. typedef enum {
  53. ESubGhzChatEvent_FreqEntered,
  54. ESubGhzChatEvent_PassEntered,
  55. ESubGhzChatEvent_MsgEntered
  56. } ESubGhzChatEvent;
  57. static void esubghz_chat_explicit_bzero(void *s, size_t len)
  58. {
  59. memset(s, 0, len);
  60. asm volatile("" ::: "memory");
  61. }
  62. static void post_rx(ESubGhzChatState *state, size_t rx_size)
  63. {
  64. furi_assert(state);
  65. if (rx_size == 0) {
  66. return;
  67. }
  68. furi_check(rx_size <= RX_TX_BUFFER_SIZE);
  69. if (!state->encrypted) {
  70. memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
  71. state->rx_str_buffer[rx_size] = 0;
  72. } else {
  73. if (rx_size < IV_BYTES + TAG_BYTES + 1) {
  74. return;
  75. }
  76. int ret = gcm_auth_decrypt(&(state->gcm_ctx),
  77. state->rx_buffer, IV_BYTES,
  78. NULL, 0,
  79. state->rx_buffer + IV_BYTES,
  80. (uint8_t *) state->rx_str_buffer,
  81. rx_size - (IV_BYTES + TAG_BYTES),
  82. state->rx_buffer + rx_size - TAG_BYTES,
  83. TAG_BYTES);
  84. state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
  85. if (ret != 0) {
  86. strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
  87. }
  88. }
  89. furi_string_cat_printf(state->chat_box_store, "\n%s",
  90. state->rx_str_buffer);
  91. notification_message(state->notification, &sequence_single_vibro);
  92. // Reset Text Box contents and focus
  93. text_box_set_text(state->chat_box,
  94. furi_string_get_cstr(state->chat_box_store));
  95. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  96. }
  97. static void freq_input_cb(void *context)
  98. {
  99. furi_assert(context);
  100. ESubGhzChatState* state = context;
  101. furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
  102. state->frequency);
  103. scene_manager_handle_custom_event(state->scene_manager,
  104. ESubGhzChatEvent_FreqEntered);
  105. }
  106. static bool freq_input_validator(const char *text, FuriString *error,
  107. void *context)
  108. {
  109. furi_assert(text);
  110. furi_assert(error);
  111. furi_assert(context);
  112. ESubGhzChatState* state = context;
  113. int ret = sscanf(text, "%lu", &(state->frequency));
  114. if (ret != 1) {
  115. furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
  116. return false;
  117. }
  118. if (!furi_hal_subghz_is_frequency_valid(state->frequency)) {
  119. furi_string_printf(error, "Frequency\n%lu\n is invalid!",
  120. state->frequency);
  121. return false;
  122. }
  123. if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
  124. furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
  125. state->frequency);
  126. return false;
  127. }
  128. return true;
  129. }
  130. static void pass_input_cb(void *context)
  131. {
  132. furi_assert(context);
  133. ESubGhzChatState* state = context;
  134. furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
  135. (state->encrypted ? "true" : "false"));
  136. scene_manager_handle_custom_event(state->scene_manager,
  137. ESubGhzChatEvent_PassEntered);
  138. }
  139. static bool pass_input_validator(const char *text, FuriString *error,
  140. void *context)
  141. {
  142. furi_assert(text);
  143. furi_assert(error);
  144. furi_assert(context);
  145. ESubGhzChatState* state = context;
  146. if (strlen(text) == 0) {
  147. state->encrypted = false;
  148. return true;
  149. }
  150. unsigned char key[KEY_BITS / 8];
  151. state->encrypted = true;
  152. sha256((unsigned char *) text, strlen(text), key);
  153. // TODO: remove this
  154. furi_string_cat_printf(state->chat_box_store, "\nKey:");
  155. int i;
  156. for (i = 0; i < KEY_BITS / 8; i++) {
  157. furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
  158. }
  159. int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
  160. esubghz_chat_explicit_bzero(key, sizeof(key));
  161. if (ret != 0) {
  162. gcm_zero_ctx(&(state->gcm_ctx));
  163. furi_string_printf(error, "Failed to\nset key!");
  164. return false;
  165. }
  166. return true;
  167. }
  168. static void chat_input_cb(void *context)
  169. {
  170. furi_assert(context);
  171. ESubGhzChatState* state = context;
  172. if (strlen(state->text_input_store) == 0) {
  173. scene_manager_handle_custom_event(state->scene_manager,
  174. ESubGhzChatEvent_MsgEntered);
  175. return;
  176. }
  177. furi_string_set(state->msg_input, state->name_prefix);
  178. furi_string_cat_str(state->msg_input, state->text_input_store);
  179. furi_string_cat_printf(state->chat_box_store, "\n%s",
  180. furi_string_get_cstr(state->msg_input));
  181. size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
  182. size_t tx_size = msg_len;
  183. if (state->encrypted) {
  184. tx_size += IV_BYTES + TAG_BYTES;
  185. furi_check(tx_size <= sizeof(state->tx_buffer));
  186. furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
  187. gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
  188. state->tx_buffer, IV_BYTES,
  189. NULL, 0,
  190. (unsigned char *)
  191. furi_string_get_cstr(state->msg_input),
  192. state->tx_buffer + IV_BYTES,
  193. msg_len,
  194. state->tx_buffer + IV_BYTES + msg_len,
  195. TAG_BYTES);
  196. } else {
  197. furi_check(tx_size <= sizeof(state->tx_buffer));
  198. memcpy(state->tx_buffer,
  199. furi_string_get_cstr(state->msg_input),
  200. tx_size);
  201. }
  202. furi_string_set_char(state->msg_input, 0, 0);
  203. // TODO: remove this
  204. furi_string_cat_printf(state->chat_box_store, "\nTXed (HEX):");
  205. size_t i;
  206. for (i = 0; i < tx_size; i++) {
  207. furi_string_cat_printf(state->chat_box_store, " %02x",
  208. state->tx_buffer[i]);
  209. }
  210. // TODO: remove this
  211. state->last_time_rx_data = furi_get_tick();
  212. furi_stream_buffer_send(state->rx_collection_buffer,
  213. state->tx_buffer, tx_size, 0);
  214. // TODO: actually transmit
  215. scene_manager_handle_custom_event(state->scene_manager,
  216. ESubGhzChatEvent_MsgEntered);
  217. }
  218. static void scene_on_enter_freq_input(void* context)
  219. {
  220. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
  221. furi_assert(context);
  222. ESubGhzChatState* state = context;
  223. snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
  224. (uint32_t) DEFAULT_FREQ);
  225. text_input_reset(state->text_input);
  226. text_input_set_result_callback(
  227. state->text_input,
  228. freq_input_cb,
  229. state,
  230. state->text_input_store,
  231. sizeof(state->text_input_store),
  232. true);
  233. text_input_set_validator(
  234. state->text_input,
  235. freq_input_validator,
  236. state);
  237. text_input_set_header_text(
  238. state->text_input,
  239. "Frequency");
  240. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  241. }
  242. static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
  243. {
  244. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
  245. furi_assert(context);
  246. ESubGhzChatState* state = context;
  247. bool consumed = false;
  248. switch(event.type) {
  249. case SceneManagerEventTypeCustom:
  250. switch(event.event) {
  251. case ESubGhzChatEvent_FreqEntered:
  252. scene_manager_next_scene(state->scene_manager,
  253. ESubGhzChatScene_PassInput);
  254. consumed = true;
  255. break;
  256. }
  257. break;
  258. case SceneManagerEventTypeBack:
  259. view_dispatcher_stop(state->view_dispatcher);
  260. consumed = true;
  261. break;
  262. default:
  263. consumed = false;
  264. break;
  265. }
  266. return consumed;
  267. }
  268. static void scene_on_exit_freq_input(void* context)
  269. {
  270. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
  271. furi_assert(context);
  272. ESubGhzChatState* state = context;
  273. text_input_reset(state->text_input);
  274. }
  275. static void scene_on_enter_pass_input(void* context)
  276. {
  277. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
  278. furi_assert(context);
  279. ESubGhzChatState* state = context;
  280. state->text_input_store[0] = 0;
  281. text_input_reset(state->text_input);
  282. text_input_set_result_callback(
  283. state->text_input,
  284. pass_input_cb,
  285. state,
  286. state->text_input_store,
  287. sizeof(state->text_input_store),
  288. true);
  289. text_input_set_validator(
  290. state->text_input,
  291. pass_input_validator,
  292. state);
  293. text_input_set_header_text(
  294. state->text_input,
  295. "Password (empty for no encr.)");
  296. text_input_set_minimum_length(state->text_input, 0);
  297. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  298. }
  299. static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
  300. {
  301. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
  302. furi_assert(context);
  303. ESubGhzChatState* state = context;
  304. bool consumed = false;
  305. switch(event.type) {
  306. case SceneManagerEventTypeCustom:
  307. switch(event.event) {
  308. case ESubGhzChatEvent_PassEntered:
  309. scene_manager_next_scene(state->scene_manager,
  310. ESubGhzChatScene_ChatInput);
  311. consumed = true;
  312. break;
  313. }
  314. break;
  315. case SceneManagerEventTypeBack:
  316. view_dispatcher_stop(state->view_dispatcher);
  317. consumed = true;
  318. break;
  319. default:
  320. consumed = false;
  321. break;
  322. }
  323. return consumed;
  324. }
  325. static void scene_on_exit_pass_input(void* context)
  326. {
  327. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
  328. furi_assert(context);
  329. ESubGhzChatState* state = context;
  330. text_input_reset(state->text_input);
  331. }
  332. static void scene_on_enter_chat_input(void* context)
  333. {
  334. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
  335. furi_assert(context);
  336. ESubGhzChatState* state = context;
  337. state->text_input_store[0] = 0;
  338. text_input_reset(state->text_input);
  339. text_input_set_result_callback(
  340. state->text_input,
  341. chat_input_cb,
  342. state,
  343. state->text_input_store,
  344. sizeof(state->text_input_store),
  345. true);
  346. text_input_set_validator(
  347. state->text_input,
  348. NULL,
  349. NULL);
  350. text_input_set_header_text(
  351. state->text_input,
  352. "Message");
  353. text_input_set_minimum_length(state->text_input, 0);
  354. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  355. }
  356. static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
  357. {
  358. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
  359. furi_assert(context);
  360. ESubGhzChatState* state = context;
  361. bool consumed = false;
  362. switch(event.type) {
  363. case SceneManagerEventTypeCustom:
  364. switch(event.event) {
  365. case ESubGhzChatEvent_MsgEntered:
  366. scene_manager_next_scene(state->scene_manager,
  367. ESubGhzChatScene_ChatBox);
  368. consumed = true;
  369. break;
  370. }
  371. break;
  372. case SceneManagerEventTypeBack:
  373. view_dispatcher_stop(state->view_dispatcher);
  374. consumed = true;
  375. break;
  376. default:
  377. consumed = false;
  378. break;
  379. }
  380. return consumed;
  381. }
  382. static void scene_on_exit_chat_input(void* context)
  383. {
  384. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
  385. furi_assert(context);
  386. ESubGhzChatState* state = context;
  387. text_input_reset(state->text_input);
  388. }
  389. static void scene_on_enter_chat_box(void* context)
  390. {
  391. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
  392. furi_assert(context);
  393. ESubGhzChatState* state = context;
  394. text_box_reset(state->chat_box);
  395. text_box_set_text(state->chat_box,
  396. furi_string_get_cstr(state->chat_box_store));
  397. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  398. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  399. }
  400. static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
  401. {
  402. UNUSED(event);
  403. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
  404. furi_assert(context);
  405. return false;
  406. }
  407. static void scene_on_exit_chat_box(void* context)
  408. {
  409. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
  410. furi_assert(context);
  411. ESubGhzChatState* state = context;
  412. text_box_reset(state->chat_box);
  413. }
  414. static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
  415. scene_on_enter_freq_input,
  416. scene_on_enter_pass_input,
  417. scene_on_enter_chat_input,
  418. scene_on_enter_chat_box
  419. };
  420. static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
  421. scene_on_event_freq_input,
  422. scene_on_event_pass_input,
  423. scene_on_event_chat_input,
  424. scene_on_event_chat_box
  425. };
  426. static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
  427. scene_on_exit_freq_input,
  428. scene_on_exit_pass_input,
  429. scene_on_exit_chat_input,
  430. scene_on_exit_chat_box
  431. };
  432. static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
  433. .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
  434. .on_event_handlers = esubghz_chat_scene_on_event_handlers,
  435. .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
  436. .scene_num = ESubGhzChatScene_MAX};
  437. static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
  438. {
  439. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
  440. furi_assert(context);
  441. ESubGhzChatState* state = context;
  442. return scene_manager_handle_custom_event(state->scene_manager, event);
  443. }
  444. static bool esubghz_chat_navigation_event_callback(void* context)
  445. {
  446. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
  447. furi_assert(context);
  448. ESubGhzChatState* state = context;
  449. return scene_manager_handle_back_event(state->scene_manager);
  450. }
  451. static void esubghz_chat_tick_event_callback(void* context)
  452. {
  453. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
  454. furi_assert(context);
  455. ESubGhzChatState* state = context;
  456. size_t avail = furi_stream_buffer_bytes_available(
  457. state->rx_collection_buffer);
  458. if (avail > 0) {
  459. uint32_t since_last_rx = furi_get_tick() -
  460. state->last_time_rx_data;
  461. if (avail == RX_TX_BUFFER_SIZE || since_last_rx >
  462. MESSAGE_COMPLETION_TIMEOUT) {
  463. size_t rx_size = furi_stream_buffer_receive(
  464. state->rx_collection_buffer,
  465. state->rx_buffer,
  466. avail, 0);
  467. post_rx(state, rx_size);
  468. furi_stream_buffer_reset(state->rx_collection_buffer);
  469. }
  470. }
  471. scene_manager_handle_tick_event(state->scene_manager);
  472. }
  473. static bool helper_strings_alloc(ESubGhzChatState *state)
  474. {
  475. furi_assert(state);
  476. state->name_prefix = furi_string_alloc();
  477. if (state->name_prefix == NULL) {
  478. return false;
  479. }
  480. state->msg_input = furi_string_alloc();
  481. if (state->msg_input == NULL) {
  482. furi_string_free(state->name_prefix);
  483. return false;
  484. }
  485. return true;
  486. }
  487. static void helper_strings_free(ESubGhzChatState *state)
  488. {
  489. furi_assert(state);
  490. furi_string_free(state->name_prefix);
  491. furi_string_free(state->msg_input);
  492. }
  493. static bool chat_box_alloc(ESubGhzChatState *state)
  494. {
  495. furi_assert(state);
  496. state->chat_box = text_box_alloc();
  497. if (state->chat_box == NULL) {
  498. return false;
  499. }
  500. state->chat_box_store = furi_string_alloc();
  501. if (state->chat_box_store == NULL) {
  502. text_box_free(state->chat_box);
  503. return false;
  504. }
  505. furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
  506. furi_string_set_char(state->chat_box_store, 0, 0);
  507. text_box_set_text(state->chat_box,
  508. furi_string_get_cstr(state->chat_box_store));
  509. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  510. return true;
  511. }
  512. static void chat_box_free(ESubGhzChatState *state)
  513. {
  514. furi_assert(state);
  515. text_box_free(state->chat_box);
  516. furi_string_free(state->chat_box_store);
  517. }
  518. int32_t esubghz_chat(void)
  519. {
  520. gcm_initialize();
  521. int32_t err = -1;
  522. FURI_LOG_I(APPLICATION_NAME, "Starting...");
  523. ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
  524. if (state == NULL) {
  525. goto err_alloc;
  526. }
  527. memset(state, 0, sizeof(*state));
  528. state->scene_manager = scene_manager_alloc(
  529. &esubghz_chat_scene_event_handlers, state);
  530. if (state->scene_manager == NULL) {
  531. goto err_alloc_sm;
  532. }
  533. state->view_dispatcher = view_dispatcher_alloc();
  534. if (state->view_dispatcher == NULL) {
  535. goto err_alloc_vd;
  536. }
  537. if (!helper_strings_alloc(state)) {
  538. goto err_alloc_hs;
  539. }
  540. state->text_input = text_input_alloc();
  541. if (state->text_input == NULL) {
  542. goto err_alloc_ti;
  543. }
  544. if (!chat_box_alloc(state)) {
  545. goto err_alloc_cb;
  546. }
  547. state->rx_collection_buffer = furi_stream_buffer_alloc(
  548. RX_TX_BUFFER_SIZE,
  549. RX_TX_BUFFER_SIZE);
  550. if (state->rx_collection_buffer == NULL) {
  551. goto err_alloc_rcb;
  552. }
  553. // set chat name prefix
  554. // TODO: handle escape chars here somehow
  555. furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
  556. furi_hal_version_get_name_ptr());
  557. view_dispatcher_enable_queue(state->view_dispatcher);
  558. view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
  559. view_dispatcher_set_custom_event_callback(
  560. state->view_dispatcher,
  561. esubghz_chat_custom_event_callback);
  562. view_dispatcher_set_navigation_event_callback(
  563. state->view_dispatcher,
  564. esubghz_chat_navigation_event_callback);
  565. view_dispatcher_set_tick_event_callback(
  566. state->view_dispatcher,
  567. esubghz_chat_tick_event_callback,
  568. TICK_INTERVAL);
  569. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
  570. text_input_get_view(state->text_input));
  571. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
  572. text_box_get_view(state->chat_box));
  573. /* no error handling here, don't know how */
  574. Gui *gui = furi_record_open(RECORD_GUI);
  575. view_dispatcher_attach_to_gui(state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  576. /* no error handling here, don't know how */
  577. state->notification = furi_record_open(RECORD_NOTIFICATION);
  578. scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
  579. view_dispatcher_run(state->view_dispatcher);
  580. err = 0;
  581. furi_record_close(RECORD_NOTIFICATION);
  582. furi_record_close(RECORD_GUI);
  583. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_Input);
  584. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  585. // clear the key and potential password
  586. esubghz_chat_explicit_bzero(state->text_input_store,
  587. sizeof(state->text_input_store));
  588. gcm_zero_ctx(&(state->gcm_ctx));
  589. furi_stream_buffer_free(state->rx_collection_buffer);
  590. err_alloc_rcb:
  591. chat_box_free(state);
  592. err_alloc_cb:
  593. text_input_free(state->text_input);
  594. err_alloc_ti:
  595. helper_strings_free(state);
  596. err_alloc_hs:
  597. view_dispatcher_free(state->view_dispatcher);
  598. err_alloc_vd:
  599. scene_manager_free(state->scene_manager);
  600. err_alloc_sm:
  601. free(state);
  602. err_alloc:
  603. if (err != 0) {
  604. FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
  605. } else {
  606. FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
  607. }
  608. return err;
  609. }