Browse Source

update apps and add new apps

MX 2 years ago
parent
commit
a3a3bbbec7

+ 14 - 9
README.md

@@ -5,7 +5,7 @@ feature that is available on the CLI. In addition it allows for basic
 encryption of messages.
 encryption of messages.
 
 
 The plugin has been tested on the official firmware (version 0.87.0) and on
 The plugin has been tested on the official firmware (version 0.87.0) and on
-Unleashed (version unlshd-057). Due to limitations of the official firmware,
+Unleashed (version unlshd-059). Due to limitations of the official firmware,
 the behavior is slightly different there.
 the behavior is slightly different there.
 
 
 Currently the use of an external antenna is not supported.
 Currently the use of an external antenna is not supported.
@@ -20,9 +20,10 @@ bugs. You have been warned.
 Once opened the plugin will ask for a frequency to operate on which must be
 Once opened the plugin will ask for a frequency to operate on which must be
 entered in HZ.
 entered in HZ.
 
 
-On the next screen the plugin will ask for a password to derive the
-cryptographic key from. If nothing (on Unleashed) or a single space (on OFW) is
-entered, the encryption is disabled.
+On the next screen the plugin will ask for the method of deriving the key. If
+"No encryption" is selected, the encryption is disabled. If "Generate Key" is
+selected, a random key is generated. Otherwise, the plugin will ask for the
+selected input method. Currently only a password and a hex key are supported.
 
 
 Finally the a message can be input. After the message is confirmed, the plugin
 Finally the a message can be input. After the message is confirmed, the plugin
 will switch to the chat view, where sent and received messages are displayed.
 will switch to the chat view, where sent and received messages are displayed.
@@ -32,10 +33,12 @@ button.
 
 
 In the chat view the keyboard can be locked by pressing and holding the OK
 In the chat view the keyboard can be locked by pressing and holding the OK
 button for a few seconds. To unlock the keyboard again quickly press the back
 button for a few seconds. To unlock the keyboard again quickly press the back
-button three times.
+button three times. By pressing the Right button the key display is opened.
+Here the currently used key is displayed in hex. This can be used to input the
+same key on another flipper.
 
 
-Pressing the back button when entering the frequency, the password or a message
-will terminate the plugin.
+Pressing the back button when entering the frequency, when selecting the method
+for deriving the key or when entering a message will terminate the plugin.
 
 
 ## Interoperability
 ## Interoperability
 
 
@@ -50,8 +53,8 @@ Messages are encrypted using 256 bit AES in GCM mode. Each message gets its own
 random IV. On reception the tag generated by GCM is verified and the message
 random IV. On reception the tag generated by GCM is verified and the message
 discarded if it doesn't match.
 discarded if it doesn't match.
 
 
-The key for the encryption is derived from the password by applying SHA-256 to
-the password once.
+If a password is used, the key for the encryption is derived from the password
+by applying SHA-256 to the password once.
 
 
 Note that deriving the key with SHA-256 means that the security of your
 Note that deriving the key with SHA-256 means that the security of your
 messages depends entirely on the strength of the password. The plugin does not
 messages depends entirely on the strength of the password. The plugin does not
@@ -73,3 +76,5 @@ expect to gain any security by using encryption.
 The implementations of AES and GCM are taken directly from
 The implementations of AES and GCM are taken directly from
 https://github.com/mko-x/SharedAES-GCM. They were released to the public domain
 https://github.com/mko-x/SharedAES-GCM. They were released to the public domain
 by Markus Kosmal.
 by Markus Kosmal.
+
+The app icon was made by [xMasterX](https://github.com/xMasterX).

+ 1 - 1
application.fam

@@ -9,7 +9,7 @@ App(
     ],
     ],
     stack_size=8 * 1024,
     stack_size=8 * 1024,
     fap_category="Sub-GHz",
     fap_category="Sub-GHz",
-    fap_icon="chat_10px.png",
+    fap_icon="assets/chat_10px.png",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
     fap_icon_assets_symbol="esubghz_chat",
     fap_icon_assets_symbol="esubghz_chat",
 )
 )

+ 0 - 0
chat_10px.png → assets/chat_10px.png


+ 105 - 0
crypto_wrapper.c

@@ -0,0 +1,105 @@
+#include <furi_hal.h>
+
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+#include "crypto/gcm.h"
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+#include "crypto_wrapper.h"
+
+struct ESugGhzChatCryptoCtx {
+	uint8_t key[KEY_BITS / 8];
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	gcm_context gcm_ctx;
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+};
+
+void crypto_init(void)
+{
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	/* init the GCM and AES tables */
+	gcm_initialize();
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}
+
+void crypto_explicit_bzero(void *s, size_t len)
+{
+	memset(s, 0, len);
+	asm volatile("" ::: "memory");
+}
+
+ESubGhzChatCryptoCtx *crypto_ctx_alloc(void)
+{
+	ESubGhzChatCryptoCtx *ret = malloc(sizeof(ESubGhzChatCryptoCtx));
+
+	if (ret != NULL) {
+		memset(ret, 0, sizeof(ESubGhzChatCryptoCtx));
+	}
+
+	return ret;
+}
+
+void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx)
+{
+	crypto_ctx_clear(ctx);
+	free(ctx);
+}
+
+void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx)
+{
+	crypto_explicit_bzero(ctx, sizeof(ESubGhzChatCryptoCtx));
+}
+
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key)
+{
+	memcpy(ctx->key, key, KEY_BITS / 8);
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	return true;
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+	return (gcm_setkey(&(ctx->gcm_ctx), key, KEY_BITS / 8) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}
+
+void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key)
+{
+	memcpy(key, ctx->key, KEY_BITS / 8);
+}
+
+bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+		uint8_t *out)
+{
+	if (in_len < MSG_OVERHEAD + 1) {
+		return false;
+	}
+
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	return (furi_hal_crypto_gcm_decrypt_and_verify(ctx->key,
+			in, in + IV_BYTES, out,
+			in_len - MSG_OVERHEAD,
+			in + in_len - TAG_BYTES) == FuriHalCryptoGCMStateOk);
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+	return (gcm_auth_decrypt(&(ctx->gcm_ctx),
+			in, IV_BYTES,
+			NULL, 0,
+			in + IV_BYTES, out, in_len - MSG_OVERHEAD,
+			in + in_len - TAG_BYTES, TAG_BYTES) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}
+
+bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+		uint8_t *out)
+{
+	furi_hal_random_fill_buf(out, IV_BYTES);
+
+#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	return (furi_hal_crypto_gcm_encrypt_and_tag(ctx->key,
+			out, in, out + IV_BYTES,
+			in_len,
+			out + IV_BYTES + in_len) == FuriHalCryptoGCMStateOk);
+#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+	return (gcm_crypt_and_tag(&(ctx->gcm_ctx), ENCRYPT,
+			out, IV_BYTES,
+			NULL, 0,
+			in, out + IV_BYTES, in_len,
+			out + IV_BYTES + in_len, TAG_BYTES) == 0);
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+}

+ 35 - 0
crypto_wrapper.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KEY_BITS 256
+#define IV_BYTES 12
+#define TAG_BYTES 16
+
+#define MSG_OVERHEAD (IV_BYTES + TAG_BYTES)
+
+typedef struct ESugGhzChatCryptoCtx ESubGhzChatCryptoCtx;
+
+void crypto_init(void);
+
+/* Function to clear sensitive memory. */
+void crypto_explicit_bzero(void *s, size_t len);
+
+ESubGhzChatCryptoCtx *crypto_ctx_alloc(void);
+void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx);
+
+void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx);
+
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key);
+void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key);
+
+bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+		uint8_t *out);
+bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
+		uint8_t *out);
+
+#ifdef __cplusplus
+}
+#endif

