Skip to content

Commit 453045a

Browse files
committed
[BLE - NRF5] Add support for long write requests.
1 parent 7963e8e commit 453045a

File tree

2 files changed

+316
-2
lines changed

2 files changed

+316
-2
lines changed

features/FEATURE_BLE/targets/TARGET_NORDIC/TARGET_NRF5/source/nRF5xGattServer.cpp

Lines changed: 259 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,47 @@
2626

2727
#include "nRF5xn.h"
2828

29+
namespace {
30+
31+
static const ble_gatts_rw_authorize_reply_params_t write_auth_queue_full_reply = {
32+
.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE,
33+
.params = {
34+
.write = {
35+
.gatt_status = BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL
36+
}
37+
}
38+
};
39+
40+
static const ble_gatts_rw_authorize_reply_params_t write_auth_invalid_offset_reply = {
41+
.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE,
42+
.params = {
43+
.write = {
44+
.gatt_status = BLE_GATT_STATUS_ATTERR_INVALID_OFFSET
45+
}
46+
}
47+
};
48+
49+
static const ble_gatts_rw_authorize_reply_params_t write_auth_succes_reply = {
50+
.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE,
51+
.params = {
52+
.write = {
53+
.gatt_status = BLE_GATT_STATUS_SUCCESS,
54+
.update = 0
55+
}
56+
}
57+
};
58+
59+
static const ble_gatts_rw_authorize_reply_params_t write_auth_invalid_reply = {
60+
.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE,
61+
.params = {
62+
.write = {
63+
.gatt_status = BLE_GATT_STATUS_ATTERR_INVALID
64+
}
65+
}
66+
};
67+
68+
}
69+
2970
/**************************************************************************/
3071
/*!
3172
@brief Adds a new service to the GATT table on the peripheral
@@ -354,6 +395,8 @@ ble_error_t nRF5xGattServer::reset(void)
354395
memset(nrfDescriptorHandles, 0, sizeof(nrfDescriptorHandles));
355396
descriptorCount = 0;
356397

398+
releaseAllWriteRequests();
399+
357400
return BLE_ERROR_NONE;
358401
}
359402

@@ -427,13 +470,33 @@ void nRF5xGattServer::hwCallback(ble_evt_t *p_ble_evt)
427470
}
428471
break;
429472

473+
case BLE_EVT_USER_MEM_REQUEST: {
474+
uint16_t conn_handle = p_ble_evt->evt.common_evt.conn_handle;
475+
476+
// allocate a new long request for this connection
477+
// NOTE: we don't care about the result at this stage,
478+
// it is not possible to cancel the operation anyway.
479+
// If the request was not allocated then it will gracefully failled
480+
// at subsequent stages.
481+
allocateLongWriteRequest(conn_handle);
482+
sd_ble_user_mem_reply(conn_handle, NULL);
483+
return;
484+
}
485+
430486
default:
431487
return;
432488
}
433489

434490
int characteristicIndex = resolveValueHandleToCharIndex(handle_value);
435491
if (characteristicIndex == -1) {
436-
return;
492+
// filter out the case were the request is a long one,
493+
// and there is no attribute handle provided
494+
uint8_t write_op = gattsEventP->params.authorize_request.request.write.op;
495+
if (eventType != GattServerEvents::GATT_EVENT_WRITE_AUTHORIZATION_REQ ||
496+
(write_op != BLE_GATTS_OP_EXEC_WRITE_REQ_NOW &&
497+
write_op != BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL)) {
498+
return;
499+
}
437500
}
438501

439502
/* Find index (charHandle) in the pool */
@@ -451,6 +514,140 @@ void nRF5xGattServer::hwCallback(ble_evt_t *p_ble_evt)
451514
break;
452515
}
453516
case GattServerEvents::GATT_EVENT_WRITE_AUTHORIZATION_REQ: {
517+
uint16_t conn_handle = gattsEventP->conn_handle;
518+
const ble_gatts_evt_write_t& input_req = gattsEventP->params.authorize_request.request.write;
519+
const uint16_t max_size = getBiggestCharacteristicSize();
520+
521+
// this is a long write request, handle it here.
522+
switch (input_req.op) {
523+
case BLE_GATTS_OP_PREP_WRITE_REQ: {
524+
// verify that the request is not outside of the possible range
525+
if ((input_req.offset + input_req.len) > max_size) {
526+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_offset_reply);
527+
releaseLongWriteRequest(conn_handle);
528+
return;
529+
}
530+
531+
// find the write request
532+
long_write_request_t* req = findLongWriteRequest(conn_handle);
533+
if (!req) {
534+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_reply);
535+
releaseLongWriteRequest(conn_handle);
536+
return;
537+
}
538+
539+
// initialize the first request by setting the offset
540+
if (req->length == 0) {
541+
req->attr_handle = input_req.handle;
542+
req->offset = input_req.offset;
543+
}
544+
545+
// it is disalowed to write backward
546+
if (input_req.offset < req->offset) {
547+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_offset_reply);
548+
releaseLongWriteRequest(conn_handle);
549+
return;
550+
}
551+
552+
// it should be the subsequent write
553+
if ((req->offset + req->length) != input_req.offset) {
554+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_offset_reply);
555+
releaseLongWriteRequest(conn_handle);
556+
return;
557+
}
558+
559+
// it is not allowed to write multiple characteristic with the same request
560+
if (input_req.handle != req->attr_handle) {
561+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_reply);
562+
releaseLongWriteRequest(conn_handle);
563+
return;
564+
}
565+
566+
// start the copy of what is in input
567+
memcpy(req->data + req->length, input_req.data, input_req.len);
568+
569+
// update the lenght of the data written
570+
req->length = req->length + input_req.len;
571+
572+
// success, signal it to the softdevice
573+
ble_gatts_rw_authorize_reply_params_t reply = {
574+
.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE,
575+
.params = {
576+
.write = {
577+
.gatt_status = BLE_GATT_STATUS_SUCCESS,
578+
.update = 1,
579+
.offset = input_req.offset,
580+
.len = input_req.len,
581+
.p_data = input_req.data
582+
}
583+
}
584+
};
585+
586+
sd_ble_gatts_rw_authorize_reply(conn_handle, &reply);
587+
} return;
588+
589+
case BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL: {
590+
releaseLongWriteRequest(conn_handle);
591+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_succes_reply);
592+
} return;
593+
594+
case BLE_GATTS_OP_EXEC_WRITE_REQ_NOW: {
595+
long_write_request_t* req = findLongWriteRequest(conn_handle);
596+
if (!req) {
597+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_reply);
598+
releaseLongWriteRequest(conn_handle);
599+
return;
600+
}
601+
602+
GattWriteAuthCallbackParams cbParams = {
603+
.connHandle = conn_handle,
604+
.handle = req->attr_handle,
605+
.offset = req->offset,
606+
.len = req->length,
607+
.data = req->data,
608+
.authorizationReply = AUTH_CALLBACK_REPLY_SUCCESS /* the callback handler must leave this member
609+
* set to AUTH_CALLBACK_REPLY_SUCCESS if the client
610+
* request is to proceed. */
611+
};
612+
uint16_t write_authorization = p_characteristics[characteristicIndex]->authorizeWrite(&cbParams);
613+
614+
// the user code didn't provide the write authorization,
615+
// just leave here.
616+
if (write_authorization != AUTH_CALLBACK_REPLY_SUCCESS) {
617+
// report the status of the operation in any cases
618+
sd_ble_gatts_rw_authorize_reply(gattsEventP->conn_handle, &write_auth_invalid_reply);
619+
releaseLongWriteRequest(conn_handle);
620+
return;
621+
}
622+
623+
// FIXME can't use ::write here, this function doesn't take the offset into account ...
624+
ble_gatts_value_t value = {
625+
.len = req->length,
626+
.offset = req->offset,
627+
.p_value = req->data
628+
};
629+
uint32_t update_err = sd_ble_gatts_value_set(conn_handle, req->attr_handle, &value);
630+
if (update_err) {
631+
sd_ble_gatts_rw_authorize_reply(conn_handle, &write_auth_invalid_reply);
632+
releaseLongWriteRequest(conn_handle);
633+
return;
634+
}
635+
636+
sd_ble_gatts_rw_authorize_reply(gattsEventP->conn_handle, &write_auth_succes_reply);
637+
638+
GattWriteCallbackParams writeParams = {
639+
.connHandle = conn_handle,
640+
.handle = req->attr_handle,
641+
.writeOp = static_cast<GattWriteCallbackParams::WriteOp_t>(input_req.op),
642+
.offset = req->offset,
643+
.len = req->length,
644+
.data = req->data,
645+
};
646+
handleDataWrittenEvent(&writeParams);
647+
releaseLongWriteRequest(conn_handle);
648+
} return;
649+
}
650+
454651
GattWriteAuthCallbackParams cbParams = {
455652
.connHandle = gattsEventP->conn_handle,
456653
.handle = handle_value,
@@ -541,3 +738,64 @@ void nRF5xGattServer::hwCallback(ble_evt_t *p_ble_evt)
541738
break;
542739
}
543740
}
741+
742+
uint16_t nRF5xGattServer::getBiggestCharacteristicSize() const {
743+
uint16_t result = 0;
744+
for (size_t i = 0; i < characteristicCount; ++i) {
745+
uint16_t current_size = p_characteristics[i]->getValueAttribute().getMaxLength();
746+
if (current_size > result) {
747+
result = current_size;
748+
}
749+
}
750+
return result;
751+
}
752+
753+
nRF5xGattServer::long_write_request_t* nRF5xGattServer::allocateLongWriteRequest(uint16_t connection_handle) {
754+
for (size_t i = 0; i < TOTAL_CONCURENT_LONG_WRITE_REQUEST; ++i) {
755+
long_write_request_t& req = long_write_requests[i];
756+
if (req.data == NULL) {
757+
uint16_t block_size = getBiggestCharacteristicSize();
758+
req.data = static_cast<uint8_t*>(malloc(block_size));
759+
req.offset = 0;
760+
req.length = 0;
761+
req.conn_handle = connection_handle;
762+
return &req;
763+
}
764+
}
765+
// if nothing has been found then return null
766+
return NULL;
767+
}
768+
769+
bool nRF5xGattServer::releaseLongWriteRequest(uint16_t connection_handle) {
770+
long_write_request_t* req = findLongWriteRequest(connection_handle);
771+
if (!req) {
772+
return false;
773+
}
774+
775+
free(req->data);
776+
req->data = NULL;
777+
778+
// the other fields are not relevant, return now
779+
return true;
780+
}
781+
782+
nRF5xGattServer::long_write_request_t* nRF5xGattServer::findLongWriteRequest(uint16_t connection_handle) {
783+
for (size_t i = 0; i < TOTAL_CONCURENT_LONG_WRITE_REQUEST; ++i) {
784+
long_write_request_t& req = long_write_requests[i];
785+
if (req.data != NULL && req.conn_handle == connection_handle) {
786+
return &req;
787+
}
788+
}
789+
// if nothing has been found then return null
790+
return NULL;
791+
}
792+
793+
void nRF5xGattServer::releaseAllWriteRequests() {
794+
for (size_t i = 0; i < TOTAL_CONCURENT_LONG_WRITE_REQUEST; ++i) {
795+
long_write_request_t& req = long_write_requests[i];
796+
if (req.data != NULL) {
797+
free(req.data);
798+
req.data = NULL;
799+
}
800+
}
801+
}

