esubghz_chat.c 29 KB

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