+ 130 - 584
esubghz_chat.c

@@ -1,107 +1,20 @@
-#include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal.h>
 #include <gui/elements.h>
 #include <gui/elements.h>
 #include <gui/gui.h>
 #include <gui/gui.h>
-#include <gui/modules/text_box.h>
-#include <gui/modules/text_input.h>
-#include <gui/view_dispatcher_i.h>
-#include <gui/view_port_i.h>
-#include <gui/scene_manager.h>
-#include <toolbox/sha256.h>
-#include <notification/notification_messages.h>
 #include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
 #include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
-#include <lib/subghz/subghz_tx_rx_worker.h>
 
 
 #include "esubghz_chat_icons.h"
 #include "esubghz_chat_icons.h"
 
 
-#include "crypto/gcm.h"
-
-#define APPLICATION_NAME "ESubGhzChat"
-
-#define DEFAULT_FREQ 433920000
-
-#define KEY_BITS 256
-#define IV_BYTES 12
-#define TAG_BYTES 16
-
-#define RX_TX_BUFFER_SIZE 1024
-
-#define CHAT_BOX_STORE_SIZE 4096
-#define TEXT_INPUT_STORE_SIZE 256
+#include "esubghz_chat_i.h"
 
 
+#define CHAT_LEAVE_DELAY 10
 #define TICK_INTERVAL 50
 #define TICK_INTERVAL 50
 #define MESSAGE_COMPLETION_TIMEOUT 500
 #define MESSAGE_COMPLETION_TIMEOUT 500
 #define TIMEOUT_BETWEEN_MESSAGES 500
 #define TIMEOUT_BETWEEN_MESSAGES 500
-#define CHAT_LEAVE_DELAY 10
 
 
 #define KBD_UNLOCK_CNT 3
 #define KBD_UNLOCK_CNT 3
 #define KBD_UNLOCK_TIMEOUT 1000
 #define KBD_UNLOCK_TIMEOUT 1000
 
 
-typedef struct {
-	SceneManager *scene_manager;
-	ViewDispatcher *view_dispatcher;
-	NotificationApp *notification;
-
-	// UI elements
-	TextBox *chat_box;
-	FuriString *chat_box_store;
-	TextInput *text_input;
-	char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
-
-	// for Sub-GHz
-	uint32_t frequency;
-	SubGhzTxRxWorker *subghz_worker;
-	const SubGhzDevice *subghz_device;
-
-	// message assembly before TX
-	FuriString *name_prefix;
-	FuriString *msg_input;
-
-	// encryption
-	bool encrypted;
-	gcm_context gcm_ctx;
-
-	// RX and TX buffers
-	uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
-	uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
-	char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
-	volatile uint32_t last_time_rx_data;
-
-	// for locking
-	ViewPortDrawCallback orig_draw_cb;
-	ViewPortInputCallback orig_input_cb;
-	bool kbd_locked;
-	uint32_t kbd_lock_msg_ticks;
-	uint8_t kbd_lock_count;
-	bool kbd_ok_input_ongoing;
-} ESubGhzChatState;
-
-typedef enum {
-	ESubGhzChatScene_FreqInput,
-	ESubGhzChatScene_PassInput,
-	ESubGhzChatScene_ChatInput,
-	ESubGhzChatScene_ChatBox,
-	ESubGhzChatScene_MAX
-} ESubGhzChatScene;
-
-typedef enum {
-	ESubGhzChatView_Input,
-	ESubGhzChatView_ChatBox,
-} ESubGhzChatView;
-
-typedef enum {
-	ESubGhzChatEvent_FreqEntered,
-	ESubGhzChatEvent_PassEntered,
-	ESubGhzChatEvent_MsgEntered
-} ESubGhzChatEvent;
-
-/* Function to clear sensitive memory. */
-static void esubghz_chat_explicit_bzero(void *s, size_t len)
-{
-	memset(s, 0, len);
-	asm volatile("" ::: "memory");
-}
-
 /* Callback for RX events from the Sub-GHz worker. Records the current ticks as
 /* Callback for RX events from the Sub-GHz worker. Records the current ticks as
  * the time of the last reception. */
  * the time of the last reception. */
 static void have_read_cb(void* context)
 static void have_read_cb(void* context)
@@ -115,21 +28,17 @@ static void have_read_cb(void* context)
 /* Decrypts a message for post_rx(). */
 /* Decrypts a message for post_rx(). */
 static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
 static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
 {
 {
-	if (rx_size < IV_BYTES + TAG_BYTES + 1) {
-		return false;
+	bool ret = crypto_ctx_decrypt(state->crypto_ctx,
+			state->rx_buffer, rx_size,
+			(uint8_t*) state->rx_str_buffer);
+
+	if (ret) {
+		state->rx_str_buffer[rx_size - (MSG_OVERHEAD)] = 0;
+	} else {
+		state->rx_str_buffer[0] = 0;
 	}
 	}
 
 
-	int ret = gcm_auth_decrypt(&(state->gcm_ctx),
-			state->rx_buffer, IV_BYTES,
-			NULL, 0,
-			state->rx_buffer + IV_BYTES,
-			(uint8_t *) state->rx_str_buffer,
-			rx_size - (IV_BYTES + TAG_BYTES),
-			state->rx_buffer + rx_size - TAG_BYTES,
-			TAG_BYTES);
-	state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
-
-	return (ret == 0);
+	return ret;
 }
 }
 
 
 /* Post RX handler, decrypts received messages, displays them in the text box
 /* Post RX handler, decrypts received messages, displays them in the text box
@@ -176,25 +85,20 @@ static void post_rx(ESubGhzChatState *state, size_t rx_size)
 
 
 /* Reads the message from msg_input, encrypts it if necessary and then
 /* Reads the message from msg_input, encrypts it if necessary and then
  * transmits it. */
  * transmits it. */
-static void tx_msg_input(ESubGhzChatState *state)
+void tx_msg_input(ESubGhzChatState *state)
 {
 {
 	/* encrypt message if necessary */
 	/* encrypt message if necessary */
 	size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
 	size_t msg_len = strlen(furi_string_get_cstr(state->msg_input));
 	size_t tx_size = msg_len;
 	size_t tx_size = msg_len;
 	if (state->encrypted) {
 	if (state->encrypted) {
-		tx_size += IV_BYTES + TAG_BYTES;
+		tx_size += MSG_OVERHEAD;
 		furi_check(tx_size <= sizeof(state->tx_buffer));
 		furi_check(tx_size <= sizeof(state->tx_buffer));
 
 
-		furi_hal_random_fill_buf(state->tx_buffer, IV_BYTES);
-		gcm_crypt_and_tag(&(state->gcm_ctx), ENCRYPT,
-				state->tx_buffer, IV_BYTES,
-				NULL, 0,
-				(unsigned char *)
+		crypto_ctx_encrypt(state->crypto_ctx,
+				(uint8_t *)
 				furi_string_get_cstr(state->msg_input),
 				furi_string_get_cstr(state->msg_input),
-				state->tx_buffer + IV_BYTES,
 				msg_len,
 				msg_len,
-				state->tx_buffer + IV_BYTES + msg_len,
-				TAG_BYTES);
+				state->tx_buffer);
 	} else {
 	} else {
 		tx_size += 2;
 		tx_size += 2;
 		furi_check(tx_size <= sizeof(state->tx_buffer));
 		furi_check(tx_size <= sizeof(state->tx_buffer));
@@ -212,72 +116,14 @@ static void tx_msg_input(ESubGhzChatState *state)
 			tx_size);
 			tx_size);
 }
 }
 
 
