esubghz_chat.c 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/elements.h>
  4. #include <gui/gui.h>
  5. #include <gui/modules/text_box.h>
  6. #include <gui/modules/text_input.h>
  7. #include <gui/view_dispatcher_i.h>
  8. #include <gui/view_port_i.h>
  9. #include <gui/scene_manager.h>
  10. #include <toolbox/sha256.h>
  11. #include <notification/notification_messages.h>
  12. #include "esubghz_chat_icons.h"
  13. #include "crypto/gcm.h"
  14. #define APPLICATION_NAME "ESubGhzChat"
  15. #define DEFAULT_FREQ 433920000
  16. #define KEY_BITS 256
  17. #define IV_BYTES 12
  18. #define TAG_BYTES 16
  19. #define RX_TX_BUFFER_SIZE 1024
  20. #define CHAT_BOX_STORE_SIZE 4096
  21. #define TEXT_INPUT_STORE_SIZE 512
  22. #define TICK_INTERVAL 50
  23. #define MESSAGE_COMPLETION_TIMEOUT 200
  24. #define TIMEOUT_BETWEEN_MESSAGES 500
  25. #define KBD_UNLOCK_CNT 3
  26. #define KBD_UNLOCK_TIMEOUT 1000
  27. typedef struct {
  28. SceneManager *scene_manager;
  29. ViewDispatcher *view_dispatcher;
  30. NotificationApp *notification;
  31. // UI elements
  32. TextBox *chat_box;
  33. FuriString *chat_box_store;
  34. TextInput *text_input;
  35. char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
  36. // selected frequency
  37. uint32_t frequency;
  38. // message assembly before TX
  39. FuriString *name_prefix;
  40. FuriString *msg_input;
  41. // encryption
  42. bool encrypted;
  43. gcm_context gcm_ctx;
  44. // RX and TX buffers
  45. uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
  46. uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
  47. char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
  48. FuriStreamBuffer *rx_collection_buffer;
  49. uint32_t last_time_rx_data;
  50. // for locking
  51. ViewPortDrawCallback orig_draw_cb;
  52. ViewPortInputCallback orig_input_cb;
  53. bool kbd_locked;
  54. uint32_t kbd_lock_msg_ticks;
  55. uint8_t kbd_lock_count;
  56. bool kbd_ok_input_ongoing;
  57. } ESubGhzChatState;
  58. typedef enum {
  59. ESubGhzChatScene_FreqInput,
  60. ESubGhzChatScene_PassInput,
  61. ESubGhzChatScene_ChatInput,
  62. ESubGhzChatScene_ChatBox,
  63. ESubGhzChatScene_MAX
  64. } ESubGhzChatScene;
  65. typedef enum {
  66. ESubGhzChatView_Input,
  67. ESubGhzChatView_ChatBox,
  68. } ESubGhzChatView;
  69. typedef enum {
  70. ESubGhzChatEvent_FreqEntered,
  71. ESubGhzChatEvent_PassEntered,
  72. ESubGhzChatEvent_MsgEntered
  73. } ESubGhzChatEvent;
  74. /* Function to clear sensitive memory. */
  75. static void esubghz_chat_explicit_bzero(void *s, size_t len)
  76. {
  77. memset(s, 0, len);
  78. asm volatile("" ::: "memory");
  79. }
  80. /* Post RX handler, decrypts received messages, displays them in the text box
  81. * and sends a notification. */
  82. static void post_rx(ESubGhzChatState *state, size_t rx_size)
  83. {
  84. furi_assert(state);
  85. if (rx_size == 0) {
  86. return;
  87. }
  88. furi_check(rx_size <= RX_TX_BUFFER_SIZE);
  89. /* decrypt if necessary */
  90. if (!state->encrypted) {
  91. memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
  92. state->rx_str_buffer[rx_size] = 0;
  93. } else {
  94. if (rx_size < IV_BYTES + TAG_BYTES + 1) {
  95. return;
  96. }
  97. int ret = gcm_auth_decrypt(&(state->gcm_ctx),
  98. state->rx_buffer, IV_BYTES,
  99. NULL, 0,
  100. state->rx_buffer + IV_BYTES,
  101. (uint8_t *) state->rx_str_buffer,
  102. rx_size - (IV_BYTES + TAG_BYTES),
  103. state->rx_buffer + rx_size - TAG_BYTES,
  104. TAG_BYTES);
  105. state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
  106. /* if decryption fails output an error message */
  107. if (ret != 0) {
  108. strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
  109. }
  110. }
  111. /* append message to text box */
  112. furi_string_cat_printf(state->chat_box_store, "\n%s",
  113. state->rx_str_buffer);
  114. /* send notification (make the flipper vibrate) */
  115. notification_message(state->notification, &sequence_single_vibro);
  116. /* reset text box contents and focus */
  117. text_box_set_text(state->chat_box,
  118. furi_string_get_cstr(state->chat_box_store));
  119. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  120. }
  121. /* Sends FreqEntered event to scene manager and displays the frequency in the
  122. * text box. */
  123. static void freq_input_cb(void *context)
  124. {
  125. furi_assert(context);
  126. ESubGhzChatState* state = context;
  127. furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
  128. state->frequency);
  129. scene_manager_handle_custom_event(state->scene_manager,
  130. ESubGhzChatEvent_FreqEntered);
  131. }
  132. /* Validates the entered frequency. */
  133. static bool freq_input_validator(const char *text, FuriString *error,
  134. void *context)
  135. {
  136. furi_assert(text);
  137. furi_assert(error);
  138. furi_assert(context);
  139. ESubGhzChatState* state = context;
  140. int ret = sscanf(text, "%lu", &(state->frequency));
  141. if (ret != 1) {
  142. furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
  143. return false;
  144. }
  145. if (!furi_hal_subghz_is_frequency_valid(state->frequency)) {
  146. furi_string_printf(error, "Frequency\n%lu\n is invalid!",
  147. state->frequency);
  148. return false;
  149. }
  150. #ifdef FW_ORIGIN_Official
  151. if (!furi_hal_region_is_frequency_allowed(state->frequency)) {
  152. #else /* FW_ORIGIN_Official */
  153. if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
  154. #endif /* FW_ORIGIN_Official */
  155. furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
  156. state->frequency);
  157. return false;
  158. }
  159. return true;
  160. }
  161. /* Sends PassEntered event to scene manager and displays whether or not
  162. * encryption has been enabled in the text box. Also clears the text input
  163. * buffer to remove the password. */
  164. static void pass_input_cb(void *context)
  165. {
  166. furi_assert(context);
  167. ESubGhzChatState* state = context;
  168. furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
  169. (state->encrypted ? "yes" : "no"));
  170. /* clear the text input buffer to remove the password */
  171. esubghz_chat_explicit_bzero(state->text_input_store,
  172. sizeof(state->text_input_store));
  173. scene_manager_handle_custom_event(state->scene_manager,
  174. ESubGhzChatEvent_PassEntered);
  175. }
  176. /* If a password was entered this derives a key from the password using a
  177. * single pass of SHA256 and initiates the AES-GCM context for encryption. If
  178. * the initiation fails, the password is rejected. */
  179. static bool pass_input_validator(const char *text, FuriString *error,
  180. void *context)
  181. {
  182. furi_assert(text);
  183. furi_assert(error);
  184. furi_assert(context);
  185. ESubGhzChatState* state = context;
  186. #ifdef FW_ORIGIN_Official
  187. if (strcmp(text, " ") == 0) {
  188. #else /* FW_ORIGIN_Official */
  189. if (strlen(text) == 0) {
  190. #endif /* FW_ORIGIN_Official */
  191. state->encrypted = false;
  192. return true;
  193. }
  194. unsigned char key[KEY_BITS / 8];
  195. state->encrypted = true;
  196. /* derive a key from the password */
  197. sha256((unsigned char *) text, strlen(text), key);
  198. // TODO: remove this
  199. furi_string_cat_printf(state->chat_box_store, "\nKey:");
  200. int i;
  201. for (i = 0; i < KEY_BITS / 8; i++) {
  202. furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
  203. }
  204. /* initiate the AES-GCM context */
  205. int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
  206. /* cleanup */
  207. esubghz_chat_explicit_bzero(key, sizeof(key));
  208. if (ret != 0) {
  209. esubghz_chat_explicit_bzero(&(state->gcm_ctx),
  210. sizeof(state->gcm_ctx));
  211. furi_string_printf(error, "Failed to\nset key!");
  212. return false;
  213. }
  214. return true;
  215. }
  216. /* If no message was entred this simply emits a MsgEntered event to the scene
  217. * manager to switch to the text box. If a message was entered it is appended
  218. * to the name string. The result is encrypted, if encryption is enabled, and
  219. * then copied into the TX buffer. The contents of the TX buffer are then
  220. * transmitted. The sent message is appended to the text box and a MsgEntered
  221. * event is sent to the scene manager to switch to the text box view. */
  222. static void chat_input_cb(void *context)
  223. {
  224. furi_assert(context);
  225. ESubGhzChatState* state = context;
  226. /* no message, just switch to the text box view */
  227. #ifdef FW_ORIGIN_Official
  228. if (strcmp(state->text_input_store, " ") == 0) {
  229. #else /* FW_ORIGIN_Official */
  230. if (strlen(state->text_input_store) == 0) {
  231. #endif /* FW_ORIGIN_Official */
  232. scene_manager_handle_custom_event(state->scene_manager,
  233. ESubGhzChatEvent_MsgEntered);
  234. return;
  235. }
  236. /* concatenate the name prefix and the actual message */
  237. furi_string_set(state->msg_input, state->name_prefix);
  238. furi_string_cat_str(state->msg_input, state->text_input_store);
  239. /* append the message to the chat box */
  240. furi_string_cat_printf(state->chat_box_store, "\n%s",
  241. furi_string_get_cstr(state->msg_input));
  242. /* encrypt message if necessary */
  243. size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
  244. size_t tx_size = msg_len;
  245. if (state->encrypted) {
  246. tx_size += IV_BYTES + TAG_BYTES;
  247. furi_check(tx_size <= sizeof(state->tx_buffer));
  248. furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
  249. gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
  250. state->tx_buffer, IV_BYTES,
  251. NULL, 0,
  252. (unsigned char *)
  253. furi_string_get_cstr(state->msg_input),
  254. state->tx_buffer + IV_BYTES,
  255. msg_len,
  256. state->tx_buffer + IV_BYTES + msg_len,
  257. TAG_BYTES);
  258. } else {
  259. furi_check(tx_size <= sizeof(state->tx_buffer));
  260. memcpy(state->tx_buffer,
  261. furi_string_get_cstr(state->msg_input),
  262. tx_size);
  263. }
  264. /* clear message input buffer */
  265. furi_string_set_char(state->msg_input, 0, 0);
  266. // TODO: remove this
  267. furi_string_cat_printf(state->chat_box_store, "\nTXed (HEX):");
  268. size_t i;
  269. for (i = 0; i < tx_size; i++) {
  270. furi_string_cat_printf(state->chat_box_store, " %02x",
  271. state->tx_buffer[i]);
  272. }
  273. // TODO: remove this
  274. state->last_time_rx_data = furi_get_tick();
  275. furi_stream_buffer_send(state->rx_collection_buffer,
  276. state->tx_buffer, tx_size, 0);
  277. // TODO: actually transmit
  278. /* switch to text box view */
  279. scene_manager_handle_custom_event(state->scene_manager,
  280. ESubGhzChatEvent_MsgEntered);
  281. }
  282. /* Prepares the frequency input scene. */
  283. static void scene_on_enter_freq_input(void* context)
  284. {
  285. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
  286. furi_assert(context);
  287. ESubGhzChatState* state = context;
  288. snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
  289. (uint32_t) DEFAULT_FREQ);
  290. text_input_reset(state->text_input);
  291. text_input_set_result_callback(
  292. state->text_input,
  293. freq_input_cb,
  294. state,
  295. state->text_input_store,
  296. sizeof(state->text_input_store),
  297. true);
  298. text_input_set_validator(
  299. state->text_input,
  300. freq_input_validator,
  301. state);
  302. text_input_set_header_text(
  303. state->text_input,
  304. "Frequency");
  305. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  306. }
  307. /* Handles scene manager events for the frequency input scene. */
  308. static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
  309. {
  310. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
  311. furi_assert(context);
  312. ESubGhzChatState* state = context;
  313. bool consumed = false;
  314. switch(event.type) {
  315. case SceneManagerEventTypeCustom:
  316. switch(event.event) {
  317. /* switch to password input scene */
  318. case ESubGhzChatEvent_FreqEntered:
  319. scene_manager_next_scene(state->scene_manager,
  320. ESubGhzChatScene_PassInput);
  321. consumed = true;
  322. break;
  323. }
  324. break;
  325. case SceneManagerEventTypeBack:
  326. /* stop the application if the user presses back here */
  327. view_dispatcher_stop(state->view_dispatcher);
  328. consumed = true;
  329. break;
  330. default:
  331. consumed = false;
  332. break;
  333. }
  334. return consumed;
  335. }
  336. /* Cleans up the frequency input scene. */
  337. static void scene_on_exit_freq_input(void* context)
  338. {
  339. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
  340. furi_assert(context);
  341. ESubGhzChatState* state = context;
  342. text_input_reset(state->text_input);
  343. }
  344. /* Prepares the password input scene. */
  345. static void scene_on_enter_pass_input(void* context)
  346. {
  347. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
  348. furi_assert(context);
  349. ESubGhzChatState* state = context;
  350. state->text_input_store[0] = 0;
  351. text_input_reset(state->text_input);
  352. text_input_set_result_callback(
  353. state->text_input,
  354. pass_input_cb,
  355. state,
  356. state->text_input_store,
  357. sizeof(state->text_input_store),
  358. true);
  359. text_input_set_validator(
  360. state->text_input,
  361. pass_input_validator,
  362. state);
  363. text_input_set_header_text(
  364. state->text_input,
  365. #ifdef FW_ORIGIN_Official
  366. "Password (space for no encr.)");
  367. #else /* FW_ORIGIN_Official */
  368. "Password (empty for no encr.)");
  369. text_input_set_minimum_length(state->text_input, 0);
  370. #endif /* FW_ORIGIN_Official */
  371. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  372. }
  373. /* Handles scene manager events for the password input scene. */
  374. static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
  375. {
  376. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
  377. furi_assert(context);
  378. ESubGhzChatState* state = context;
  379. bool consumed = false;
  380. switch(event.type) {
  381. case SceneManagerEventTypeCustom:
  382. switch(event.event) {
  383. /* switch to message input scene */
  384. case ESubGhzChatEvent_PassEntered:
  385. scene_manager_next_scene(state->scene_manager,
  386. ESubGhzChatScene_ChatInput);
  387. consumed = true;
  388. break;
  389. }
  390. break;
  391. case SceneManagerEventTypeBack:
  392. /* stop the application if the user presses back here */
  393. view_dispatcher_stop(state->view_dispatcher);
  394. consumed = true;
  395. break;
  396. default:
  397. consumed = false;
  398. break;
  399. }
  400. return consumed;
  401. }
  402. /* Cleans up the password input scene. */
  403. static void scene_on_exit_pass_input(void* context)
  404. {
  405. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
  406. furi_assert(context);
  407. ESubGhzChatState* state = context;
  408. text_input_reset(state->text_input);
  409. }
  410. /* Prepares the message input scene. */
  411. static void scene_on_enter_chat_input(void* context)
  412. {
  413. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
  414. furi_assert(context);
  415. ESubGhzChatState* state = context;
  416. state->text_input_store[0] = 0;
  417. text_input_reset(state->text_input);
  418. text_input_set_result_callback(
  419. state->text_input,
  420. chat_input_cb,
  421. state,
  422. state->text_input_store,
  423. sizeof(state->text_input_store),
  424. true);
  425. text_input_set_validator(
  426. state->text_input,
  427. NULL,
  428. NULL);
  429. text_input_set_header_text(
  430. state->text_input,
  431. #ifdef FW_ORIGIN_Official
  432. "Message (space for none)");
  433. #else /* FW_ORIGIN_Official */
  434. "Message");
  435. text_input_set_minimum_length(state->text_input, 0);
  436. #endif /* FW_ORIGIN_Official */
  437. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  438. }
  439. /* Handles scene manager events for the message input scene. */
  440. static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
  441. {
  442. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
  443. furi_assert(context);
  444. ESubGhzChatState* state = context;
  445. bool consumed = false;
  446. switch(event.type) {
  447. case SceneManagerEventTypeCustom:
  448. switch(event.event) {
  449. /* switch to text box scene */
  450. case ESubGhzChatEvent_MsgEntered:
  451. scene_manager_next_scene(state->scene_manager,
  452. ESubGhzChatScene_ChatBox);
  453. consumed = true;
  454. break;
  455. }
  456. break;
  457. case SceneManagerEventTypeBack:
  458. /* stop the application if the user presses back here */
  459. view_dispatcher_stop(state->view_dispatcher);
  460. consumed = true;
  461. break;
  462. default:
  463. consumed = false;
  464. break;
  465. }
  466. return consumed;
  467. }
  468. /* Cleans up the password input scene. */
  469. static void scene_on_exit_chat_input(void* context)
  470. {
  471. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
  472. furi_assert(context);
  473. ESubGhzChatState* state = context;
  474. text_input_reset(state->text_input);
  475. }
  476. /* Prepares the text box scene. */
  477. static void scene_on_enter_chat_box(void* context)
  478. {
  479. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
  480. furi_assert(context);
  481. ESubGhzChatState* state = context;
  482. text_box_reset(state->chat_box);
  483. text_box_set_text(state->chat_box,
  484. furi_string_get_cstr(state->chat_box_store));
  485. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  486. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  487. }
  488. /* Handles scene manager events for the text box scene. No events are handled
  489. * here. */
  490. static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
  491. {
  492. UNUSED(event);
  493. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
  494. furi_assert(context);
  495. return false;
  496. }
  497. /* Cleans up the text box scene. */
  498. static void scene_on_exit_chat_box(void* context)
  499. {
  500. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
  501. furi_assert(context);
  502. ESubGhzChatState* state = context;
  503. text_box_reset(state->chat_box);
  504. }
  505. /* Scene entry handlers. */
  506. static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
  507. scene_on_enter_freq_input,
  508. scene_on_enter_pass_input,
  509. scene_on_enter_chat_input,
  510. scene_on_enter_chat_box
  511. };
  512. /* Scene event handlers. */
  513. static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
  514. scene_on_event_freq_input,
  515. scene_on_event_pass_input,
  516. scene_on_event_chat_input,
  517. scene_on_event_chat_box
  518. };
  519. /* Scene exit handlers. */
  520. static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
  521. scene_on_exit_freq_input,
  522. scene_on_exit_pass_input,
  523. scene_on_exit_chat_input,
  524. scene_on_exit_chat_box
  525. };
  526. /* Handlers for the scene manager. */
  527. static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
  528. .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
  529. .on_event_handlers = esubghz_chat_scene_on_event_handlers,
  530. .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
  531. .scene_num = ESubGhzChatScene_MAX};
  532. /* Whether or not to display the locked message. */
  533. static bool kbd_lock_msg_display(ESubGhzChatState *state)
  534. {
  535. return (state->kbd_lock_msg_ticks != 0);
  536. }
  537. /* Whether or not to hide the locked message again. */
  538. static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
  539. {
  540. if (state->kbd_lock_msg_ticks == 0) {
  541. return false;
  542. }
  543. if (furi_get_tick() - state->kbd_lock_msg_ticks > KBD_UNLOCK_TIMEOUT) {
  544. return true;
  545. }
  546. return false;
  547. }
  548. /* Resets the timeout for the locked message and turns off the backlight if
  549. * specified. */
  550. static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
  551. {
  552. state->kbd_lock_msg_ticks = 0;
  553. state->kbd_lock_count = 0;
  554. if (backlight_off) {
  555. notification_message(state->notification,
  556. &sequence_display_backlight_off);
  557. }
  558. }
  559. /* Locks the keyboard. */
  560. static void kbd_lock(ESubGhzChatState *state)
  561. {
  562. state->kbd_locked = true;
  563. kbd_lock_msg_reset(state, true);
  564. }
  565. /* Unlocks the keyboard. */
  566. static void kbd_unlock(ESubGhzChatState *state)
  567. {
  568. state->kbd_locked = false;
  569. kbd_lock_msg_reset(state, false);
  570. }
  571. /* Custom event callback for view dispatcher. Just calls scene manager. */
  572. static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
  573. {
  574. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
  575. furi_assert(context);
  576. ESubGhzChatState* state = context;
  577. return scene_manager_handle_custom_event(state->scene_manager, event);
  578. }
  579. /* Navigation event callback for view dispatcher. Just calls scene manager. */
  580. static bool esubghz_chat_navigation_event_callback(void* context)
  581. {
  582. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
  583. furi_assert(context);
  584. ESubGhzChatState* state = context;
  585. return scene_manager_handle_back_event(state->scene_manager);
  586. }
  587. /* Tick event callback for view dispatcher. Called every TICK_INTERVAL. Resets
  588. * the locked message if necessary. Retrieves a received message from the
  589. * rx_collection_buffer and calls post_rx(). Then calls the scene manager. */
  590. static void esubghz_chat_tick_event_callback(void* context)
  591. {
  592. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
  593. furi_assert(context);
  594. ESubGhzChatState* state = context;
  595. /* reset locked message if necessary */
  596. if (kbd_lock_msg_reset_timeout(state)) {
  597. kbd_lock_msg_reset(state, true);
  598. }
  599. /* if the maximum message size was reached or the
  600. * MESSAGE_COMPLETION_TIMEOUT has expired, retrieve a message and call
  601. * post_rx() */
  602. size_t avail = furi_stream_buffer_bytes_available(
  603. state->rx_collection_buffer);
  604. if (avail > 0) {
  605. uint32_t since_last_rx = furi_get_tick() -
  606. state->last_time_rx_data;
  607. if (avail == RX_TX_BUFFER_SIZE || since_last_rx >
  608. MESSAGE_COMPLETION_TIMEOUT) {
  609. size_t rx_size = furi_stream_buffer_receive(
  610. state->rx_collection_buffer,
  611. state->rx_buffer,
  612. avail, 0);
  613. post_rx(state, rx_size);
  614. furi_stream_buffer_reset(state->rx_collection_buffer);
  615. }
  616. }
  617. /* call scene manager */
  618. scene_manager_handle_tick_event(state->scene_manager);
  619. }
  620. /* Hooks into the view port's draw callback to overlay the keyboard locked
  621. * message. */
  622. static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
  623. {
  624. FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
  625. furi_assert(canvas);
  626. furi_assert(context);
  627. ESubGhzChatState* state = context;
  628. /* call original callback */
  629. state->orig_draw_cb(canvas, state->view_dispatcher);
  630. /* display if the keyboard is locked */
  631. if (state->kbd_locked) {
  632. canvas_set_font(canvas, FontPrimary);
  633. elements_multiline_text_framed(canvas, 42, 30, "Locked");
  634. }
  635. /* display the unlock message if necessary */
  636. if (kbd_lock_msg_display(state)) {
  637. canvas_set_font(canvas, FontSecondary);
  638. elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
  639. elements_multiline_text(canvas, 65, 26, "To unlock\npress:");
  640. canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8);
  641. canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8);
  642. canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8);
  643. canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42);
  644. }
  645. }
  646. /* Hooks into the view port's input callback to handle the user locking the
  647. * keyboard. */
  648. static void esubghz_hooked_input_callback(InputEvent* event, void* context)
  649. {
  650. FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
  651. furi_assert(event);
  652. furi_assert(context);
  653. ESubGhzChatState* state = context;
  654. /* if the keyboard is locked no key presses are forwarded */
  655. if (state->kbd_locked) {
  656. /* key has been pressed, display the unlock message and
  657. * initiate the timer */
  658. if (state->kbd_lock_count == 0) {
  659. state->kbd_lock_msg_ticks = furi_get_tick();
  660. }
  661. /* back button has been pressed, increase the lock counter */
  662. if (event->key == InputKeyBack && event->type ==
  663. InputTypeShort) {
  664. state->kbd_lock_count++;
  665. }
  666. /* unlock the keyboard */
  667. if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
  668. kbd_unlock(state);
  669. }
  670. /* do not handle the event */
  671. return;
  672. }
  673. if (event->key == InputKeyOk) {
  674. /* if we are in the chat view and no input is ongoing, allow
  675. * locking */
  676. if (state->view_dispatcher->current_view ==
  677. text_box_get_view(state->chat_box) &&
  678. !(state->kbd_ok_input_ongoing)) {
  679. /* lock keyboard upon long press of Ok button */
  680. if (event->type == InputTypeLong) {
  681. kbd_lock(state);
  682. }
  683. /* do not handle any Ok key events to prevent blocking
  684. * of other keys */
  685. return;
  686. }
  687. /* handle ongoing inputs when chaning to chat view */
  688. if (event->type == InputTypePress) {
  689. state->kbd_ok_input_ongoing = true;
  690. } else if (event->type == InputTypeRelease) {
  691. state->kbd_ok_input_ongoing = false;
  692. }
  693. }
  694. /* call original callback */
  695. state->orig_input_cb(event, state->view_dispatcher);
  696. }
  697. static bool helper_strings_alloc(ESubGhzChatState *state)
  698. {
  699. furi_assert(state);
  700. state->name_prefix = furi_string_alloc();
  701. if (state->name_prefix == NULL) {
  702. return false;
  703. }
  704. state->msg_input = furi_string_alloc();
  705. if (state->msg_input == NULL) {
  706. furi_string_free(state->name_prefix);
  707. return false;
  708. }
  709. return true;
  710. }
  711. static void helper_strings_free(ESubGhzChatState *state)
  712. {
  713. furi_assert(state);
  714. furi_string_free(state->name_prefix);
  715. furi_string_free(state->msg_input);
  716. }
  717. static bool chat_box_alloc(ESubGhzChatState *state)
  718. {
  719. furi_assert(state);
  720. state->chat_box = text_box_alloc();
  721. if (state->chat_box == NULL) {
  722. return false;
  723. }
  724. state->chat_box_store = furi_string_alloc();
  725. if (state->chat_box_store == NULL) {
  726. text_box_free(state->chat_box);
  727. return false;
  728. }
  729. furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
  730. furi_string_set_char(state->chat_box_store, 0, 0);
  731. text_box_set_text(state->chat_box,
  732. furi_string_get_cstr(state->chat_box_store));
  733. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  734. return true;
  735. }
  736. static void chat_box_free(ESubGhzChatState *state)
  737. {
  738. furi_assert(state);
  739. text_box_free(state->chat_box);
  740. furi_string_free(state->chat_box_store);
  741. }
  742. int32_t esubghz_chat(void)
  743. {
  744. /* init the GCM and AES tables */
  745. gcm_initialize();
  746. int32_t err = -1;
  747. FURI_LOG_I(APPLICATION_NAME, "Starting...");
  748. /* allocate necessary structs and buffers */
  749. ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
  750. if (state == NULL) {
  751. goto err_alloc;
  752. }
  753. memset(state, 0, sizeof(*state));
  754. state->scene_manager = scene_manager_alloc(
  755. &esubghz_chat_scene_event_handlers, state);
  756. if (state->scene_manager == NULL) {
  757. goto err_alloc_sm;
  758. }
  759. state->view_dispatcher = view_dispatcher_alloc();
  760. if (state->view_dispatcher == NULL) {
  761. goto err_alloc_vd;
  762. }
  763. if (!helper_strings_alloc(state)) {
  764. goto err_alloc_hs;
  765. }
  766. state->text_input = text_input_alloc();
  767. if (state->text_input == NULL) {
  768. goto err_alloc_ti;
  769. }
  770. if (!chat_box_alloc(state)) {
  771. goto err_alloc_cb;
  772. }
  773. state->rx_collection_buffer = furi_stream_buffer_alloc(
  774. RX_TX_BUFFER_SIZE,
  775. RX_TX_BUFFER_SIZE);
  776. if (state->rx_collection_buffer == NULL) {
  777. goto err_alloc_rcb;
  778. }
  779. /* set chat name prefix */
  780. // TODO: handle escape chars here somehow
  781. furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
  782. furi_hal_version_get_name_ptr());
  783. /* get notification record, we use this to make the flipper vibrate */
  784. /* no error handling here, don't know how */
  785. state->notification = furi_record_open(RECORD_NOTIFICATION);
  786. /* hook into the view port's draw and input callbacks */
  787. state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
  788. state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
  789. view_port_draw_callback_set(state->view_dispatcher->view_port,
  790. esubghz_hooked_draw_callback, state);
  791. view_port_input_callback_set(state->view_dispatcher->view_port,
  792. esubghz_hooked_input_callback, state);
  793. view_dispatcher_enable_queue(state->view_dispatcher);
  794. /* set callbacks for view dispatcher */
  795. view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
  796. view_dispatcher_set_custom_event_callback(
  797. state->view_dispatcher,
  798. esubghz_chat_custom_event_callback);
  799. view_dispatcher_set_navigation_event_callback(
  800. state->view_dispatcher,
  801. esubghz_chat_navigation_event_callback);
  802. view_dispatcher_set_tick_event_callback(
  803. state->view_dispatcher,
  804. esubghz_chat_tick_event_callback,
  805. TICK_INTERVAL);
  806. /* add our two views to the view dispatcher */
  807. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
  808. text_input_get_view(state->text_input));
  809. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
  810. text_box_get_view(state->chat_box));
  811. /* get the GUI record and attach the view dispatcher to the GUI */
  812. /* no error handling here, don't know how */
  813. Gui *gui = furi_record_open(RECORD_GUI);
  814. view_dispatcher_attach_to_gui(state->view_dispatcher, gui,
  815. ViewDispatcherTypeFullscreen);
  816. /* switch to the frequency input scene */
  817. scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
  818. /* run the view dispatcher, this call only returns when we close the
  819. * application */
  820. view_dispatcher_run(state->view_dispatcher);
  821. err = 0;
  822. /* close GUI record */
  823. furi_record_close(RECORD_GUI);
  824. /* remove our two views from the view dispatcher */
  825. view_dispatcher_remove_view(state->view_dispatcher,
  826. ESubGhzChatView_Input);
  827. view_dispatcher_remove_view(state->view_dispatcher,
  828. ESubGhzChatView_ChatBox);
  829. /* close notification record */
  830. furi_record_close(RECORD_NOTIFICATION);
  831. /* clear the key and potential password */
  832. esubghz_chat_explicit_bzero(state->text_input_store,
  833. sizeof(state->text_input_store));
  834. esubghz_chat_explicit_bzero(&(state->gcm_ctx), sizeof(state->gcm_ctx));
  835. /* free everything we allocated*/
  836. furi_stream_buffer_free(state->rx_collection_buffer);
  837. err_alloc_rcb:
  838. chat_box_free(state);
  839. err_alloc_cb:
  840. text_input_free(state->text_input);
  841. err_alloc_ti:
  842. helper_strings_free(state);
  843. err_alloc_hs:
  844. view_dispatcher_free(state->view_dispatcher);
  845. err_alloc_vd:
  846. scene_manager_free(state->scene_manager);
  847. err_alloc_sm:
  848. free(state);
  849. err_alloc:
  850. if (err != 0) {
  851. FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
  852. } else {
  853. FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
  854. }
  855. return err;
  856. }