Просмотр исходного кода

[FL-1922] BLE buffer overflow (#789)

* rpc: increase RPC buffer size. Add get available buffer size API
* bt: add flow control characteristic to serial service
* ble: change updating flow control characteristic logic
* rpc: add buffer is empty callback
* bt: add notification about empty RPC buffer
* ble: add more debug info
* serial_service: add mutex guarding available buffer size
* ble: remove debug logs in serial service
gornekich 4 лет назад
Родитель
Сommit
54dc16134d

+ 9 - 2
applications/bt/bt_service/bt.c

@@ -81,7 +81,7 @@ Bt* bt_alloc() {
 }
 
 // Called from GAP thread from Serial service
-static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) {
+static uint16_t bt_on_data_received_callback(uint8_t* data, uint16_t size, void* context) {
     furi_assert(context);
     Bt* bt = context;
 
@@ -89,6 +89,7 @@ static void bt_on_data_received_callback(uint8_t* data, uint16_t size, void* con
     if(bytes_processed != size) {
         FURI_LOG_E(BT_SERVICE_TAG, "Only %d of %d bytes processed by RPC", bytes_processed, size);
     }
+    return rpc_session_get_available_size(bt->rpc_session);
 }
 
 // Called from GAP thread from Serial service
@@ -118,6 +119,11 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt
     }
 }
 
+static void bt_rpc_buffer_is_empty_callback(void* context) {
+    furi_assert(context);
+    furi_hal_bt_notify_buffer_is_empty();
+}
+
 // Called from GAP thread
 static void bt_on_gap_event_callback(BleEvent event, void* context) {
     furi_assert(context);
@@ -132,9 +138,10 @@ static void bt_on_gap_event_callback(BleEvent event, void* context) {
         FURI_LOG_I(BT_SERVICE_TAG, "Open RPC connection");
         bt->rpc_session = rpc_session_open(bt->rpc);
         rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback);
+        rpc_session_set_buffer_is_empty_callback(bt->rpc_session, bt_rpc_buffer_is_empty_callback);
         rpc_session_set_context(bt->rpc_session, bt);
         furi_hal_bt_set_data_event_callbacks(
-            bt_on_data_received_callback, bt_on_data_sent_callback, bt);
+            RPC_BUFFER_SIZE, bt_on_data_received_callback, bt_on_data_sent_callback, bt);
         // Update battery level
         PowerInfo info;
         power_get_info(bt->power, &info);

+ 26 - 1
applications/rpc/rpc.c

