mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-15 16:24:28 +08:00
e594ed81be
This adds implementation for parsing and writing Mesh configuration into JSON format. Also, parse stored unprovisioned device composiotion.
1361 lines
28 KiB
C
1361 lines
28 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <ell/ell.h>
|
|
|
|
#include "mesh/mesh-defs.h"
|
|
#include "mesh/util.h"
|
|
|
|
#include "mesh/mesh-db.h"
|
|
|
|
#define CHECK_KEY_IDX_RANGE(x) (((x) >= 0) && ((x) <= 4095))
|
|
|
|
static bool get_int(json_object *jobj, const char *keyword, int *value)
|
|
{
|
|
json_object *jvalue;
|
|
|
|
if (!json_object_object_get_ex(jobj, keyword, &jvalue))
|
|
return false;
|
|
|
|
*value = json_object_get_int(jvalue);
|
|
if (errno == EINVAL)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool add_key(json_object *jobject, const char *desc,
|
|
const uint8_t key[16])
|
|
{
|
|
json_object *jstring;
|
|
char hexstr[33];
|
|
|
|
hex2str((uint8_t *) key, 16, hexstr, 33);
|
|
jstring = json_object_new_string(hexstr);
|
|
if (!jstring)
|
|
return false;
|
|
|
|
json_object_object_add(jobject, desc, jstring);
|
|
return true;
|
|
}
|
|
|
|
static json_object *get_element_model(json_object *jnode, int ele_idx,
|
|
uint32_t mod_id, bool vendor)
|
|
{
|
|
json_object *jelements, *jelement, *jmodels;
|
|
int i, num_mods;
|
|
size_t len;
|
|
char buf[9];
|
|
|
|
if (!vendor)
|
|
snprintf(buf, 5, "%4.4x", (uint16_t)mod_id);
|
|
else
|
|
snprintf(buf, 9, "%8.8x", mod_id);
|
|
|
|
json_object_object_get_ex(jnode, "elements", &jelements);
|
|
if (!jelements)
|
|
return NULL;
|
|
|
|
jelement = json_object_array_get_idx(jelements, ele_idx);
|
|
if (!jelement)
|
|
return NULL;
|
|
|
|
json_object_object_get_ex(jelement, "models", &jmodels);
|
|
if (!jmodels)
|
|
return NULL;
|
|
|
|
num_mods = json_object_array_length(jmodels);
|
|
if (!num_mods)
|
|
return NULL;
|
|
|
|
if (!vendor) {
|
|
snprintf(buf, 5, "%4.4x", mod_id);
|
|
len = 4;
|
|
} else {
|
|
snprintf(buf, 9, "%8.8x", mod_id);
|
|
len = 8;
|
|
}
|
|
|
|
for (i = 0; i < num_mods; ++i) {
|
|
json_object *jmodel, *jvalue;
|
|
char *str;
|
|
|
|
jmodel = json_object_array_get_idx(jmodels, i);
|
|
json_object_object_get_ex(jmodel, "modelId", &jvalue);
|
|
if (!jvalue)
|
|
return NULL;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str)
|
|
return NULL;
|
|
|
|
if (!strncmp(str, buf, len))
|
|
return jmodel;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool jarray_has_string(json_object *jarray, char *str, size_t len)
|
|
{
|
|
int i, sz = json_object_array_length(jarray);
|
|
|
|
for (i = 0; i < sz; ++i) {
|
|
json_object *jentry;
|
|
char *str_entry;
|
|
|
|
jentry = json_object_array_get_idx(jarray, i);
|
|
str_entry = (char *)json_object_get_string(jentry);
|
|
if (!str_entry)
|
|
continue;
|
|
|
|
if (!strncmp(str, str_entry, len))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static json_object *jarray_string_del(json_object *jarray, char *str,
|
|
size_t len)
|
|
{
|
|
int i, sz = json_object_array_length(jarray);
|
|
json_object *jarray_new;
|
|
|
|
jarray_new = json_object_new_array();
|
|
if (!jarray_new)
|
|
return NULL;
|
|
|
|
for (i = 0; i < sz; ++i) {
|
|
json_object *jentry;
|
|
char *str_entry;
|
|
|
|
jentry = json_object_array_get_idx(jarray, i);
|
|
str_entry = (char *)json_object_get_string(jentry);
|
|
if (str_entry && !strncmp(str, str_entry, len))
|
|
continue;
|
|
|
|
json_object_array_add(jarray_new, jentry);
|
|
}
|
|
|
|
return jarray_new;
|
|
}
|
|
|
|
static json_object *get_key_object(json_object *jarray, uint16_t idx)
|
|
{
|
|
int i, sz = json_object_array_length(jarray);
|
|
|
|
for (i = 0; i < sz; ++i) {
|
|
json_object *jentry, *jvalue;
|
|
uint32_t jidx;
|
|
|
|
jentry = json_object_array_get_idx(jarray, i);
|
|
if (!json_object_object_get_ex(jentry, "index", &jvalue))
|
|
return NULL;
|
|
|
|
jidx = json_object_get_int(jvalue);
|
|
|
|
if (jidx == idx)
|
|
return jentry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static json_object *jarray_key_del(json_object *jarray, int16_t idx)
|
|
{
|
|
json_object *jarray_new;
|
|
int i, sz = json_object_array_length(jarray);
|
|
char idx_str[5];
|
|
|
|
snprintf(idx_str, 5, "%4.4x", idx);
|
|
|
|
jarray_new = json_object_new_array();
|
|
if (!jarray_new)
|
|
return NULL;
|
|
|
|
for (i = 0; i < sz; ++i) {
|
|
json_object *jentry, *jvalue;
|
|
char *str;
|
|
|
|
jentry = json_object_array_get_idx(jarray, i);
|
|
|
|
if (json_object_object_get_ex(jentry, "index", &jvalue)) {
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (str && !strncmp(str, idx_str, 4))
|
|
continue;
|
|
}
|
|
|
|
json_object_array_add(jarray_new, jentry);
|
|
}
|
|
|
|
return jarray_new;
|
|
}
|
|
|
|
bool mesh_db_read_iv_index(json_object *jobj, uint32_t *idx, bool *update)
|
|
{
|
|
int tmp;
|
|
|
|
/* IV index */
|
|
if (!get_int(jobj, "IVindex", &tmp))
|
|
return false;
|
|
|
|
*idx = (uint32_t) tmp;
|
|
|
|
if (!get_int(jobj, "IVupdate", &tmp))
|
|
return false;
|
|
|
|
*update = tmp ? true : false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_read_device_key(json_object *jobj, uint8_t key_buf[16])
|
|
{
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
if (!key_buf)
|
|
return false;
|
|
|
|
if (!json_object_object_get_ex(jobj, "deviceKey", &jvalue) ||
|
|
!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), key_buf, 16))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_read_app_keys(json_object *jobj, mesh_db_app_key_cb cb,
|
|
void *user_data)
|
|
{
|
|
json_object *jarray;
|
|
int len;
|
|
int i;
|
|
|
|
if (!cb)
|
|
return true;
|
|
|
|
json_object_object_get_ex(jobj, "appKeys", &jarray);
|
|
if (!jarray || (json_object_get_type(jarray) != json_type_array))
|
|
return false;
|
|
|
|
len = json_object_array_length(jarray);
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
json_object *jtemp, *jvalue;
|
|
int app_idx, net_idx;
|
|
bool key_refresh = false;
|
|
char *str;
|
|
uint8_t key[16];
|
|
uint8_t new_key[16];
|
|
|
|
jtemp = json_object_array_get_idx(jarray, i);
|
|
|
|
if (!get_int(jtemp, "index", &app_idx))
|
|
return false;
|
|
|
|
if (!CHECK_KEY_IDX_RANGE(app_idx))
|
|
return false;
|
|
|
|
if (!get_int(jtemp, "boundNetKey", &net_idx))
|
|
return false;
|
|
|
|
if (!CHECK_KEY_IDX_RANGE(net_idx))
|
|
return false;
|
|
|
|
json_object_object_get_ex(jtemp, "oldKey", &jvalue);
|
|
if (jvalue) {
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), key, 16))
|
|
return false;
|
|
key_refresh = true;
|
|
}
|
|
|
|
json_object_object_get_ex(jtemp, "key", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), key_refresh ? new_key : key, 16))
|
|
return false;
|
|
|
|
if (!cb((uint16_t)net_idx, (uint16_t) app_idx, key,
|
|
key_refresh ? new_key : NULL, user_data))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_read_net_keys(json_object *jobj, mesh_db_net_key_cb cb,
|
|
void *user_data)
|
|
{
|
|
json_object *jarray;
|
|
int len;
|
|
int i;
|
|
|
|
if (!cb)
|
|
return true;
|
|
|
|
json_object_object_get_ex(jobj, "netKeys", &jarray);
|
|
if (!jarray || (json_object_get_type(jarray) != json_type_array))
|
|
return false;
|
|
|
|
len = json_object_array_length(jarray);
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
json_object *jtemp, *jvalue;
|
|
int idx;
|
|
char *str;
|
|
bool key_refresh = false;
|
|
int phase;
|
|
uint8_t key[16];
|
|
uint8_t new_key[16];
|
|
|
|
jtemp = json_object_array_get_idx(jarray, i);
|
|
|
|
if (!get_int(jtemp, "index", &idx))
|
|
return false;
|
|
|
|
if (!CHECK_KEY_IDX_RANGE(idx))
|
|
return false;
|
|
|
|
json_object_object_get_ex(jtemp, "oldKey", &jvalue);
|
|
if (jvalue) {
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), key, 16))
|
|
return false;
|
|
key_refresh = true;
|
|
}
|
|
|
|
json_object_object_get_ex(jtemp, "key", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), key_refresh ? new_key : key, 16))
|
|
return false;
|
|
|
|
json_object_object_get_ex(jtemp, "keyRefresh", &jvalue);
|
|
if (!jvalue)
|
|
phase = KEY_REFRESH_PHASE_NONE;
|
|
else
|
|
phase = json_object_get_int(jvalue);
|
|
|
|
|
|
if (!cb((uint16_t)idx, key, key_refresh ? new_key : NULL, phase,
|
|
user_data))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_net_key_add(json_object *jobj, uint16_t idx,
|
|
const uint8_t key[16], int phase)
|
|
{
|
|
json_object *jarray, *jentry = NULL, *jstring;
|
|
char buf[5];
|
|
|
|
json_object_object_get_ex(jobj, "netKeys", &jarray);
|
|
if (!jarray && (phase != KEY_REFRESH_PHASE_NONE))
|
|
return false;
|
|
|
|
if (jarray)
|
|
jentry = get_key_object(jarray, idx);
|
|
|
|
/*
|
|
* The key entry should exist if the key is updated
|
|
* (i.e., Key Refresh is underway)
|
|
*/
|
|
if (!jentry && (phase != KEY_REFRESH_PHASE_NONE))
|
|
return false;
|
|
|
|
if (jentry) {
|
|
uint8_t buf[16];
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
json_object_object_get_ex(jentry, "key", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), buf, sizeof(buf)))
|
|
return false;
|
|
|
|
/* If the same key, return success */
|
|
if (memcmp(key, buf, 16) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (phase == KEY_REFRESH_PHASE_NONE) {
|
|
jentry = json_object_new_object();
|
|
if (!jentry)
|
|
goto fail;
|
|
|
|
snprintf(buf, 5, "%4.4x", idx);
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
goto fail;
|
|
|
|
json_object_object_add(jentry, "index", jstring);
|
|
|
|
snprintf(buf, 5, "%4.4x", idx);
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
goto fail;
|
|
|
|
if (!add_key(jentry, "key", key))
|
|
goto fail;
|
|
|
|
if (!jarray) {
|
|
jarray = json_object_new_array();
|
|
if (!jarray)
|
|
goto fail;
|
|
json_object_object_add(jobj, "netKeys", jarray);
|
|
}
|
|
|
|
json_object_array_add(jarray, jentry);
|
|
|
|
} else {
|
|
|
|
if (!json_object_object_get_ex(jentry, "key", &jstring))
|
|
return false;
|
|
|
|
json_object_object_add(jentry, "oldKey", jstring);
|
|
json_object_object_del(jentry, "key");
|
|
|
|
if (!add_key(jentry, "key", key))
|
|
return false;
|
|
}
|
|
|
|
|
|
json_object_object_add(jentry, "keyRefresh",
|
|
json_object_new_int(phase));
|
|
|
|
return true;
|
|
fail:
|
|
|
|
if (jentry)
|
|
json_object_put(jentry);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mesh_db_net_key_del(json_object *jobj, uint16_t idx)
|
|
{
|
|
json_object *jarray, *jarray_new;
|
|
|
|
json_object_object_get_ex(jobj, "netKeys", &jarray);
|
|
if (!jarray)
|
|
return true;
|
|
|
|
/* Check if matching entry exists */
|
|
if (!get_key_object(jarray, idx))
|
|
return true;
|
|
|
|
if (json_object_array_length(jarray) == 1) {
|
|
json_object_object_del(jobj, "netKeys");
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* There is no easy way to delete a value from json array.
|
|
* Create a new copy without specified element and
|
|
* then remove old array.
|
|
*/
|
|
jarray_new = jarray_key_del(jarray, idx);
|
|
if (!jarray_new)
|
|
return false;
|
|
|
|
json_object_object_del(jobj, "netKeys");
|
|
json_object_object_add(jobj, "netKeys", jarray_new);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_device_key(json_object *jnode, uint8_t *key)
|
|
{
|
|
return add_key(jnode, "deviceKey", key);
|
|
}
|
|
|
|
bool mesh_db_app_key_add(json_object *jobj, uint16_t net_idx, uint16_t app_idx,
|
|
const uint8_t key[16], bool update)
|
|
{
|
|
json_object *jarray, *jentry = NULL, *jstring = NULL;
|
|
char buf[5];
|
|
|
|
json_object_object_get_ex(jobj, "appKeys", &jarray);
|
|
if (!jarray && update)
|
|
return false;
|
|
|
|
if (jarray)
|
|
jentry = get_key_object(jarray, app_idx);
|
|
|
|
/* The key entry should exist if the key is updated */
|
|
if (!jentry && update)
|
|
return false;
|
|
|
|
if (jentry) {
|
|
uint8_t buf[16];
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
json_object_object_get_ex(jentry, "key", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), buf, sizeof(buf)))
|
|
return false;
|
|
|
|
/* If the same key, return success */
|
|
if (memcmp(key, buf, 16) == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!update) {
|
|
jentry = json_object_new_object();
|
|
if (!jentry)
|
|
goto fail;
|
|
|
|
snprintf(buf, 5, "%4.4x", app_idx);
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
goto fail;
|
|
|
|
json_object_object_add(jentry, "index", jstring);
|
|
|
|
snprintf(buf, 5, "%4.4x", net_idx);
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
goto fail;
|
|
|
|
json_object_object_add(jentry, "boundNetKey", jstring);
|
|
|
|
if (!add_key(jentry, "key", key))
|
|
goto fail;
|
|
|
|
if (!jarray) {
|
|
jarray = json_object_new_array();
|
|
if (!jarray)
|
|
goto fail;
|
|
json_object_object_add(jobj, "appKeys", jarray);
|
|
}
|
|
|
|
json_object_array_add(jarray, jentry);
|
|
|
|
} else {
|
|
|
|
if (!json_object_object_get_ex(jentry, "key", &jstring))
|
|
return false;
|
|
|
|
json_object_object_add(jentry, "oldKey", jstring);
|
|
json_object_object_del(jentry, "key");
|
|
|
|
if (!add_key(jentry, "key", key))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
fail:
|
|
|
|
if (jentry)
|
|
json_object_put(jentry);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mesh_db_app_key_del(json_object *jobj, uint16_t net_idx, uint16_t idx)
|
|
{
|
|
json_object *jarray, *jarray_new;
|
|
|
|
json_object_object_get_ex(jobj, "appKeys", &jarray);
|
|
if (!jarray)
|
|
return true;
|
|
|
|
/* Check if matching entry exists */
|
|
if (!get_key_object(jarray, idx))
|
|
return true;
|
|
|
|
if (json_object_array_length(jarray) == 1) {
|
|
json_object_object_del(jobj, "appKeys");
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* There is no easy way to delete a value from json array.
|
|
* Create a new copy without specified element and
|
|
* then remove old array.
|
|
*/
|
|
jarray_new = jarray_key_del(jarray, idx);
|
|
if (!jarray_new)
|
|
return false;
|
|
|
|
json_object_object_del(jobj, "appKeys");
|
|
json_object_object_add(jobj, "appKeys", jarray_new);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_model_binding_add(json_object *jnode, uint8_t ele_idx, bool vendor,
|
|
uint32_t mod_id, uint16_t app_idx)
|
|
{
|
|
json_object *jmodel, *jstring, *jarray;
|
|
char buf[5];
|
|
|
|
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
|
|
if (!jmodel)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jmodel, "bind", &jarray);
|
|
|
|
snprintf(buf, 5, "%4.4x", app_idx);
|
|
|
|
if (jarray && jarray_has_string(jarray, buf, 4))
|
|
return true;
|
|
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
return false;
|
|
|
|
if (!jarray) {
|
|
jarray = json_object_new_array();
|
|
if (!jarray) {
|
|
json_object_put(jstring);
|
|
return false;
|
|
}
|
|
json_object_object_add(jmodel, "bind", jarray);
|
|
}
|
|
|
|
json_object_array_add(jarray, jstring);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_model_binding_del(json_object *jnode, uint8_t ele_idx, bool vendor,
|
|
uint32_t mod_id, uint16_t app_idx)
|
|
{
|
|
json_object *jmodel, *jarray, *jarray_new;
|
|
char buf[5];
|
|
|
|
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
|
|
if (!jmodel)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jmodel, "bind", &jarray);
|
|
|
|
snprintf(buf, 5, "%4.4x", app_idx);
|
|
|
|
if (!jarray || !jarray_has_string(jarray, buf, 4))
|
|
return true;
|
|
|
|
if (json_object_array_length(jarray) == 1) {
|
|
json_object_object_del(jmodel, "bind");
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* There is no easy way to delete a value from json array.
|
|
* Create a new copy without specified element and
|
|
* then remove old array.
|
|
*/
|
|
jarray_new = jarray_string_del(jarray, buf, 4);
|
|
if (!jarray_new)
|
|
return false;
|
|
|
|
json_object_object_del(jmodel, "bind");
|
|
json_object_object_add(jmodel, "bind", jarray_new);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void free_model(void *data)
|
|
{
|
|
struct mesh_db_model *mod = data;
|
|
|
|
l_free(mod->bindings);
|
|
l_free(mod->subs);
|
|
l_free(mod->pub);
|
|
l_free(mod);
|
|
}
|
|
|
|
static void free_element(void *data)
|
|
{
|
|
struct mesh_db_element *ele = data;
|
|
|
|
l_queue_destroy(ele->models, free_model);
|
|
l_free(ele);
|
|
}
|
|
|
|
static bool parse_bindings(json_object *jbindings, struct mesh_db_model *mod)
|
|
{
|
|
int cnt;
|
|
int i;
|
|
|
|
cnt = json_object_array_length(jbindings);
|
|
if (cnt > 0xffff)
|
|
return false;
|
|
|
|
mod->num_subs = cnt;
|
|
|
|
/* Allow empty bindings list */
|
|
if (!cnt)
|
|
return true;
|
|
|
|
mod->bindings = l_new(uint16_t, cnt);
|
|
if (!mod->bindings)
|
|
return false;
|
|
|
|
for (i = 0; i < cnt; ++i) {
|
|
int idx;
|
|
json_object *jvalue;
|
|
|
|
jvalue = json_object_array_get_idx(jbindings, i);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
idx = json_object_get_int(jvalue);
|
|
if (!CHECK_KEY_IDX_RANGE(idx))
|
|
return false;
|
|
|
|
mod->bindings[i] = (uint16_t) idx;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool parse_models(json_object *jmodels, struct mesh_db_element *ele)
|
|
{
|
|
int i, num_models;
|
|
|
|
num_models = json_object_array_length(jmodels);
|
|
if (!num_models)
|
|
return true;
|
|
|
|
for (i = 0; i < num_models; ++i) {
|
|
json_object *jmodel, *jarray, *jvalue;
|
|
struct mesh_db_model *mod;
|
|
uint32_t id;
|
|
int len;
|
|
char *str;
|
|
|
|
jmodel = json_object_array_get_idx(jmodels, i);
|
|
if (!jmodel)
|
|
goto fail;
|
|
|
|
mod = l_new(struct mesh_db_model, 1);
|
|
if (!ele)
|
|
goto fail;
|
|
|
|
json_object_object_get_ex(jmodel, "modelId", &jvalue);
|
|
str = (char *)json_object_get_string(jvalue);
|
|
|
|
len = strlen(str);
|
|
|
|
if (len != 4 && len != 8)
|
|
goto fail;
|
|
|
|
if (len == 4) {
|
|
if (sscanf(str, "%04x", &id) != 1)
|
|
goto fail;
|
|
|
|
id |= VENDOR_ID_MASK;
|
|
} else if (len == 8) {
|
|
if (sscanf(str, "%08x", &id) != 1)
|
|
goto fail;
|
|
} else
|
|
goto fail;
|
|
|
|
mod->id = id;
|
|
|
|
if (len == 8)
|
|
mod->vendor = true;
|
|
|
|
json_object_object_get_ex(jmodel, "bind", &jarray);
|
|
|
|
if (jarray && (json_object_get_type(jmodels) != json_type_array
|
|
|| !parse_bindings(jarray, mod)))
|
|
goto fail;
|
|
|
|
/* TODO add pub/sub */
|
|
l_queue_push_tail(ele->models, mod);
|
|
}
|
|
|
|
return true;
|
|
|
|
fail:
|
|
l_queue_destroy(ele->models, free_model);
|
|
return false;
|
|
}
|
|
|
|
static bool parse_elements(json_object *jelements, struct mesh_db_node *node)
|
|
{
|
|
int i, num_ele;
|
|
|
|
num_ele = json_object_array_length(jelements);
|
|
if (!num_ele)
|
|
/* Allow "empty" nodes */
|
|
return true;
|
|
|
|
node->elements = l_queue_new();
|
|
if (!node->elements)
|
|
return false;
|
|
|
|
for (i = 0; i < num_ele; ++i) {
|
|
json_object *jelement;
|
|
json_object *jmodels;
|
|
json_object *jvalue;
|
|
struct mesh_db_element *ele;
|
|
int index;
|
|
char *str;
|
|
|
|
jelement = json_object_array_get_idx(jelements, i);
|
|
if (!jelement)
|
|
goto fail;
|
|
|
|
if (!get_int(jelement, "elementIndex", &index) ||
|
|
index > num_ele)
|
|
goto fail;
|
|
|
|
ele = l_new(struct mesh_db_element, 1);
|
|
if (!ele)
|
|
goto fail;
|
|
|
|
ele->index = index;
|
|
ele->models = l_queue_new();
|
|
if (!ele->models)
|
|
goto fail;
|
|
|
|
json_object_object_get_ex(jelement, "location", &jvalue);
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &(ele->location)) != 1)
|
|
goto fail;
|
|
|
|
json_object_object_get_ex(jelement, "models", &jmodels);
|
|
|
|
if (jmodels && (json_object_get_type(jmodels) != json_type_array
|
|
|| !parse_models(jmodels, ele)))
|
|
goto fail;
|
|
|
|
l_queue_push_tail(node->elements, ele);
|
|
}
|
|
|
|
return true;
|
|
|
|
fail:
|
|
l_queue_destroy(node->elements, free_element);
|
|
node->elements = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int get_mode(json_object *jvalue)
|
|
{
|
|
const char *str;
|
|
|
|
str = json_object_get_string(jvalue);
|
|
if (!str)
|
|
return 0xffffffff;
|
|
|
|
if (!strncasecmp(str, "disabled", strlen("disabled")))
|
|
return MESH_MODE_DISABLED;
|
|
|
|
if (!strncasecmp(str, "enabled", strlen("enabled")))
|
|
return MESH_MODE_ENABLED;
|
|
|
|
if (!strncasecmp(str, "unsupported", strlen("unsupported")))
|
|
return MESH_MODE_UNSUPPORTED;
|
|
|
|
return 0xffffffff;
|
|
}
|
|
|
|
static void parse_features(json_object *jconfig, struct mesh_db_node *node)
|
|
{
|
|
json_object *jvalue, *jrelay;
|
|
int mode, count;
|
|
uint16_t interval;
|
|
|
|
json_object_object_get_ex(jconfig, "proxy", &jvalue);
|
|
if (jvalue) {
|
|
mode = get_mode(jvalue);
|
|
if (mode <= MESH_MODE_UNSUPPORTED)
|
|
node->modes.proxy = mode;
|
|
}
|
|
|
|
json_object_object_get_ex(jconfig, "friend", &jvalue);
|
|
if (jvalue) {
|
|
mode = get_mode(jvalue);
|
|
if (mode <= MESH_MODE_UNSUPPORTED)
|
|
node->modes.friend = mode;
|
|
}
|
|
|
|
json_object_object_get_ex(jconfig, "lowPower", &jvalue);
|
|
if (jvalue) {
|
|
mode = get_mode(jvalue);
|
|
if (mode <= MESH_MODE_UNSUPPORTED)
|
|
node->modes.friend = mode;
|
|
}
|
|
|
|
json_object_object_get_ex(jconfig, "beacon", &jvalue);
|
|
if (jvalue) {
|
|
mode = get_mode(jvalue);
|
|
if (mode <= MESH_MODE_ENABLED)
|
|
node->modes.beacon = mode;
|
|
}
|
|
|
|
json_object_object_get_ex(jconfig, "relay", &jrelay);
|
|
if (!jrelay)
|
|
return;
|
|
|
|
json_object_object_get_ex(jrelay, "mode", &jvalue);
|
|
if (jvalue) {
|
|
mode = get_mode(jvalue);
|
|
if (mode <= MESH_MODE_UNSUPPORTED)
|
|
node->modes.relay.state = mode;
|
|
else
|
|
return;
|
|
} else
|
|
return;
|
|
|
|
json_object_object_get_ex(jrelay, "count", &jvalue);
|
|
if (!jvalue)
|
|
return;
|
|
|
|
/* TODO: check range */
|
|
count = json_object_get_int(jvalue);
|
|
node->modes.relay.cnt = count;
|
|
|
|
json_object_object_get_ex(jrelay, "interval", &jvalue);
|
|
if (!jvalue)
|
|
return;
|
|
|
|
/* TODO: check range */
|
|
interval = json_object_get_int(jvalue);
|
|
node->modes.relay.interval = interval;
|
|
}
|
|
|
|
static bool parse_composition(json_object *jcomp, struct mesh_db_node *node)
|
|
{
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
/* All the fields in node composition are mandatory */
|
|
json_object_object_get_ex(jcomp, "cid", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &node->cid) != 1)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jcomp, "pid", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &node->pid) != 1)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jcomp, "vid", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &node->vid) != 1)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jcomp, "crpl", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &node->crpl) != 1)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint16_t get_prov_flags(json_object *jarray, uint16_t max_value)
|
|
{
|
|
int i, cnt;
|
|
uint16_t result = 0;
|
|
|
|
cnt = json_object_array_length(jarray);
|
|
if (!cnt)
|
|
return 0;
|
|
|
|
for (i = 0; i < cnt; ++i) {
|
|
json_object *jvalue;
|
|
int value;
|
|
|
|
jvalue = json_object_array_get_idx(jarray, i);
|
|
value = json_object_get_int(jvalue);
|
|
if (value > 16)
|
|
continue;
|
|
|
|
if ((1 << value) > max_value)
|
|
continue;
|
|
|
|
result |= (1 << value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool mesh_db_read_node(json_object *jnode, mesh_db_node_cb cb, void *user_data)
|
|
{
|
|
struct mesh_db_node node;
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
if (!cb) {
|
|
l_info("Node read callback is required");
|
|
return false;
|
|
}
|
|
|
|
memset(&node, 0, sizeof(node));
|
|
|
|
if (!parse_composition(jnode, &node)) {
|
|
l_info("Failed to parse local node composition");
|
|
return false;
|
|
}
|
|
|
|
parse_features(jnode, &node);
|
|
|
|
json_object_object_get_ex(jnode, "unicastAddress", &jvalue);
|
|
if (!jvalue) {
|
|
l_info("Bad config: Unicast address must be present");
|
|
return false;
|
|
}
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (sscanf(str, "%04hx", &node.unicast) != 1)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jnode, "defaultTTL", &jvalue);
|
|
if (jvalue) {
|
|
int ttl = json_object_get_int(jvalue);
|
|
|
|
if (ttl < 0 || ttl == 1 || ttl > DEFAULT_TTL)
|
|
return false;
|
|
node.ttl = (uint8_t) ttl;
|
|
}
|
|
|
|
json_object_object_get_ex(jnode, "sequenceNumber", &jvalue);
|
|
if (jvalue)
|
|
node.seq_number = json_object_get_int(jvalue);
|
|
|
|
json_object_object_get_ex(jnode, "elements", &jvalue);
|
|
if (jvalue && json_object_get_type(jvalue) == json_type_array) {
|
|
if (!parse_elements(jvalue, &node))
|
|
return false;
|
|
}
|
|
|
|
return cb(&node, user_data);
|
|
}
|
|
|
|
bool mesh_db_read_unprovisioned_device(json_object *jnode, mesh_db_node_cb cb,
|
|
void *user_data)
|
|
{
|
|
struct mesh_db_node node;
|
|
json_object *jvalue;
|
|
char *str;
|
|
|
|
if (!cb) {
|
|
l_info("Device read callback is required");
|
|
return false;
|
|
}
|
|
|
|
memset(&node, 0, sizeof(node));
|
|
|
|
if (!parse_composition(jnode, &node)) {
|
|
l_info("Failed to parse local device composition");
|
|
return false;
|
|
}
|
|
|
|
parse_features(jnode, &node);
|
|
|
|
json_object_object_get_ex(jnode, "elements", &jvalue);
|
|
if (jvalue && json_object_get_type(jvalue) == json_type_array) {
|
|
if (!parse_elements(jvalue, &node))
|
|
return false;
|
|
}
|
|
|
|
json_object_object_get_ex(jnode, "UUID", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), node.uuid, 16))
|
|
return false;
|
|
|
|
return cb(&node, user_data);
|
|
}
|
|
|
|
bool mesh_db_read_prov_info(json_object *jnode, struct mesh_db_prov *prov)
|
|
{
|
|
json_object *jprov, *jarray, *jvalue, *jobj;
|
|
int value;
|
|
char *str;
|
|
|
|
if (!prov)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jnode, "provision", &jprov);
|
|
if (!jprov)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jprov, "algorithms", &jarray);
|
|
if (!jarray || json_object_get_type(jarray) != json_type_array)
|
|
return false;
|
|
|
|
prov->algorithm = get_prov_flags(jarray, ALG_FIPS_256_ECC);
|
|
if (!prov->algorithm) {
|
|
l_info("At least one algorithm must be indicated");
|
|
return false;
|
|
}
|
|
|
|
json_object_object_get_ex(jprov, "outputOOB", &jobj);
|
|
json_object_object_get_ex(jobj, "size", &jvalue);
|
|
value = json_object_get_int(jvalue);
|
|
if (value > 8)
|
|
return false;
|
|
|
|
prov->output_oob.size = (uint8_t) value;
|
|
json_object_object_get_ex(jobj, "actions", &jarray);
|
|
if (!jarray || json_object_get_type(jarray) != json_type_array)
|
|
return false;
|
|
|
|
prov->output_oob.actions = get_prov_flags(jarray, OOB_OUT_ALPHA);
|
|
|
|
json_object_object_get_ex(jprov, "inputOOB", &jobj);
|
|
json_object_object_get_ex(jobj, "size", &jvalue);
|
|
value = json_object_get_int(jvalue);
|
|
if (value > 8)
|
|
return false;
|
|
|
|
prov->input_oob.size = (uint8_t) value;
|
|
json_object_object_get_ex(jobj, "actions", &jarray);
|
|
if (!jarray || json_object_get_type(jarray) != json_type_array)
|
|
return false;
|
|
|
|
prov->input_oob.actions = get_prov_flags(jarray, OOB_IN_ALPHA);
|
|
|
|
json_object_object_get_ex(jprov, "publicType", &jvalue);
|
|
prov->pub_type = (json_object_get_boolean(jvalue)) ? 1 : 0;
|
|
|
|
json_object_object_get_ex(jprov, "staticType", &jvalue);
|
|
prov->static_type = (json_object_get_boolean(jvalue)) ? 1 : 0;
|
|
|
|
json_object_object_get_ex(jprov, "privateKey", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
str = (char *)json_object_get_string(jvalue);
|
|
if (!str2hex(str, strlen(str), prov->priv_key, 32))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_uint16_hex(json_object *jobj, const char *desc,
|
|
uint16_t value)
|
|
{
|
|
json_object *jstring;
|
|
char buf[5];
|
|
|
|
snprintf(buf, 5, "%4.4x", value);
|
|
jstring = json_object_new_string(buf);
|
|
if (!jstring)
|
|
return false;
|
|
|
|
json_object_object_add(jobj, desc, jstring);
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_int(json_object *jobj, const char *keyword, int value)
|
|
{
|
|
json_object *jvalue;
|
|
|
|
json_object_object_del(jobj, keyword);
|
|
|
|
jvalue = json_object_new_int(value);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
json_object_object_add(jobj, keyword, jvalue);
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_bool(json_object *jobj, const char *keyword, bool value)
|
|
{
|
|
json_object *jvalue;
|
|
|
|
json_object_object_del(jobj, keyword);
|
|
|
|
jvalue = json_object_new_boolean(value);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
json_object_object_add(jobj, keyword, jvalue);
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_mode(json_object *jobj, const char *keyword, int value)
|
|
{
|
|
json_object *jstring;
|
|
|
|
switch (value) {
|
|
case MESH_MODE_DISABLED:
|
|
jstring = json_object_new_string("disabled");
|
|
break;
|
|
case MESH_MODE_ENABLED:
|
|
jstring = json_object_new_string("enabled");
|
|
break;
|
|
case MESH_MODE_UNSUPPORTED:
|
|
jstring = json_object_new_string("unsupported");
|
|
break;
|
|
default:
|
|
return false;
|
|
};
|
|
|
|
if (!jstring)
|
|
return false;
|
|
|
|
json_object_object_add(jobj, keyword, jstring);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_relay_mode(json_object *jnode, uint8_t mode, uint8_t count,
|
|
uint16_t interval)
|
|
{
|
|
json_object *jrelay;
|
|
|
|
json_object_object_del(jnode, "relay");
|
|
|
|
jrelay = json_object_new_object();
|
|
if (jrelay)
|
|
return false;
|
|
|
|
if (!mesh_db_write_mode(jrelay, "mode", mode))
|
|
goto fail;
|
|
|
|
if (!mesh_db_write_int(jrelay, "count", count))
|
|
goto fail;
|
|
|
|
if (!mesh_db_write_int(jrelay, "interval", interval))
|
|
goto fail;
|
|
|
|
json_object_object_add(jnode, "relay", jrelay);
|
|
|
|
return true;
|
|
fail:
|
|
json_object_put(jrelay);
|
|
return false;
|
|
}
|
|
|
|
bool mesh_db_read_net_transmit(json_object *jobj, uint8_t *cnt,
|
|
uint16_t *interval)
|
|
{
|
|
json_object *jretransmit, *jvalue;
|
|
|
|
json_object_object_get_ex(jobj, "retransmit", &jretransmit);
|
|
if (!jretransmit)
|
|
return false;
|
|
|
|
json_object_object_get_ex(jretransmit, "count", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
*cnt = (uint8_t) json_object_get_int(jvalue);
|
|
|
|
json_object_object_get_ex(jretransmit, "interval", &jvalue);
|
|
if (!jvalue)
|
|
return false;
|
|
|
|
*interval = (uint16_t) json_object_get_int(jvalue);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mesh_db_write_net_transmit(json_object *jobj, uint8_t cnt,
|
|
uint16_t interval)
|
|
{
|
|
json_object *jretransmit;
|
|
|
|
json_object_object_del(jobj, "retransmit");
|
|
|
|
jretransmit = json_object_new_object();
|
|
if (jretransmit)
|
|
return false;
|
|
|
|
if (!mesh_db_write_int(jretransmit, "count", cnt))
|
|
goto fail;
|
|
|
|
if (!mesh_db_write_int(jretransmit, "interval", interval))
|
|
goto fail;
|
|
|
|
json_object_object_add(jobj, "retransmit", jretransmit);
|
|
|
|
return true;
|
|
fail:
|
|
json_object_put(jretransmit);
|
|
return false;
|
|
|
|
}
|
|
|
|
bool mesh_db_write_iv_index(json_object *jobj, uint32_t idx, bool update)
|
|
{
|
|
int tmp = update ? 1 : 0;
|
|
|
|
if (!mesh_db_write_int(jobj, "IVindex", idx))
|
|
return false;
|
|
|
|
if (!mesh_db_write_int(jobj, "IVupdate", tmp))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void mesh_db_remove_property(json_object *jobj, const char *desc)
|
|
{
|
|
json_object_object_del(jobj, desc);
|
|
}
|