-/* Sends FreqEntered event to scene manager and displays the frequency in the
- * text box. */
-static void freq_input_cb(void *context)
-{
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
-			state->frequency);
-
-	scene_manager_handle_custom_event(state->scene_manager,
-			ESubGhzChatEvent_FreqEntered);
-}
-
-/* Validates the entered frequency. */
-static bool freq_input_validator(const char *text, FuriString *error,
-		void *context)
-{
-	furi_assert(text);
-	furi_assert(error);
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-        int ret = sscanf(text, "%lu", &(state->frequency));
-	if (ret != 1) {
-		furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
-		return false;
-	}
-
-	if (!subghz_devices_is_frequency_valid(state->subghz_device,
-				state->frequency)) {
-		furi_string_printf(error, "Frequency\n%lu\n is invalid!",
-				state->frequency);
-		return false;
-	}
-
-#ifdef FW_ORIGIN_Official
-	if (!furi_hal_region_is_frequency_allowed(state->frequency)) {
-#else /* FW_ORIGIN_Official */
-	if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
-#endif /* FW_ORIGIN_Official */
-		furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
-				state->frequency);
-		return false;
-	}
-
-	return true;
-}
-
-/* Sends PassEntered event to scene manager and displays whether or not
- * encryption has been enabled in the text box. Also clears the text input
- * buffer to remove the password and starts the Sub-GHz worker. After starting
- * the worker a join message is transmitted. */
-static void pass_input_cb(void *context)
+/* Displays whether or not encryption has been enabled in the text box. Also
+ * clears the text input buffer to remove the password and starts the Sub-GHz
+ * worker. After starting the worker a join message is transmitted. */
+void enter_chat(ESubGhzChatState *state)
 {
 {
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
 	furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
 	furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s",
 			(state->encrypted ? "yes" : "no"));
 			(state->encrypted ? "yes" : "no"));
 
 
-	/* clear the text input buffer to remove the password */
-	esubghz_chat_explicit_bzero(state->text_input_store,
-			sizeof(state->text_input_store));
-
 	subghz_tx_rx_worker_start(state->subghz_worker, state->subghz_device,
 	subghz_tx_rx_worker_start(state->subghz_worker, state->subghz_device,
 			state->frequency);
 			state->frequency);
 
 
@@ -290,90 +136,14 @@ static void pass_input_cb(void *context)
 
 
 	/* clear message input buffer */
 	/* clear message input buffer */
 	furi_string_set_char(state->msg_input, 0, 0);
 	furi_string_set_char(state->msg_input, 0, 0);
-
-	scene_manager_handle_custom_event(state->scene_manager,
-			ESubGhzChatEvent_PassEntered);
-}
-
-/* If a password was entered this derives a key from the password using a
- * single pass of SHA256 and initiates the AES-GCM context for encryption. If
- * the initiation fails, the password is rejected. */
-static bool pass_input_validator(const char *text, FuriString *error,
-		void *context)
-{
-	furi_assert(text);
-	furi_assert(error);
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-#ifdef FW_ORIGIN_Official
-	if (strlen(text) == 0) {
-		furi_string_printf(error, "Enter a\npassword!");
-		return false;
-	}
-
-	if (strcmp(text, " ") == 0) {
-#else /* FW_ORIGIN_Official */
-	if (strlen(text) == 0) {
-#endif /* FW_ORIGIN_Official */
-		state->encrypted = false;
-		return true;
-	}
-
-	unsigned char key[KEY_BITS / 8];
-
-	/* derive a key from the password */
-	sha256((unsigned char *) text, strlen(text), key);
-
-	/* initiate the AES-GCM context */
-	int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
-
-	/* cleanup */
-	esubghz_chat_explicit_bzero(key, sizeof(key));
-
-	if (ret != 0) {
-		esubghz_chat_explicit_bzero(&(state->gcm_ctx),
-				sizeof(state->gcm_ctx));
-		furi_string_printf(error, "Failed to\nset key!");
-		return false;
-	}
-
-	state->encrypted = true;
-
-	return true;
 }
 }
 
 
-/* If no message was entred this simply emits a MsgEntered event to the scene
- * manager to switch to the text box. If a message was entered it is appended
- * to the name string. The result is encrypted, if encryption is enabled, and
- * then copied into the TX buffer. The contents of the TX buffer are then
- * transmitted. The sent message is appended to the text box and a MsgEntered
- * event is sent to the scene manager to switch to the text box view. */
-static void chat_input_cb(void *context)
+/* Sends a leave message */
+void exit_chat(ESubGhzChatState *state)
 {
 {
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	/* no message, just switch to the text box view */
-#ifdef FW_ORIGIN_Official
-	if (strcmp(state->text_input_store, " ") == 0) {
-#else /* FW_ORIGIN_Official */
-	if (strlen(state->text_input_store) == 0) {
-#endif /* FW_ORIGIN_Official */
-		scene_manager_handle_custom_event(state->scene_manager,
-				ESubGhzChatEvent_MsgEntered);
-		return;
-	}
-
-	/* concatenate the name prefix and the actual message */
+	/* concatenate the name prefix and leave message */
 	furi_string_set(state->msg_input, state->name_prefix);
 	furi_string_set(state->msg_input, state->name_prefix);
-	furi_string_cat_str(state->msg_input, ": ");
-	furi_string_cat_str(state->msg_input, state->text_input_store);
-
-	/* append the message to the chat box */
-	furi_string_cat_printf(state->chat_box_store, "\n%s",
-		furi_string_get_cstr(state->msg_input));
+	furi_string_cat_str(state->msg_input, " left chat.");
 
 
 	/* encrypt and transmit message */
 	/* encrypt and transmit message */
 	tx_msg_input(state);
 	tx_msg_input(state);
@@ -381,333 +151,10 @@ static void chat_input_cb(void *context)
 	/* clear message input buffer */
 	/* clear message input buffer */
 	furi_string_set_char(state->msg_input, 0, 0);
 	furi_string_set_char(state->msg_input, 0, 0);
 
 
-	/* switch to text box view */
-	scene_manager_handle_custom_event(state->scene_manager,
-			ESubGhzChatEvent_MsgEntered);
-}
-
-/* Prepares the frequency input scene. */
-static void scene_on_enter_freq_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
-			(uint32_t) DEFAULT_FREQ);
-	text_input_reset(state->text_input);
-	text_input_set_result_callback(
-			state->text_input,
-			freq_input_cb,
-			state,
-			state->text_input_store,
-			sizeof(state->text_input_store),
-			true);
-	text_input_set_validator(
-			state->text_input,
-			freq_input_validator,
-			state);
-	text_input_set_header_text(
-			state->text_input,
-			"Frequency");
-
-	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
-}
-
-/* Handles scene manager events for the frequency input scene. */
-static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	bool consumed = false;
-
-	switch(event.type) {
-	case SceneManagerEventTypeCustom:
-		switch(event.event) {
-		/* switch to password input scene */
-		case ESubGhzChatEvent_FreqEntered:
-			scene_manager_next_scene(state->scene_manager,
-					ESubGhzChatScene_PassInput);
-			consumed = true;
-			break;
-		}
-		break;
-
-	case SceneManagerEventTypeBack:
-		/* stop the application if the user presses back here */
-		view_dispatcher_stop(state->view_dispatcher);
-		consumed = true;
-		break;
-
-	default:
-		consumed = false;
-		break;
-	}
-
-	return consumed;
-}
-
-/* Cleans up the frequency input scene. */
-static void scene_on_exit_freq_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	text_input_reset(state->text_input);
+	/* wait for leave message to be delivered */
+	furi_delay_ms(CHAT_LEAVE_DELAY);
 }
 }
 
 