features/FEATURE_BLE/targets/TARGET_NORDIC/TARGET_NRF5/source/nRF5xGattServer.h

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,29 @@ class nRF5xGattServer : public GattServer
4545
private:
4646
const static unsigned BLE_TOTAL_CHARACTERISTICS = 20;
4747
const static unsigned BLE_TOTAL_DESCRIPTORS = 8;
48+
const static unsigned TOTAL_CONCURENT_LONG_WRITE_REQUEST = 3;
49+
50+
private:
51+
struct long_write_request_t {
52+
// the connection handle for a long write request
53+
uint16_t conn_handle;
54+
55+
// the attribute handle for the long write request
56+
// This implementation folow the bluetooth route
57+
// where a write request target a single characteristic
58+
// (see BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] - 4.9.4)
59+
uint16_t attr_handle;
60+
61+
// offset of the transaction
62+
uint16_t offset;
63+
64+
// length of the data
65+
uint16_t length;
66+
67+
// current data
68+
uint8_t* data;
69+
};
70+
4871

4972
private:
5073
/**
@@ -79,19 +102,52 @@ class nRF5xGattServer : public GattServer
79102
return -1;
80103
}
81104

105+
/**
106+
* Return the biggest size used by a characteristic in the server
107+
*/
108+
uint16_t getBiggestCharacteristicSize() const;
109+
110+
/**
111+
* Allocate a new write long request. return null if no requests are available.
112+
* @param connection_handle The connection handle which where the request will
113+
* happen.
114+
* @return the allocated request or NULL if no requests are available.
115+
*/
116+
long_write_request_t* allocateLongWriteRequest(uint16_t connection_handle);
117+
118+
/**
119+
* Release a long write request and free a slot for subsequent write long requests.
120+
* @param connection_handle The connection handle associated with the request
121+
* @return true if the request where allocated and was release, false otherwise.
122+
*/
123+
bool releaseLongWriteRequest(uint16_t connection_handle);
124+
125+
/**
126+
* Find a long write request from a characteristic handle
127+
* @param connection_handle The connection handle associated with the reauest.
128+
* @return a pointer to the request if found otherwise NULL.
129+
*/
130+
long_write_request_t* findLongWriteRequest(uint16_t connection_handle);
131+
132+
/**
133+
* Release all pending write requests.
134+
*/
135+
void releaseAllWriteRequests();
136+
82137
private:
83138
GattCharacteristic *p_characteristics[BLE_TOTAL_CHARACTERISTICS];
84139
ble_gatts_char_handles_t nrfCharacteristicHandles[BLE_TOTAL_CHARACTERISTICS];
85140
GattAttribute *p_descriptors[BLE_TOTAL_DESCRIPTORS];
86141
uint8_t descriptorCount;
87142
uint16_t nrfDescriptorHandles[BLE_TOTAL_DESCRIPTORS];
143+
long_write_request_t long_write_requests[TOTAL_CONCURENT_LONG_WRITE_REQUEST];
88144

89145
/*
90146
* Allow instantiation from nRF5xn when required.
91147
*/
92148
friend class nRF5xn;
93149

94-
nRF5xGattServer() : GattServer(), p_characteristics(), nrfCharacteristicHandles(), p_descriptors(), descriptorCount(0), nrfDescriptorHandles() {
150+
nRF5xGattServer() : GattServer(), p_characteristics(), nrfCharacteristicHandles(), p_descriptors(), descriptorCount(0), nrfDescriptorHandles(), long_write_requests() {
95151
/* empty */
96152
}
97153

0 commit comments

Comments
 (0)