esubghz_chat.c 30 KB

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