-/* Prepares the password input scene. */
-static void scene_on_enter_pass_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	state->text_input_store[0] = 0;
-	text_input_reset(state->text_input);
-	text_input_set_result_callback(
-			state->text_input,
-			pass_input_cb,
-			state,
-			state->text_input_store,
-			sizeof(state->text_input_store),
-			true);
-	text_input_set_validator(
-			state->text_input,
-			pass_input_validator,
-			state);
-	text_input_set_header_text(
-			state->text_input,
-#ifdef FW_ORIGIN_Official
-			"Password (space for no encr.)");
-#else /* FW_ORIGIN_Official */
-			"Password (empty for no encr.)");
-	text_input_set_minimum_length(state->text_input, 0);
-#endif /* FW_ORIGIN_Official */
-
-	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
-}
-
-/* Handles scene manager events for the password input scene. */
-static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	bool consumed = false;
-
-	switch(event.type) {
-	case SceneManagerEventTypeCustom:
-		switch(event.event) {
-		/* switch to message input scene */
-		case ESubGhzChatEvent_PassEntered:
-			scene_manager_next_scene(state->scene_manager,
-					ESubGhzChatScene_ChatInput);
-			consumed = true;
-			break;
-		}
-		break;
-
-	case SceneManagerEventTypeBack:
-		/* stop the application if the user presses back here */
-		view_dispatcher_stop(state->view_dispatcher);
-		consumed = true;
-		break;
-
-	default:
-		consumed = false;
-		break;
-	}
-
-	return consumed;
-}
-
-/* Cleans up the password input scene. */
-static void scene_on_exit_pass_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	text_input_reset(state->text_input);
-}
-
-/* Prepares the message input scene. */
-static void scene_on_enter_chat_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	state->text_input_store[0] = 0;
-	text_input_reset(state->text_input);
-	text_input_set_result_callback(
-			state->text_input,
-			chat_input_cb,
-			state,
-			state->text_input_store,
-			sizeof(state->text_input_store),
-			true);
-	text_input_set_validator(
-			state->text_input,
-			NULL,
-			NULL);
-	text_input_set_header_text(
-			state->text_input,
-#ifdef FW_ORIGIN_Official
-			"Message (space for none)");
-#else /* FW_ORIGIN_Official */
-			"Message");
-	text_input_set_minimum_length(state->text_input, 0);
-#endif /* FW_ORIGIN_Official */
-
-	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
-}
-
-/* Handles scene manager events for the message input scene. */
-static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	bool consumed = false;
-
-	switch(event.type) {
-	case SceneManagerEventTypeCustom:
-		switch(event.event) {
-		/* switch to text box scene */
-		case ESubGhzChatEvent_MsgEntered:
-			scene_manager_next_scene(state->scene_manager,
-					ESubGhzChatScene_ChatBox);
-			consumed = true;
-			break;
-		}
-		break;
-
-	case SceneManagerEventTypeBack:
-		/* stop the application and send a leave message if the user
-		 * presses back here */
-
-		/* concatenate the name prefix and leave message */
-		furi_string_set(state->msg_input, state->name_prefix);
-		furi_string_cat_str(state->msg_input, " left chat.");
-
-		/* encrypt and transmit message */
-		tx_msg_input(state);
-
-		/* clear message input buffer */
-		furi_string_set_char(state->msg_input, 0, 0);
-
-		/* wait for leave message to be delivered */
-                furi_delay_ms(CHAT_LEAVE_DELAY);
-
-		view_dispatcher_stop(state->view_dispatcher);
-		consumed = true;
-		break;
-
-	default:
-		consumed = false;
-		break;
-	}
-
-	return consumed;
-}
-
-/* Cleans up the password input scene. */
-static void scene_on_exit_chat_input(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	text_input_reset(state->text_input);
-}
-
-/* Prepares the text box scene. */
-static void scene_on_enter_chat_box(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	text_box_reset(state->chat_box);
-	text_box_set_text(state->chat_box,
-			furi_string_get_cstr(state->chat_box_store));
-	text_box_set_focus(state->chat_box, TextBoxFocusEnd);
-
-	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
-}
-
-/* Handles scene manager events for the text box scene. No events are handled
- * here. */
-static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
-{
-	UNUSED(event);
-
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
-
-	furi_assert(context);
-
-	return false;
-}
-
-/* Cleans up the text box scene. */
-static void scene_on_exit_chat_box(void* context)
-{
-	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
-
-	furi_assert(context);
-	ESubGhzChatState* state = context;
-
-	text_box_reset(state->chat_box);
-}
-
-/* Scene entry handlers. */
-static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
-	scene_on_enter_freq_input,
-	scene_on_enter_pass_input,
-	scene_on_enter_chat_input,
-	scene_on_enter_chat_box
-};
-
-/* Scene event handlers. */
-static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
-	scene_on_event_freq_input,
-	scene_on_event_pass_input,
-	scene_on_event_chat_input,
-	scene_on_event_chat_box
-};
-
-/* Scene exit handlers. */
-static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
-	scene_on_exit_freq_input,
-	scene_on_exit_pass_input,
-	scene_on_exit_chat_input,
-	scene_on_exit_chat_box
-};
-
-/* Handlers for the scene manager. */
-static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
-	.on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
-	.on_event_handlers = esubghz_chat_scene_on_event_handlers,
-	.on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
-	.scene_num = ESubGhzChatScene_MAX};
-
 /* Whether or not to display the locked message. */
 /* Whether or not to display the locked message. */
 static bool kbd_lock_msg_display(ESubGhzChatState *state)
 static bool kbd_lock_msg_display(ESubGhzChatState *state)
 {
 {
@@ -892,7 +339,7 @@ static void esubghz_hooked_input_callback(InputEvent* event, void* context)
 			return;
 			return;
 		}
 		}
 
 
-		/* handle ongoing inputs when chaning to chat view */
+		/* handle ongoing inputs when changing to chat view */
 		if (event->type == InputTypePress) {
 		if (event->type == InputTypePress) {
 			state->kbd_ok_input_ongoing = true;
 			state->kbd_ok_input_ongoing = true;
 		} else if (event->type == InputTypeRelease) {
 		} else if (event->type == InputTypeRelease) {
@@ -900,6 +347,57 @@ static void esubghz_hooked_input_callback(InputEvent* event, void* context)
 		}
 		}
 	}
 	}
 
 
