cstructmemory-managementzephyr-rtos

Zephyr - C - Deep copy of struct produce unexpected result


Some context

I'm currently working with Bluetooth Low Energy on an embedded system using Zephyr.

During the services/characteristics/CCCDs discovering (my embedded device is the BLE central), I want to store every characteristics so I can use them later (mainly read them).

For that I'm using the linked list provide by Zephyr and I allocate my node using k_calloc().

typedef struct lst_chrc_node_s {
  sys_snode_t     node;
  struct bt_uuid  *uuid;
  uint16_t        value_handle;
  uint8_t         properties;
  uint8_t         type;
} lst_chrc_node_t;

lst_chrc_node_t *list_create_node(struct bt_gatt_chrc *chrc, uint8_t type)
{
  lst_chrc_node_t *new_node = k_calloc(1, sizeof(new_node));
  if (!new_node)
    return NULL;
  
  new_node->uuid = k_calloc(1, sizeof(new_node->uuid));
  if (!new_node->uuid)
  {
    k_free(new_node);
    return NULL;
  }
  
  print_uuid128("UUID that will be store", chrc->uuid);
  *new_node->uuid = *chrc->uuid;
  new_node->value_handle = chrc->value_handle;
  new_node->properties = chrc->properties;
  new_node->type = type;
  print_uuid128("UUID that was stored   ", new_node->uuid);

  return new_node;
}

As you can see, I want to store the struct bt_gatt_chrc *chrc that is passed in the function's arguments. However, this structure contains a constant pointer to an other struct named bt_uuid. This bt_uuid struct is important because I will need it to read the correct characteristic from the BLE peripheral.

My linked list's node doesn't have directly the bt_gatt_chrc struct but has the same member one by one. This because I can't directly copy the bt_gatt_chrc from argument to linked list's node because the const pointer to bt_uuid has the same address when discovering services, characteristics and CCCDs (It change when discovering a different GATT Attribute's type but will still the same for this type of attribute).

The problem

After creating the node and copying data in it, the bt_uuid value isn't the same as the one I received from the BLE peripheral.

Here is the log for one standard UUID:

[00:00:04.293,914] <inf> main: UUID received           UUID 128: 070002a4-5720-0045-2020-0013072a0000
[00:00:04.293,945] <inf> main: UUID that will be store UUID 128: 070002a4-5720-0045-2020-0013072a0000
[00:00:04.293,975] <inf> main: UUID that was stored    UUID 128: 00000d00-0d61-8c00-0200-020003000000

I feel pretty stupid because AFAIU, my issue seems clearly to be in this line *new_node->uuid = *chrc->uuid; but I don't see what I'm doing wrong. I tried to cast the const struct bt_uuid to just struct bt_uuid, the result wasn't good either (it looks like even more wrong as the entire 128-bit UUID changes between each standard characteristics (defined by the Bluetooth SIG) instead of staying pretty close to the same (like just 2-3 digits change between characteristics without casting the struct).

Is it because the bt_uuid is a "tentative" type and so I can't use it this way ? Can someone rephrase what is a tentative type ? I'm clearly not sure about my understanding of this.


Solution

  • From uuid.h:

    enum {
            BT_UUID_TYPE_16,
            BT_UUID_TYPE_32,
            BT_UUID_TYPE_128,
    };
     
    #define BT_UUID_SIZE_16                  2
     
    #define BT_UUID_SIZE_32                  4
     
    #define BT_UUID_SIZE_128                 16
     
    struct bt_uuid {
            uint8_t type;
    };
     
    struct bt_uuid_16 {
            struct bt_uuid uuid;
            uint16_t val;
    };
     
    struct bt_uuid_32 {
            struct bt_uuid uuid;
            uint32_t val;
    };
     
    struct bt_uuid_128 {
            struct bt_uuid uuid;
            uint8_t val[BT_UUID_SIZE_128];
    };
    

    The struct bt_uuid * should point to enough memory to store a struct bt_uuid_16, a struct bt_uuid_32, or a struct bt_uuid_128, depending on the value of the type member BT_UUID_TYPE_16, BT_UUID_TYPE_32, or BT_UUID_TYPE_128.

    EDIT: The line lst_chrc_node_t *new_node = k_calloc(1, sizeof(new_node)); in OP's original code is wrong (spotted by @chux in the comment below, and @ach in the comment on the question), and has been corrected in the code below.

    Untested, modified code:

    lst_chrc_node_t *list_create_node(struct bt_gatt_chrc *chrc, uint8_t type)
    {
      lst_chrc_node_t *new_node;
      size_t bt_uuid_size;
    
      switch (chrc->uuid->type)
      {
      case BT_UUID_TYPE_16:
        bt_uuid_size = sizeof(struct bt_uuid_16);
        break;
      case BT_UUID_TYPE_32:
        bt_uuid_size = sizeof(struct bt_uuid_32);
        break;
      case BT_UUID_TYPE_128:
        bt_uuid_size = sizeof(struct bt_uuid_128);
        break;
      default:
        return NULL;
      }
      new_node = k_calloc(1, sizeof(*new_node));
      if (!new_node)
        return NULL;
      
      new_node->uuid = k_calloc(1, bt_uuid_size);
      if (!new_node->uuid)
      {
        k_free(new_node);
        return NULL;
      }
      
      print_uuid128("UUID that will be store", chrc->uuid);
      memcpy(new_node->uuid, chrc->uuid, bt_uuid_size);
      new_node->value_handle = chrc->value_handle;
      new_node->properties = chrc->properties;
      new_node->type = type;
      print_uuid128("UUID that was stored   ", new_node->uuid);
    
      return new_node;
    }