// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2017 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "src/shared/util.h" #include "src/shared/shell.h" #include "gdbus/gdbus.h" #include "tools/mesh/config-model.h" #include "tools/mesh-gatt/mesh-net.h" #include "tools/mesh-gatt/node.h" #include "tools/mesh-gatt/keys.h" #include "tools/mesh-gatt/gatt.h" #include "tools/mesh-gatt/net.h" #include "tools/mesh-gatt/prov-db.h" #include "tools/mesh-gatt/util.h" struct mesh_model { struct mesh_model_ops cbs; void *user_data; GList *bindings; GList *subscriptions; uint32_t id; struct mesh_publication *pub; }; struct mesh_element { GList *models; uint16_t loc; uint8_t index; }; struct mesh_node { const char *name; GList *net_keys; GList *app_keys; void *prov; GList *elements; uint32_t iv_index; uint32_t seq_number; uint16_t primary_net_idx; uint16_t primary; uint16_t oob; uint16_t features; uint8_t dev_uuid[16]; uint8_t dev_key[16]; uint8_t num_ele; uint8_t ttl; bool provisioner; struct mesh_node_composition *comp; }; static GList *nodes; static struct mesh_node *local_node; static int match_node_unicast(const void *a, const void *b) { const struct mesh_node *node = a; uint16_t dst = GPOINTER_TO_UINT(b); if (dst >= node->primary && dst <= (node->primary + node->num_ele - 1)) return 0; return -1; } static int match_device_uuid(const void *a, const void *b) { const struct mesh_node *node = a; const uint8_t *uuid = b; return memcmp(node->dev_uuid, uuid, 16); } static int match_element_idx(const void *a, const void *b) { const struct mesh_element *element = a; uint32_t index = GPOINTER_TO_UINT(b); return (element->index == index) ? 0 : -1; } static int match_model_id(const void *a, const void *b) { const struct mesh_model *model = a; uint32_t id = GPOINTER_TO_UINT(b); return (model->id == id) ? 0 : -1; } struct mesh_node *node_find_by_addr(uint16_t addr) { GList *l; if (!IS_UNICAST(addr)) return NULL; l = g_list_find_custom(nodes, GUINT_TO_POINTER(addr), match_node_unicast); if (l) return l->data; else return NULL; } struct mesh_node *node_find_by_uuid(uint8_t uuid[16]) { GList *l; l = g_list_find_custom(nodes, uuid, match_device_uuid); if (l) return l->data; else return NULL; } struct mesh_node *node_create_new(struct prov_svc_data *prov) { struct mesh_node *node; if (node_find_by_uuid(prov->dev_uuid)) return NULL; node = g_malloc0(sizeof(struct mesh_node)); if (!node) return NULL; memcpy(node->dev_uuid, prov->dev_uuid, 16); node->oob = prov->oob; nodes = g_list_append(nodes, node); return node; } struct mesh_node *node_new(void) { struct mesh_node *node; node = g_malloc0(sizeof(struct mesh_node)); if (!node) return NULL; nodes = g_list_append(nodes, node); return node; } static void model_free(void *data) { struct mesh_model *model = data; g_list_free(model->bindings); g_list_free(model->subscriptions); g_free(model->pub); g_free(model); } static void element_free(void *data) { struct mesh_element *element = data; g_list_free_full(element->models, model_free); g_free(element); } static void free_node_resources(void *data) { struct mesh_node *node = data; g_list_free(node->net_keys); g_list_free(node->app_keys); g_list_free_full(node->elements, element_free); if(node->comp) g_free(node->comp); g_free(node); } void node_free(struct mesh_node *node) { if (!node) return; nodes = g_list_remove(nodes, node); free_node_resources(node); } void node_cleanup(void) { g_list_free_full(nodes, free_node_resources); local_node = NULL; } bool node_is_provisioned(struct mesh_node *node) { return (!IS_UNASSIGNED(node->primary)); } void *node_get_prov(struct mesh_node *node) { return node->prov; } void node_set_prov(struct mesh_node *node, void *prov) { node->prov = prov; } bool node_app_key_add(struct mesh_node *node, uint16_t idx) { uint32_t index; uint16_t net_idx; if (!node) return false; net_idx = keys_app_key_get_bound(idx); if (net_idx == NET_IDX_INVALID) return false; if (!g_list_find(node->net_keys, GUINT_TO_POINTER(net_idx))) return false; index = (net_idx << 16) + idx; if (g_list_find(node->app_keys, GUINT_TO_POINTER(index))) return false; node->app_keys = g_list_append(node->app_keys, GUINT_TO_POINTER(index)); return true; } bool node_net_key_add(struct mesh_node *node, uint16_t index) { if(!node) return false; if (g_list_find(node->net_keys, GUINT_TO_POINTER(index))) return false; node->net_keys = g_list_append(node->net_keys, GUINT_TO_POINTER(index)); return true; } bool node_net_key_delete(struct mesh_node *node, uint16_t index) { GList *l; if(!node) return false; l = g_list_find(node->net_keys, GUINT_TO_POINTER(index)); if (!l) return false; node->net_keys = g_list_remove(node->net_keys, GUINT_TO_POINTER(index)); /* TODO: remove all associated app keys and bindings */ return true; } bool node_app_key_delete(struct mesh_node *node, uint16_t net_idx, uint16_t idx) { GList *l; uint32_t index; if(!node) return false; index = (net_idx << 16) + idx; l = g_list_find(node->app_keys, GUINT_TO_POINTER(index)); if (!l) return false; node->app_keys = g_list_remove(node->app_keys, GUINT_TO_POINTER(index)); /* TODO: remove all associated bindings */ return true; } void node_set_primary(struct mesh_node *node, uint16_t unicast) { node->primary = unicast; } uint16_t node_get_primary(struct mesh_node *node) { if (!node) return UNASSIGNED_ADDRESS; else return node->primary; } void node_set_device_key(struct mesh_node *node, uint8_t *key) { if (!node || !key) return; memcpy(node->dev_key, key, 16); } uint8_t *node_get_device_key(struct mesh_node *node) { if (!node) return NULL; else return node->dev_key; } void node_set_num_elements(struct mesh_node *node, uint8_t num_ele) { node->num_ele = num_ele; } uint8_t node_get_num_elements(struct mesh_node *node) { return node->num_ele; } GList *node_get_net_keys(struct mesh_node *node) { if (!node) return NULL; else return node->net_keys; } GList *node_get_app_keys(struct mesh_node *node) { if (!node) return NULL; else return node->app_keys; } bool node_parse_composition(struct mesh_node *node, uint8_t *data, uint16_t len) { struct mesh_node_composition *comp; uint16_t features; int i; comp = g_malloc0(sizeof(struct mesh_node_composition)); if (!comp) return false; /* skip page -- We only support Page Zero */ data++; len--; comp->cid = get_le16(&data[0]); comp->pid = get_le16(&data[2]); comp->vid = get_le16(&data[4]); comp->crpl = get_le16(&data[6]); features = get_le16(&data[8]); data += 10; len -= 10; comp->relay = !!(features & MESH_FEATURE_RELAY); comp->proxy = !!(features & MESH_FEATURE_PROXY); comp->friend = !!(features & MESH_FEATURE_FRIEND); comp->lpn = !!(features & MESH_FEATURE_LPN); for (i = 0; i< node->num_ele; i++) { uint8_t m, v; uint32_t mod_id; uint16_t vendor_id; struct mesh_element *ele; ele = g_malloc0(sizeof(struct mesh_element)); if (!ele) { g_free(comp); return false; } ele->index = i; ele->loc = get_le16(data); data += 2; node->elements = g_list_append(node->elements, ele); m = *data++; v = *data++; len -= 2; while (len >= 2 && m--) { mod_id = get_le16(data); /* initialize uppper 16 bits to 0xffff for SIG models */ mod_id |= 0xffff0000; if (!node_set_model(node, ele->index, mod_id)) { g_free(comp); return false; } data += 2; len -= 2; } while (len >= 4 && v--) { mod_id = get_le16(data + 2); vendor_id = get_le16(data); mod_id |= (vendor_id << 16); if (!node_set_model(node, ele->index, mod_id)) { g_free(comp); return false; } data += 4; len -= 4; } } node->comp = comp; return true; } bool node_set_local_node(struct mesh_node *node) { if (local_node) { bt_shell_printf("Local node already registered\n"); return false; } net_register_unicast(node->primary, node->num_ele); local_node = node; local_node->provisioner = true; return true; } struct mesh_node *node_get_local_node() { return local_node; } uint16_t node_get_primary_net_idx(struct mesh_node *node) { if (node == NULL) return NET_IDX_INVALID; return node->primary_net_idx; } static bool deliver_model_data(struct mesh_element* element, uint16_t src, uint16_t app_idx, uint8_t *data, uint16_t len) { GList *l; for(l = element->models; l; l = l->next) { struct mesh_model *model = l->data; if (!g_list_find(model->bindings, GUINT_TO_POINTER(app_idx))) continue; if (model->cbs.recv && model->cbs.recv(src, data, len, model->user_data)) return true; } return false; } void node_local_data_handler(uint16_t src, uint32_t dst, uint32_t iv_index, uint32_t seq_num, uint16_t app_idx, uint8_t *data, uint16_t len) { GList *l; bool res; uint64_t iv_seq; uint64_t iv_seq_remote; uint8_t ele_idx; struct mesh_element *element; struct mesh_node *remote; bool loopback; if (!local_node || seq_num > 0xffffff) return; iv_seq = iv_index << 24; iv_seq |= seq_num; remote = node_find_by_addr(src); if (!remote) { if (local_node->provisioner) { bt_shell_printf("Remote node unknown (%4.4x)\n", src); return; } remote = g_new0(struct mesh_node, 1); if (!remote) return; /* Not Provisioner; Assume all SRC elements stand alone */ remote->primary = src; remote->num_ele = 1; nodes = g_list_append(nodes, remote); } loopback = (src < (local_node->primary + local_node->num_ele) && src >= local_node->primary); if (!loopback) { iv_seq_remote = remote->iv_index << 24; iv_seq |= remote->seq_number; if (iv_seq_remote >= iv_seq) { bt_shell_printf("Replayed message detected " "(%016" PRIx64 " >= %016" PRIx64 ")\n", iv_seq_remote, iv_seq); return; } } if (IS_GROUP(dst) || IS_VIRTUAL(dst)) { /* TODO: if subscription address, deliver to subscribers */ return; } if (IS_ALL_NODES(dst)) { ele_idx = 0; } else { if (dst >= (local_node->primary + local_node->num_ele) || dst < local_node->primary) return; ele_idx = dst - local_node->primary; } l = g_list_find_custom(local_node->elements, GUINT_TO_POINTER(ele_idx), match_element_idx); /* This should not happen */ if (!l) return; element = l->data; res = deliver_model_data(element, src, app_idx, data, len); if (res && !loopback) { /* TODO: Save remote in Replay Protection db */ remote->iv_index = iv_index; remote->seq_number = seq_num; prov_db_node_set_iv_seq(remote, iv_index, seq_num); } } static gboolean restore_model_state(gpointer data) { struct mesh_model *model = data; GList *l; struct mesh_model_ops *ops; ops = &model->cbs; if (model->bindings && ops->bind) { for (l = model->bindings; l; l = l->next) { if (ops->bind(GPOINTER_TO_UINT(l->data), ACTION_ADD) != MESH_STATUS_SUCCESS) break; } } if (model->pub && ops->pub) ops->pub(model->pub); g_idle_remove_by_data(data); return true; } bool node_local_model_register(uint8_t ele_idx, uint16_t model_id, struct mesh_model_ops *ops, void *user_data) { uint32_t id = 0xffff0000 | model_id; return node_local_vendor_model_register(ele_idx, id, ops, user_data); } bool node_local_vendor_model_register(uint8_t ele_idx, uint32_t model_id, struct mesh_model_ops *ops, void *user_data) { struct mesh_element *ele; struct mesh_model *model; GList *l; if (!local_node) return false; l = g_list_find_custom(local_node->elements, GUINT_TO_POINTER(ele_idx), match_element_idx); if (!l) return false; ele = l->data; l = g_list_find_custom(ele->models, GUINT_TO_POINTER(model_id), match_model_id); if (!l) return false; model = l->data; model->cbs = *ops; model->user_data = user_data; if (model_id >= 0xffff0000) model_id = model_id & 0xffff; /* Silently assign device key binding to configuration models */ if (model_id == CONFIG_SERVER_MODEL_ID || model_id == CONFIG_CLIENT_MODEL_ID) { model->bindings = g_list_append(model->bindings, GUINT_TO_POINTER(APP_IDX_DEV)); } else { g_idle_add(restore_model_state, model); } return true; } bool node_set_element(struct mesh_node *node, uint8_t ele_idx) { struct mesh_element *ele; GList *l; if (!node) return false; l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx), match_element_idx); if (l) return false; ele = g_malloc0(sizeof(struct mesh_element)); if (!ele) return false; ele->index = ele_idx; node->elements = g_list_append(node->elements, ele); return true; } bool node_set_model(struct mesh_node *node, uint8_t ele_idx, uint32_t id) { struct mesh_element *ele; struct mesh_model *model; GList *l; if (!node) return false; l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx), match_element_idx); if (!l) return false; ele = l->data; l = g_list_find_custom(ele->models, GUINT_TO_POINTER(id), match_model_id); if (l) return true; model = g_malloc0(sizeof(struct mesh_model)); if (!model) return false; model->id = id; ele->models = g_list_append(ele->models, model); return true; } bool node_set_composition(struct mesh_node *node, struct mesh_node_composition *comp) { if (!node || !comp || node->comp) return false; node->comp = g_malloc0(sizeof(struct mesh_node_composition)); if (!node->comp) return false; *(node->comp) = *comp; return true; } struct mesh_node_composition *node_get_composition(struct mesh_node *node) { if (!node) return NULL; return node->comp; } static struct mesh_model *get_model(struct mesh_node *node, uint8_t ele_idx, uint32_t model_id) { struct mesh_element *ele; GList *l; if (!node) return NULL; l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx), match_element_idx); if (!l) return NULL; ele = l->data; l = g_list_find_custom(ele->models, GUINT_TO_POINTER(model_id), match_model_id); if (!l) return NULL; return l->data; } bool node_add_binding(struct mesh_node *node, uint8_t ele_idx, uint32_t model_id, uint16_t app_idx) { struct mesh_model *model; GList *l; model = get_model(node, ele_idx, model_id); if (!model) return false; l = g_list_find(model->bindings, GUINT_TO_POINTER(app_idx)); if (l) return false; if ((node == local_node) && model->cbs.bind) { if (!model->cbs.bind(app_idx, ACTION_ADD)) return false; } model->bindings = g_list_append(model->bindings, GUINT_TO_POINTER(app_idx)); return true; } bool node_add_subscription(struct mesh_node *node, uint8_t ele_idx, uint32_t model_id, uint16_t addr) { struct mesh_model *model; GList *l; model = get_model(node, ele_idx, model_id); if (!model) return false; l = g_list_find(model->subscriptions, GUINT_TO_POINTER(addr)); if (l) return false; model->subscriptions = g_list_append(model->subscriptions, GUINT_TO_POINTER(addr)); return true; } uint8_t node_get_default_ttl(struct mesh_node *node) { if (!node) return DEFAULT_TTL; else if (node == local_node) return net_get_default_ttl(); else return node->ttl; } bool node_set_default_ttl(struct mesh_node *node, uint8_t ttl) { if (!node) return false; node->ttl = ttl; if (node == local_node || local_node == NULL) return net_set_default_ttl(ttl); return true; } bool node_set_sequence_number(struct mesh_node *node, uint32_t seq) { if (!node) return false; node->seq_number = seq; if (node == local_node || local_node == NULL) return net_set_seq_num(seq); return true; } uint32_t node_get_sequence_number(struct mesh_node *node) { if (!node) return 0xffffffff; else if (node == local_node) return net_get_seq_num(); return node->seq_number; } bool node_set_iv_index(struct mesh_node *node, uint32_t iv_index) { if (!node) return false; node->iv_index = iv_index; return true; } uint32_t node_get_iv_index(struct mesh_node *node) { bool update; if (!node) return 0xffffffff; else if (node == local_node) return net_get_iv_index(&update); return node->iv_index; } bool node_model_pub_set(struct mesh_node *node, uint8_t ele, uint32_t model_id, struct mesh_publication *pub) { struct mesh_model *model; model = get_model(node, ele, model_id); if(!model) return false; if (!model->pub) model->pub = g_malloc0(sizeof(struct mesh_publication)); if (!model) return false; memcpy(model->pub, pub, (sizeof(struct mesh_publication))); if((node == local_node) && model->cbs.pub) model->cbs.pub(pub); return true; } struct mesh_publication *node_model_pub_get(struct mesh_node *node, uint8_t ele, uint32_t model_id) { struct mesh_model *model; model = get_model(node, ele, model_id); if(!model) return NULL; else return model->pub; }