blob: 30c90170da791feab115a1a3e66c6cc43a2d18bd [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2018-2019 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
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <ell/ell.h>
#include <json-c/json.h>
#include "mesh/mesh-defs.h"
#include "mesh/util.h"
#include "mesh/mesh-config.h"
/* To prevent local node JSON cache thrashing, minimum update times */
#define MIN_SEQ_CACHE_TRIGGER 32
#define MIN_SEQ_CACHE_VALUE (2 * 32)
#define MIN_SEQ_CACHE_TIME (5 * 60)
#define CHECK_KEY_IDX_RANGE(x) ((x) <= 4095)
struct mesh_config {
json_object *jnode;
char *node_dir_path;
uint8_t uuid[16];
uint32_t write_seq;
struct timeval write_time;
struct l_queue *idles;
};
struct write_info {
struct mesh_config *cfg;
void *user_data;
mesh_config_status_func_t cb;
};
static const char *cfgnode_name = "/node.json";
static const char *bak_ext = ".bak";
static const char *tmp_ext = ".tmp";
static bool save_config(json_object *jnode, const char *fname)
{
FILE *outfile;
const char *str;
bool result = false;
outfile = fopen(fname, "w");
if (!outfile) {
l_error("Failed to save configuration to %s", fname);
return false;
}
str = json_object_to_json_string_ext(jnode, JSON_C_TO_STRING_PRETTY);
if (fwrite(str, sizeof(char), strlen(str), outfile) < strlen(str))
l_warn("Incomplete write of mesh configuration");
else
result = true;
fclose(outfile);
return result;
}
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_u64_value(json_object *jobj, const char *desc,
const uint8_t u64[8])
{
json_object *jstring;
char hexstr[17];
hex2str((uint8_t *) u64, 8, hexstr, 17);
jstring = json_object_new_string(hexstr);
if (!jstring)
return false;
json_object_object_del(jobj, desc);
json_object_object_add(jobj, desc, jstring);
return true;
}
static bool add_key_value(json_object *jobj, 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_del(jobj, desc);
json_object_object_add(jobj, desc, jstring);
return true;
}
static int get_element_index(json_object *jnode, uint16_t ele_addr)
{
json_object *jvalue, *jelements;
uint16_t addr, num_ele;
char *str;
if (!json_object_object_get_ex(jnode, "unicastAddress", &jvalue))
return -1;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &addr) != 1)
return -1;
if (!json_object_object_get_ex(jnode, "elements", &jelements))
return -1;
num_ele = json_object_array_length(jelements);
if (ele_addr >= addr + num_ele || ele_addr < addr)
return -1;
return ele_addr - addr;
}
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 (!json_object_object_get_ex(jnode, "elements", &jelements))
return NULL;
jelement = json_object_array_get_idx(jelements, ele_idx);
if (!jelement)
return NULL;
if (!json_object_object_get_ex(jelement, "models", &jmodels))
return NULL;
num_mods = json_object_array_length(jmodels);
if (!num_mods)
return NULL;
if (!vendor) {
snprintf(buf, 5, "%4.4x", (uint16_t)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);
if (!json_object_object_get_ex(jmodel, "modelId", &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 void jarray_string_del(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 && !strncmp(str, str_entry, len)) {
json_object_array_del_idx(jarray, i, 1);
return;
}
}
}
static bool get_key_index(json_object *jobj, const char *keyword,
uint16_t *index)
{
int idx;
if (!get_int(jobj, keyword, &idx))
return false;
if (!CHECK_KEY_IDX_RANGE(idx))
return false;
*index = (uint16_t) idx;
return true;
}
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;
uint16_t jidx;
jentry = json_object_array_get_idx(jarray, i);
if (!get_key_index(jentry, "index", &jidx))
return NULL;
if (jidx == idx)
return jentry;
}
return NULL;
}
static void jarray_key_del(json_object *jarray, int16_t idx)
{
int i, sz = json_object_array_length(jarray);
for (i = 0; i < sz; ++i) {
json_object *jentry;
uint16_t nidx;
jentry = json_object_array_get_idx(jarray, i);
if (get_key_index(jentry, "index", &nidx) && nidx == idx) {
json_object_array_del_idx(jarray, i, 1);
return;
}
}
}
static bool read_unicast_address(json_object *jobj, uint16_t *unicast)
{
json_object *jvalue;
char *str;
if (!json_object_object_get_ex(jobj, "unicastAddress", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", unicast) != 1)
return false;
return true;
}
static bool read_default_ttl(json_object *jobj, uint8_t *ttl)
{
json_object *jvalue;
int val;
/* defaultTTL is optional */
if (!json_object_object_get_ex(jobj, "defaultTTL", &jvalue))
return true;
val = json_object_get_int(jvalue);
if (!val && errno == EINVAL)
return false;
if (val < 0 || val == 1 || val > TTL_MASK)
return false;
*ttl = (uint8_t) val;
return true;
}
static bool read_seq_number(json_object *jobj, uint32_t *seq_number)
{
json_object *jvalue;
int val;
/* sequenceNumber is optional */
if (!json_object_object_get_ex(jobj, "sequenceNumber", &jvalue))
return true;
val = json_object_get_int(jvalue);
if (!val && errno == EINVAL)
return false;
if (val < 0 || val > SEQ_MASK + 1)
return false;
*seq_number = (uint32_t) val;
return true;
}
static bool 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;
}
static bool read_token(json_object *jobj, uint8_t token[8])
{
json_object *jvalue;
char *str;
if (!token)
return false;
if (!json_object_object_get_ex(jobj, "token", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (!str2hex(str, strlen(str), token, 8))
return false;
return true;
}
static bool 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))
return false;
str = (char *)json_object_get_string(jvalue);
if (!str2hex(str, strlen(str), key_buf, 16))
return false;
return true;
}
static bool read_comp_pages(json_object *jobj, struct mesh_config_node *node)
{
json_object *jarray, *jentry;
struct mesh_config_comp_page *page;
int len;
int i;
if (!json_object_object_get_ex(jobj, "pages", &jarray))
return true;
if (json_object_get_type(jarray) != json_type_array)
return false;
len = json_object_array_length(jarray);
for (i = 0; i < len; i++) {
size_t clen;
char *str;
jentry = json_object_array_get_idx(jarray, i);
str = (char *)json_object_get_string(jentry);
clen = strlen(str);
if (clen < ((MIN_COMP_SIZE * 2) + 1))
continue;
clen = (clen / 2) - 1;
page = l_malloc(sizeof(struct mesh_config_comp_page) + clen);
if (!str2hex(str + 2, clen * 2, page->data, clen))
goto parse_fail;
if (sscanf(str, "%02hhx", &page->page_num) != 1)
goto parse_fail;
page->len = clen;
l_queue_push_tail(node->pages, page);
}
return true;
parse_fail:
l_free(page);
return false;
}
static bool read_app_keys(json_object *jobj, struct mesh_config_node *node)
{
json_object *jarray;
int len;
int i;
if (!json_object_object_get_ex(jobj, "appKeys", &jarray))
return true;
if (json_object_get_type(jarray) != json_type_array)
return false;
/* Allow empty AppKey array */
len = json_object_array_length(jarray);
if (!len)
return true;
for (i = 0; i < len; ++i) {
json_object *jtemp, *jvalue;
char *str;
struct mesh_config_appkey *appkey;
appkey = l_new(struct mesh_config_appkey, 1);
jtemp = json_object_array_get_idx(jarray, i);
if (!get_key_index(jtemp, "index", &appkey->app_idx))
goto fail;
if (!get_key_index(jtemp, "boundNetKey", &appkey->net_idx))
goto fail;
if (!json_object_object_get_ex(jtemp, "key", &jvalue))
goto fail;
str = (char *)json_object_get_string(jvalue);
if (!str2hex(str, strlen(str), appkey->new_key, 16))
goto fail;
if (json_object_object_get_ex(jtemp, "oldKey", &jvalue))
str = (char *)json_object_get_string(jvalue);
if (!str2hex(str, strlen(str), appkey->key, 16))
goto fail;
l_queue_push_tail(node->appkeys, appkey);
}
return true;
fail:
l_queue_destroy(node->appkeys, l_free);
node->appkeys = NULL;
return false;
}
static bool read_net_keys(json_object *jobj, struct mesh_config_node *node)
{
json_object *jarray;
int len;
int i;
/* At least one NetKey must be present for a provisioned node */
if (!json_object_object_get_ex(jobj, "netKeys", &jarray))
return false;
if (json_object_get_type(jarray) != json_type_array)
return false;
len = json_object_array_length(jarray);
if (!len)
return false;
for (i = 0; i < len; ++i) {
json_object *jtemp, *jvalue;
char *str;
struct mesh_config_netkey *netkey;
netkey = l_new(struct mesh_config_netkey, 1);
jtemp = json_object_array_get_idx(jarray, i);
if (!get_key_index(jtemp, "index", &netkey->idx))
goto fail;
if (!json_object_object_get_ex(jtemp, "key", &jvalue))
goto fail;
str = (char *)json_object_get_string(jvalue);
if (!str2hex(str, strlen(str), netkey->new_key, 16))
goto fail;
if (!json_object_object_get_ex(jtemp, "keyRefresh", &jvalue))
netkey->phase = KEY_REFRESH_PHASE_NONE;
else
netkey->phase = (uint8_t) json_object_get_int(jvalue);
if (netkey->phase > KEY_REFRESH_PHASE_TWO)
goto fail;
if (json_object_object_get_ex(jtemp, "oldKey", &jvalue)) {
if (netkey->phase == KEY_REFRESH_PHASE_NONE)
goto fail;
str = (char *)json_object_get_string(jvalue);
}
if (!str2hex(str, strlen(str), netkey->key, 16))
goto fail;
l_queue_push_tail(node->netkeys, netkey);
}
return true;
fail:
l_queue_destroy(node->netkeys, l_free);
node->netkeys = NULL;
return false;
}
static bool write_int(json_object *jobj, const char *desc, int val)
{
json_object *jvalue;
jvalue = json_object_new_int(val);
if (!jvalue)
return false;
json_object_object_del(jobj, desc);
json_object_object_add(jobj, desc, jvalue);
return true;
}
bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t idx,
const uint8_t key[16])
{
json_object *jnode, *jarray = NULL, *jentry = NULL;
if (!cfg)
return false;
jnode = cfg->jnode;
l_debug("netKey %4.4x", idx);
json_object_object_get_ex(jnode, "netKeys", &jarray);
if (jarray)
jentry = get_key_object(jarray, idx);
/* Do not allow direct overwrite */
if (jentry)
return false;
jentry = json_object_new_object();
if (!jentry)
return false;
if (!write_int(jentry, "index", idx))
goto fail;
if (!add_key_value(jentry, "key", key))
goto fail;
json_object_object_add(jentry, "keyRefresh",
json_object_new_int(KEY_REFRESH_PHASE_NONE));
if (!jarray) {
jarray = json_object_new_array();
if (!jarray)
goto fail;
json_object_object_add(jnode, "netKeys", jarray);
}
json_object_array_add(jarray, jentry);
return save_config(jnode, cfg->node_dir_path);
fail:
if (jentry)
json_object_put(jentry);
return false;
}
bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx,
const uint8_t key[16])
{
json_object *jnode, *jarray, *jentry, *jstring;
const char *str;
if (!cfg)
return false;
jnode = cfg->jnode;
if (!json_object_object_get_ex(jnode, "netKeys", &jarray))
return false;
jentry = get_key_object(jarray, idx);
/* Net key must be already recorded */
if (!jentry)
return false;
if (!json_object_object_get_ex(jentry, "key", &jstring))
return false;
str = json_object_get_string(jstring);
jstring = json_object_new_string(str);
json_object_object_add(jentry, "oldKey", jstring);
json_object_object_del(jentry, "key");
if (!add_key_value(jentry, "key", key))
return false;
json_object_object_add(jentry, "keyRefresh",
json_object_new_int(KEY_REFRESH_PHASE_ONE));
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t idx)
{
json_object *jnode, *jarray;
if (!cfg)
return false;
jnode = cfg->jnode;
if (!json_object_object_get_ex(jnode, "netKeys", &jarray))
return true;
jarray_key_del(jarray, idx);
if (!json_object_array_length(jarray))
json_object_object_del(jnode, "netKeys");
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key)
{
if (!cfg || !add_key_value(cfg->jnode, "deviceKey", key))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_write_token(struct mesh_config *cfg, uint8_t *token)
{
if (!cfg || !add_u64_value(cfg->jnode, "token", token))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx,
uint16_t app_idx, const uint8_t key[16])
{
json_object *jnode, *jarray = NULL, *jentry = NULL;
if (!cfg)
return false;
jnode = cfg->jnode;
json_object_object_get_ex(jnode, "appKeys", &jarray);
if (jarray)
jentry = get_key_object(jarray, app_idx);
/* Do not allow direct overrwrite */
if (jentry)
return false;
jentry = json_object_new_object();
if (!jentry)
return false;
if (!write_int(jentry, "index", app_idx))
goto fail;
if (!write_int(jentry, "boundNetKey", net_idx))
goto fail;
if (!add_key_value(jentry, "key", key))
goto fail;
if (!jarray) {
jarray = json_object_new_array();
if (!jarray)
goto fail;
json_object_object_add(jnode, "appKeys", jarray);
}
json_object_array_add(jarray, jentry);
return save_config(jnode, cfg->node_dir_path);
fail:
if (jentry)
json_object_put(jentry);
return false;
}
bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx,
const uint8_t key[16])
{
json_object *jnode, *jarray, *jentry = NULL, *jstring = NULL;
const char *str;
if (!cfg)
return false;
jnode = cfg->jnode;
if (!json_object_object_get_ex(jnode, "appKeys", &jarray))
return false;
/* The key entry should exist if the key is updated */
jentry = get_key_object(jarray, app_idx);
if (!jentry)
return false;
if (!json_object_object_get_ex(jentry, "key", &jstring))
return false;
str = json_object_get_string(jstring);
jstring = json_object_new_string(str);
json_object_object_add(jentry, "oldKey", jstring);
json_object_object_del(jentry, "key");
/* TODO: "Rewind" if add_key_value fails */
if (!add_key_value(jentry, "key", key))
return false;
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx,
uint16_t idx)
{
json_object *jnode, *jarray;
if (!cfg)
return false;
jnode = cfg->jnode;
if (!json_object_object_get_ex(jnode, "appKeys", &jarray))
return true;
jarray_key_del(jarray, idx);
if (!json_object_array_length(jarray))
json_object_object_del(jnode, "appKeys");
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
uint16_t app_idx)
{
json_object *jnode, *jmodel, *jstring, *jarray = NULL;
int ele_idx;
char buf[5];
if (!cfg)
return false;
jnode = cfg->jnode;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
snprintf(buf, 5, "%4.4x", app_idx);
json_object_object_get_ex(jmodel, "bind", &jarray);
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 save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
uint16_t app_idx)
{
json_object *jnode, *jmodel, *jarray;
int ele_idx;
char buf[5];
if (!cfg)
return false;
jnode = cfg->jnode;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
if (!json_object_object_get_ex(jmodel, "bind", &jarray))
return true;
snprintf(buf, 5, "%4.4x", app_idx);
jarray_string_del(jarray, buf, 4);
if (!json_object_array_length(jarray))
json_object_object_del(jmodel, "bind");
return save_config(jnode, cfg->node_dir_path);
}
static void free_model(void *data)
{
struct mesh_config_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_config_element *ele = data;
l_queue_destroy(ele->models, free_model);
l_free(ele);
}
static bool parse_bindings(json_object *jarray, struct mesh_config_model *mod)
{
int cnt;
int i;
cnt = json_object_array_length(jarray);
if (cnt > 0xffff)
return false;
mod->num_bindings = cnt;
/* Allow empty bindings list */
if (!cnt)
return true;
mod->bindings = l_new(uint16_t, cnt);
for (i = 0; i < cnt; ++i) {
uint16_t idx;
const char *str;
json_object *jvalue;
jvalue = json_object_array_get_idx(jarray, i);
if (!jvalue)
return false;
str = json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &idx) != 1)
return false;
if (!CHECK_KEY_IDX_RANGE(idx))
return false;
mod->bindings[i] = (uint16_t) idx;
}
return true;
}
static struct mesh_config_pub *parse_model_publication(json_object *jpub)
{
json_object *jvalue;
struct mesh_config_pub *pub;
int len, value;
char *str;
if (!json_object_object_get_ex(jpub, "address", &jvalue))
return NULL;
str = (char *)json_object_get_string(jvalue);
len = strlen(str);
pub = l_new(struct mesh_config_pub, 1);
switch (len) {
case 4:
if (sscanf(str, "%04hx", &pub->addr) != 1)
goto fail;
break;
case 32:
if (!str2hex(str, len, pub->virt_addr, 16))
goto fail;
pub->virt = true;
break;
default:
goto fail;
}
if (!get_key_index(jpub, "index", &pub->idx))
goto fail;
if (!get_int(jpub, "ttl", &value))
goto fail;
pub->ttl = (uint8_t) value;
if (!get_int(jpub, "period", &value))
goto fail;
pub->period = value;
if (!get_int(jpub, "credentials", &value))
goto fail;
pub->credential = (uint8_t) value;
if (!json_object_object_get_ex(jpub, "retransmit", &jvalue))
goto fail;
if (!get_int(jvalue, "count", &value))
goto fail;
pub->cnt = (uint8_t) value;
if (!get_int(jvalue, "interval", &value))
goto fail;
pub->interval = (uint8_t) value;
return pub;
fail:
l_free(pub);
return NULL;
}
static bool parse_model_subscriptions(json_object *jsubs,
struct mesh_config_model *mod)
{
struct mesh_config_sub *subs;
int i, cnt;
if (json_object_get_type(jsubs) != json_type_array)
return NULL;
cnt = json_object_array_length(jsubs);
/* Allow empty array */
if (!cnt)
return true;
subs = l_new(struct mesh_config_sub, cnt);
for (i = 0; i < cnt; ++i) {
char *str;
int len;
json_object *jvalue;
jvalue = json_object_array_get_idx(jsubs, i);
if (!jvalue)
return false;
str = (char *)json_object_get_string(jvalue);
len = strlen(str);
switch (len) {
case 4:
if (sscanf(str, "%04hx", &subs[i].addr.grp) != 1)
goto fail;
break;
case 32:
if (!str2hex(str, len, subs[i].addr.label, 16))
goto fail;
subs[i].virt = true;
break;
default:
goto fail;
}
}
mod->num_subs = cnt;
mod->subs = subs;
return true;
fail:
l_free(subs);
return false;
}
static bool parse_models(json_object *jmodels, struct mesh_config_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_config_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_config_model, 1);
if (!json_object_object_get_ex(jmodel, "modelId", &jvalue))
goto fail;
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;
} else if (len == 8) {
if (sscanf(str, "%08x", &id) != 1)
goto fail;
mod->vendor = true;
} else
goto fail;
mod->id = id;
if (json_object_object_get_ex(jmodel, "bind", &jarray)) {
if (json_object_get_type(jarray) != json_type_array ||
!parse_bindings(jarray, mod))
goto fail;
}
if (json_object_object_get_ex(jmodel, "pubEnabled", &jvalue))
mod->pub_enabled = json_object_get_boolean(jvalue);
else
mod->pub_enabled = true;
if (json_object_object_get_ex(jmodel, "subEnabled", &jvalue))
mod->sub_enabled = json_object_get_boolean(jvalue);
else
mod->sub_enabled = true;
if (json_object_object_get_ex(jmodel, "publish", &jvalue)) {
mod->pub = parse_model_publication(jvalue);
if (!mod->pub)
goto fail;
}
if (json_object_object_get_ex(jmodel, "subscribe", &jarray)) {
if (!parse_model_subscriptions(jarray, mod))
goto fail;
}
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 *jelems, struct mesh_config_node *node)
{
int i, num_ele;
if (json_object_get_type(jelems) != json_type_array)
return false;
num_ele = json_object_array_length(jelems);
if (!num_ele)
/* Allow "empty" nodes */
return true;
for (i = 0; i < num_ele; ++i) {
json_object *jelement;
json_object *jmodels;
json_object *jvalue;
struct mesh_config_element *ele;
int index;
char *str;
jelement = json_object_array_get_idx(jelems, i);
if (!jelement)
goto fail;
if (!get_int(jelement, "elementIndex", &index) ||
index > num_ele)
goto fail;
ele = l_new(struct mesh_config_element, 1);
ele->index = index;
ele->models = l_queue_new();
l_queue_push_tail(node->elements, ele);
if (!json_object_object_get_ex(jelement, "location", &jvalue))
goto fail;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &(ele->location)) != 1)
goto fail;
if (json_object_object_get_ex(jelement, "models", &jmodels)) {
if (json_object_get_type(jmodels) != json_type_array ||
!parse_models(jmodels, ele))
goto fail;
}
}
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_config_node *node)
{
json_object *jvalue, *jrelay;
int mode, count;
uint16_t interval;
if (json_object_object_get_ex(jconfig, "proxy", &jvalue)) {
mode = get_mode(jvalue);
if (mode <= MESH_MODE_UNSUPPORTED)
node->modes.proxy = mode;
}
if (json_object_object_get_ex(jconfig, "friend", &jvalue)) {
mode = get_mode(jvalue);
if (mode <= MESH_MODE_UNSUPPORTED)
node->modes.friend = mode;
}
if (json_object_object_get_ex(jconfig, "lowPower", &jvalue)) {
mode = get_mode(jvalue);
if (mode <= MESH_MODE_UNSUPPORTED)
node->modes.lpn = mode;
}
if (json_object_object_get_ex(jconfig, "beacon", &jvalue)) {
mode = get_mode(jvalue);
if (mode <= MESH_MODE_UNSUPPORTED)
node->modes.beacon = mode;
}
if (!json_object_object_get_ex(jconfig, "relay", &jrelay))
return;
if (json_object_object_get_ex(jrelay, "mode", &jvalue)) {
mode = get_mode(jvalue);
if (mode <= MESH_MODE_UNSUPPORTED)
node->modes.relay.state = mode;
else
return;
} else
return;
if (!json_object_object_get_ex(jrelay, "count", &jvalue))
return;
/* TODO: check range */
count = json_object_get_int(jvalue);
node->modes.relay.cnt = count;
if (!json_object_object_get_ex(jrelay, "interval", &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_config_node *node)
{
json_object *jvalue;
char *str;
/* All the fields in node composition are mandatory */
if (!json_object_object_get_ex(jcomp, "cid", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &node->cid) != 1)
return false;
if (!json_object_object_get_ex(jcomp, "pid", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &node->pid) != 1)
return false;
if (!json_object_object_get_ex(jcomp, "vid", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &node->vid) != 1)
return false;
if (!json_object_object_get_ex(jcomp, "crpl", &jvalue))
return false;
str = (char *)json_object_get_string(jvalue);
if (sscanf(str, "%04hx", &node->crpl) != 1)
return false;
return true;
}
static bool read_net_transmit(json_object *jobj, struct mesh_config_node *node)
{
json_object *jrtx, *jvalue;
uint16_t interval;
uint8_t cnt;
if (!json_object_object_get_ex(jobj, "retransmit", &jrtx))
return true;
if (!json_object_object_get_ex(jrtx, "count", &jvalue))
return false;
/* TODO: add range checking */
cnt = (uint8_t) json_object_get_int(jvalue);
if (!json_object_object_get_ex(jrtx, "interval", &jvalue))
return false;
interval = (uint16_t) json_object_get_int(jvalue);
node->net_transmit = l_new(struct mesh_config_transmit, 1);
node->net_transmit->count = cnt;
node->net_transmit->interval = interval;
return true;
}
static bool read_node(json_object *jnode, struct mesh_config_node *node)
{
json_object *jvalue;
if (!read_iv_index(jnode, &node->iv_index, &node->iv_update)) {
l_info("Failed to read IV index");
return false;
}
if (!read_token(jnode, node->token)) {
l_info("Failed to read node token");
return false;
}
if (!read_device_key(jnode, node->dev_key)) {
l_info("Failed to read node device key");
return false;
}
if (!parse_composition(jnode, node)) {
l_info("Failed to parse local node composition");
return false;
}
parse_features(jnode, node);
if (!read_unicast_address(jnode, &node->unicast)) {
l_info("Failed to parse unicast address");
return false;
}
if (!read_default_ttl(jnode, &node->ttl)) {
l_info("Failed to parse default ttl");
return false;
}
if (!read_seq_number(jnode, &node->seq_number)) {
l_info("Failed to parse sequence number");
return false;
}
/* Check for required "elements" property */
if (!json_object_object_get_ex(jnode, "elements", &jvalue))
return false;
if (!read_net_transmit(jnode, node)) {
l_info("Failed to read node net transmit parameters");
return false;
}
if (!read_net_keys(jnode, node)) {
l_info("Failed to read net keys");
return false;
}
if (!read_app_keys(jnode, node)) {
l_info("Failed to read app keys");
return false;
}
if (!read_comp_pages(jnode, node)) {
l_info("Failed to read Composition Pages");
return false;
}
if (!parse_elements(jvalue, node)) {
l_info("Failed to parse elements");
return false;
}
return true;
}
static bool 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_del(jobj, desc);
json_object_object_add(jobj, desc, jstring);
return true;
}
static bool write_uint32_hex(json_object *jobj, const char *desc, uint32_t val)
{
json_object *jstring;
char buf[9];
snprintf(buf, 9, "%8.8x", val);
jstring = json_object_new_string(buf);
if (!jstring)
return false;
json_object_object_del(jobj, desc);
json_object_object_add(jobj, desc, jstring);
return true;
}
static const char *mode_to_string(int mode)
{
switch (mode) {
case MESH_MODE_DISABLED:
return "disabled";
case MESH_MODE_ENABLED:
return "enabled";
default:
return "unsupported";
}
}
static bool write_mode(json_object *jobj, const char *desc, int value)
{
json_object *jstring;
jstring = json_object_new_string(mode_to_string(value));
if (!jstring)
return false;
json_object_object_del(jobj, desc);
json_object_object_add(jobj, desc, jstring);
return true;
}
bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword,
int value)
{
if (!cfg || !write_mode(cfg->jnode, keyword, value))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
static bool write_relay_mode(json_object *jobj, uint8_t mode,
uint8_t count, uint16_t interval)
{
json_object *jrelay;
json_object_object_del(jobj, "relay");
jrelay = json_object_new_object();
if (!jrelay)
return false;
if (!write_mode(jrelay, "mode", mode))
goto fail;
if (!write_int(jrelay, "count", count))
goto fail;
if (!write_int(jrelay, "interval", interval))
goto fail;
json_object_object_add(jobj, "relay", jrelay);
return true;
fail:
json_object_put(jrelay);
return false;
}
bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast)
{
if (!cfg || !write_uint16_hex(cfg->jnode, "unicastAddress", unicast))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode,
uint8_t count, uint16_t interval)
{
if (!cfg || !write_relay_mode(cfg->jnode, mode, count, interval))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt,
uint16_t interval)
{
json_object *jnode, *jrtx;
if (!cfg)
return false;
jnode = cfg->jnode;
jrtx = json_object_new_object();
if (!jrtx)
return false;
if (!write_int(jrtx, "count", cnt))
goto fail;
if (!write_int(jrtx, "interval", interval))
goto fail;
json_object_object_del(jnode, "retransmit");
json_object_object_add(jnode, "retransmit", jrtx);
return save_config(cfg->jnode, cfg->node_dir_path);
fail:
json_object_put(jrtx);
return false;
}
bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx,
bool update)
{
json_object *jnode;
int tmp = update ? 1 : 0;
if (!cfg)
return false;
jnode = cfg->jnode;
if (!write_int(jnode, "IVindex", idx))
return false;
if (!write_int(jnode, "IVupdate", tmp))
return false;
return save_config(jnode, cfg->node_dir_path);
}
static void add_model(void *a, void *b)
{
struct mesh_config_model *mod = a;
json_object *jmodels = b, *jmodel, *jval;
jmodel = json_object_new_object();
if (!jmodel)
return;
if (!mod->vendor)
write_uint16_hex(jmodel, "modelId",
(uint16_t) mod->id);
else
write_uint32_hex(jmodel, "modelId", mod->id);
jval = json_object_new_boolean(mod->sub_enabled);
json_object_object_add(jmodel, "subEnabled", jval);
jval = json_object_new_boolean(mod->pub_enabled);
json_object_object_add(jmodel, "pubEnabled", jval);
json_object_array_add(jmodels, jmodel);
}
/* Add unprovisioned node (local) */
static struct mesh_config *create_config(const char *cfg_path,
const uint8_t uuid[16],
struct mesh_config_node *node)
{
struct mesh_config_modes *modes = &node->modes;
const struct l_queue_entry *entry;
json_object *jnode, *jelems;
struct mesh_config *cfg;
if (!cfg_path || !node)
return NULL;
jnode = json_object_new_object();
/* CID, PID, VID, crpl */
if (!write_uint16_hex(jnode, "cid", node->cid))
return NULL;
if (!write_uint16_hex(jnode, "pid", node->pid))
return NULL;
if (!write_uint16_hex(jnode, "vid", node->vid))
return NULL;
if (!write_uint16_hex(jnode, "crpl", node->crpl))
return NULL;
/* Features: relay, LPN, friend, proxy*/
if (!write_relay_mode(jnode, modes->relay.state,
modes->relay.cnt, modes->relay.interval))
return NULL;
if (!write_mode(jnode, "lowPower", modes->lpn))
return NULL;
if (!write_mode(jnode, "friend", modes->friend))
return NULL;
if (!write_mode(jnode, "proxy", modes->proxy))
return NULL;
/* Beaconing state */
if (!write_mode(jnode, "beacon", modes->beacon))
return NULL;
/* Sequence number */
json_object_object_add(jnode, "sequenceNumber",
json_object_new_int(node->seq_number));
/* Default TTL */
json_object_object_add(jnode, "defaultTTL",
json_object_new_int(node->ttl));
/* Elements */
jelems = json_object_new_array();
if (!jelems)
return NULL;
entry = l_queue_get_entries(node->elements);
for (; entry; entry = entry->next) {
struct mesh_config_element *ele = entry->data;
json_object *jelement, *jmodels;
jelement = json_object_new_object();
if (!jelement) {
json_object_put(jelems);
return NULL;
}
write_int(jelement, "elementIndex", ele->index);
write_uint16_hex(jelement, "location", ele->location);
json_object_array_add(jelems, jelement);
/* Models */
if (l_queue_isempty(ele->models))
continue;
jmodels = json_object_new_array();
if (!jmodels) {
json_object_put(jelems);
return NULL;
}
json_object_object_add(jelement, "models", jmodels);
l_queue_foreach(ele->models, add_model, jmodels);
}
json_object_object_add(jnode, "elements", jelems);
cfg = l_new(struct mesh_config, 1);
cfg->jnode = jnode;
memcpy(cfg->uuid, uuid, 16);
cfg->node_dir_path = l_strdup(cfg_path);
cfg->write_seq = node->seq_number;
cfg->idles = l_queue_new();
gettimeofday(&cfg->write_time, NULL);
return cfg;
}
struct mesh_config *mesh_config_create(const char *cfgdir_name,
const uint8_t uuid[16], struct mesh_config_node *db_node)
{
char uuid_buf[33];
char name_buf[PATH_MAX];
struct mesh_config *cfg;
size_t max_len = strlen(cfgnode_name) + strlen(bak_ext);
if (!hex2str((uint8_t *) uuid, 16, uuid_buf, sizeof(uuid_buf)))
return NULL;
snprintf(name_buf, PATH_MAX, "%s/%s", cfgdir_name, uuid_buf);
if (strlen(name_buf) + max_len >= PATH_MAX)
return NULL;
/* Create a new directory and node.json file */
if (mkdir(name_buf, 0755) != 0)
return NULL;
snprintf(name_buf, PATH_MAX, "%s/%s%s", cfgdir_name, uuid_buf,
cfgnode_name);
l_debug("New node config %s", name_buf);
cfg = create_config(name_buf, uuid, db_node);
if (!cfg)
return NULL;
if (!mesh_config_save(cfg, true, NULL, NULL)) {
mesh_config_release(cfg);
return NULL;
}
return cfg;
}
static void finish_key_refresh(json_object *jobj, uint16_t net_idx)
{
json_object *jarray;
int i, len;
/* Clean up all the bound appkeys */
if (!json_object_object_get_ex(jobj, "appKeys", &jarray))
return;
len = json_object_array_length(jarray);
for (i = 0; i < len; ++i) {
json_object *jentry;
uint16_t idx;
jentry = json_object_array_get_idx(jarray, i);
if (!get_key_index(jentry, "boundNetKey", &idx))
continue;
if (idx != net_idx)
continue;
json_object_object_del(jentry, "oldKey");
if (!get_key_index(jentry, "index", &idx))
continue;
}
}
bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx,
uint8_t phase)
{
json_object *jnode, *jarray, *jentry = NULL;
if (!cfg)
return false;
jnode = cfg->jnode;
if (json_object_object_get_ex(jnode, "netKeys", &jarray))
jentry = get_key_object(jarray, idx);
if (!jentry)
return false;
json_object_object_del(jentry, "keyRefresh");
json_object_object_add(jentry, "keyRefresh",
json_object_new_int(phase));
if (phase == KEY_REFRESH_PHASE_NONE) {
json_object_object_del(jentry, "oldKey");
finish_key_refresh(jnode, idx);
}
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
struct mesh_config_pub *pub)
{
json_object *jnode, *jmodel, *jpub, *jrtx;
bool res;
int ele_idx;
if (!cfg)
return false;
jnode = cfg->jnode;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
json_object_object_del(jmodel, "publish");
jpub = json_object_new_object();
if (!jpub)
return false;
if (pub->virt)
res = add_key_value(jpub, "address", pub->virt_addr);
else
res = write_uint16_hex(jpub, "address", pub->addr);
if (!res)
goto fail;
if (!write_int(jpub, "index", pub->idx))
goto fail;
if (!write_int(jpub, "ttl", pub->ttl))
goto fail;
if (!write_int(jpub, "period", pub->period))
goto fail;
if (!write_int(jpub, "credentials", pub->credential ? 1 : 0))
goto fail;
jrtx = json_object_new_object();
if (!jrtx)
goto fail;
if (!write_int(jrtx, "count", pub->cnt))
goto fail;
if (!write_int(jrtx, "interval", pub->interval))
goto fail;
json_object_object_add(jpub, "retransmit", jrtx);
json_object_object_add(jmodel, "publish", jpub);
return save_config(jnode, cfg->node_dir_path);
fail:
json_object_put(jpub);
return false;
}
static bool delete_model_property(json_object *jnode, uint16_t ele_addr,
uint32_t mod_id, bool vendor, const char *keyword)
{
json_object *jmodel;
int ele_idx;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
json_object_object_del(jmodel, keyword);
return true;
}
bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t addr,
uint32_t mod_id, bool vendor)
{
if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor,
"publish"))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
static void del_page(json_object *jarray, uint8_t page)
{
char buf[3];
int i, len;
if (!jarray)
return;
snprintf(buf, 3, "%2.2x", page);
len = json_object_array_length(jarray);
for (i = 0; i < len; i++) {
json_object *jentry;
char *str;
jentry = json_object_array_get_idx(jarray, i);
str = (char *)json_object_get_string(jentry);
/* Delete matching page(s) */
if (!memcmp(str, buf, 2))
json_object_array_del_idx(jarray, i, 1);
}
}
bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page,
uint8_t *data, uint16_t size)
{
json_object *jnode, *jstring, *jarray = NULL;
char *buf;
int len;
if (!cfg)
return false;
jnode = cfg->jnode;
json_object_object_get_ex(jnode, "pages", &jarray);
len = (size * 2) + 3;
buf = l_malloc(len);
snprintf(buf, len, "%2.2x", page);
hex2str(data, size, buf + 2, len - 2);
if (jarray && jarray_has_string(jarray, buf, len)) {
l_free(buf);
return true;
} else if (!jarray) {
jarray = json_object_new_array();
json_object_object_add(jnode, "pages", jarray);
} else
del_page(jarray, page);
jstring = json_object_new_string(buf);
json_object_array_add(jarray, jstring);
l_free(buf);
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_comp_page_mv(struct mesh_config *cfg, uint8_t old, uint8_t nw)
{
json_object *jnode, *jarray = NULL;
uint8_t *data;
char *str;
char old_buf[3];
int i, len, dlen = 0;
bool status = true;
if (!cfg || old == nw)
return false;
jnode = cfg->jnode;
json_object_object_get_ex(jnode, "pages", &jarray);
if (!jarray)
return false;
snprintf(old_buf, 3, "%2.2x", old);
data = l_malloc(MAX_MSG_LEN);
len = json_object_array_length(jarray);
for (i = 0; i < len; i++) {
json_object *jentry;
jentry = json_object_array_get_idx(jarray, i);
str = (char *)json_object_get_string(jentry);
/* Delete matching page(s) but save data*/
if (!memcmp(str, old_buf, 2)) {
dlen = strlen(str + 2);
str2hex(str + 2, dlen, data, MAX_MSG_LEN);
dlen /= 2;
json_object_array_del_idx(jarray, i, 1);
}
}
if (dlen)
status = mesh_config_comp_page_add(cfg, nw, data, dlen);
l_free(data);
return status;
}
bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
struct mesh_config_sub *sub)
{
json_object *jnode, *jmodel, *jstring, *jarray = NULL;
int ele_idx, len;
char buf[33];
if (!cfg)
return false;
jnode = cfg->jnode;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
if (!sub->virt) {
snprintf(buf, 5, "%4.4x", sub->addr.grp);
len = 4;
} else {
hex2str(sub->addr.label, 16, buf, 33);
len = 32;
}
json_object_object_get_ex(jmodel, "subscribe", &jarray);
if (jarray && jarray_has_string(jarray, buf, len))
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, "subscribe", jarray);
}
json_object_array_add(jarray, jstring);
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
struct mesh_config_sub *sub)
{
json_object *jnode, *jmodel, *jarray;
char buf[33];
int len, ele_idx;
if (!cfg)
return false;
jnode = cfg->jnode;
ele_idx = get_element_index(jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
if (!json_object_object_get_ex(jmodel, "subscribe", &jarray))
return true;
if (!sub->virt) {
snprintf(buf, 5, "%4.4x", sub->addr.grp);
len = 4;
} else {
hex2str(sub->addr.label, 16, buf, 33);
len = 32;
}
jarray_string_del(jarray, buf, len);
if (!json_object_array_length(jarray))
json_object_object_del(jmodel, "subscribe");
return save_config(jnode, cfg->node_dir_path);
}
bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t addr,
uint32_t mod_id, bool vendor)
{
if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor,
"subscribe"))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
bool enable)
{
json_object *jmodel, *jval;
int ele_idx;
if (!cfg)
return false;
ele_idx = get_element_index(cfg->jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
json_object_object_del(jmodel, "pubDisabled");
jval = json_object_new_boolean(!enable);
json_object_object_add(jmodel, "pubDisabled", jval);
if (!enable)
json_object_object_del(jmodel, "publish");
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr,
uint32_t mod_id, bool vendor,
bool enable)
{
json_object *jmodel, *jval;
int ele_idx;
if (!cfg)
return false;
ele_idx = get_element_index(cfg->jnode, ele_addr);
if (ele_idx < 0)
return false;
jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor);
if (!jmodel)
return false;
json_object_object_del(jmodel, "subEnabled");
jval = json_object_new_boolean(enable);
json_object_object_add(jmodel, "subEnabled", jval);
if (!enable)
json_object_object_del(jmodel, "subscribe");
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
bool cache)
{
int value = 0;
uint32_t cached = 0;
if (!cfg)
return false;
if (!cache) {
if (!write_int(cfg->jnode, "sequenceNumber", seq))
return false;
return mesh_config_save(cfg, true, NULL, NULL);
}
/* If resetting seq to Zero, make sure cached value reset as well */
if (seq && get_int(cfg->jnode, "sequenceNumber", &value))
cached = (uint32_t)value;
/*
* When sequence number approaches value stored on disk, calculate
* average time between sequence number updates, then overcommit the
* sequence number by MIN_SEQ_CACHE_TIME seconds worth of traffic or
* MIN_SEQ_CACHE_VALUE (whichever is greater) to avoid frequent writes
* to disk and to protect against crashes.
*
* The real value will be saved when daemon shuts down properly.
*/
if (seq + MIN_SEQ_CACHE_TRIGGER >= cached) {
struct timeval now;
struct timeval elapsed;
uint64_t elapsed_ms;
gettimeofday(&now, NULL);
timersub(&now, &cfg->write_time, &elapsed);
elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000;
/*
* If time since last write is zero, this means that
* idle_save_config is already pending, so we don't need to do
* anything.
*/
if (!elapsed_ms)
return true;
cached = seq + (seq - cfg->write_seq) *
1000 * MIN_SEQ_CACHE_TIME / elapsed_ms;
if (cached < seq + MIN_SEQ_CACHE_VALUE)
cached = seq + MIN_SEQ_CACHE_VALUE;
/* Cap the seq cache maximum to fixed out-of-range value.
* If daemon restarts with out-of-range value, no packets
* are to be sent until IV Update procedure completes.
*/
if (cached > SEQ_MASK)
cached = SEQ_MASK + 1;
cfg->write_seq = seq;
/* Don't rewrite NVM storage if unchanged */
if (value == (int) cached)
return true;
l_debug("Seq Cache: %d -> %d", seq, cached);
if (!write_int(cfg->jnode, "sequenceNumber", cached))
return false;
return mesh_config_save(cfg, false, NULL, NULL);
}
return true;
}
bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl)
{
if (!cfg || !write_int(cfg->jnode, "defaultTTL", ttl))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_update_company_id(struct mesh_config *cfg, uint16_t cid)
{
if (!cfg || !write_uint16_hex(cfg->jnode, "cid", cid))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_update_product_id(struct mesh_config *cfg, uint16_t pid)
{
if (!cfg || !write_uint16_hex(cfg->jnode, "pid", pid))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_update_version_id(struct mesh_config *cfg, uint16_t vid)
{
if (!cfg || !write_uint16_hex(cfg->jnode, "vid", vid))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
bool mesh_config_update_crpl(struct mesh_config *cfg, uint16_t crpl)
{
if (!cfg || !write_uint16_hex(cfg->jnode, "crpl", crpl))
return false;
return save_config(cfg->jnode, cfg->node_dir_path);
}
static bool load_node(const char *fname, const uint8_t uuid[16],
mesh_config_node_func_t cb, void *user_data)
{
int fd;
char *str;
struct stat st;
ssize_t sz;
bool result = false;
json_object *jnode;
struct mesh_config_node node;
if (!cb) {
l_info("Node read callback is required");
return false;
}
l_info("Loading configuration from %s", fname);
fd = open(fname, O_RDONLY);
if (fd < 0)
return false;
if (fstat(fd, &st) == -1) {
close(fd);
return false;
}
str = (char *) l_new(char, st.st_size + 1);
if (!str) {
close(fd);
return false;
}
sz = read(fd, str, st.st_size);
if (sz != st.st_size) {
l_error("Failed to read configuration file %s", fname);
goto done;
}
jnode = json_tokener_parse(str);
if (!jnode)
goto done;
memset(&node, 0, sizeof(node));
node.elements = l_queue_new();
node.netkeys = l_queue_new();
node.appkeys = l_queue_new();
node.pages = l_queue_new();
result = read_node(jnode, &node);
if (result) {
struct mesh_config *cfg = l_new(struct mesh_config, 1);
cfg->jnode = jnode;
memcpy(cfg->uuid, uuid, 16);
cfg->node_dir_path = l_strdup(fname);
cfg->write_seq = node.seq_number;
cfg->idles = l_queue_new();
gettimeofday(&cfg->write_time, NULL);
result = cb(&node, uuid, cfg, user_data);
if (!result) {
l_free(cfg->idles);
l_free(cfg->node_dir_path);
l_free(cfg);
}
}
/* Done with the node: free resources */
l_free(node.net_transmit);
l_queue_destroy(node.netkeys, l_free);
l_queue_destroy(node.appkeys, l_free);
l_queue_destroy(node.pages, l_free);
l_queue_destroy(node.elements, free_element);
if (!result)
json_object_put(jnode);
done:
close(fd);
if (str)
l_free(str);
return result;
}
static void release_idle(void *data)
{
struct l_idle *idle = data;
l_idle_remove(idle);
}
void mesh_config_release(struct mesh_config *cfg)
{
if (!cfg)
return;
l_queue_destroy(cfg->idles, release_idle);
l_free(cfg->node_dir_path);
json_object_put(cfg->jnode);
l_free(cfg);
}
static void idle_save_config(struct l_idle *idle, void *user_data)
{
struct write_info *info = user_data;
char *fname_tmp, *fname_bak, *fname_cfg;
bool result = false;
fname_cfg = info->cfg->node_dir_path;
fname_tmp = l_strdup_printf("%s%s", fname_cfg, tmp_ext);
fname_bak = l_strdup_printf("%s%s", fname_cfg, bak_ext);
remove(fname_tmp);
result = save_config(info->cfg->jnode, fname_tmp);
if (result) {
remove(fname_bak);
rename(fname_cfg, fname_bak);
rename(fname_tmp, fname_cfg);
}
remove(fname_tmp);
l_free(fname_tmp);
l_free(fname_bak);
gettimeofday(&info->cfg->write_time, NULL);
if (info->cb)
info->cb(info->user_data, result);
if (idle) {
l_queue_remove(info->cfg->idles, idle);
l_idle_remove(idle);
}
l_free(info);
}
bool mesh_config_save(struct mesh_config *cfg, bool no_wait,
mesh_config_status_func_t cb, void *user_data)
{
struct write_info *info;
if (!cfg)
return false;
info = l_new(struct write_info, 1);
info->cfg = cfg;
info->cb = cb;
info->user_data = user_data;
if (no_wait) {
idle_save_config(NULL, info);
} else {
struct l_idle *idle;
idle = l_idle_create(idle_save_config, info, NULL);
l_queue_push_tail(cfg->idles, idle);
}
return true;
}
bool mesh_config_load_nodes(const char *cfgdir_name, mesh_config_node_func_t cb,
void *user_data)
{
DIR *cfgdir;
struct dirent *entry;
size_t path_len = strlen(cfgdir_name) + strlen(cfgnode_name) +
strlen(bak_ext);
create_dir(cfgdir_name);
cfgdir = opendir(cfgdir_name);
if (!cfgdir) {
l_error("Failed to open mesh node storage directory: %s",
cfgdir_name);
return false;
}
while ((entry = readdir(cfgdir)) != NULL) {
char *dirname, *fname, *bak;
uint8_t uuid[16];
size_t node_len;
if (entry->d_type != DT_DIR)
continue;
/* Check path length */
node_len = strlen(entry->d_name);
if (path_len + node_len + 1 >= PATH_MAX)
continue;
if (!str2hex(entry->d_name, node_len, uuid, sizeof(uuid)))
continue;
dirname = l_strdup_printf("%s/%s", cfgdir_name, entry->d_name);
fname = l_strdup_printf("%s%s", dirname, cfgnode_name);
if (!load_node(fname, uuid, cb, user_data)) {
/* Fall-back to Backup version */
bak = l_strdup_printf("%s%s", fname, bak_ext);
if (load_node(bak, uuid, cb, user_data)) {
remove(fname);
rename(bak, fname);
}
l_free(bak);
}
l_free(fname);
l_free(dirname);
}
closedir(cfgdir);
return true;
}
void mesh_config_destroy_nvm(struct mesh_config *cfg)
{
char *node_dir, *node_name;
char uuid[33];
if (!cfg)
return;
node_dir = dirname(cfg->node_dir_path);
l_debug("Delete node config %s", node_dir);
if (!hex2str(cfg->uuid, 16, uuid, sizeof(uuid)))
return;
node_name = basename(node_dir);
/* Make sure path name of node follows expected guidelines */
if (strcmp(node_name, uuid))
return;
del_path(node_dir);
}