@@ -53,6 +53,7 @@ static RpcSystemCallbacks rpc_systems[] = {
 
 struct RpcSession {
     RpcSendBytesCallback send_bytes_callback;
+    RpcBufferIsEmptyCallback buffer_is_empty_callback;
     RpcSessionClosedCallback closed_callback;
     void* context;
     osMutexId_t callbacks_mutex;
@@ -292,7 +293,7 @@ static Rpc* rpc_alloc(void) {
     rpc->busy_mutex = osMutexNew(NULL);
     rpc->busy = false;
     rpc->events = osEventFlagsNew(NULL);
-    rpc->stream = xStreamBufferCreate(256, 1);
+    rpc->stream = xStreamBufferCreate(RPC_BUFFER_SIZE, 1);
 
     rpc->decoded_message = furi_alloc(sizeof(PB_Main));
     rpc->decoded_message->cb_content.funcs.decode = content_callback;
@@ -365,6 +366,7 @@ static void rpc_free_session(RpcSession* session) {
     session->context = NULL;
     session->closed_callback = NULL;
     session->send_bytes_callback = NULL;
+    session->buffer_is_empty_callback = NULL;
 }
 
 void rpc_session_set_context(RpcSession* session, void* context) {
@@ -397,6 +399,18 @@ void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallba
     osMutexRelease(session->callbacks_mutex);
 }
 
+void rpc_session_set_buffer_is_empty_callback(
+    RpcSession* session,
+    RpcBufferIsEmptyCallback callback) {
+    furi_assert(session);
+    furi_assert(callback);
+    furi_assert(session->rpc->busy);
+
+    osMutexAcquire(session->callbacks_mutex, osWaitForever);
+    session->buffer_is_empty_callback = callback;
+    osMutexRelease(session->callbacks_mutex);
+}
+
 /* Doesn't forbid using rpc_feed_bytes() after session close - it's safe.
  * Because any bytes received in buffer will be flushed before next session.
  * If bytes get into stream buffer before it's get epmtified and this
@@ -415,6 +429,12 @@ size_t
     return bytes_sent;
 }
 
+size_t rpc_session_get_available_size(RpcSession* session) {
+    furi_assert(session);
+    Rpc* rpc = session->rpc;
+    return xStreamBufferSpacesAvailable(rpc->stream);
+}
+
 bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
     Rpc* rpc = istream->state;
     uint32_t flags = 0;
@@ -425,6 +445,11 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
     while(1) {
         bytes_received +=
             xStreamBufferReceive(rpc->stream, buf + bytes_received, count - bytes_received, 0);
+        if(xStreamBufferIsEmpty(rpc->stream)) {
+            if(rpc->session.buffer_is_empty_callback) {
+                rpc->session.buffer_is_empty_callback(rpc->session.context);
+            }
+        }
         if(count == bytes_received) {
             break;
         } else {

+ 21 - 0
applications/rpc/rpc.h

@@ -4,6 +4,8 @@
 #include <stdbool.h>
 #include "cmsis_os.h"
 
+#define RPC_BUFFER_SIZE (1024)
+
 /** Rpc interface. Used for opening session only. */
 typedef struct Rpc Rpc;
 /** Rpc session interface */
@@ -11,6 +13,8 @@ typedef struct RpcSession RpcSession;
 
 /** Callback to send to client any data (e.g. response to command) */
 typedef void (*RpcSendBytesCallback)(void* context, uint8_t* bytes, size_t bytes_len);
+/** Callback to notify client that buffer is empty */
+typedef void (*RpcBufferIsEmptyCallback)(void* context);
 /** Callback to notify transport layer that close_session command
  * is received. Any other actions lays on transport layer.
  * No destruction or session close preformed. */
@@ -59,6 +63,15 @@ void rpc_session_set_context(RpcSession* session, void* context);
  */
 void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallback callback);
 
+/** Set callback to notify that buffer is empty
+ *
+ * @param   session     pointer to RpcSession descriptor
+ * @param   callback    callback to notify client that buffer is empty (can be NULL)
+ */
+void rpc_session_set_buffer_is_empty_callback(
+    RpcSession* session,
+    RpcBufferIsEmptyCallback callback);
+
 /** Set callback to be called when RPC command to close session is received
  *  WARN: It's forbidden to call RPC API within RpcSessionClosedCallback
  *
@@ -77,3 +90,11 @@ void rpc_session_set_close_callback(RpcSession* session, RpcSessionClosedCallbac
  * @return              actually consumed bytes
  */
 size_t rpc_session_feed(RpcSession* session, uint8_t* buffer, size_t size, TickType_t timeout);
+
+/** Get available size of RPC buffer
+ *
+ * @param   session     pointer to RpcSession descriptor
+ *
+ * @return              bytes available in buffer
+ */
+size_t rpc_session_get_available_size(RpcSession* session);

+ 6 - 0
core/furi/common_defines.h

@@ -64,4 +64,10 @@
 
 #ifndef TOSTRING
 #define TOSTRING(x) STRINGIFY(x)
+#endif
+
+#ifndef REVERSE_BYTES_U32
+#define REVERSE_BYTES_U32(x)                                                        \
+    ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \
+     (((x)&0xFF000000) >> 24))
 #endif

+ 60 - 7
firmware/targets/f6/ble-glue/serial_service.c

@@ -10,16 +10,21 @@ typedef struct {
     uint16_t svc_handle;
     uint16_t rx_char_handle;
     uint16_t tx_char_handle;
+    uint16_t flow_ctrl_char_handle;
+    osMutexId_t buff_size_mtx;
+    uint32_t buff_size;
+    uint16_t bytes_ready_to_receive;
     SerialSvcDataReceivedCallback on_received_cb;
     SerialSvcDataSentCallback on_sent_cb;
     void* context;
 } SerialSvc;
 
-static SerialSvc* serial_svc;
+static SerialSvc* serial_svc = NULL;
 
 static const uint8_t service_uuid[] = {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f};
-static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
 static const uint8_t char_tx_uuid[] = {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
+static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
+static const uint8_t flow_ctrl_uuid[] = {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
 
 static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) {
     SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
@@ -36,7 +41,17 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) {
             } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) {
                 FURI_LOG_D(SERIAL_SERVICE_TAG, "Received %d bytes", attribute_modified->Attr_Data_Length);
                 if(serial_svc->on_received_cb) {
-                    serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context);
+                    furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK);
+                    if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) {
+                        FURI_LOG_W(
+                            SERIAL_SERVICE_TAG, "Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!",
+                            attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive);
+                    }
+                    serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length);
+                    uint32_t buff_free_size =
+                        serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context);
+                    FURI_LOG_D(SERIAL_SERVICE_TAG, "Available buff size: %d", buff_free_size);
+                    furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK);
                 }
                 ret = SVCCTL_EvtAckFlowEnable;
             }
