This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST vs BLE_GATTS_EVT_WRITE

My goal is to have a writeable characteristic that I can reject on my peripheral, based on the state of the application.

In my test application, based on the nus uart service example, I initially received the BLE_GATTS_EVT_WRITE event and echoed the data to the terminal via uart. I then set the wr_auth bit in the attribute meta data for the characteristic and received the BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event.

When I handle the event, my test application simply toggles accepting and rejecting the write. On the central side, the iPhone sees the correct behavior: alternately succeeding and failing to write the characteristic value.

I was expecting that when my application authorized the write that I would then get a subsequent BLE_GATTS_EVT_WRITE event to process the final value. That does not seem to happen. Instead, it looks like I'm supposed to process the data after calling sd_ble_gatts_rw_authorize_reply(). Is that correct?

I attempted to do that and it almost worked. I found that my data is being truncated. The request in the event has the correct offset and length for the data, but only the first two data bytes pointed to by the data pointer are correct. Here's the data of the request and my reply and the result of the call to sd_ble_gatts_rw_authorize_reply().

Variable	Value	Location	Type	

request	<struct>	0x20003490	struct <Unnamed 87>	
type	'.' (0x02)	0x20003490	uint8_t	
request	<union>	0x20003492	union <Unnamed 89>	
read	<struct>	0x20003492	struct <Unnamed 86>	
write	<struct>	0x20003492	struct <Unnamed 42>	
handle	0x000D	0x20003492	uint16_t	
uuid	<struct>	0x20003494	ble_uuid_t	
op	'.' (0x01)	0x20003498	uint8_t	
auth_required	'.' (0x01)	0x20003499	uint8_t	
offset	0	0x2000349A	uint16_t	
len	4	0x2000349C	uint16_t	
data	<array>"pl∏["	0x2000349E	uint8_t[1]	

reply	<struct>	0x20003480	struct <Unnamed 84>	
type	'.' (0x02)	0x20003480	uint8_t	
params	<union>	0x20003484	union <Unnamed 88>	
read	<struct>	0x20003484	struct <Unnamed 83>	
write	<struct>	0x20003484	struct <Unnamed 83>	
gatt_status	0	0x20003484	uint16_t	
update	'.' (0x01)	0x20003486	uint8_t	
offset	0	0x20003488	uint16_t	
len	4	0x2000348A	uint16_t	
p_data	0x2000349E	0x2000348C	uint8_t const *	
	'p' (0x70)	0x2000349E	uint8_t	

errCode	NRF_SUCCESS	R5	ErrorCodeEnum	

The data is "play". The first two characters are correct, but the next two are garbage. After the call to sd_ble_gatts_rw_authorize_reply(), my destination buffer has the same four data bytes, so the call is kind of doing what I expected.

bleUartTxBuffer	<array>"pl∏["	0x20004B50	unsigned char[20]	
[0]	'p' (0x70)	0x20004B50	unsigned char	
[1]	'l' (0x6C)	0x20004B51	unsigned char	
[2]	'∏' (0xB8)	0x20004B52	unsigned char	
[3]	'[' (0x5B)	0x20004B53	unsigned char	
[4]	'.' (0x02)	0x20004B54	unsigned char	
[5]	'\0' (0x00)	0x20004B55	unsigned char	
[6]	'ˇ' (0xFF)	0x20004B56	unsigned char	
[7]	'\0' (0x00)	0x20004B57	unsigned char	
[8]	'\0' (0x00)	0x20004B58	unsigned char	
[9]	'\0' (0x00)	0x20004B59	unsigned char	
[10]	'\0' (0x00)	0x20004B5A	unsigned char	
[11]	'\0' (0x00)	0x20004B5B	unsigned char	
[12]	'\0' (0x00)	0x20004B5C	unsigned char	
[13]	'\0' (0x00)	0x20004B5D	unsigned char	
[14]	'\0' (0x00)	0x20004B5E	unsigned char	
[15]	'\0' (0x00)	0x20004B5F	unsigned char	
[16]	'\0' (0x00)	0x20004B60	unsigned char	
[17]	'\0' (0x00)	0x20004B61	unsigned char	
[18]	'\0' (0x00)	0x20004B62	unsigned char	
[19]	'\0' (0x00)	0x20004B63	unsigned char	

If the only change I make to my code is to clear the wr_auth bit then I, instead, get the BLE_GATTS_EVT_WRITE event and everything works as expected. My data is fine.

My basic question is about the relationship between the two events. It looks like setting the wr_auth bit means I will receive only the BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event and clearing the bit means I will receive only the BLE_GATTS_EVT_WRITE event. Is that right?

If not, any idea why I wouldn't receive the latter. If so, any idea why the request would point to partial data?

  • Hi,

    In the case where authorization is not needed, you get a BLE_GATTS_EVT_WRITE event. The characteristic value has already been updated and contains the value provided by the peer.

    In the case where authorization is needed, you get a BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event. You must manually pass the new characteristic value inside the ble_gatts_rw_authorize_reply_params_t struct which is the second argument to sd_ble_gatts_rw_authorize_reply(). Since this time you set the value from the application, you will not receive any event notifying that it has changed.

    If you need the value after the event has been processed, there are two options:

    If it is stored as the characteristic value, you can get it through sd_ble_gatts_value_get().

    If it is not stored as the characteristic value, you must make sure to copy the contents to a local variable when handling the event. The event resides in SoftDevice RAM space, and all you get is a pointer to it. The contents gets corrupted if (when) the SoftDevice decides to use that RAM space for something else.

    What exactly happens in your case (and why) is hard to tell without having a look at your implementation. If you want us to have a look you can attach your code confidentially on MyPage, or post it here in the open.

    Regards,

    Terje

  • I'm going to summarize my experience, but give the correct answer to Terje, so he gets the credit.

    I hadn't gotten any responses to my query for a few days, so I threw the code away and redesigned my service to report the application state to the client to let the client deal with it. Once Terje confirmed the behavior of the BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST event (i.e. that it supplants the BLE_GATTS_EVT_WRITE event), I started with a copy of my working uart service example that implements the BLE_GATTS_EVT_WRITE event. I then set the wr_auth bit in the attribute meta data. I rewrote the BLE_GATTS_EVT_WRITE event handler to perform the authorization and it worked perfectly. Since I didn't save the original test code, I have no idea what was wrong.

    Thanks to Terje for confirming the event behavior. I had inferred the behavior from my experiments, but just knowing meant that I could write the correct code in ten minutes and have it work out of the gate.

    One other data point, the nRF Connect iPhone app provides a dialog box for entering the value of a byte to write. There is a choice of using a Write Command or a Write Request. Command is the default, but only the request invokes the authorization behavior.

  • Could you elaborate on your solution a bit more? I'm seeing the same issue where the first 2 bytes are correct but the rest are garbage. My code is shown in this question

  • Looks like you answered your own question in the other thread. Yes, that's a tricky C pointer issue that is not obvious when using deeply nested unions like in the event messages. The take away is to not copy structured event data by value. Use a pointer into the event message. Glad you solved your problem relatively quickly.

  • I was cleaning up some stuff and happened to see the block of debug text I pasted into my question. Then I noticed that the reply structure ranged in memory from 0x20003480-0x2000348F and the request structure ranged from 0x20003490-0x2000349F. Clearly, they are both local variables on the stack, so in retrospect, I made the exact same mistake as goldwake: creating a request local, rather than a pointer to the request.

Related