|
@@ -38,15 +38,25 @@ typedef struct {
|
|
|
SceneManager *scene_manager;
|
|
SceneManager *scene_manager;
|
|
|
ViewDispatcher *view_dispatcher;
|
|
ViewDispatcher *view_dispatcher;
|
|
|
NotificationApp *notification;
|
|
NotificationApp *notification;
|
|
|
|
|
+
|
|
|
|
|
+ // UI elements
|
|
|
TextBox *chat_box;
|
|
TextBox *chat_box;
|
|
|
FuriString *chat_box_store;
|
|
FuriString *chat_box_store;
|
|
|
TextInput *text_input;
|
|
TextInput *text_input;
|
|
|
char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
|
|
char text_input_store[TEXT_INPUT_STORE_SIZE + 1];
|
|
|
|
|
+
|
|
|
|
|
+ // selected frequency
|
|
|
|
|
+ uint32_t frequency;
|
|
|
|
|
+
|
|
|
|
|
+ // message assembly before TX
|
|
|
FuriString *name_prefix;
|
|
FuriString *name_prefix;
|
|
|
FuriString *msg_input;
|
|
FuriString *msg_input;
|
|
|
|
|
+
|
|
|
|
|
+ // encryption
|
|
|
bool encrypted;
|
|
bool encrypted;
|
|
|
- uint32_t frequency;
|
|
|
|
|
gcm_context gcm_ctx;
|
|
gcm_context gcm_ctx;
|
|
|
|
|
+
|
|
|
|
|
+ // RX and TX buffers
|
|
|
uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
|
|
uint8_t rx_buffer[RX_TX_BUFFER_SIZE];
|
|
|
uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
|
|
uint8_t tx_buffer[RX_TX_BUFFER_SIZE];
|
|
|
char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
|
|
char rx_str_buffer[RX_TX_BUFFER_SIZE + 1];
|
|
@@ -81,12 +91,15 @@ typedef enum {
|
|
|
ESubGhzChatEvent_MsgEntered
|
|
ESubGhzChatEvent_MsgEntered
|
|
|
} ESubGhzChatEvent;
|
|
} ESubGhzChatEvent;
|
|
|
|
|
|
|
|
|
|
+/* Function to clear sensitive memory. */
|
|
|
static void esubghz_chat_explicit_bzero(void *s, size_t len)
|
|
static void esubghz_chat_explicit_bzero(void *s, size_t len)
|
|
|
{
|
|
{
|
|
|
memset(s, 0, len);
|
|
memset(s, 0, len);
|
|
|
asm volatile("" ::: "memory");
|
|
asm volatile("" ::: "memory");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Post RX handler, decrypts received messages, displays them in the text box
|
|
|
|
|
+ * and sends a notification. */
|
|
|
static void post_rx(ESubGhzChatState *state, size_t rx_size)
|
|
static void post_rx(ESubGhzChatState *state, size_t rx_size)
|
|
|
{
|
|
{
|
|
|
furi_assert(state);
|
|
furi_assert(state);
|
|
@@ -97,6 +110,7 @@ static void post_rx(ESubGhzChatState *state, size_t rx_size)
|
|
|
|
|
|
|
|
furi_check(rx_size <= RX_TX_BUFFER_SIZE);
|
|
furi_check(rx_size <= RX_TX_BUFFER_SIZE);
|
|
|
|
|
|
|
|
|
|
+ /* decrypt if necessary */
|
|
|
if (!state->encrypted) {
|
|
if (!state->encrypted) {
|
|
|
memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
|
|
memcpy(state->rx_str_buffer, state->rx_buffer, rx_size);
|
|
|
state->rx_str_buffer[rx_size] = 0;
|
|
state->rx_str_buffer[rx_size] = 0;
|
|
@@ -115,22 +129,27 @@ static void post_rx(ESubGhzChatState *state, size_t rx_size)
|
|
|
TAG_BYTES);
|
|
TAG_BYTES);
|
|
|
state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
|
|
state->rx_str_buffer[rx_size - (IV_BYTES + TAG_BYTES)] = 0;
|
|
|
|
|
|
|
|
|
|
+ /* if decryption fails output an error message */
|
|
|
if (ret != 0) {
|
|
if (ret != 0) {
|
|
|
strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
|
|
strcpy(state->rx_str_buffer, "ERR: Decryption failed!");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* append message to text box */
|
|
|
furi_string_cat_printf(state->chat_box_store, "\n%s",
|
|
furi_string_cat_printf(state->chat_box_store, "\n%s",
|
|
|
state->rx_str_buffer);
|
|
state->rx_str_buffer);
|
|
|
|
|
|
|
|
|
|
+ /* send notification (make the flipper vibrate) */
|
|
|
notification_message(state->notification, &sequence_single_vibro);
|
|
notification_message(state->notification, &sequence_single_vibro);
|
|
|
|
|
|
|
|
- // Reset Text Box contents and focus
|
|
|
|
|
|
|
+ /* reset text box contents and focus */
|
|
|
text_box_set_text(state->chat_box,
|
|
text_box_set_text(state->chat_box,
|
|
|
furi_string_get_cstr(state->chat_box_store));
|
|
furi_string_get_cstr(state->chat_box_store));
|
|
|
text_box_set_focus(state->chat_box, TextBoxFocusEnd);
|
|
text_box_set_focus(state->chat_box, TextBoxFocusEnd);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Sends FreqEntered event to scene manager and displays the frequency in the
|
|
|
|
|
+ * text box. */
|
|
|
static void freq_input_cb(void *context)
|
|
static void freq_input_cb(void *context)
|
|
|
{
|
|
{
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
@@ -143,6 +162,7 @@ static void freq_input_cb(void *context)
|
|
|
ESubGhzChatEvent_FreqEntered);
|
|
ESubGhzChatEvent_FreqEntered);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Validates the entered frequency. */
|
|
|
static bool freq_input_validator(const char *text, FuriString *error,
|
|
static bool freq_input_validator(const char *text, FuriString *error,
|
|
|
void *context)
|
|
void *context)
|
|
|
{
|
|
{
|
|
@@ -173,6 +193,8 @@ static bool freq_input_validator(const char *text, FuriString *error,
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Sends PassEntered event to scene manager and displays whether or not
|
|
|
|
|
+ * encryption has been enabled in the text box. */
|
|
|
static void pass_input_cb(void *context)
|
|
static void pass_input_cb(void *context)
|
|
|
{
|
|
{
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
@@ -185,6 +207,9 @@ static void pass_input_cb(void *context)
|
|
|
ESubGhzChatEvent_PassEntered);
|
|
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,
|
|
static bool pass_input_validator(const char *text, FuriString *error,
|
|
|
void *context)
|
|
void *context)
|
|
|
{
|
|
{
|
|
@@ -202,6 +227,8 @@ static bool pass_input_validator(const char *text, FuriString *error,
|
|
|
unsigned char key[KEY_BITS / 8];
|
|
unsigned char key[KEY_BITS / 8];
|
|
|
|
|
|
|
|
state->encrypted = true;
|
|
state->encrypted = true;
|
|
|
|
|
+
|
|
|
|
|
+ /* derive a key from the password */
|
|
|
sha256((unsigned char *) text, strlen(text), key);
|
|
sha256((unsigned char *) text, strlen(text), key);
|
|
|
|
|
|
|
|
// TODO: remove this
|
|
// TODO: remove this
|
|
@@ -211,12 +238,15 @@ static bool pass_input_validator(const char *text, FuriString *error,
|
|
|
furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
|
|
furi_string_cat_printf(state->chat_box_store, " %02x", key[i]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* initiate the AES-GCM context */
|
|
|
int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
|
|
int ret = gcm_setkey(&(state->gcm_ctx), key, KEY_BITS / 8);
|
|
|
|
|
|
|
|
|
|
+ /* cleanup */
|
|
|
esubghz_chat_explicit_bzero(key, sizeof(key));
|
|
esubghz_chat_explicit_bzero(key, sizeof(key));
|
|
|
|
|
|
|
|
if (ret != 0) {
|
|
if (ret != 0) {
|
|
|
- gcm_zero_ctx(&(state->gcm_ctx));
|
|
|
|
|
|
|
+ esubghz_chat_explicit_bzero(&(state->gcm_ctx),
|
|
|
|
|
+ sizeof(state->gcm_ctx));
|
|
|
furi_string_printf(error, "Failed to\nset key!");
|
|
furi_string_printf(error, "Failed to\nset key!");
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
@@ -224,23 +254,33 @@ static bool pass_input_validator(const char *text, FuriString *error,
|
|
|
return 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)
|
|
static void chat_input_cb(void *context)
|
|
|
{
|
|
{
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
|
ESubGhzChatState* state = context;
|
|
ESubGhzChatState* state = context;
|
|
|
|
|
|
|
|
|
|
+ /* no message, just switch to the text box view */
|
|
|
if (strlen(state->text_input_store) == 0) {
|
|
if (strlen(state->text_input_store) == 0) {
|
|
|
scene_manager_handle_custom_event(state->scene_manager,
|
|
scene_manager_handle_custom_event(state->scene_manager,
|
|
|
ESubGhzChatEvent_MsgEntered);
|
|
ESubGhzChatEvent_MsgEntered);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* concatenate the name prefix and the actual 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, state->text_input_store);
|
|
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_cat_printf(state->chat_box_store, "\n%s",
|
|
|
furi_string_get_cstr(state->msg_input));
|
|
furi_string_get_cstr(state->msg_input));
|
|
|
|
|
|
|
|
|
|
+ /* 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) {
|
|
@@ -264,6 +304,7 @@ static void chat_input_cb(void *context)
|
|
|
tx_size);
|
|
tx_size);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* clear message input buffer */
|
|
|
furi_string_set_char(state->msg_input, 0, 0);
|
|
furi_string_set_char(state->msg_input, 0, 0);
|
|
|
|
|
|
|
|
// TODO: remove this
|
|
// TODO: remove this
|
|
@@ -281,10 +322,12 @@ static void chat_input_cb(void *context)
|
|
|
|
|
|
|
|
// TODO: actually transmit
|
|
// TODO: actually transmit
|
|
|
|
|
|
|
|
|
|
+ /* switch to text box view */
|
|
|
scene_manager_handle_custom_event(state->scene_manager,
|
|
scene_manager_handle_custom_event(state->scene_manager,
|
|
|
ESubGhzChatEvent_MsgEntered);
|
|
ESubGhzChatEvent_MsgEntered);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Prepares the frequency input scene. */
|
|
|
static void scene_on_enter_freq_input(void* context)
|
|
static void scene_on_enter_freq_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input");
|
|
@@ -313,6 +356,7 @@ static void scene_on_enter_freq_input(void* context)
|
|
|
view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
|
|
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)
|
|
static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input");
|
|
@@ -325,6 +369,7 @@ static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
|
|
|
switch(event.type) {
|
|
switch(event.type) {
|
|
|
case SceneManagerEventTypeCustom:
|
|
case SceneManagerEventTypeCustom:
|
|
|
switch(event.event) {
|
|
switch(event.event) {
|
|
|
|
|
+ /* switch to password input scene */
|
|
|
case ESubGhzChatEvent_FreqEntered:
|
|
case ESubGhzChatEvent_FreqEntered:
|
|
|
scene_manager_next_scene(state->scene_manager,
|
|
scene_manager_next_scene(state->scene_manager,
|
|
|
ESubGhzChatScene_PassInput);
|
|
ESubGhzChatScene_PassInput);
|
|
@@ -334,6 +379,7 @@ static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
case SceneManagerEventTypeBack:
|
|
case SceneManagerEventTypeBack:
|
|
|
|
|
+ /* stop the application if the user presses back here */
|
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
|
consumed = true;
|
|
consumed = true;
|
|
|
break;
|
|
break;
|
|
@@ -346,6 +392,7 @@ static bool scene_on_event_freq_input(void* context, SceneManagerEvent event)
|
|
|
return consumed;
|
|
return consumed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Cleans up the frequency input scene. */
|
|
|
static void scene_on_exit_freq_input(void* context)
|
|
static void scene_on_exit_freq_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input");
|
|
@@ -356,6 +403,7 @@ static void scene_on_exit_freq_input(void* context)
|
|
|
text_input_reset(state->text_input);
|
|
text_input_reset(state->text_input);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Prepares the password input scene. */
|
|
|
static void scene_on_enter_pass_input(void* context)
|
|
static void scene_on_enter_pass_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input");
|
|
@@ -384,6 +432,7 @@ static void scene_on_enter_pass_input(void* context)
|
|
|
view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
|
|
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)
|
|
static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input");
|
|
@@ -396,6 +445,7 @@ static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
|
|
|
switch(event.type) {
|
|
switch(event.type) {
|
|
|
case SceneManagerEventTypeCustom:
|
|
case SceneManagerEventTypeCustom:
|
|
|
switch(event.event) {
|
|
switch(event.event) {
|
|
|
|
|
+ /* switch to message input scene */
|
|
|
case ESubGhzChatEvent_PassEntered:
|
|
case ESubGhzChatEvent_PassEntered:
|
|
|
scene_manager_next_scene(state->scene_manager,
|
|
scene_manager_next_scene(state->scene_manager,
|
|
|
ESubGhzChatScene_ChatInput);
|
|
ESubGhzChatScene_ChatInput);
|
|
@@ -405,6 +455,7 @@ static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
case SceneManagerEventTypeBack:
|
|
case SceneManagerEventTypeBack:
|
|
|
|
|
+ /* stop the application if the user presses back here */
|
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
|
consumed = true;
|
|
consumed = true;
|
|
|
break;
|
|
break;
|
|
@@ -417,6 +468,7 @@ static bool scene_on_event_pass_input(void* context, SceneManagerEvent event)
|
|
|
return consumed;
|
|
return consumed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Cleans up the password input scene. */
|
|
|
static void scene_on_exit_pass_input(void* context)
|
|
static void scene_on_exit_pass_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input");
|
|
@@ -427,6 +479,7 @@ static void scene_on_exit_pass_input(void* context)
|
|
|
text_input_reset(state->text_input);
|
|
text_input_reset(state->text_input);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Prepares the message input scene. */
|
|
|
static void scene_on_enter_chat_input(void* context)
|
|
static void scene_on_enter_chat_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input");
|
|
@@ -455,6 +508,7 @@ static void scene_on_enter_chat_input(void* context)
|
|
|
view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
|
|
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)
|
|
static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input");
|
|
@@ -467,6 +521,7 @@ static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
|
|
|
switch(event.type) {
|
|
switch(event.type) {
|
|
|
case SceneManagerEventTypeCustom:
|
|
case SceneManagerEventTypeCustom:
|
|
|
switch(event.event) {
|
|
switch(event.event) {
|
|
|
|
|
+ /* switch to text box scene */
|
|
|
case ESubGhzChatEvent_MsgEntered:
|
|
case ESubGhzChatEvent_MsgEntered:
|
|
|
scene_manager_next_scene(state->scene_manager,
|
|
scene_manager_next_scene(state->scene_manager,
|
|
|
ESubGhzChatScene_ChatBox);
|
|
ESubGhzChatScene_ChatBox);
|
|
@@ -476,6 +531,7 @@ static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
|
|
|
break;
|
|
break;
|
|
|
|
|
|
|
|
case SceneManagerEventTypeBack:
|
|
case SceneManagerEventTypeBack:
|
|
|
|
|
+ /* stop the application if the user presses back here */
|
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
view_dispatcher_stop(state->view_dispatcher);
|
|
|
consumed = true;
|
|
consumed = true;
|
|
|
break;
|
|
break;
|
|
@@ -488,6 +544,7 @@ static bool scene_on_event_chat_input(void* context, SceneManagerEvent event)
|
|
|
return consumed;
|
|
return consumed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Cleans up the password input scene. */
|
|
|
static void scene_on_exit_chat_input(void* context)
|
|
static void scene_on_exit_chat_input(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input");
|
|
@@ -498,6 +555,7 @@ static void scene_on_exit_chat_input(void* context)
|
|
|
text_input_reset(state->text_input);
|
|
text_input_reset(state->text_input);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Prepares the text box scene. */
|
|
|
static void scene_on_enter_chat_box(void* context)
|
|
static void scene_on_enter_chat_box(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box");
|
|
@@ -513,6 +571,8 @@ static void scene_on_enter_chat_box(void* context)
|
|
|
view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
|
|
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)
|
|
static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
|
|
|
{
|
|
{
|
|
|
UNUSED(event);
|
|
UNUSED(event);
|
|
@@ -524,6 +584,7 @@ static bool scene_on_event_chat_box(void* context, SceneManagerEvent event)
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Cleans up the text box scene. */
|
|
|
static void scene_on_exit_chat_box(void* context)
|
|
static void scene_on_exit_chat_box(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
|
|
FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box");
|
|
@@ -534,6 +595,7 @@ static void scene_on_exit_chat_box(void* context)
|
|
|
text_box_reset(state->chat_box);
|
|
text_box_reset(state->chat_box);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Scene entry handlers. */
|
|
|
static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
|
|
static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
|
|
|
scene_on_enter_freq_input,
|
|
scene_on_enter_freq_input,
|
|
|
scene_on_enter_pass_input,
|
|
scene_on_enter_pass_input,
|
|
@@ -541,6 +603,7 @@ static void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = {
|
|
|
scene_on_enter_chat_box
|
|
scene_on_enter_chat_box
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/* Scene event handlers. */
|
|
|
static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
|
|
static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
|
|
|
scene_on_event_freq_input,
|
|
scene_on_event_freq_input,
|
|
|
scene_on_event_pass_input,
|
|
scene_on_event_pass_input,
|
|
@@ -548,6 +611,7 @@ static bool (*const esubghz_chat_scene_on_event_handlers[])(void*, SceneManagerE
|
|
|
scene_on_event_chat_box
|
|
scene_on_event_chat_box
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/* Scene exit handlers. */
|
|
|
static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
|
|
static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
|
|
|
scene_on_exit_freq_input,
|
|
scene_on_exit_freq_input,
|
|
|
scene_on_exit_pass_input,
|
|
scene_on_exit_pass_input,
|
|
@@ -555,17 +619,20 @@ static void (*const esubghz_chat_scene_on_exit_handlers[])(void*) = {
|
|
|
scene_on_exit_chat_box
|
|
scene_on_exit_chat_box
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/* Handlers for the scene manager. */
|
|
|
static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
|
|
static const SceneManagerHandlers esubghz_chat_scene_event_handlers = {
|
|
|
.on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
|
|
.on_enter_handlers = esubghz_chat_scene_on_enter_handlers,
|
|
|
.on_event_handlers = esubghz_chat_scene_on_event_handlers,
|
|
.on_event_handlers = esubghz_chat_scene_on_event_handlers,
|
|
|
.on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
|
|
.on_exit_handlers = esubghz_chat_scene_on_exit_handlers,
|
|
|
.scene_num = ESubGhzChatScene_MAX};
|
|
.scene_num = ESubGhzChatScene_MAX};
|
|
|
|
|
|
|
|
|
|
+/* Whether or not to display the locked message. */
|
|
|
static bool kbd_lock_msg_display(ESubGhzChatState *state)
|
|
static bool kbd_lock_msg_display(ESubGhzChatState *state)
|
|
|
{
|
|
{
|
|
|
return (state->kbd_lock_msg_ticks != 0);
|
|
return (state->kbd_lock_msg_ticks != 0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Whether or not to hide the locked message again. */
|
|
|
static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
|
|
static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
|
|
|
{
|
|
{
|
|
|
if (state->kbd_lock_msg_ticks == 0) {
|
|
if (state->kbd_lock_msg_ticks == 0) {
|
|
@@ -579,6 +646,8 @@ static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state)
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Resets the timeout for the locked message and turns off the backlight if
|
|
|
|
|
+ * specified. */
|
|
|
static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
|
|
static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
|
|
|
{
|
|
{
|
|
|
state->kbd_lock_msg_ticks = 0;
|
|
state->kbd_lock_msg_ticks = 0;
|
|
@@ -590,18 +659,21 @@ static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Locks the keyboard. */
|
|
|
static void kbd_lock(ESubGhzChatState *state)
|
|
static void kbd_lock(ESubGhzChatState *state)
|
|
|
{
|
|
{
|
|
|
state->kbd_locked = true;
|
|
state->kbd_locked = true;
|
|
|
kbd_lock_msg_reset(state, true);
|
|
kbd_lock_msg_reset(state, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Unlocks the keyboard. */
|
|
|
static void kbd_unlock(ESubGhzChatState *state)
|
|
static void kbd_unlock(ESubGhzChatState *state)
|
|
|
{
|
|
{
|
|
|
state->kbd_locked = false;
|
|
state->kbd_locked = false;
|
|
|
kbd_lock_msg_reset(state, false);
|
|
kbd_lock_msg_reset(state, false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Custom event callback for view dispatcher. Just calls scene manager. */
|
|
|
static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
|
|
static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback");
|
|
@@ -610,6 +682,7 @@ static bool esubghz_chat_custom_event_callback(void* context, uint32_t event)
|
|
|
return scene_manager_handle_custom_event(state->scene_manager, event);
|
|
return scene_manager_handle_custom_event(state->scene_manager, event);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Navigation event callback for view dispatcher. Just calls scene manager. */
|
|
|
static bool esubghz_chat_navigation_event_callback(void* context)
|
|
static bool esubghz_chat_navigation_event_callback(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback");
|
|
@@ -618,6 +691,9 @@ static bool esubghz_chat_navigation_event_callback(void* context)
|
|
|
return scene_manager_handle_back_event(state->scene_manager);
|
|
return scene_manager_handle_back_event(state->scene_manager);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Tick event callback for view dispatcher. Called every TICK_INTERVAL. Resets
|
|
|
|
|
+ * the locked message if necessary. Retrieves a received message from the
|
|
|
|
|
+ * rx_collection_buffer and calls post_rx(). Then calls the scene manager. */
|
|
|
static void esubghz_chat_tick_event_callback(void* context)
|
|
static void esubghz_chat_tick_event_callback(void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback");
|
|
@@ -625,10 +701,14 @@ static void esubghz_chat_tick_event_callback(void* context)
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
|
ESubGhzChatState* state = context;
|
|
ESubGhzChatState* state = context;
|
|
|
|
|
|
|
|
|
|
+ /* reset locked message if necessary */
|
|
|
if (kbd_lock_msg_reset_timeout(state)) {
|
|
if (kbd_lock_msg_reset_timeout(state)) {
|
|
|
kbd_lock_msg_reset(state, true);
|
|
kbd_lock_msg_reset(state, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* if the maximum message size was reached or the
|
|
|
|
|
+ * MESSAGE_COMPLETION_TIMEOUT has expired, retrieve a message and call
|
|
|
|
|
+ * post_rx() */
|
|
|
size_t avail = furi_stream_buffer_bytes_available(
|
|
size_t avail = furi_stream_buffer_bytes_available(
|
|
|
state->rx_collection_buffer);
|
|
state->rx_collection_buffer);
|
|
|
if (avail > 0) {
|
|
if (avail > 0) {
|
|
@@ -645,9 +725,12 @@ static void esubghz_chat_tick_event_callback(void* context)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* call scene manager */
|
|
|
scene_manager_handle_tick_event(state->scene_manager);
|
|
scene_manager_handle_tick_event(state->scene_manager);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Hooks into the view port's draw callback to overlay the keyboard locked
|
|
|
|
|
+ * message. */
|
|
|
static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
|
|
static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback");
|
|
@@ -657,13 +740,16 @@ static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
|
ESubGhzChatState* state = context;
|
|
ESubGhzChatState* state = context;
|
|
|
|
|
|
|
|
|
|
+ /* call original callback */
|
|
|
state->orig_draw_cb(canvas, state->view_dispatcher);
|
|
state->orig_draw_cb(canvas, state->view_dispatcher);
|
|
|
|
|
|
|
|
|
|
+ /* display if the keyboard is locked */
|
|
|
if (state->kbd_locked) {
|
|
if (state->kbd_locked) {
|
|
|
canvas_set_font(canvas, FontPrimary);
|
|
canvas_set_font(canvas, FontPrimary);
|
|
|
elements_multiline_text_framed(canvas, 42, 30, "Locked");
|
|
elements_multiline_text_framed(canvas, 42, 30, "Locked");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* display the unlock message if necessary */
|
|
|
if (kbd_lock_msg_display(state)) {
|
|
if (kbd_lock_msg_display(state)) {
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
|
|
elements_bold_rounded_frame(canvas, 14, 8, 99, 48);
|
|
@@ -675,6 +761,8 @@ static void esubghz_hooked_draw_callback(Canvas* canvas, void* context)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* Hooks into the view port's input callback to handle the user locking the
|
|
|
|
|
+ * keyboard. */
|
|
|
static void esubghz_hooked_input_callback(InputEvent* event, void* context)
|
|
static void esubghz_hooked_input_callback(InputEvent* event, void* context)
|
|
|
{
|
|
{
|
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
|
|
FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback");
|
|
@@ -684,21 +772,26 @@ static void esubghz_hooked_input_callback(InputEvent* event, void* context)
|
|
|
furi_assert(context);
|
|
furi_assert(context);
|
|
|
ESubGhzChatState* state = context;
|
|
ESubGhzChatState* state = context;
|
|
|
|
|
|
|
|
|
|
+ /* if the keyboard is locked no key presses are forwarded */
|
|
|
if (state->kbd_locked) {
|
|
if (state->kbd_locked) {
|
|
|
|
|
+ /* key has been pressed, display the unlock message and
|
|
|
|
|
+ * initiate the timer */
|
|
|
if (state->kbd_lock_count == 0) {
|
|
if (state->kbd_lock_count == 0) {
|
|
|
state->kbd_lock_msg_ticks = furi_get_tick();
|
|
state->kbd_lock_msg_ticks = furi_get_tick();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* back button has been pressed, increase the lock counter */
|
|
|
if (event->key == InputKeyBack && event->type ==
|
|
if (event->key == InputKeyBack && event->type ==
|
|
|
InputTypeShort) {
|
|
InputTypeShort) {
|
|
|
state->kbd_lock_count++;
|
|
state->kbd_lock_count++;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* unlock the keyboard */
|
|
|
if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
|
|
if (state->kbd_lock_count >= KBD_UNLOCK_CNT) {
|
|
|
kbd_unlock(state);
|
|
kbd_unlock(state);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // do not handle the event
|
|
|
|
|
|
|
+ /* do not handle the event */
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -708,6 +801,7 @@ static void esubghz_hooked_input_callback(InputEvent* event, void* context)
|
|
|
if (state->view_dispatcher->current_view ==
|
|
if (state->view_dispatcher->current_view ==
|
|
|
text_box_get_view(state->chat_box) &&
|
|
text_box_get_view(state->chat_box) &&
|
|
|
!(state->kbd_ok_input_ongoing)) {
|
|
!(state->kbd_ok_input_ongoing)) {
|
|
|
|
|
+ /* lock keyboard upon long press of Ok button */
|
|
|
if (event->type == InputTypeLong) {
|
|
if (event->type == InputTypeLong) {
|
|
|
kbd_lock(state);
|
|
kbd_lock(state);
|
|
|
}
|
|
}
|
|
@@ -725,6 +819,7 @@ static void esubghz_hooked_input_callback(InputEvent* event, void* context)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /* call original callback */
|
|
|
state->orig_input_cb(event, state->view_dispatcher);
|
|
state->orig_input_cb(event, state->view_dispatcher);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -788,12 +883,15 @@ 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();
|
|
gcm_initialize();
|
|
|
|
|
|
|
|
int32_t err = -1;
|
|
int32_t err = -1;
|
|
|
|
|
|
|
|
FURI_LOG_I(APPLICATION_NAME, "Starting...");
|
|
FURI_LOG_I(APPLICATION_NAME, "Starting...");
|
|
|
|
|
|
|
|
|
|
+ /* allocate necessary structs and buffers */
|
|
|
|
|
+
|
|
|
ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
|
|
ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState));
|
|
|
if (state == NULL) {
|
|
if (state == NULL) {
|
|
|
goto err_alloc;
|
|
goto err_alloc;
|
|
@@ -831,14 +929,16 @@ int32_t esubghz_chat(void)
|
|
|
goto err_alloc_rcb;
|
|
goto err_alloc_rcb;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // set chat name prefix
|
|
|
|
|
|
|
+ /* set chat name prefix */
|
|
|
// TODO: handle escape chars here somehow
|
|
// TODO: handle escape chars here somehow
|
|
|
furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
|
|
furi_string_printf(state->name_prefix, "\033[0;33m%s\033[0m: ",
|
|
|
furi_hal_version_get_name_ptr());
|
|
furi_hal_version_get_name_ptr());
|
|
|
|
|
|
|
|
|
|
+ /* get notification record, we use this to make the flipper vibrate */
|
|
|
/* no error handling here, don't know how */
|
|
/* no error handling here, don't know how */
|
|
|
state->notification = furi_record_open(RECORD_NOTIFICATION);
|
|
state->notification = furi_record_open(RECORD_NOTIFICATION);
|
|
|
|
|
|
|
|
|
|
+ /* hook into the view port's draw and input callbacks */
|
|
|
state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
|
|
state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback;
|
|
|
state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
|
|
state->orig_input_cb = state->view_dispatcher->view_port->input_callback;
|
|
|
view_port_draw_callback_set(state->view_dispatcher->view_port,
|
|
view_port_draw_callback_set(state->view_dispatcher->view_port,
|
|
@@ -848,6 +948,7 @@ int32_t esubghz_chat(void)
|
|
|
|
|
|
|
|
view_dispatcher_enable_queue(state->view_dispatcher);
|
|
view_dispatcher_enable_queue(state->view_dispatcher);
|
|
|
|
|
|
|
|
|
|
+ /* set callbacks for view dispatcher */
|
|
|
view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
|
|
view_dispatcher_set_event_callback_context(state->view_dispatcher, state);
|
|
|
view_dispatcher_set_custom_event_callback(
|
|
view_dispatcher_set_custom_event_callback(
|
|
|
state->view_dispatcher,
|
|
state->view_dispatcher,
|
|
@@ -860,31 +961,46 @@ int32_t esubghz_chat(void)
|
|
|
esubghz_chat_tick_event_callback,
|
|
esubghz_chat_tick_event_callback,
|
|
|
TICK_INTERVAL);
|
|
TICK_INTERVAL);
|
|
|
|
|
|
|
|
|
|
+ /* add our two views to the view dispatcher */
|
|
|
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_ChatBox,
|
|
view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox,
|
|
|
text_box_get_view(state->chat_box));
|
|
text_box_get_view(state->chat_box));
|
|
|
|
|
|
|
|
|
|
+ /* 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 */
|
|
|
Gui *gui = furi_record_open(RECORD_GUI);
|
|
Gui *gui = furi_record_open(RECORD_GUI);
|
|
|
- view_dispatcher_attach_to_gui(state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
|
|
|
|
|
|
+ view_dispatcher_attach_to_gui(state->view_dispatcher, gui,
|
|
|
|
|
+ ViewDispatcherTypeFullscreen);
|
|
|
|
|
|
|
|
|
|
+ /* switch to the frequency input scene */
|
|
|
scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
|
|
scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput);
|
|
|
|
|
+
|
|
|
|
|
+ /* run the view dispatcher, this call only returns when we close the
|
|
|
|
|
+ * application */
|
|
|
view_dispatcher_run(state->view_dispatcher);
|
|
view_dispatcher_run(state->view_dispatcher);
|
|
|
|
|
|
|
|
err = 0;
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
+ /* close GUI record */
|
|
|
furi_record_close(RECORD_GUI);
|
|
furi_record_close(RECORD_GUI);
|
|
|
- view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_Input);
|
|
|
|
|
- view_dispatcher_remove_view(state->view_dispatcher, ESubGhzChatView_ChatBox);
|
|
|
|
|
|
|
|
|
|
|
|
+ /* remove our two views from the view dispatcher */
|
|
|
|
|
+ view_dispatcher_remove_view(state->view_dispatcher,
|
|
|
|
|
+ ESubGhzChatView_Input);
|
|
|
|
|
+ view_dispatcher_remove_view(state->view_dispatcher,
|
|
|
|
|
+ ESubGhzChatView_ChatBox);
|
|
|
|
|
+
|
|
|
|
|
+ /* 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,
|
|
esubghz_chat_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));
|
|
esubghz_chat_explicit_bzero(&(state->gcm_ctx), sizeof(state->gcm_ctx));
|
|
|
|
|
|
|
|
|
|
+ /* free everything we allocated*/
|
|
|
|
|
+
|
|
|
furi_stream_buffer_free(state->rx_collection_buffer);
|
|
furi_stream_buffer_free(state->rx_collection_buffer);
|
|
|
|
|
|
|
|
err_alloc_rcb:
|
|
err_alloc_rcb:
|