@@ -58,7 +73,7 @@ void serial_svc_start() {
     SVCCTL_RegisterSvcHandler(serial_svc_event_handler);
 
     // Add service
-    status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &serial_svc->svc_handle);
+    status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle);
     if(status) {
         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Serial service: %d", status);
     }
@@ -78,7 +93,7 @@ void serial_svc_start() {
 
     // Add TX characteristic
     status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_tx_uuid,
-                                SERIAL_SVC_DATA_LEN_MAX,                                  
+                                SERIAL_SVC_DATA_LEN_MAX,
                                 CHAR_PROP_READ | CHAR_PROP_INDICATE,
                                 ATTR_PERMISSION_AUTHEN_READ,
                                 GATT_DONT_NOTIFY_EVENTS,
@@ -88,12 +103,45 @@ void serial_svc_start() {
     if(status) {
         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add TX characteristic: %d", status);
     }
+    // Add Flow Control characteristic
+    status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)flow_ctrl_uuid,
+                                sizeof(uint32_t),
+                                CHAR_PROP_READ | CHAR_PROP_NOTIFY,
+                                ATTR_PERMISSION_AUTHEN_READ,
+                                GATT_DONT_NOTIFY_EVENTS,
+                                10,
+                                CHAR_VALUE_LEN_CONSTANT,
+                                &serial_svc->flow_ctrl_char_handle);
+    if(status) {
+        FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Flow Control characteristic: %d", status);
+    }
+    // Allocate buffer size mutex
+    serial_svc->buff_size_mtx = osMutexNew(NULL);
 }
 
-void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+    furi_assert(serial_svc);
     serial_svc->on_received_cb = on_received_cb;
     serial_svc->on_sent_cb = on_sent_cb;
     serial_svc->context = context;
+    serial_svc->buff_size = buff_size;
+    serial_svc->bytes_ready_to_receive = buff_size;
+    uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
+    aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed);
+}
+
+void serial_svc_notify_buffer_is_empty() {
+    furi_assert(serial_svc);
+    furi_assert(serial_svc->buff_size_mtx);
+
+    furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK);
+    if(serial_svc->bytes_ready_to_receive == 0) {
+        FURI_LOG_D(SERIAL_SERVICE_TAG, "Buffer is empty. Notifying client");
+        serial_svc->bytes_ready_to_receive = serial_svc->buff_size;
+        uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
+        aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed);
+    }
+    furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK);
 }
 
 void serial_svc_stop() {
@@ -108,11 +156,17 @@ void serial_svc_stop() {
         if(status) {
             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete RX characteristic: %d", status);
         }
+        status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle);
+        if(status) {
+            FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Flow Control characteristic: %d", status);
+        }
         // Delete service
         status = aci_gatt_del_service(serial_svc->svc_handle);
         if(status) {
             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Serial service: %d", status);
         }
+        // Delete buffer size mutex
+        osMutexDelete(serial_svc->buff_size_mtx);
         free(serial_svc);
         serial_svc = NULL;
     }
