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

issues with writable characteristic

I have a service a writeable characteristic and I'm finding the following issues.

Characteristic is read/write, not variable length, size of 4, no read or write auth, no extended attributes (ie no reliable/long writes)

  1. If I write the characteristic with more than 4 bytes (but less than 20) there's a correct 'invalid attribute value length' error returned. However if I write with less than 4 bytes the write succeeds and only some bytes of the value are updated, but the length remains at 4. Since it's fixed length, I would expect an invalid attribute value length error in this case too.

Edit: and I'm wrong for expecting that as someone kindly pointed me at the spec and it says

If the attribute value has a fixed length and the Attribute Value parameter length is less than or equal to the length of the attribute value, the octets of the attribute value parameter length shall be written; all other octets in this attribute value shall be unchanged.

That still leaves the other two

  1. If I send a prepared write for that characteristic even though it's fixed-length and doesn't have any extended attributes (nor does any other characteristic) I get a USER MEM request and the rw_auth calls. Since I'm not expecting any long writes, I don't look for those events or handle them. If a characteristic is not set up for reliable/long writes, why doesn't the softdevice instantly reject the prepare write with an error message? I would expect the 'Attribute Not Long' error. I would only expect to ever get the user memory and prepare/commit calls for attributes which have the extended reliable_wr property set true.
  2. If I do try to handle the rw_auth and send back a proper error code, like 'Attribute Not Long'' the sd_ble_gatts_rw_authorize_reply() function returns an INVALID_PARAM error. It seems that I am only allowed to send a very few gatt_status codes, others are rejected as invalid params, why?

You can see this in the sample code too, if you run say the uart example and use lightblue to attempt to send a > 20 byte value to the writable characteristic, for which it will send out a prepare write, nothing will happen and eventually the connection will drop - this is because internally a memory request has been sent and not handled so the connection times out. It's true that a client ought to see the lack of extended characteristics and not attempt a prepared/long write, but if one is sent, the softdevice should reject it.

  • Hi there,

    First of all, the answer you gave yourself to the first question is correct, and we implement the spec as it's written.

    Let me try to answer your 2 remaining questions:

    1. Characteristic properties are hints, nothing else. The stack treats those properties simply as raw data, and does not enforce any limitations based on them. They are populated by the application and they are supposed to be read/checked by the peer application, but ultimately only permissions are enforced by the stack. This is again per specification. I know it's a bit confusing, but if the stack was to enforce characteristic properties we would be violating the specification, since the only checks the ATT implementation is supposed to perform are permission-related checks. You mention prepare write operations, but the same is true for any other one (like a write request on a characteristic that doesn't have the write without response property set).

    2. You are mixing two things here. If none of the attributes written to by the prepare writes require authorization (and you have provided memory to the stack with user_mem_reply), you will not get any RW_AUTHORIZE_REQUEST events. And in fact if you provide memory to the stack the queued writes will execute transparently just like any other write (request or command) would without you having a chance to approve/reject the operation, just like with any other write. See here for an example of this. If you on the other hand do not provide memory to the stack because you want to handle all prepare write operations yourself (see example here) then you can reply to each RW_AUTHORIZE_REQUEST with any of the ones allowed by the stack.

    This is the list of errors you can use:

    BLE_GATT_STATUS_ATTERR_INSUF_AUTHORIZATION
    BLE_GATT_STATUS_ATTERR_INSUF_ENC_KEY_SIZE
    BLE_GATT_STATUS_ATTERR_INVALID_ATT_VAL_LENGTH
    BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR
    BLE_GATT_STATUS_ATTERR_INSUF_RESOURCES
    BLE_GATT_STATUS_ATTERR_READ_NOT_PERMITTED
    BLE_GATT_STATUS_ATTERR_WRITE_NOT_PERMITTED
    BLE_GATT_STATUS_ATTERR_INSUF_AUTHENTICATION
    BLE_GATT_STATUS_ATTERR_INSUF_ENCRYPTION
    

    along with application errors, which are the ones designed to notify the peer of application-level error conditions (like for example properties violated):

    BLE_GATT_STATUS_ATTERR_APP_BEGIN <= error <= BLE_GATT_STATUS_ATTERR_APP_END 
    

    I understand that you need to deal with this when you're not interested at all in queued writes, but you have to bear in mind that queued writes are like any other write operation and the stack cannot simply reject it autonomously, just like it can't reject any other write operation unless it targets an attribute that is explicitly authorized.

    Carles

  • Thanks for the list of allowed error code , I was trying to figure it out. Is there a reason that neither BLE_GATT_STATUS_ATTERR_INVALID_OFFSET nor BLE_GATT_STATUS_ATTERR_PREPARE_QUEUE_FULL are in that list? Yes in this case I am handling the prepared write myself and not returning a memory chunk, that's unfortunate but necessary for this particular application for one particular characteristic. When you get the memory request you have no idea which characteristics are going to be in the prepared write (you can't know) so I have to return NULL and deal with it. That means however that I can run out of memory during a Prepare Write and should send QUEUE_FULL and when I process the queue later if there's an INVALID_OFFSET I'm supposed to return that, but can't because they're not on the list. I guess I'll use INSUF_RESOURCES for the first and ATT_VAL_LENGTH for the second but not ideal

  • Yes the reason those 2 error codes you mention are not on that list is that we consider those to be only for internal stack usage. However I do agree that the INVALID_OFFSET and PREPARE_QUEUE_FULL should be made usable for the app too, since it can be handling its own queue. I have created an internal issue for that and hopefully we'll evaluate it and, if applicable, fix it soon. I understand that the application cannot know how much memory it will need, and neither can the stack, so that's why we pushed the decision up to the application so that it can be tested/tuned without having to update the SD

  • I see looking at 2. again and see that if you have no authorised attributes and you provide user memory then the stack already has parsed out the values and committed them, I was originally confused by the phrase 'app parses the memory it provided' and thought it still needed to commit the values itself, now I see it doesn't. I wish I could use that, regrettably I have to validate the value on one written characteristic and provide an application error if invalid so I have to do the auth. Since I have to do it for one, I found the easiest code path is to do it for them all.

    Thanks for the detailed explanation - that - plus some of the reading I did after posting the original question, has cleared it up a lot and I think I can improve my implementation a bit more.

  • Sure, you're welcome. Thank you for the feedback, it will contribute to a better SD in the future. Can you mark the question as answered? Thanks!

Related