+	if (event->key == InputKeyLeft) {
+		/* if we are in the chat view and no input is ongoing, allow
+		 * switching to msg input */
+		if (state->view_dispatcher->current_view ==
+				text_box_get_view(state->chat_box) &&
+				!(state->kbd_left_input_ongoing)) {
+			/* go to msg input upon short press of Left button */
+			if (event->type == InputTypeShort) {
+				view_dispatcher_send_custom_event(state->view_dispatcher,
+						ESubGhzChatEvent_GotoMsgInput);
+			}
+
+			/* do not handle any Left key events to prevent
+			 * blocking of other keys */
+			return;
+		}
+
+		/* handle ongoing inputs when changing to chat view */
+		if (event->type == InputTypePress) {
+			state->kbd_left_input_ongoing = true;
+		} else if (event->type == InputTypeRelease) {
+			state->kbd_left_input_ongoing = false;
+		}
+	}
+
+	if (event->key == InputKeyRight) {
+		/* if we are in the chat view and no input is ongoing, allow
+		 * switching to key display */
+		if (state->view_dispatcher->current_view ==
+				text_box_get_view(state->chat_box) &&
+				!(state->kbd_right_input_ongoing)) {
+			/* go to key display upon short press of Right button
+			 */
+			if (event->type == InputTypeShort) {
+				view_dispatcher_send_custom_event(state->view_dispatcher,
+						ESubGhzChatEvent_GotoKeyDisplay);
+			}
+
+			/* do not handle any Right key events to prevent
+			 * blocking of other keys */
+			return;
+		}
+
+		/* handle ongoing inputs when changing to chat view */
+		if (event->type == InputTypePress) {
+			state->kbd_right_input_ongoing = true;
+		} else if (event->type == InputTypeRelease) {
+			state->kbd_right_input_ongoing = false;
+		}
+	}
+
 	/* call original callback */
 	/* call original callback */
 	state->orig_input_cb(event, state->view_dispatcher);
 	state->orig_input_cb(event, state->view_dispatcher);
 }
 }
@@ -964,8 +462,8 @@ static void chat_box_free(ESubGhzChatState *state)
 
 
 int32_t esubghz_chat(void)
 int32_t esubghz_chat(void)
 {
 {
-	/* init the GCM and AES tables */
-	gcm_initialize();
+	/* init the crypto system */
+	crypto_init();
 
 
 	int32_t err = -1;
 	int32_t err = -1;
 
 
@@ -994,20 +492,40 @@ int32_t esubghz_chat(void)
 		goto err_alloc_hs;
 		goto err_alloc_hs;
 	}
 	}
 
 
+	state->menu = menu_alloc();
+	if (state->menu == NULL) {
+		goto err_alloc_menu;
+	}
+
 	state->text_input = text_input_alloc();
 	state->text_input = text_input_alloc();
 	if (state->text_input == NULL) {
 	if (state->text_input == NULL) {
 		goto err_alloc_ti;
 		goto err_alloc_ti;
 	}
 	}
 
 
+	state->hex_key_input = byte_input_alloc();
+	if (state->hex_key_input == NULL) {
+		goto err_alloc_hki;
+	}
+
 	if (!chat_box_alloc(state)) {
 	if (!chat_box_alloc(state)) {
 		goto err_alloc_cb;
 		goto err_alloc_cb;
 	}
 	}
 
 
+	state->key_display = dialog_ex_alloc();
+	if (state->key_display == NULL) {
+		goto err_alloc_kd;
+	}
+
 	state->subghz_worker = subghz_tx_rx_worker_alloc();
 	state->subghz_worker = subghz_tx_rx_worker_alloc();
 	if (state->subghz_worker == NULL) {
 	if (state->subghz_worker == NULL) {
 		goto err_alloc_worker;
 		goto err_alloc_worker;
 	}
 	}
 
 
+	state->crypto_ctx = crypto_ctx_alloc();
+	if (state->crypto_ctx == NULL) {
+		goto err_alloc_crypto;
+	}
+
 	/* set the have_read callback of the Sub-GHz worker */
 	/* set the have_read callback of the Sub-GHz worker */
 	subghz_tx_rx_worker_set_callback_have_read(state->subghz_worker,
 	subghz_tx_rx_worker_set_callback_have_read(state->subghz_worker,
 			have_read_cb, state);
 			have_read_cb, state);
@@ -1051,10 +569,16 @@ int32_t esubghz_chat(void)
 			TICK_INTERVAL);
 			TICK_INTERVAL);
 
 
 	/* add our two views to the view dispatcher */
 	/* add our two views to the view dispatcher */
+	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Menu,
+			menu_get_view(state->menu));
 	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
 	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input,
 			text_input_get_view(state->text_input));
 			text_input_get_view(state->text_input));
+	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_HexKeyInput,
+			byte_input_get_view(state->hex_key_input));
 	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
 	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
 			text_box_get_view(state->chat_box));
 			text_box_get_view(state->chat_box));
+	view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay,
+			dialog_ex_get_view(state->key_display));
 
 
 	/* get the GUI record and attach the view dispatcher to the GUI */
 	/* get the GUI record and attach the view dispatcher to the GUI */
 	/* no error handling here, don't know how */
 	/* no error handling here, don't know how */
@@ -1071,6 +595,7 @@ int32_t esubghz_chat(void)
 
 
 	/* if it is running, stop the Sub-GHz worker */
 	/* if it is running, stop the Sub-GHz worker */
 	if (subghz_tx_rx_worker_is_running(state->subghz_worker)) {
 	if (subghz_tx_rx_worker_is_running(state->subghz_worker)) {
+		exit_chat(state);
 		subghz_tx_rx_worker_stop(state->subghz_worker);
 		subghz_tx_rx_worker_stop(state->subghz_worker);
 	}
 	}
 
 
@@ -1080,18 +605,27 @@ int32_t esubghz_chat(void)
 	furi_record_close(RECORD_GUI);
 	furi_record_close(RECORD_GUI);
 
 
 	/* remove our two views from the view dispatcher */
 	/* remove our two views from the view dispatcher */
+	view_dispatcher_remove_view(state->view_dispatcher,
+			ESubGhzChatView_Menu);
 	view_dispatcher_remove_view(state->view_dispatcher,
 	view_dispatcher_remove_view(state->view_dispatcher,
 			ESubGhzChatView_Input);
 			ESubGhzChatView_Input);
+	view_dispatcher_remove_view(state->view_dispatcher,
+			ESubGhzChatView_HexKeyInput);
 	view_dispatcher_remove_view(state->view_dispatcher,
 	view_dispatcher_remove_view(state->view_dispatcher,
 			ESubGhzChatView_ChatBox);
 			ESubGhzChatView_ChatBox);
+	view_dispatcher_remove_view(state->view_dispatcher,
+			ESubGhzChatView_KeyDisplay);
 
 
 	/* close notification record */
 	/* close notification record */
 	furi_record_close(RECORD_NOTIFICATION);
 	furi_record_close(RECORD_NOTIFICATION);
 
 
 	/* clear the key and potential password */
 	/* clear the key and potential password */
-	esubghz_chat_explicit_bzero(state->text_input_store,
+	crypto_explicit_bzero(state->text_input_store,
 			sizeof(state->text_input_store));
 			sizeof(state->text_input_store));
-	esubghz_chat_explicit_bzero(&(state->gcm_ctx), sizeof(state->gcm_ctx));
+	crypto_explicit_bzero(state->hex_key_input_store,
+			sizeof(state->hex_key_input_store));
+	crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str));
+	crypto_ctx_clear(state->crypto_ctx);
 
 
 	/* deinit devices */
 	/* deinit devices */
 	subghz_devices_deinit();
 	subghz_devices_deinit();
@@ -1101,15 +635,27 @@ int32_t esubghz_chat(void)
 
 
 	/* free everything we allocated */
 	/* free everything we allocated */
 
 
+	crypto_ctx_free(state->crypto_ctx);
+
+err_alloc_crypto:
 	subghz_tx_rx_worker_free(state->subghz_worker);
 	subghz_tx_rx_worker_free(state->subghz_worker);
 
 
 err_alloc_worker:
 err_alloc_worker:
+	dialog_ex_free(state->key_display);
+
+err_alloc_kd:
 	chat_box_free(state);
 	chat_box_free(state);
 
 
 err_alloc_cb:
 err_alloc_cb:
+	byte_input_free(state->hex_key_input);
+
+err_alloc_hki:
 	text_input_free(state->text_input);
 	text_input_free(state->text_input);
 
 
 err_alloc_ti:
 err_alloc_ti:
+	menu_free(state->menu);
+
+err_alloc_menu:
 	helper_strings_free(state);
 	helper_strings_free(state);
 
 
 err_alloc_hs:
 err_alloc_hs:

+ 101 - 0
esubghz_chat_i.h

@@ -0,0 +1,101 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/view_dispatcher_i.h>
+#include <gui/view_port_i.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/byte_input.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/text_input.h>
+#include <notification/notification_messages.h>
+#include <lib/subghz/subghz_tx_rx_worker.h>
+#include <toolbox/sha256.h>
+
+#include "crypto_wrapper.h"
+#include "scenes/esubghz_chat_scene.h"
+
+#define APPLICATION_NAME "ESubGhzChat"
+
+#define DEFAULT_FREQ 433920000
+
+#define RX_TX_BUFFER_SIZE 1024
+
+#define CHAT_BOX_STORE_SIZE 4096
+#define TEXT_INPUT_STORE_SIZE 256
+
+#define KEY_HEX_STR_SIZE ((KEY_BITS / 8) * 3)
+
+typedef struct {
+	SceneManager *scene_manager;
+	ViewDispatcher *view_dispatcher;
+	NotificationApp *notification;
+
+	// UI elements
+	Menu *menu;
+	TextBox *chat_box;
+	FuriString *chat_box_store;
+	TextInput *text_input;
+	char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
+	ByteInput *hex_key_input;
+	uint8_t hex_key_input_store[KEY_BITS / 8];
+	DialogEx *key_display;
+	char key_hex_str[KEY_HEX_STR_SIZE + 1];
+
+	// for Sub-GHz
+	uint32_t frequency;
+	SubGhzTxRxWorker *subghz_worker;
+	const SubGhzDevice *subghz_device;
+
+	// message assembly before TX
+	FuriString *name_prefix;
+	FuriString *msg_input;
+
+	// encryption
+	bool encrypted;
+	ESubGhzChatCryptoCtx *crypto_ctx;
+
+	// RX and TX buffers
+	uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
+	uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
+	char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
+	volatile uint32_t last_time_rx_data;
+
+	// for locking
+	ViewPortDrawCallback orig_draw_cb;
+	ViewPortInputCallback orig_input_cb;
+	bool kbd_locked;
+	uint32_t kbd_lock_msg_ticks;
+	uint8_t kbd_lock_count;
+
+	// for ongoing inputs
+	bool kbd_ok_input_ongoing;
+	bool kbd_left_input_ongoing;
+	bool kbd_right_input_ongoing;
+} ESubGhzChatState;
+
+typedef enum {
+	ESubGhzChatEvent_FreqEntered,
+	ESubGhzChatEvent_KeyMenuNoEncryption,
+	ESubGhzChatEvent_KeyMenuPassword,
+	ESubGhzChatEvent_KeyMenuHexKey,
+	ESubGhzChatEvent_KeyMenuGenKey,
+	ESubGhzChatEvent_PassEntered,
+	ESubGhzChatEvent_HexKeyEntered,
+	ESubGhzChatEvent_MsgEntered,
+	ESubGhzChatEvent_GotoMsgInput,
+	ESubGhzChatEvent_GotoKeyDisplay,
+	ESubGhzChatEvent_KeyDisplayBack
+} ESubGhzChatEvent;
+
+typedef enum {
+	ESubGhzChatView_Menu,
+	ESubGhzChatView_Input,
+	ESubGhzChatView_HexKeyInput,
+	ESubGhzChatView_ChatBox,
+	ESubGhzChatView_KeyDisplay,
+} ESubGhzChatView;
+
+void tx_msg_input(ESubGhzChatState *state);
+void enter_chat(ESubGhzChatState *state);

+ 65 - 0
scenes/esubghz_chat_chat_box.c

@@ -0,0 +1,65 @@
+#include "../esubghz_chat_i.h"
+
+/* Prepares the text box scene. */
+void scene_on_enter_chat_box(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	text_box_reset(state->chat_box);
+	text_box_set_text(state->chat_box,
+			furi_string_get_cstr(state->chat_box_store));
+	text_box_set_focus(state->chat_box, TextBoxFocusEnd);
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
+}
+
+/* Handles scene manager events for the text box scene. */
+bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to message input scene */
+		case ESubGhzChatEvent_GotoMsgInput:
+			if (!scene_manager_previous_scene(
+						state->scene_manager)) {
+				view_dispatcher_stop(state->view_dispatcher);
+			}
+			consumed = true;
+			break;
+		case ESubGhzChatEvent_GotoKeyDisplay:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_KeyDisplay);
+			consumed = true;
+			break;
+		}
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the text box scene. */
+void scene_on_exit_chat_box(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	text_box_reset(state->chat_box);
+}

+ 123 - 0
scenes/esubghz_chat_chat_input.c

@@ -0,0 +1,123 @@
+#include "../esubghz_chat_i.h"
+
+/* If no message was entred this simply emits a MsgEntered event to the scene
+ * manager to switch to the text box. If a message was entered it is appended
+ * to the name string. The result is encrypted, if encryption is enabled, and
+ * then copied into the TX buffer. The contents of the TX buffer are then
+ * transmitted. The sent message is appended to the text box and a MsgEntered
+ * event is sent to the scene manager to switch to the text box view. */
+static void chat_input_cb(void *context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	/* no message, just switch to the text box view */
+#ifdef FW_ORIGIN_Official
+	if (strcmp(state->text_input_store, " ") == 0) {
+#else /* FW_ORIGIN_Official */
+	if (strlen(state->text_input_store) == 0) {
+#endif /* FW_ORIGIN_Official */
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_MsgEntered);
+		return;
+	}
+
+	/* concatenate the name prefix and the actual message */
+	furi_string_set(state->msg_input, state->name_prefix);
+	furi_string_cat_str(state->msg_input, ": ");
+	furi_string_cat_str(state->msg_input, state->text_input_store);
+
+	/* append the message to the chat box */
+	furi_string_cat_printf(state->chat_box_store, "\n%s",
+		furi_string_get_cstr(state->msg_input));
+
+	/* encrypt and transmit message */
+	tx_msg_input(state);
+
+	/* clear message input buffer */
+	furi_string_set_char(state->msg_input, 0, 0);
+
+	/* switch to text box view */
+	scene_manager_handle_custom_event(state->scene_manager,
+			ESubGhzChatEvent_MsgEntered);
+}
+
+/* Prepares the message input scene. */
+void scene_on_enter_chat_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	state->text_input_store[0] = 0;
+	text_input_reset(state->text_input);
+	text_input_set_result_callback(
+			state->text_input,
+			chat_input_cb,
+			state,
+			state->text_input_store,
+			sizeof(state->text_input_store),
+			true);
+	text_input_set_validator(
+			state->text_input,
+			NULL,
+			NULL);
+	text_input_set_header_text(
+			state->text_input,
+#ifdef FW_ORIGIN_Official
+			"Message (space for none)");
+#else /* FW_ORIGIN_Official */
+			"Message");
+	text_input_set_minimum_length(state->text_input, 0);
+#endif /* FW_ORIGIN_Official */
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the message input scene. */
+bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to text box scene */
+		case ESubGhzChatEvent_MsgEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatBox);
+			consumed = true;
+			break;
+		}
+		break;
+
+	case SceneManagerEventTypeBack:
+		/* stop the application if the user presses back here */
+		view_dispatcher_stop(state->view_dispatcher);
+		consumed = true;
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the password input scene. */
+void scene_on_exit_chat_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	text_input_reset(state->text_input);
+}