@@ -122,7 +176,6 @@ bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) {
     if(data_len > SERIAL_SVC_DATA_LEN_MAX) {
         return false;
     }
-    FURI_LOG_D(SERIAL_SERVICE_TAG, "Updating char %d len", data_len);
     tBleStatus result = aci_gatt_update_char_value(serial_svc->svc_handle,
                                         serial_svc->tx_char_handle,
                                         0,

+ 4 - 2
firmware/targets/f6/ble-glue/serial_service.h

@@ -9,12 +9,14 @@
 extern "C" {
 #endif
 
-typedef void(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context);
+typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context);
 typedef void(*SerialSvcDataSentCallback)(void* context);
 
 void serial_svc_start();
 
-void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+
+void serial_svc_notify_buffer_is_empty();
 
 void serial_svc_stop();
 

+ 6 - 2
firmware/targets/f6/furi-hal/furi-hal-bt.c

@@ -59,8 +59,12 @@ void furi_hal_bt_stop_advertising() {
     }
 }
 
-void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
-    serial_svc_set_callbacks(on_received_cb, on_sent_cb, context);
+void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+    serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context);
+}
+
+void furi_hal_bt_notify_buffer_is_empty() {
+    serial_svc_notify_buffer_is_empty();
 }
 
 bool furi_hal_bt_tx(uint8_t* data, uint16_t size) {

+ 60 - 7
firmware/targets/f7/ble-glue/serial_service.c

@@ -10,16 +10,21 @@ typedef struct {
     uint16_t svc_handle;
     uint16_t rx_char_handle;
     uint16_t tx_char_handle;
+    uint16_t flow_ctrl_char_handle;
+    osMutexId_t buff_size_mtx;
+    uint32_t buff_size;
+    uint16_t bytes_ready_to_receive;
     SerialSvcDataReceivedCallback on_received_cb;
     SerialSvcDataSentCallback on_sent_cb;
     void* context;
 } SerialSvc;
 
-static SerialSvc* serial_svc;
+static SerialSvc* serial_svc = NULL;
 
 static const uint8_t service_uuid[] = {0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f};
-static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
 static const uint8_t char_tx_uuid[] = {0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
+static const uint8_t char_rx_uuid[] = {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
+static const uint8_t flow_ctrl_uuid[] = {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
 
 static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) {
     SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
@@ -36,7 +41,17 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void *event) {
             } else if(attribute_modified->Attr_Handle == serial_svc->rx_char_handle + 1) {
                 FURI_LOG_D(SERIAL_SERVICE_TAG, "Received %d bytes", attribute_modified->Attr_Data_Length);
                 if(serial_svc->on_received_cb) {
-                    serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context);
+                    furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK);
+                    if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) {
+                        FURI_LOG_W(
+                            SERIAL_SERVICE_TAG, "Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!",
+                            attribute_modified->Attr_Data_Length, serial_svc->bytes_ready_to_receive);
+                    }
+                    serial_svc->bytes_ready_to_receive -= MIN(serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length);
+                    uint32_t buff_free_size =
+                        serial_svc->on_received_cb(attribute_modified->Attr_Data, attribute_modified->Attr_Data_Length, serial_svc->context);
+                    FURI_LOG_D(SERIAL_SERVICE_TAG, "Available buff size: %d", buff_free_size);
+                    furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK);
                 }
                 ret = SVCCTL_EvtAckFlowEnable;
             }
@@ -58,7 +73,7 @@ void serial_svc_start() {
     SVCCTL_RegisterSvcHandler(serial_svc_event_handler);
 
     // Add service
-    status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 6, &serial_svc->svc_handle);
+    status = aci_gatt_add_service(UUID_TYPE_128, (Service_UUID_t *)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle);
     if(status) {
         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Serial service: %d", status);
     }
@@ -78,7 +93,7 @@ void serial_svc_start() {
 
     // Add TX characteristic
     status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)char_tx_uuid,
-                                SERIAL_SVC_DATA_LEN_MAX,                                  
+                                SERIAL_SVC_DATA_LEN_MAX,
                                 CHAR_PROP_READ | CHAR_PROP_INDICATE,
                                 ATTR_PERMISSION_AUTHEN_READ,
                                 GATT_DONT_NOTIFY_EVENTS,
@@ -88,12 +103,45 @@ void serial_svc_start() {
     if(status) {
         FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add TX characteristic: %d", status);
     }
+    // Add Flow Control characteristic
+    status = aci_gatt_add_char(serial_svc->svc_handle, UUID_TYPE_128, (const Char_UUID_t*)flow_ctrl_uuid,
+                                sizeof(uint32_t),
+                                CHAR_PROP_READ | CHAR_PROP_NOTIFY,
+                                ATTR_PERMISSION_AUTHEN_READ,
+                                GATT_DONT_NOTIFY_EVENTS,
+                                10,
+                                CHAR_VALUE_LEN_CONSTANT,
+                                &serial_svc->flow_ctrl_char_handle);
+    if(status) {
+        FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to add Flow Control characteristic: %d", status);
+    }
+    // Allocate buffer size mutex
+    serial_svc->buff_size_mtx = osMutexNew(NULL);
 }
 
