Skip to content
Snippets Groups Projects
Select Git revision
  • f1c328f35792f41d7d05470679fea860a61264b8
  • master default protected
  • v0.2.1
  • v0.2.0
4 results

nmf.py

Blame
  • db-device.c 22.94 KiB
    /* db - Database for xAAL Agent
     * Part of the xaaws software
     * (c) 2019 Christophe Lohr <christophe.lohr@imt-atlantique.fr>
     *
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    #include <string.h>
    #include <errno.h>
    #include <uuid/uuid.h>
    #include <regex.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    #include <xaal_jc.h>
    
    #include "db-device.h"
    
    #include "db-schema.h"
    
    
    /*
     * Section Devices
     */
    
    
    /* load a DB of devices from a cbor file */
    void load_devices(devices_t *devices, char *file) {
      struct cbor_load_result cresult;
      cbor_item_t *cdevices;
      unsigned char *file_map;
      struct stat sb;
      size_t i, sz;
      int fd;
    
      if (!file) return;
    
      fd = open(file, O_RDONLY);
      if (fd == -1) {
        fprintf(stderr, "Could not open(r) database file %s. %s\n", file, strerror(errno));
        return;
      }
    
      if (fstat(fd, &sb) == -1) { /* get file size */
        fprintf(stderr, "Could not do fstat on database file %s. %s\n", file, strerror(errno));
        return;
      }
    
      file_map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
      if (file_map == MAP_FAILED) {
        fprintf(stderr, "Could not mmap database file %s. %s\n", file, strerror(errno));
        return;
      }
    
      cdevices = cbor_load(file_map, sb.st_size, &cresult);
      munmap(file_map, sb.st_size);
      close(fd);
      if ( !cdevices ) {
        fprintf(stderr, "Could not parse database file %s. CBOR error; code %d, position %lu\n",
                file, cresult.error.code, cresult.error.position);
        return;
      }
    
      if ( cbor_isa_array(cdevices) ) {
        sz = cbor_array_size(cdevices);
        for (i = 0; i < sz; i++)
          update_device(devices, cbor_move(cbor_array_get(cdevices, i)));
      }
      cbor_decref(&cdevices);
    }
    
    
    
    /* dump DB of devices to a cbor file */
    void dump_devices(devices_t *devices, char *file) {
      cbor_item_t *cdevices, *cdevice;
      device_t *device;
      unsigned char *buffer;
      size_t size, len;
      int fd;
    
      cdevices = cbor_new_indefinite_array();
      TAILQ_FOREACH(device, devices, entries) {
        cdevice = get_cbor_device(device); /* address, dev_type, description */
        cbor_map_add(cdevice, (struct cbor_pair){ cbor_move(cbor_build_string("values")), cbor_move(get_cbor_values(device)) }); /* attributes */
        cbor_map_add(cdevice, (struct cbor_pair){ cbor_move(cbor_build_string("map")), cbor_move(get_cbor_kvs(device)) }); /* metadata */
        cbor_array_push(cdevices, cbor_move(cdevice));
      }
    
      fd = open(file, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
      if (fd == -1) {
        fprintf(stderr, "Could not open(w+) database file %s. %s\n", file, strerror(errno));
        return;
      }
    
      len = cbor_serialize_alloc(cdevices, &buffer, &size);
      if ( write(fd, buffer, len) != len )
        fprintf(stderr, "Writing database file: %s\n", strerror(errno));
      free(buffer);
      close(fd);
    
      cbor_decref(&cdevices);
    }
    
    
    
    
    /* update a device from loading cbor data */
    void update_device(devices_t *devices, cbor_item_t *cdevice) {
      cbor_item_t *caddress, *ctimeout, *cvals, *cval, *cvalue, *cdate, *cmap;
      char *dev_type, *attribute, *attrtype;
      uuid_t addr;
      device_t *device;
      size_t sz, i, len;
      time_t date, now = time(NULL);
    
      caddress = xAAL_cbor_map_get(cdevice, "address");
      if ( !xAAL_cbor_is_uuid(caddress, &addr) )
        return;
    
      dev_type = xAAL_cbor_string_dup(xAAL_cbor_map_get(cdevice, "dev_type"), &len);
      if ( !dev_type )
        return;
    
      if ( !validate_identifier(dev_type) ) {
        free(dev_type);
        return;
      }
    
      device = add_device(devices, &addr);
    
      if (device->devinfo.dev_type) {
        if (strcmp(device->devinfo.dev_type, dev_type) != 0) {
          free(dev_type);
          return;
        }
        free(dev_type);
      } else
        device->devinfo.dev_type = dev_type;
    
      update_description(device, cdevice);
    
      ctimeout = xAAL_cbor_map_get(cdevice, "timeout");
      if ( cbor_is_int(ctimeout) )
        device->timeout = cbor_get_int(ctimeout);
    
      cvals =  xAAL_cbor_map_get(cdevice, "values");
      if ( cvals && cbor_isa_array(cvals) ) {
        sz = cbor_array_size(cvals);
        for (i = 0; i < sz; i++) {
          cval = cbor_move(cbor_array_get(cvals, i));
          attribute = xAAL_cbor_string_dup(xAAL_cbor_map_get(cval, "name"), &len);
          if ( !attribute )
            continue;
          if ( !validate_identifier(attribute) ) {
            free(attribute);
            continue;
          }
          attrtype = xAAL_cbor_string_dup(xAAL_cbor_map_get(cval, "type"), &len);
          if ( !validate_identifier(attrtype) ) {
            free(attrtype);
            attrtype  = NULL;
          }
          cvalue = xAAL_cbor_map_get(cval, "value");
          if ( !cvalue ) {
            free(attribute);
            continue;
          }
          cdate = xAAL_cbor_map_get(cval, "date");
          if ( cdate && cbor_is_int(cdate) )
            date = cbor_get_int(cdate);
          else
            date = now;
          update_value(&(device->vals), attribute, attrtype, cvalue, date);
          if (attrtype) free(attrtype);
        }
      }
    
      cmap =  xAAL_cbor_map_get(cdevice, "map");
      if ( cmap && cbor_isa_map(cmap) )
        update_map(&(device->map), cmap);
    }
    
    
    
    
    /* select a device by its address */
    device_t *select_device(devices_t *devices, const uuid_t *addr) {
      device_t *np;
    
      TAILQ_FOREACH(np, devices, entries)
        if (uuid_compare(*addr, np->devinfo.addr) == 0)
          return np;
      return NULL;
    }
    
    
    /* select or insert a device (its addr) */
    device_t *add_device(devices_t *devices, const uuid_t *addr) {
      device_t *np;
    
      TAILQ_FOREACH(np, devices, entries)
        if (uuid_compare(*addr, np->devinfo.addr) == 0)
          return np;
      np = (device_t *)malloc(sizeof(device_t));
      uuid_copy(np->devinfo.addr, *addr);
      np->devinfo.dev_type = NULL;
      np->devinfo.vendor_id = NULL;
      np->devinfo.product_id = NULL;
      np->devinfo.hw_id = NULL;
      np->devinfo.version = NULL;
      np->devinfo.group_id = NULL;
      np->devinfo.url = NULL;
      np->devinfo.schema = NULL;
      np->devinfo.info = NULL;
      np->devinfo.unsupported_attributes = NULL;
      np->devinfo.unsupported_methods = NULL;
      np->devinfo.unsupported_notifications = NULL;
      np->timeout = 0;
      TAILQ_INIT(&(np->vals));
      TAILQ_INIT(&(np->map));
      TAILQ_INSERT_TAIL(devices, np, entries);
      return np;
    }
    
    
    /* update the description of a device */
    void update_description(device_t *device, cbor_item_t *cdescription) {
      cbor_item_t  *chw_id, *cuAttributes, *cuMethods, *cuNotifications;
      char *vendor_id, *product_id, *version, *url, *schema, *info;
      uuid_t *group_id;
      size_t len;
    
      vendor_id = xAAL_cbor_string_dup(xAAL_cbor_map_get(cdescription, "vendor_id"), &len);
      if ( vendor_id )
        device->devinfo.vendor_id = vendor_id;
    
      product_id = xAAL_cbor_string_dup(xAAL_cbor_map_get(cdescription, "product_id"), &len);
      if ( product_id )
        device->devinfo.product_id = product_id;
    
      chw_id = xAAL_cbor_map_get(cdescription, "hw_id");
      if ( chw_id )
        device->devinfo.hw_id = cbor_incref(chw_id);
    
      version = xAAL_cbor_string_dup(xAAL_cbor_map_get(cdescription, "version"), &len);
      if ( version )
        device->devinfo.version = version;
    
      group_id = (uuid_t *)malloc(sizeof(uuid_t));
      xAAL_cbor_map_get(cdescription, "group_id");
      if ( xAAL_cbor_is_uuid(xAAL_cbor_map_get(cdescription, "group_id"), group_id) )
        device->devinfo.group_id = group_id;
      else
        free(group_id);
    
      url = xAAL_cbor_string_dup(xAAL_cbor_untag(xAAL_cbor_map_get(cdescription, "url")), &len);
      if ( url )
        device->devinfo.url = url;
    
      schema = xAAL_cbor_string_dup(xAAL_cbor_untag(xAAL_cbor_map_get(cdescription, "schema")), &len);
      if ( schema )
        device->devinfo.schema = schema;
    
      info = xAAL_cbor_string_dup(xAAL_cbor_map_get(cdescription, "info"), &len);
      if ( info )
        device->devinfo.info = info;
    
      cuAttributes = xAAL_cbor_map_get(cdescription, "unsupported_attributes");
      if ( cuAttributes && cbor_isa_array(cuAttributes) )
        device->devinfo.unsupported_attributes = xAAL_strings_unpack(cuAttributes);
    
      cuMethods = xAAL_cbor_map_get(cdescription, "unsupported_methods");
      if ( cuMethods && cbor_isa_array(cuMethods) )
        device->devinfo.unsupported_methods = xAAL_strings_unpack(cuMethods);
    
      cuNotifications = xAAL_cbor_map_get(cdescription, "unsupported_notifications");
      if ( cuNotifications && cbor_isa_array(cuNotifications) )
        device->devinfo.unsupported_notifications = xAAL_strings_unpack(cuNotifications);
    }
    
    
    /* get (cbor) devices (having a given key in its kv map, or all) */
    cbor_item_t *get_cbor_devices(devices_t *devices, const char *key) {
      cbor_item_t *carray = cbor_new_indefinite_array();
      cbor_item_t *crow;
      device_t *np = NULL;
    
      TAILQ_FOREACH(np, devices, entries)
        if ( (key == NULL) || (select_kv(&(np->map), key) != NULL) ) {
          crow = cbor_new_definite_map(3);
          cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("address")), cbor_move(cbor_build_bytestring(np->devinfo.addr, 16)) });
          cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("dev_type")), cbor_move(cbor_build_string(np->devinfo.dev_type)) });
          cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("timeout")), cbor_move(xAAL_cbor_build_int(np->timeout)) });
          cbor_array_push(carray, crow);
        }
      return carray;
    }
    
    
    /* get (json) devices (having a given key in its map, or all) */
    json_object *get_json_devices(devices_t *devices, const char *key) {
      json_object *jarray = json_object_new_array();
      json_object *jrow;
      device_t *np = NULL;
      char uuid[37];
    
      TAILQ_FOREACH(np, devices, entries)
        if ( (key == NULL) || (select_kv(&(np->map), key) != NULL) ) {
          jrow = json_object_new_object();
          uuid_unparse(np->devinfo.addr, uuid);
          json_object_object_add(jrow, "address", json_object_new_string(uuid));
          if (np->devinfo.dev_type)
            json_object_object_add(jrow, "dev_type", json_object_new_string(np->devinfo.dev_type));
          if (np->timeout)
            json_object_object_add(jrow, "timeout", json_object_new_int64(np->timeout));
          json_object_array_add(jarray, jrow);
        }
      return jarray;
    }
    
    
    /* serialize a device to cbor */
    cbor_item_t *get_cbor_device(device_t *device) {
      cbor_item_t *cobj = cbor_new_indefinite_map();
    
      cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("address")), cbor_move(cbor_build_bytestring(device->devinfo.addr, 16)) });
      cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("dev_type")), cbor_move(cbor_build_string(device->devinfo.dev_type)) });
      if (device->devinfo.vendor_id)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("vendor_id")), cbor_move(cbor_build_string(device->devinfo.vendor_id)) });
      if (device->devinfo.product_id)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("product_id")), cbor_move(cbor_build_string(device->devinfo.product_id)) });
      if (device->devinfo.hw_id)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("hw_id")), device->devinfo.hw_id });
      if (device->devinfo.version)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("version")), cbor_move(cbor_build_string(device->devinfo.version)) });
      if (device->devinfo.group_id)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("group_id")), cbor_move(cbor_build_bytestring(*(device->devinfo.group_id), 16)) });
      if (device->devinfo.url)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("url")), cbor_move(cbor_build_string(device->devinfo.url)) });
      if (device->devinfo.schema)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("schema")), cbor_move(cbor_build_string(device->devinfo.schema)) });
      if (device->devinfo.info)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("info")), cbor_move(cbor_build_string(device->devinfo.info)) });
      if (device->devinfo.unsupported_attributes)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("unsupported_attributes")), cbor_move(xAAL_strings_pack(device->devinfo.unsupported_attributes)) });
      if (device->devinfo.unsupported_methods)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("unsupported_methods")), cbor_move(xAAL_strings_pack(device->devinfo.unsupported_methods)) });
      if (device->devinfo.unsupported_notifications)
        cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("unsupported_notifications")), cbor_move(xAAL_strings_pack(device->devinfo.unsupported_notifications)) });
      cbor_map_add(cobj, (struct cbor_pair){ cbor_move(cbor_build_string("timeout")), cbor_move(xAAL_cbor_build_int(device->timeout)) });
    
      return cobj;
    }
    
    
    /* serialize a null terminatied list of strings to json */
    json_object *strings2json(char **list) {
      json_object *jarray = json_object_new_array();
      char **idx;
      for (idx = list; *idx != NULL; idx++)
        json_object_array_add(jarray, json_object_new_string(*idx));
      return jarray;
    }
    
    
    /* serialize a device to json */
    json_object *get_json_device(device_t *device) {
      json_object *jobj = json_object_new_object();
      char uuid[37];
    
      uuid_unparse(device->devinfo.addr, uuid);
      json_object_object_add(jobj, "address", json_object_new_string(uuid));
      json_object_object_add(jobj, "dev_type", json_object_new_string(device->devinfo.dev_type));
      if (device->devinfo.vendor_id)
        json_object_object_add(jobj, "vendor_id",
    			   json_object_new_string(device->devinfo.vendor_id));
      if (device->devinfo.product_id)
        json_object_object_add(jobj, "product_id",
    			   json_object_new_string(device->devinfo.product_id));
      if (device->devinfo.hw_id)
        json_object_object_add(jobj, "hw_id",
    			   xAAL_cbor2json(device->devinfo.hw_id));
      if (device->devinfo.version)
        json_object_object_add(jobj, "version",
    			   json_object_new_string(device->devinfo.version));
      if (device->devinfo.group_id) {
        uuid_unparse(*(device->devinfo.group_id), uuid);
        json_object_object_add(jobj, "group_id", json_object_new_string(uuid));
      }
      if (device->devinfo.url)
        json_object_object_add(jobj, "url",
    			   json_object_new_string(device->devinfo.url));
      if (device->devinfo.schema)
        json_object_object_add(jobj, "schema",
    			   json_object_new_string(device->devinfo.schema));
      if (device->devinfo.info)
        json_object_object_add(jobj, "info",
    			   json_object_new_string(device->devinfo.info));
      if (device->devinfo.unsupported_attributes)
        json_object_object_add(jobj, "unsupported_attributes",
    			   strings2json(device->devinfo.unsupported_attributes));
      if (device->devinfo.unsupported_methods)
        json_object_object_add(jobj, "unsupported_methods",
    			   strings2json(device->devinfo.unsupported_methods));
      if (device->devinfo.unsupported_notifications)
        json_object_object_add(jobj, "unsupported_notifications",
    			   strings2json(device->devinfo.unsupported_notifications));
      json_object_object_add(jobj, "timeout", json_object_new_int64(device->timeout));
    
      return jobj;
    }
    
    
    /* select a device and serialize it to cbor */
    cbor_item_t *get_cbor_device_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_cbor_device(device);
      return cbor_new_indefinite_map();
    }
    
    /* select a device and serialize it to cbor */
    json_object *get_json_device_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_json_device(device);
      return json_object_new_object();;
    }
    
    
    /* free a null-terminated vector of strings */
    void free_vector(char **items) {
      char **item;
      for (item = items; *item != NULL; item++)
        free(*item);
      free(items);
    }
    
    
    /* delete a device */
    void delete_device(devices_t *devices, device_t *device) {
      TAILQ_REMOVE(devices, device, entries);
      if (device->devinfo.dev_type)
        free(device->devinfo.dev_type);
      if (device->devinfo.vendor_id)
        free(device->devinfo.vendor_id);
      if (device->devinfo.product_id)
        free(device->devinfo.product_id);
      if (device->devinfo.hw_id)
        cbor_decref(&device->devinfo.hw_id);
      if (device->devinfo.version)
        free(device->devinfo.version);
      if (device->devinfo.group_id)
        free(device->devinfo.group_id);
      if (device->devinfo.url)
        free(device->devinfo.url);
      if (device->devinfo.schema)
        free(device->devinfo.schema);
      if (device->devinfo.info)
        free(device->devinfo.info);
      if (device->devinfo.unsupported_attributes)
        free_vector(device->devinfo.unsupported_attributes);
      if (device->devinfo.unsupported_methods)
        free_vector(device->devinfo.unsupported_methods);
      if (device->devinfo.unsupported_notifications)
        free_vector(device->devinfo.unsupported_notifications);
      delete_map(&(device->map));
      delete_values(&(device->vals));
      free(device);
    }
    
    
    
    
    /*
     * Section Values of Attributes of a Device
     */
    
    /* select a value by its name */
    val_t *select_value(vals_t *vals, const char *name) {
      val_t *val;
    
      TAILQ_FOREACH(val, vals, entries)
        if (val->name && (strcmp(val->name, name) == 0) )
          return val;
      return NULL;
    }
    
    
    /* insert or update the value of an attribute of a device */
    void update_value(vals_t *vals, const char *name, const char *type, cbor_item_t *value, time_t date) {
      val_t *val;
      
      if (!name)
        return;
        
      val = select_value(vals, name);
    
      if (!val) {
        val = (val_t *)malloc(sizeof(val_t));
        val->name = strdup(name);
        val->type = type ? strdup(type) : NULL;
        val->date = 0;
        TAILQ_INSERT_TAIL(vals, val, entries);
      } else {
        if (type) {
          if (val->type && strcmp(val->type, type))
            free(val->type);
          val->type = strdup(type);
        }
      }
    
      if (date > val->date) {
        val->value = cbor_incref(value);
        val->date = date;
      }
    }
    
    
    /* serialize values of attributes of a device to cbor */
    cbor_item_t *get_cbor_values(device_t *device) {
      cbor_item_t *carray = cbor_new_indefinite_array();
      cbor_item_t *crow;
      val_t *np = NULL;
    
      TAILQ_FOREACH(np, &(device->vals), entries) {
        crow = cbor_new_definite_map(3);
        cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("name")), cbor_move(cbor_build_string(np->name)) });
        if (np->type)
          cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("type")), cbor_move(cbor_build_string(np->type)) });
        cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("value")), np->value });
        cbor_map_add(crow, (struct cbor_pair){ cbor_move(cbor_build_string("date")), cbor_move(xAAL_cbor_build_int(np->date)) });
        cbor_array_push(carray, cbor_move(crow));
      }
      return carray;
    }
    
    
    /* serialize values of attributes of a device to json */
    json_object *get_json_values(device_t *device) {
      json_object *jarray = json_object_new_array();
      json_object *jrow;
      val_t *np = NULL;
    
      TAILQ_FOREACH(np, &(device->vals), entries) {
        jrow = json_object_new_object();
        json_object_object_add(jrow, "name", json_object_new_string(np->name));
        if (np->type)
          json_object_object_add(jrow, "type", json_object_new_string(np->type));
        json_object_object_add(jrow, "value", xAAL_cbor2json(np->value));
        json_object_object_add(jrow, "date", json_object_new_int64(np->date));
        json_object_array_add(jarray, jrow);
      }
      return jarray;
    }
    
    
    /* select a device and serialize its attributes values to cbor */
    cbor_item_t *get_cbor_values_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_cbor_values(device);
      return NULL;
    }
    
    
    /* select a device and serialize its attributes values to json */
    json_object *get_json_values_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_json_values(device);
      return NULL;
    }
    
    
    /* delete the list of values */
    void delete_values(vals_t *vals) {
      while (vals->tqh_first != NULL) {
        cbor_decref(&vals->tqh_first->value);
        if (vals->tqh_first->name) free(vals->tqh_first->name);
        if (vals->tqh_first->type) free(vals->tqh_first->type);
        TAILQ_REMOVE(vals, vals->tqh_first, entries);
        free(vals->tqh_first);
      }
    }
    
    
    
    
    /*
     * Section Map (keys-values metadata) of a Device
     */
    
    
    /* select a key-value in the map if present */
    kv_t *select_kv(map_t *map, const char *key) {
      kv_t *np;
    
      TAILQ_FOREACH(np, map, entries)
        if (strcmp(key, np->key) == 0)
          return np;
      return NULL;
    }
    
    
    /* insert a key-value in the map */
    void insert_kv(map_t *map, const char *key, const char *value) {
      kv_t *np;
    
      TAILQ_FOREACH(np, map, entries)
        if (strcmp(key, np->key) == 0) {
          if (!strcmp(value, np->value) == 0) {
    	free(np->value);
    	np->value = strdup(value);
          }
          return;
        }
      np = (kv_t *)malloc(sizeof(kv_t));
      np->key = strdup(key);
      np->value = strdup(value);
      TAILQ_INSERT_TAIL(map, np, entries);
    }
    
    
    /* serialize KVs-map of a device in cbor */
    cbor_item_t *get_cbor_kvs(device_t *device) {
      cbor_item_t *cmap = cbor_new_indefinite_map();
      kv_t *np = NULL;
    
      TAILQ_FOREACH(np, &(device->map), entries)
        cbor_map_add(cmap, (struct cbor_pair){ cbor_move(cbor_build_string(np->key)), cbor_move(cbor_build_string(np->value)) });
      return cmap;
    }
    
    
    /* serialize KVs-map of a device in json */
    json_object *get_json_map(device_t *device) {
      json_object *jmap = json_object_new_object();
      kv_t *np = NULL;
    
      TAILQ_FOREACH(np, &(device->map), entries)
        json_object_object_add(jmap, np->key, json_object_new_string(np->value));
      return jmap;
    }
    
    
    /* get (cbor) KVs-map of a device by addr */
    cbor_item_t *get_cbor_kvs_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_cbor_kvs(device);
      return NULL;
    }
    
    
    /* get (json) KVs-map of a device by addr */
    json_object *get_json_kvs_by_addr(devices_t *devices, const uuid_t *addr) {
      device_t *device = select_device(devices, addr);
      if (device)
        return get_json_map(device);
      return NULL;
    }
    
    
    /* delete a key-value in the map */
    void delete_kv(map_t *map, const char *key) {
      kv_t *np;
    
      TAILQ_FOREACH(np, map, entries)
        if (strcmp(key, np->key) == 0) {
          TAILQ_REMOVE(map, np, entries);
          free(np->key);
          free(np->value);
          free(np);
          return;
        }
    }
    
    
    /* delete the map of kv */
    void delete_map(map_t *map) {
      kv_t *np;
    
      TAILQ_FOREACH(np, map, entries) {
        TAILQ_REMOVE(map, np, entries);
        free(np->key);
        free(np->value);
        free(np);
      }
    }
    
    
    /* update the map (merge) */
    void update_map(map_t *map, cbor_item_t *cmap) {
      struct cbor_pair *pairs;
      size_t len, i, sz;
      char *key, *value;
    
      if ( !cmap || !cbor_isa_map(cmap) )
        return;
    
      pairs = cbor_map_handle(cmap);
      sz = cbor_map_size(cmap);
      for (i = 0; i < sz; i++) {
        key = xAAL_cbor_string_dup(pairs[i].key, &len);
        if ( key ) {
          if ( cbor_is_null(pairs[i].value) )
    	delete_kv(map, key);
          else {
    	value = xAAL_cbor_string_dup(pairs[i].value, &len);
    	insert_kv(map, key, value);
    	free(value);
          }
          free(key);
        }
      }
    }