+ 127 - 0
scenes/esubghz_chat_freq_input.c

@@ -0,0 +1,127 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends FreqEntered event to scene manager and displays the frequency in the
+ * text box. */
+static void freq_input_cb(void *context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
+			state->frequency);
+
+	scene_manager_handle_custom_event(state->scene_manager,
+			ESubGhzChatEvent_FreqEntered);
+}
+
+/* Validates the entered frequency. */
+static bool freq_input_validator(const char *text, FuriString *error,
+		void *context)
+{
+	furi_assert(text);
+	furi_assert(error);
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+        int ret = sscanf(text, "%lu", &(state->frequency));
+	if (ret != 1) {
+		furi_string_printf(error, "Please enter\nfrequency\nin Hz!");
+		return false;
+	}
+
+	if (!subghz_devices_is_frequency_valid(state->subghz_device,
+				state->frequency)) {
+		furi_string_printf(error, "Frequency\n%lu\n is invalid!",
+				state->frequency);
+		return false;
+	}
+
+#ifdef FW_ORIGIN_Official
+	if (!furi_hal_region_is_frequency_allowed(state->frequency)) {
+#else /* FW_ORIGIN_Official */
+	if (!furi_hal_subghz_is_tx_allowed(state->frequency)) {
+#endif /* FW_ORIGIN_Official */
+		furi_string_printf(error, "TX forbidden\non frequency\n%lu!",
+				state->frequency);
+		return false;
+	}
+
+	return true;
+}
+
+/* Prepares the frequency input scene. */
+void scene_on_enter_freq_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu",
+			(uint32_t) DEFAULT_FREQ);
+	text_input_reset(state->text_input);
+	text_input_set_result_callback(
+			state->text_input,
+			freq_input_cb,
+			state,
+			state->text_input_store,
+			sizeof(state->text_input_store),
+			true);
+	text_input_set_validator(
+			state->text_input,
+			freq_input_validator,
+			state);
+	text_input_set_header_text(
+			state->text_input,
+			"Frequency");
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the frequency input scene. */
+bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to password input scene */
+		case ESubGhzChatEvent_FreqEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_KeyMenu);
+			consumed = true;
+			break;
+		}
+		break;
+
+	case SceneManagerEventTypeBack:
+		/* stop the application if the user presses back here */
+		view_dispatcher_stop(state->view_dispatcher);
+		consumed = true;
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the frequency input scene. */
+void scene_on_exit_freq_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	text_input_reset(state->text_input);
+}

+ 90 - 0
scenes/esubghz_chat_hex_key_input.c

@@ -0,0 +1,90 @@
+#include "../esubghz_chat_i.h"
+
+/* Sets the entered bytes as the key, enters the chat and sends a HexKeyEntered
+ * event to the scene manager. */
+static void hex_key_input_cb(void* context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	/* initiate the crypto context */
+	bool ret = crypto_ctx_set_key(state->crypto_ctx,
+			state->hex_key_input_store);
+
+	/* cleanup */
+	crypto_explicit_bzero(state->hex_key_input_store,
+			sizeof(state->hex_key_input_store));
+
+	if (!ret) {
+		crypto_ctx_clear(state->crypto_ctx);
+		return;
+	}
+
+	state->encrypted = true;
+
+	enter_chat(state);
+
+	scene_manager_handle_custom_event(state->scene_manager,
+			ESubGhzChatEvent_HexKeyEntered);
+}
+
+/* Prepares the hex key input scene. */
+void scene_on_enter_hex_key_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_hex_key_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	byte_input_set_result_callback(state->hex_key_input,
+			hex_key_input_cb,
+			NULL,
+			state,
+			state->hex_key_input_store,
+			sizeof(state->hex_key_input_store));
+
+	view_dispatcher_switch_to_view(state->view_dispatcher,
+			ESubGhzChatView_HexKeyInput);
+}
+
+/* Handles scene manager events for the hex key input scene. */
+bool scene_on_event_hex_key_input(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_hex_key_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to message input scene */
+		case ESubGhzChatEvent_HexKeyEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatInput);
+			consumed = true;
+			break;
+		}
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the hex key input scene. */
+void scene_on_exit_hex_key_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_hex_key_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	crypto_explicit_bzero(state->hex_key_input_store,
+			sizeof(state->hex_key_input_store));
+}

+ 110 - 0
scenes/esubghz_chat_key_display.c

@@ -0,0 +1,110 @@
+#include "../esubghz_chat_i.h"
+
+void key_display_result_cb(DialogExResult result, void* context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	switch(result) {
+	case DialogExResultLeft:
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_KeyDisplayBack);
+		break;
+
+	default:
+		break;
+	}
+}
+
+/* Prepares the key display scene. */
+void scene_on_enter_key_display(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_display");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	if (state->encrypted) {
+		uint8_t key[KEY_BITS / 8];
+		crypto_ctx_get_key(state->crypto_ctx, key);
+		snprintf(state->key_hex_str, KEY_HEX_STR_SIZE,
+				"%02hX%02hX%02hX%02hX"
+				"%02hX%02hX%02hX%02hX\n"
+				"%02hX%02hX%02hX%02hX"
+				"%02hX%02hX%02hX%02hX\n"
+				"%02hX%02hX%02hX%02hX"
+				"%02hX%02hX%02hX%02hX\n"
+				"%02hX%02hX%02hX%02hX"
+				"%02hX%02hX%02hX%02hX",
+				key[0], key[1], key[2], key[3],
+				key[4], key[5], key[6], key[7],
+				key[8], key[9], key[10], key[11],
+				key[12], key[13], key[14], key[15],
+				key[16], key[17], key[18], key[19],
+				key[20], key[21], key[22], key[23],
+				key[24], key[25], key[26], key[27],
+				key[28], key[29], key[30], key[31]);
+		crypto_explicit_bzero(key, sizeof(key));
+	} else {
+		strcpy(state->key_hex_str, "No Key");
+	}
+
+	dialog_ex_reset(state->key_display);
+
+	dialog_ex_set_text(state->key_display, state->key_hex_str, 64, 2,
+			AlignCenter, AlignTop);
+
+	dialog_ex_set_icon(state->key_display, 0, 0, NULL);
+
+	dialog_ex_set_left_button_text(state->key_display, "Back");
+
+	dialog_ex_set_result_callback(state->key_display,
+			key_display_result_cb);
+	dialog_ex_set_context(state->key_display, state);
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay);
+}
+
+/* Handles scene manager events for the key display scene. */
+bool scene_on_event_key_display(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_display");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to message input scene */
+		case ESubGhzChatEvent_KeyDisplayBack:
+			if (!scene_manager_previous_scene(
+						state->scene_manager)) {
+				view_dispatcher_stop(state->view_dispatcher);
+			}
+			consumed = true;
+			break;
+		}
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the key display scene. */
+void scene_on_exit_key_display(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_display");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	dialog_ex_reset(state->key_display);
+	crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str));
+}

+ 172 - 0
scenes/esubghz_chat_key_menu.c

