esubghz_chat.c 27 KB

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