esubghz_chat.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <gui/modules/text_box.h>
  5. #include <gui/modules/text_input.h>
  6. #include <gui/view_dispatcher.h>
  7. #include <gui/scene_manager.h>
  8. #include <toolbox/sha256.h>
  9. #include "crypto/gcm.h"
  10. #define APPLICATION_NAME "ESubGhzChat"
  11. #define DEFAULT_FREQ 433920000
  12. #define KEY_BITS 256
  13. #define IV_BYTES 12
  14. #define TAG_BYTES 16
  15. #define RX_TX_BUFFER_SIZE 1024
  16. #define CHAT_BOX_STORE_SIZE 4096
  17. #define TEXT_INPUT_STORE_SIZE 512
  18. typedef struct {
  19. SceneManager *scene_manager;
  20. ViewDispatcher *view_dispatcher;
  21. TextBox *chat_box;
  22. FuriString *chat_box_store;
  23. TextInput *text_input;
  24. char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
  25. FuriString *name_prefix;
  26. FuriString *msg_input;
  27. bool encrypted;
  28. uint32_t frequency;
  29. gcm_context gcm_ctx;
  30. uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
  31. } ESubGhzChatState;
  32. typedef enum {
  33. ESubGhzChatScene_FreqInput,
  34. ESubGhzChatScene_PassInput,
  35. ESubGhzChatScene_ChatInput,
  36. ESubGhzChatScene_ChatBox,
  37. ESubGhzChatScene_MAX
  38. } ESubGhzChatScene;
  39. typedef enum {
  40. ESubGhzChatView_Input,
  41. ESubGhzChatView_ChatBox,
  42. } ESubGhzChatView;
  43. typedef enum {
  44. ESubGhzChatEvent_FreqEntered,
  45. ESubGhzChatEvent_PassEntered,
  46. ESubGhzChatEvent_MsgEntered
  47. } ESubGhzChatEvent;
  48. static void esubghz_chat_explicit_bzero(void *s, size_t len)
  49. {
  50. memset(s, 0, len);
  51. asm volatile("" ::: "memory");
  52. }
  53. static void freq_input_cb(void *context)
  54. {
  55. furi_assert(context);
  56. ESubGhzChatState* state = context;
  57. furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
  58. state->frequency);
  59. scene_manager_handle_custom_event(state->scene_manager,
  60. ESubGhzChatEvent_FreqEntered);
  61. }
  62. static bool freq_input_validator(const char *text, FuriString *error,
  63. void *context)
  64. {
  65. furi_assert(text);
  66. furi_assert(error);
  67. furi_assert(context);
  68. ESubGhzChatState* state = context;
  69. int ret = sscanf(text, "%lu", &(state->frequency));
  70. if (ret != 1) {
  71. furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
  72. return false;
  73. }
  74. if (!furi_hal_subghz_is_frequency_valid(state->frequency)) {
  75. furi_string_printf(error, "Frequency\n%lu\n is invalid!",
  76. state->frequency);
  77. return false;
  78. }
  79. if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
  80. furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
  81. state->frequency);
  82. return false;
  83. }
  84. return true;
  85. }
  86. static void pass_input_cb(void *context)
  87. {
  88. furi_assert(context);
  89. ESubGhzChatState* state = context;
  90. furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
  91. (state->encrypted ? "true" : "false"));
  92. scene_manager_handle_custom_event(state->scene_manager,
  93. ESubGhzChatEvent_PassEntered);
  94. }
  95. static bool pass_input_validator(const char *text, FuriString *error,
  96. void *context)
  97. {
  98. furi_assert(text);
  99. furi_assert(error);
  100. furi_assert(context);
  101. ESubGhzChatState* state = context;
  102. if (strlen(text) == 0) {
  103. state->encrypted = false;
  104. return true;
  105. }
  106. unsigned char key[KEY_BITS / 8];
  107. state->encrypted = true;
  108. sha256((unsigned char *) text, strlen(text), key);
  109. // TODO: remove this
  110. furi_string_cat_printf(state->chat_box_store, "\nKey:");
  111. int i;
  112. for (i = 0; i < KEY_BITS / 8; i++) {
  113. furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
  114. }
  115. int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
  116. esubghz_chat_explicit_bzero(key, sizeof(key));
  117. if (ret != 0) {
  118. gcm_zero_ctx(&(state->gcm_ctx));
  119. furi_string_printf(error, "Failed to\nset key!");
  120. return false;
  121. }
  122. return true;
  123. }
  124. static void chat_input_cb(void *context)
  125. {
  126. furi_assert(context);
  127. ESubGhzChatState* state = context;
  128. if (strlen(state->text_input_store) == 0) {
  129. scene_manager_handle_custom_event(state->scene_manager,
  130. ESubGhzChatEvent_MsgEntered);
  131. return;
  132. }
  133. furi_string_set(state->msg_input, state->name_prefix);
  134. furi_string_cat_str(state->msg_input, state->text_input_store);
  135. furi_string_cat_printf(state->chat_box_store, "\n%s",
  136. furi_string_get_cstr(state->msg_input));
  137. size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
  138. size_t tx_size = msg_len;
  139. if (state->encrypted) {
  140. tx_size += IV_BYTES + TAG_BYTES;
  141. furi_check(tx_size <= sizeof(state->tx_buffer));
  142. furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
  143. gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
  144. state->tx_buffer, IV_BYTES,
  145. NULL, 0,
  146. (unsigned char *)
  147. furi_string_get_cstr(state->msg_input),
  148. state->tx_buffer + IV_BYTES,
  149. msg_len,
  150. state->tx_buffer + IV_BYTES + msg_len,
  151. TAG_BYTES);
  152. } else {
  153. furi_check(tx_size <= sizeof(state->tx_buffer));
  154. memcpy(state->tx_buffer,
  155. furi_string_get_cstr(state->msg_input),
  156. tx_size);
  157. }
  158. furi_string_set_char(state->msg_input, 0, 0);
  159. // TODO: remove this
  160. furi_string_cat_printf(state->chat_box_store, "\nTXed (HEX):");
  161. size_t i;
  162. for (i = 0; i < tx_size; i++) {
  163. furi_string_cat_printf(state->chat_box_store, " %02x",
  164. state->tx_buffer[i]);
  165. }
  166. // TODO: actually transmit
  167. scene_manager_handle_custom_event(state->scene_manager,
  168. ESubGhzChatEvent_MsgEntered);
  169. }
  170. static void scene_on_enter_freq_input(void* context)
  171. {
  172. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
  173. furi_assert(context);
  174. ESubGhzChatState* state = context;
  175. snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
  176. (uint32_t) DEFAULT_FREQ);
  177. text_input_reset(state->text_input);
  178. text_input_set_result_callback(
  179. state->text_input,
  180. freq_input_cb,
  181. state,
  182. state->text_input_store,
  183. sizeof(state->text_input_store),
  184. true);
  185. text_input_set_validator(
  186. state->text_input,
  187. freq_input_validator,
  188. state);
  189. text_input_set_header_text(
  190. state->text_input,
  191. "Frequency");
  192. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  193. }
  194. static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
  195. {
  196. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
  197. furi_assert(context);
  198. ESubGhzChatState* state = context;
  199. bool consumed = false;
  200. switch(event.type) {
  201. case SceneManagerEventTypeCustom:
  202. switch(event.event) {
  203. case ESubGhzChatEvent_FreqEntered:
  204. scene_manager_next_scene(state->scene_manager,
  205. ESubGhzChatScene_PassInput);
  206. consumed = true;
  207. break;
  208. }
  209. break;
  210. case SceneManagerEventTypeBack:
  211. view_dispatcher_stop(state->view_dispatcher);
  212. consumed = true;
  213. break;
  214. default:
  215. consumed = false;
  216. break;
  217. }
  218. return consumed;
  219. }
  220. static void scene_on_exit_freq_input(void* context)
  221. {
  222. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
  223. furi_assert(context);
  224. ESubGhzChatState* state = context;
  225. text_input_reset(state->text_input);
  226. }
  227. static void scene_on_enter_pass_input(void* context)
  228. {
  229. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
  230. furi_assert(context);
  231. ESubGhzChatState* state = context;
  232. state->text_input_store[0] = 0;
  233. text_input_reset(state->text_input);
  234. text_input_set_result_callback(
  235. state->text_input,
  236. pass_input_cb,
  237. state,
  238. state->text_input_store,
  239. sizeof(state->text_input_store),
  240. true);
  241. text_input_set_validator(
  242. state->text_input,
  243. pass_input_validator,
  244. state);
  245. text_input_set_header_text(
  246. state->text_input,
  247. "Password (empty for no encr.)");
  248. text_input_set_minimum_length(state->text_input, 0);
  249. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  250. }
  251. static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
  252. {
  253. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
  254. furi_assert(context);
  255. ESubGhzChatState* state = context;
  256. bool consumed = false;
  257. switch(event.type) {
  258. case SceneManagerEventTypeCustom:
  259. switch(event.event) {
  260. case ESubGhzChatEvent_PassEntered:
  261. scene_manager_next_scene(state->scene_manager,
  262. ESubGhzChatScene_ChatInput);
  263. consumed = true;
  264. break;
  265. }
  266. break;
  267. case SceneManagerEventTypeBack:
  268. view_dispatcher_stop(state->view_dispatcher);
  269. consumed = true;
  270. break;
  271. default:
  272. consumed = false;
  273. break;
  274. }
  275. return consumed;
  276. }
  277. static void scene_on_exit_pass_input(void* context)
  278. {
  279. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
  280. furi_assert(context);
  281. ESubGhzChatState* state = context;
  282. text_input_reset(state->text_input);
  283. }
  284. static void scene_on_enter_chat_input(void* context)
  285. {
  286. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
  287. furi_assert(context);
  288. ESubGhzChatState* state = context;
  289. state->text_input_store[0] = 0;
  290. text_input_reset(state->text_input);
  291. text_input_set_result_callback(
  292. state->text_input,
  293. chat_input_cb,
  294. state,
  295. state->text_input_store,
  296. sizeof(state->text_input_store),
  297. true);
  298. text_input_set_validator(
  299. state->text_input,
  300. NULL,
  301. NULL);
  302. text_input_set_header_text(
  303. state->text_input,
  304. "Message");
  305. text_input_set_minimum_length(state->text_input, 0);
  306. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
  307. }
  308. static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
  309. {
  310. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
  311. furi_assert(context);
  312. ESubGhzChatState* state = context;
  313. bool consumed = false;
  314. switch(event.type) {
  315. case SceneManagerEventTypeCustom:
  316. switch(event.event) {
  317. case ESubGhzChatEvent_MsgEntered:
  318. scene_manager_next_scene(state->scene_manager,
  319. ESubGhzChatScene_ChatBox);
  320. consumed = true;
  321. break;
  322. }
  323. break;
  324. case SceneManagerEventTypeBack:
  325. view_dispatcher_stop(state->view_dispatcher);
  326. consumed = true;
  327. break;
  328. default:
  329. consumed = false;
  330. break;
  331. }
  332. return consumed;
  333. }
  334. static void scene_on_exit_chat_input(void* context)
  335. {
  336. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
  337. furi_assert(context);
  338. ESubGhzChatState* state = context;
  339. text_input_reset(state->text_input);
  340. }
  341. static void scene_on_enter_chat_box(void* context)
  342. {
  343. FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
  344. furi_assert(context);
  345. ESubGhzChatState* state = context;
  346. text_box_reset(state->chat_box);
  347. text_box_set_text(state->chat_box,
  348. furi_string_get_cstr(state->chat_box_store));
  349. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  350. view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  351. }
  352. static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
  353. {
  354. UNUSED(event);
  355. FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
  356. furi_assert(context);
  357. // TODO
  358. return false;
  359. }
  360. static void scene_on_exit_chat_box(void* context)
  361. {
  362. FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
  363. furi_assert(context);
  364. ESubGhzChatState* state = context;
  365. text_box_reset(state->chat_box);
  366. }
  367. static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
  368. scene_on_enter_freq_input,
  369. scene_on_enter_pass_input,
  370. scene_on_enter_chat_input,
  371. scene_on_enter_chat_box
  372. };
  373. static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
  374. scene_on_event_freq_input,
  375. scene_on_event_pass_input,
  376. scene_on_event_chat_input,
  377. scene_on_event_chat_box
  378. };
  379. static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
  380. scene_on_exit_freq_input,
  381. scene_on_exit_pass_input,
  382. scene_on_exit_chat_input,
  383. scene_on_exit_chat_box
  384. };
  385. static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
  386. .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
  387. .on_event_handlers = esubghz_chat_scene_on_event_handlers,
  388. .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
  389. .scene_num = ESubGhzChatScene_MAX};
  390. static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
  391. {
  392. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
  393. furi_assert(context);
  394. ESubGhzChatState* state = context;
  395. return scene_manager_handle_custom_event(state->scene_manager, event);
  396. }
  397. static bool esubghz_chat_navigation_event_callback(void* context)
  398. {
  399. FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
  400. furi_assert(context);
  401. ESubGhzChatState* state = context;
  402. return scene_manager_handle_back_event(state->scene_manager);
  403. }
  404. static bool helper_strings_alloc(ESubGhzChatState *state)
  405. {
  406. furi_assert(state);
  407. state->name_prefix = furi_string_alloc();
  408. if (state->name_prefix == NULL) {
  409. return false;
  410. }
  411. state->msg_input = furi_string_alloc();
  412. if (state->msg_input == NULL) {
  413. furi_string_free(state->name_prefix);
  414. return false;
  415. }
  416. return true;
  417. }
  418. static void helper_strings_free(ESubGhzChatState *state)
  419. {
  420. furi_assert(state);
  421. furi_string_free(state->name_prefix);
  422. furi_string_free(state->msg_input);
  423. }
  424. static bool chat_box_alloc(ESubGhzChatState *state)
  425. {
  426. furi_assert(state);
  427. state->chat_box = text_box_alloc();
  428. if (state->chat_box == NULL) {
  429. return false;
  430. }
  431. state->chat_box_store = furi_string_alloc();
  432. if (state->chat_box_store == NULL) {
  433. text_box_free(state->chat_box);
  434. return false;
  435. }
  436. furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE);
  437. furi_string_set_char(state->chat_box_store, 0, 0);
  438. text_box_set_text(state->chat_box,
  439. furi_string_get_cstr(state->chat_box_store));
  440. text_box_set_focus(state->chat_box, TextBoxFocusEnd);
  441. return true;
  442. }
  443. static void chat_box_free(ESubGhzChatState *state)
  444. {
  445. furi_assert(state);
  446. text_box_free(state->chat_box);
  447. furi_string_free(state->chat_box_store);
  448. }
  449. int32_t esubghz_chat(void)
  450. {
  451. gcm_initialize();
  452. int32_t err = -1;
  453. FURI_LOG_I(APPLICATION_NAME, "Starting...");
  454. ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
  455. if (state == NULL) {
  456. goto err_alloc;
  457. }
  458. memset(state, 0, sizeof(*state));
  459. state->scene_manager = scene_manager_alloc(
  460. &esubghz_chat_scene_event_handlers, state);
  461. if (state->scene_manager == NULL) {
  462. goto err_alloc_sm;
  463. }
  464. state->view_dispatcher = view_dispatcher_alloc();
  465. if (state->view_dispatcher == NULL) {
  466. goto err_alloc_vd;
  467. }
  468. if (!helper_strings_alloc(state)) {
  469. goto err_alloc_hs;
  470. }
  471. state->text_input = text_input_alloc();
  472. if (state->text_input == NULL) {
  473. goto err_alloc_ti;
  474. }
  475. if (!chat_box_alloc(state)) {
  476. goto err_alloc_cb;
  477. }
  478. // set chat name prefix
  479. // TODO: handle escape chars here somehow
  480. furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
  481. furi_hal_version_get_name_ptr());
  482. view_dispatcher_enable_queue(state->view_dispatcher);
  483. view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
  484. view_dispatcher_set_custom_event_callback(
  485. state->view_dispatcher,
  486. esubghz_chat_custom_event_callback);
  487. view_dispatcher_set_navigation_event_callback(
  488. state->view_dispatcher,
  489. esubghz_chat_navigation_event_callback);
  490. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
  491. text_input_get_view(state->text_input));
  492. view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
  493. text_box_get_view(state->chat_box));
  494. /* no error handling here, don't know how */
  495. Gui *gui = furi_record_open(RECORD_GUI);
  496. view_dispatcher_attach_to_gui(state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
  497. scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
  498. view_dispatcher_run(state->view_dispatcher);
  499. err = 0;
  500. furi_record_close(RECORD_GUI);
  501. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_Input);
  502. view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
  503. // clear the key and potential password
  504. esubghz_chat_explicit_bzero(state->text_input_store,
  505. sizeof(state->text_input_store));
  506. gcm_zero_ctx(&(state->gcm_ctx));
  507. chat_box_free(state);
  508. err_alloc_cb:
  509. text_input_free(state->text_input);
  510. err_alloc_ti:
  511. helper_strings_free(state);
  512. err_alloc_hs:
  513. view_dispatcher_free(state->view_dispatcher);
  514. err_alloc_vd:
  515. scene_manager_free(state->scene_manager);
  516. err_alloc_sm:
  517. free(state);
  518. err_alloc:
  519. if (err != 0) {
  520. FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!");
  521. } else {
  522. FURI_LOG_I(APPLICATION_NAME, "Clean exit.");
  523. }
  524. return err;
  525. }