@@ -0,0 +1,172 @@
+#include "../esubghz_chat_i.h"
+
+typedef enum {
+	ESubGhzChatKeyMenuItems_NoEncryption,
+	ESubGhzChatKeyMenuItems_Password,
+	ESubGhzChatKeyMenuItems_HexKey,
+	ESubGhzChatKeyMenuItems_GenKey,
+} ESubGhzChatKeyMenuItems;
+
+static void key_menu_cb(void* context, uint32_t index)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	uint8_t key[KEY_BITS / 8];
+
+	switch(index) {
+	case ESubGhzChatKeyMenuItems_NoEncryption:
+		state->encrypted = false;
+		enter_chat(state);
+
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_KeyMenuNoEncryption);
+		break;
+
+	case ESubGhzChatKeyMenuItems_Password:
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_KeyMenuPassword);
+		break;
+
+	case ESubGhzChatKeyMenuItems_HexKey:
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_KeyMenuHexKey);
+		break;
+
+	case ESubGhzChatKeyMenuItems_GenKey:
+		/* generate a random key */
+		furi_hal_random_fill_buf(key, KEY_BITS / 8);
+
+		/* initiate the crypto context */
+		bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
+
+		/* cleanup */
+		crypto_explicit_bzero(key, sizeof(key));
+
+		if (!ret) {
+			crypto_ctx_clear(state->crypto_ctx);
+			return;
+		}
+
+		/* set encrypted flag and enter the chat */
+		state->encrypted = true;
+		enter_chat(state);
+
+		scene_manager_handle_custom_event(state->scene_manager,
+				ESubGhzChatEvent_KeyMenuGenKey);
+		break;
+
+	default:
+		break;
+	}
+}
+
+/* Prepares the key menu scene. */
+void scene_on_enter_key_menu(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_menu");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	menu_reset(state->menu);
+
+	menu_add_item(
+		state->menu,
+		"No encryption",
+		NULL,
+		ESubGhzChatKeyMenuItems_NoEncryption,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Password",
+		NULL,
+		ESubGhzChatKeyMenuItems_Password,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Hex Key",
+		NULL,
+		ESubGhzChatKeyMenuItems_HexKey,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Generate Key",
+		NULL,
+		ESubGhzChatKeyMenuItems_GenKey,
+		key_menu_cb,
+		state
+	);
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Menu);
+}
+
+/* Handles scene manager events for the key menu scene. */
+bool scene_on_event_key_menu(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_menu");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to message input scene */
+		case ESubGhzChatEvent_KeyMenuNoEncryption:
+		case ESubGhzChatEvent_KeyMenuGenKey:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatInput);
+			consumed = true;
+			break;
+
+		/* switch to password input scene */
+		case ESubGhzChatEvent_KeyMenuPassword:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_PassInput);
+			consumed = true;
+			break;
+
+		/* switch to hex key input scene */
+		case ESubGhzChatEvent_KeyMenuHexKey:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_HexKeyInput);
+			consumed = true;
+			break;
+		}
+
+		break;
+
+	case SceneManagerEventTypeBack:
+		/* stop the application if the user presses back here */
+		view_dispatcher_stop(state->view_dispatcher);
+		consumed = true;
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the key menu scene. */
+void scene_on_exit_key_menu(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_menu");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	menu_reset(state->menu);
+}
+

+ 126 - 0
scenes/esubghz_chat_pass_input.c

@@ -0,0 +1,126 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends PassEntered event to scene manager and enters the chat. */
+static void pass_input_cb(void *context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	crypto_explicit_bzero(state->text_input_store,
+			sizeof(state->text_input_store));
+
+	enter_chat(state);
+
+	scene_manager_handle_custom_event(state->scene_manager,
+			ESubGhzChatEvent_PassEntered);
+}
+
+/* If a password was entered this derives a key from the password using a
+ * single pass of SHA256 and initiates the AES-GCM context for encryption. If
+ * the initiation fails, the password is rejected. */
+static bool pass_input_validator(const char *text, FuriString *error,
+		void *context)
+{
+	furi_assert(text);
+	furi_assert(error);
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	if (strlen(text) == 0) {
+		furi_string_printf(error, "Enter a\npassword!");
+		return false;
+	}
+
+	unsigned char key[KEY_BITS / 8];
+
+	/* derive a key from the password */
+	sha256((unsigned char *) text, strlen(text), key);
+
+	/* initiate the crypto context */
+	bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
+
+	/* cleanup */
+	crypto_explicit_bzero(key, sizeof(key));
+
+	if (!ret) {
+		crypto_ctx_clear(state->crypto_ctx);
+		furi_string_printf(error, "Failed to\nset key!");
+		return false;
+	}
+
+	state->encrypted = true;
+
+	return true;
+}
+
+/* Prepares the password input scene. */
+void scene_on_enter_pass_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	state->text_input_store[0] = 0;
+	text_input_reset(state->text_input);
+	text_input_set_result_callback(
+			state->text_input,
+			pass_input_cb,
+			state,
+			state->text_input_store,
+			sizeof(state->text_input_store),
+			true);
+	text_input_set_validator(
+			state->text_input,
+			pass_input_validator,
+			state);
+	text_input_set_header_text(
+			state->text_input,
+			"Password");
+
+	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
+}
+
+/* Handles scene manager events for the password input scene. */
+bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	bool consumed = false;
+
+	switch(event.type) {
+	case SceneManagerEventTypeCustom:
+		switch(event.event) {
+		/* switch to message input scene */
+		case ESubGhzChatEvent_PassEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatInput);
+			consumed = true;
+			break;
+		}
+		break;
+
+	default:
+		consumed = false;
+		break;
+	}
+
+	return consumed;
+}
+
+/* Cleans up the password input scene. */
+void scene_on_exit_pass_input(void* context)
+{
+	FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
+
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	text_input_reset(state->text_input);
+	crypto_explicit_bzero(state->text_input_store,
+			sizeof(state->text_input_store));
+}

+ 30 - 0
scenes/esubghz_chat_scene.c

@@ -0,0 +1,30 @@
+#include "esubghz_chat_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_enter_##name,
+void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_event_##name,
+bool (*const esubghz_chat_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) scene_on_exit_##name,
+void (*const esubghz_chat_scene_on_exit_handlers[])(void* context) = {
+#include "esubghz_chat_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
+    .on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
+    .on_event_handlers = esubghz_chat_scene_on_event_handlers,
+    .on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
+    .scene_num = ESubGhzChatScene_MAX,
+};

+ 29 - 0
scenes/esubghz_chat_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) ESubGhzChatScene_##id,
+typedef enum {
+#include "esubghz_chat_scene_config.h"
+	ESubGhzChatScene_MAX
+} ESubGhzChatScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers esubghz_chat_scene_event_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_enter_##name(void*);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+	bool scene_on_event_##name(void* context, SceneManagerEvent event);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void scene_on_exit_##name(void* context);
+#include "esubghz_chat_scene_config.h"
+#undef ADD_SCENE

+ 7 - 0
scenes/esubghz_chat_scene_config.h

@@ -0,0 +1,7 @@
+ADD_SCENE(esubghz_chat, freq_input, FreqInput)
+ADD_SCENE(esubghz_chat, key_menu, KeyMenu)
+ADD_SCENE(esubghz_chat, pass_input, PassInput)
+ADD_SCENE(esubghz_chat, hex_key_input, HexKeyInput)
+ADD_SCENE(esubghz_chat, chat_input, ChatInput)
+ADD_SCENE(esubghz_chat, chat_box, ChatBox)
+ADD_SCENE(esubghz_chat, key_display, KeyDisplay)