-void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+    furi_assert(serial_svc);
     serial_svc->on_received_cb = on_received_cb;
     serial_svc->on_sent_cb = on_sent_cb;
     serial_svc->context = context;
+    serial_svc->buff_size = buff_size;
+    serial_svc->bytes_ready_to_receive = buff_size;
+    uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
+    aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed);
+}
+
+void serial_svc_notify_buffer_is_empty() {
+    furi_assert(serial_svc);
+    furi_assert(serial_svc->buff_size_mtx);
+
+    furi_check(osMutexAcquire(serial_svc->buff_size_mtx, osWaitForever) == osOK);
+    if(serial_svc->bytes_ready_to_receive == 0) {
+        FURI_LOG_D(SERIAL_SERVICE_TAG, "Buffer is empty. Notifying client");
+        serial_svc->bytes_ready_to_receive = serial_svc->buff_size;
+        uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
+        aci_gatt_update_char_value(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle, 0, sizeof(uint32_t), (uint8_t*)&buff_size_reversed);
+    }
+    furi_check(osMutexRelease(serial_svc->buff_size_mtx) == osOK);
 }
 
 void serial_svc_stop() {
@@ -108,11 +156,17 @@ void serial_svc_stop() {
         if(status) {
             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete RX characteristic: %d", status);
         }
+        status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->flow_ctrl_char_handle);
+        if(status) {
+            FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Flow Control characteristic: %d", status);
+        }
         // Delete service
         status = aci_gatt_del_service(serial_svc->svc_handle);
         if(status) {
             FURI_LOG_E(SERIAL_SERVICE_TAG, "Failed to delete Serial service: %d", status);
         }
+        // Delete buffer size mutex
+        osMutexDelete(serial_svc->buff_size_mtx);
         free(serial_svc);
         serial_svc = NULL;
     }
@@ -122,7 +176,6 @@ bool serial_svc_update_tx(uint8_t* data, uint8_t data_len) {
     if(data_len > SERIAL_SVC_DATA_LEN_MAX) {
         return false;
     }
-    FURI_LOG_D(SERIAL_SERVICE_TAG, "Updating char %d len", data_len);
     tBleStatus result = aci_gatt_update_char_value(serial_svc->svc_handle,
                                         serial_svc->tx_char_handle,
                                         0,

+ 4 - 2
firmware/targets/f7/ble-glue/serial_service.h

@@ -9,12 +9,14 @@
 extern "C" {
 #endif
 
-typedef void(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context);
+typedef uint16_t(*SerialSvcDataReceivedCallback)(uint8_t* buff, uint16_t size, void* context);
 typedef void(*SerialSvcDataSentCallback)(void* context);
 
 void serial_svc_start();
 
-void serial_svc_set_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+void serial_svc_set_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+
+void serial_svc_notify_buffer_is_empty();
 
 void serial_svc_stop();
 

+ 6 - 2
firmware/targets/f7/furi-hal/furi-hal-bt.c

@@ -59,8 +59,12 @@ void furi_hal_bt_stop_advertising() {
     }
 }
 
-void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
-    serial_svc_set_callbacks(on_received_cb, on_sent_cb, context);
+void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context) {
+    serial_svc_set_callbacks(buff_size, on_received_cb, on_sent_cb, context);
+}
+
+void furi_hal_bt_notify_buffer_is_empty() {
+    serial_svc_notify_buffer_is_empty();
 }
 
 bool furi_hal_bt_tx(uint8_t* data, uint16_t size) {

+ 4 - 1
firmware/targets/furi-hal-include/furi-hal-bt.h

@@ -92,7 +92,10 @@ void furi_hal_bt_set_key_storage_change_callback(BleGlueKeyStorageChangedCallbac
  * @param on_sent_cb - SerialSvcDataSentCallback instance
  * @param context - pointer to context
  */
-void furi_hal_bt_set_data_event_callbacks(SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+void furi_hal_bt_set_data_event_callbacks(uint16_t buff_size, SerialSvcDataReceivedCallback on_received_cb, SerialSvcDataSentCallback on_sent_cb, void* context);
+
+/** Notify that buffer is empty */
+void furi_hal_bt_notify_buffer_is_empty();
 
 /** Send data through BLE
  * @param data - data buffer