esubghz_chat.c 14 KB

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