esubghz_chat.c 27 KB

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