[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
This commit is contained in:
		@@ -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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								applications/rpc/rpc.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										27
									
								
								applications/rpc/rpc.c
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -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
									
								
								applications/rpc/rpc.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										21
									
								
								applications/rpc/rpc.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -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
									
								
								core/furi/common_defines.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										6
									
								
								core/furi/common_defines.h
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -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
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user