2020-01-18 04:23:21

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ 0/4] Add functionality to mesh-cfgclient tool

This set of patches adds more functionality to mesh configuratioon tool
as well as a bit of code tightening.


Inga Stotland (4):
tools/mesh: Refactor code for generating model ID
tools/mesh: Add length checks for rxed messages
tools/mesh: Add support for Vendor Model App Get/List
tools/mesh: Implement model group subscription commands

tools/mesh/cfgcli.c | 233 ++++++++++++++++++++++++++++++--------------
1 file changed, 159 insertions(+), 74 deletions(-)

--
2.21.1


2020-01-18 04:24:13

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ 2/4] tools/mesh: Add length checks for rxed messages

This adds missing validation of received responses.
If the length of a received message does not pass the
validation, it's not processed.
---
tools/mesh/cfgcli.c | 40 +++++++++++++++++++++-------------------
1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/tools/mesh/cfgcli.c b/tools/mesh/cfgcli.c
index 23fca4df6..934205f0b 100644
--- a/tools/mesh/cfgcli.c
+++ b/tools/mesh/cfgcli.c
@@ -73,9 +73,9 @@ static uint32_t parms[8];
static struct cfg_cmd cmds[] = {
{ OP_APPKEY_ADD, OP_APPKEY_STATUS, "AppKeyAdd" },
{ OP_APPKEY_DELETE, OP_APPKEY_STATUS, "AppKeyDelete" },
- { OP_APPKEY_GET, OP_APPKEY_LIST, "AppKeyGet"},
- { OP_APPKEY_LIST, NO_RESPONSE, "AppKeyList"},
- { OP_APPKEY_STATUS, NO_RESPONSE, "AppKeyStatus"},
+ { OP_APPKEY_GET, OP_APPKEY_LIST, "AppKeyGet" },
+ { OP_APPKEY_LIST, NO_RESPONSE, "AppKeyList" },
+ { OP_APPKEY_STATUS, NO_RESPONSE, "AppKeyStatus" },
{ OP_APPKEY_UPDATE, OP_APPKEY_STATUS, "AppKeyUpdate" },
{ OP_DEV_COMP_GET, OP_DEV_COMP_STATUS, "DeviceCompositionGet" },
{ OP_DEV_COMP_STATUS, NO_RESPONSE, "DeviceCompositionStatus" },
@@ -356,7 +356,7 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,
} else
return false;

- bt_shell_printf("Received %s\n", opcode_str(opcode));
+ bt_shell_printf("Received %s (len %u)\n", opcode_str(opcode), len);

req = get_req_by_rsp(src, (opcode & ~OP_UNRELIABLE));
if (req) {
@@ -581,12 +581,12 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.19 */
case OP_CONFIG_MODEL_SUB_STATUS:
+ if (len != 7 && len != 9)
+ return true;
+
bt_shell_printf("\nNode %4.4x Subscription status %s\n",
src, mesh_status_str(data[0]));

- if (data[0] != MESH_STATUS_SUCCESS)
- return true;
-
ele_addr = get_le16(data + 1);
addr = get_le16(data + 3);
bt_shell_printf("Element Addr\t%4.4x\n", ele_addr);
@@ -599,13 +599,12 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.27 */
case OP_CONFIG_MODEL_SUB_LIST:
+ if (len < 5)
+ return true;

bt_shell_printf("\nNode %4.4x Subscription List status %s\n",
src, mesh_status_str(data[0]));

- if (data[0] != MESH_STATUS_SUCCESS)
- return true;
-
bt_shell_printf("Element Addr\t%4.4x\n", get_le16(data + 1));
bt_shell_printf("Model ID\t%4.4x\n", get_le16(data + 3));

@@ -616,12 +615,12 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.50 */
case OP_MODEL_APP_LIST:
+ if (len < 5)
+ return true;
+
bt_shell_printf("\nNode %4.4x Model AppIdx status %s\n",
src, mesh_status_str(data[0]));

- if (data[0] != MESH_STATUS_SUCCESS)
- return true;
-
bt_shell_printf("Element Addr\t%4.4x\n", get_le16(data + 1));
bt_shell_printf("Model ID\t%4.4x\n", get_le16(data + 3));

@@ -632,12 +631,12 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.63 */
case OP_CONFIG_HEARTBEAT_PUB_STATUS:
+ if (len != 10)
+ return true;
+
bt_shell_printf("\nNode %4.4x Heartbeat publish status %s\n",
src, mesh_status_str(data[0]));

- if (data[0] != MESH_STATUS_SUCCESS)
- return true;
-
bt_shell_printf("Destination\t%4.4x\n", get_le16(data + 1));
bt_shell_printf("Count\t\t%2.2x\n", data[3]);
bt_shell_printf("Period\t\t%2.2x\n", data[4]);
@@ -648,12 +647,12 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.66 */
case OP_CONFIG_HEARTBEAT_SUB_STATUS:
+ if (len != 9)
+ return true;
+
bt_shell_printf("\nNode %4.4x Heartbeat subscribe status %s\n",
src, mesh_status_str(data[0]));

- if (data[0] != MESH_STATUS_SUCCESS)
- return true;
-
bt_shell_printf("Source\t\t%4.4x\n", get_le16(data + 1));
bt_shell_printf("Destination\t%4.4x\n", get_le16(data + 3));
bt_shell_printf("Period\t\t%2.2x\n", data[5]);
@@ -673,6 +672,9 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,

/* Per Mesh Profile 4.3.2.54 */
case OP_NODE_RESET_STATUS:
+ if (len != 1)
+ return true;
+
bt_shell_printf("Node %4.4x reset status %s\n",
src, mesh_status_str(data[0]));

--
2.21.1

2020-01-18 04:25:14

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ 3/4] tools/mesh: Add support for Vendor Model App Get/List

This adds implementation for sending Config Vendor App Get message
and receiving Config Vendor App List response.
---
tools/mesh/cfgcli.c | 37 +++++++++++++++++++++++++++++--------
1 file changed, 29 insertions(+), 8 deletions(-)

diff --git a/tools/mesh/cfgcli.c b/tools/mesh/cfgcli.c
index 934205f0b..f9aaf137d 100644
--- a/tools/mesh/cfgcli.c
+++ b/tools/mesh/cfgcli.c
@@ -629,6 +629,24 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,
get_le16(data + i));
break;

+ case OP_VEND_MODEL_APP_LIST:
+ if (len < 7)
+ return true;
+
+ bt_shell_printf("\nNode %4.4x Vendor Model AppIdx status %s\n",
+ src, mesh_status_str(data[0]));
+
+ if (data[0] != MESH_STATUS_SUCCESS)
+ return true;
+
+ bt_shell_printf("Element Addr\t%4.4x\n", get_le16(data + 1));
+ print_mod_id(data + 3, true, "");
+
+ for (i = 7; i < len; i += 2)
+ bt_shell_printf("Model AppIdx\t%4.4x\n",
+ get_le16(data + i));
+ break;
+
/* Per Mesh Profile 4.3.2.63 */
case OP_CONFIG_HEARTBEAT_PUB_STATUS:
if (len != 10)
@@ -1287,24 +1305,26 @@ static void cmd_mod_appidx_get(int argc, char *argv[])
uint16_t n;
uint8_t msg[32];
int parm_cnt;
-
- n = mesh_opcode_set(OP_MODEL_APP_GET, msg);
+ bool vendor;
+ uint32_t opcode;

parm_cnt = read_input_parameters(argc, argv);
- if (parm_cnt != 2) {
+ if (parm_cnt != 2 && parm_cnt != 3) {
bt_shell_printf("Bad arguments: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

- /* Per Mesh Profile 4.3.2.49 */
+ vendor = (parm_cnt == 3);
+ opcode = !vendor ? OP_MODEL_APP_GET : OP_VEND_MODEL_APP_GET;
+ n = mesh_opcode_set(opcode, msg);
+
/* Element Address */
put_le16(parms[0], msg + n);
n += 2;
/* Model ID */
- put_le16(parms[1], msg + n);
- n += 2;
+ n += put_model_id(msg + n, &parms[1], vendor);

- if (!config_send(msg, n, OP_MODEL_APP_GET))
+ if (!config_send(msg, n, opcode))
return bt_shell_noninteractive_quit(EXIT_FAILURE);

return bt_shell_noninteractive_quit(EXIT_SUCCESS);
@@ -1498,7 +1518,8 @@ static const struct bt_shell_menu cfg_menu = {
{"unbind", "<ele_addr> <app_idx> <model_id> [vendor_id]",
cmd_del_binding,
"Remove AppKey from a model"},
- {"mod-appidx-get", "<ele_addr> <model_id>", cmd_mod_appidx_get,
+ {"mod-appidx-get", "<ele_addr> <model_id> [vendor_id]",
+ cmd_mod_appidx_get,
"Get model app_idx"},
{"ttl-set", "<ttl>", cmd_ttl_set,
"Set default TTL"},
--
2.21.1

2020-01-18 04:25:14

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ 1/4] tools/mesh: Refactor code for generating model ID

Common code for populating message buffer with Model ID
(either SIG or vendor defined) for misc commands is refactored
into a single function.
---
tools/mesh/cfgcli.c | 61 +++++++++++++++++++++------------------------
1 file changed, 28 insertions(+), 33 deletions(-)

diff --git a/tools/mesh/cfgcli.c b/tools/mesh/cfgcli.c
index 50d2ce706..23fca4df6 100644
--- a/tools/mesh/cfgcli.c
+++ b/tools/mesh/cfgcli.c
@@ -240,11 +240,11 @@ static void add_request(uint32_t opcode)
l_queue_push_tail(requests, req);
}

-static uint32_t print_mod_id(uint8_t *data, bool vid, const char *offset)
+static uint32_t print_mod_id(uint8_t *data, bool vendor, const char *offset)
{
uint32_t mod_id;

- if (!vid) {
+ if (!vendor) {
mod_id = get_le16(data);
bt_shell_printf("%sModel Id\t%4.4x\n", offset, mod_id);
mod_id = VENDOR_ID_MASK | mod_id;
@@ -691,6 +691,21 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,
return true;
}

+static uint16_t put_model_id(uint8_t *buf, uint32_t *args, bool vendor)
+{
+ uint16_t n = 2;
+
+ if (vendor) {
+ put_le16(args[1], buf);
+ buf += 2;
+ n = 4;
+ }
+
+ put_le16(args[0], buf);
+
+ return n;
+}
+
static uint32_t read_input_parameters(int argc, char *argv[])
{
uint32_t i;
@@ -973,14 +988,7 @@ static void cmd_bind(uint32_t opcode, int argc, char *argv[])
put_le16(parms[1], msg + n);
n += 2;

- if (parm_cnt == 4) {
- put_le16(parms[3], msg + n);
- put_le16(parms[2], msg + n + 2);
- n += 4;
- } else {
- put_le16(parms[2], msg + n);
- n += 2;
- }
+ n += put_model_id(msg + n, &parms[2], parm_cnt == 4);

if (!config_send(msg, n, opcode))
return bt_shell_noninteractive_quit(EXIT_FAILURE);
@@ -1178,14 +1186,7 @@ static void cmd_pub_set(int argc, char *argv[])
msg[n++] = parms[4];

/* Model Id */
- if (parm_cnt == 7) {
- put_le16(parms[6], msg + n);
- put_le16(parms[5], msg + n + 2);
- n += 4;
- } else {
- put_le16(parms[5], msg + n);
- n += 2;
- }
+ n += put_model_id(msg + n, &parms[5], parm_cnt == 7);

if (!config_send(msg, n, OP_CONFIG_MODEL_PUB_SET))
return bt_shell_noninteractive_quit(EXIT_FAILURE);
@@ -1212,14 +1213,7 @@ static void cmd_pub_get(int argc, char *argv[])
n += 2;

/* Model Id */
- if (parm_cnt == 3) {
- put_le16(parms[2], msg + n);
- put_le16(parms[1], msg + n + 2);
- n += 4;
- } else {
- put_le16(parms[1], msg + n);
- n += 2;
- }
+ n += put_model_id(msg + n, &parms[1], parm_cnt == 3);

if (!config_send(msg, n, OP_CONFIG_MODEL_PUB_GET))
return bt_shell_noninteractive_quit(EXIT_FAILURE);
@@ -1497,21 +1491,22 @@ static const struct bt_shell_menu cfg_menu = {
"Delete AppKey"},
{"appkey-get", "<net_idx>", cmd_appkey_get,
"List AppKeys bound to the NetKey"},
- {"bind", "<ele_addr> <app_idx> <mod_id> [vendor_id]", cmd_add_binding,
+ {"bind", "<ele_addr> <app_idx> <model_id> [vendor_id]", cmd_add_binding,
"Bind AppKey to a model"},
- {"unbind", "<ele_addr> <app_idx> <mod_id> [vendor_id]", cmd_del_binding,
+ {"unbind", "<ele_addr> <app_idx> <model_id> [vendor_id]",
+ cmd_del_binding,
"Remove AppKey from a model"},
- {"mod-appidx-get", "<ele_addr> <model id>", cmd_mod_appidx_get,
+ {"mod-appidx-get", "<ele_addr> <model_id>", cmd_mod_appidx_get,
"Get model app_idx"},
{"ttl-set", "<ttl>", cmd_ttl_set,
"Set default TTL"},
{"ttl-get", NULL, cmd_ttl_get,
"Get default TTL"},
{"pub-set", "<ele_addr> <pub_addr> <app_idx> <per (step|res)> "
- "<re-xmt (cnt|per)> <mod id> [vendor_id]",
+ "<re-xmt (cnt|per)> <model_id> [vendor_id]",
cmd_pub_set,
"Set publication"},
- {"pub-get", "<ele_addr> <model>", cmd_pub_get,
+ {"pub-get", "<ele_addr> <model_id> [vendor_id]", cmd_pub_get,
"Get publication"},
{"proxy-set", "<proxy>", cmd_proxy_set,
"Set proxy state"},
@@ -1546,9 +1541,9 @@ static const struct bt_shell_menu cfg_menu = {
"Set heartbeat subscribe"},
{"hb-sub-get", NULL, cmd_hb_sub_get,
"Get heartbeat subscribe"},
- {"sub-add", "<ele_addr> <sub_addr> <model id>", cmd_sub_add,
+ {"sub-add", "<ele_addr> <sub_addr> <model_id>", cmd_sub_add,
"Add subscription"},
- {"sub-get", "<ele_addr> <model id>", cmd_sub_get,
+ {"sub-get", "<ele_addr> <model_id>", cmd_sub_get,
"Get subscription"},
{"node-reset", NULL, cmd_node_reset,
"Reset a node and remove it from network"},
--
2.21.1

2020-01-18 04:25:48

by Stotland, Inga

[permalink] [raw]
Subject: [PATCH BlueZ 4/4] tools/mesh: Implement model group subscription commands

This adds implementation for:
Config Model Subscription Delete
Config Model Subscription Delete All
Config Model Subscription Overwrite

and adds handling vendor model IDs for
Config Model Subscription Add
Config Model Subscription Get
---
tools/mesh/cfgcli.c | 105 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 86 insertions(+), 19 deletions(-)

diff --git a/tools/mesh/cfgcli.c b/tools/mesh/cfgcli.c
index f9aaf137d..cfa573de3 100644
--- a/tools/mesh/cfgcli.c
+++ b/tools/mesh/cfgcli.c
@@ -246,14 +246,15 @@ static uint32_t print_mod_id(uint8_t *data, bool vendor, const char *offset)

if (!vendor) {
mod_id = get_le16(data);
- bt_shell_printf("%sModel Id\t%4.4x\n", offset, mod_id);
+ bt_shell_printf("%sModel ID\t%4.4x\n", offset, mod_id);
mod_id = VENDOR_ID_MASK | mod_id;
} else {
mod_id = get_le16(data + 2);
- bt_shell_printf("%sModel Id\t%4.4x %4.4x\n", offset,
+ bt_shell_printf("%sModel ID\t%4.4x %4.4x\n", offset,
get_le16(data), mod_id);
mod_id = get_le16(data) << 16 | mod_id;
}
+
return mod_id;
}

@@ -606,13 +607,29 @@ static bool msg_recvd(uint16_t src, uint16_t idx, uint8_t *data,
src, mesh_status_str(data[0]));

bt_shell_printf("Element Addr\t%4.4x\n", get_le16(data + 1));
- bt_shell_printf("Model ID\t%4.4x\n", get_le16(data + 3));
+ print_mod_id(data + 3, false, "");

for (i = 5; i < len; i += 2)
bt_shell_printf("Subscr Addr\t%4.4x\n",
get_le16(data + i));
break;

+ case OP_CONFIG_VEND_MODEL_SUB_LIST:
+ if (len < 7)
+ return true;
+
+ bt_shell_printf("\nNode %4.4x Subscription List status %s\n",
+ src, mesh_status_str(data[0]));
+
+ bt_shell_printf("Element Addr\t%4.4x\n", get_le16(data + 1));
+ print_mod_id(data + 3, true, "");
+
+ for (i = 7; i < len; i += 2)
+ bt_shell_printf("Subscr Addr\t%4.4x\n",
+ get_le16(data + i));
+ break;
+
+
/* Per Mesh Profile 4.3.2.50 */
case OP_MODEL_APP_LIST:
if (len < 5)
@@ -1241,60 +1258,104 @@ static void cmd_pub_get(int argc, char *argv[])
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

-static void cmd_sub_add(int argc, char *argv[])
+static void subscription_cmd(int argc, char *argv[], uint32_t opcode)
{
uint16_t n;
uint8_t msg[32];
int parm_cnt;

- n = mesh_opcode_set(OP_CONFIG_MODEL_SUB_ADD, msg);
+ n = mesh_opcode_set(opcode, msg);

parm_cnt = read_input_parameters(argc, argv);
- if (parm_cnt != 3) {
+ if (parm_cnt != 3 && parm_cnt != 4) {
bt_shell_printf("Bad arguments: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

- /* Per Mesh Profile 4.3.2.19 */
/* Element Address */
put_le16(parms[0], msg + n);
n += 2;
/* Subscription Address */
put_le16(parms[1], msg + n);
n += 2;
- /* SIG Model ID */
- put_le16(parms[2], msg + n);
- n += 2;

- if (!config_send(msg, n, OP_CONFIG_MODEL_SUB_ADD))
+ /* Model ID */
+ n += put_model_id(msg + n, &parms[2], parm_cnt == 4);
+
+ if (!config_send(msg, n, opcode))
return bt_shell_noninteractive_quit(EXIT_FAILURE);

return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

-static void cmd_sub_get(int argc, char *argv[])
+static void cmd_sub_add(int argc, char *argv[])
+{
+ subscription_cmd(argc, argv, OP_CONFIG_MODEL_SUB_ADD);
+}
+
+static void cmd_sub_del(int argc, char *argv[])
+{
+ subscription_cmd(argc, argv, OP_CONFIG_MODEL_SUB_DELETE);
+}
+
+static void cmd_sub_ovwrt(int argc, char *argv[])
+{
+ subscription_cmd(argc, argv, OP_CONFIG_MODEL_SUB_OVERWRITE);
+}
+
+static void cmd_sub_del_all(int argc, char *argv[])
{
uint16_t n;
uint8_t msg[32];
int parm_cnt;

- n = mesh_opcode_set(OP_CONFIG_MODEL_SUB_GET, msg);
+ n = mesh_opcode_set(OP_CONFIG_MODEL_SUB_DELETE_ALL, msg);

parm_cnt = read_input_parameters(argc, argv);
- if (parm_cnt != 2) {
+ if (parm_cnt != 2 && parm_cnt != 3) {
bt_shell_printf("Bad arguments: %s\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}

- /* Per Mesh Profile 4.3.2.27 */
/* Element Address */
put_le16(parms[0], msg + n);
n += 2;
+
/* Model ID */
- put_le16(parms[1], msg + n);
+ n += put_model_id(msg + n, &parms[1], parm_cnt == 3);
+
+ if (!config_send(msg, n, OP_CONFIG_MODEL_SUB_DELETE_ALL))
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_sub_get(int argc, char *argv[])
+{
+ uint16_t n;
+ uint8_t msg[32];
+ int parm_cnt;
+ bool vendor;
+ uint32_t opcode;
+
+ parm_cnt = read_input_parameters(argc, argv);
+ if (parm_cnt != 2 && parm_cnt != 3) {
+ bt_shell_printf("Bad arguments: %s\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ vendor = (parm_cnt == 3);
+ opcode = !vendor ? OP_CONFIG_MODEL_SUB_GET :
+ OP_CONFIG_VEND_MODEL_SUB_GET;
+ n = mesh_opcode_set(opcode, msg);
+
+ /* Element Address */
+ put_le16(parms[0], msg + n);
n += 2;
+ /* Model ID */
+ n += put_model_id(msg + n, &parms[1], vendor);

- if (!config_send(msg, n, OP_CONFIG_MODEL_SUB_GET))
+ if (!config_send(msg, n, opcode))
return bt_shell_noninteractive_quit(EXIT_FAILURE);

return bt_shell_noninteractive_quit(EXIT_SUCCESS);
@@ -1564,9 +1625,15 @@ static const struct bt_shell_menu cfg_menu = {
"Set heartbeat subscribe"},
{"hb-sub-get", NULL, cmd_hb_sub_get,
"Get heartbeat subscribe"},
- {"sub-add", "<ele_addr> <sub_addr> <model_id>", cmd_sub_add,
+ {"sub-add", "<ele_addr> <sub_addr> <model_id> [vendor]", cmd_sub_add,
"Add subscription"},
- {"sub-get", "<ele_addr> <model_id>", cmd_sub_get,
+ {"sub-del", "<ele_addr> <sub_addr> <model_id> [vendor]", cmd_sub_del,
+ "Delete subscription"},
+ {"sub-wrt", "<ele_addr> <sub_addr> <model_id> [vendor]", cmd_sub_ovwrt,
+ "Overwrite subscription"},
+ {"sub-del-all", "<ele_addr> <model_id> [vendor]", cmd_sub_del_all,
+ "Delete subscription"},
+ {"sub-get", "<ele_addr> <model_id> [vendor]", cmd_sub_get,
"Get subscription"},
{"node-reset", NULL, cmd_node_reset,
"Reset a node and remove it from network"},
--
2.21.1

2020-01-22 16:49:16

by Gix, Brian

[permalink] [raw]
Subject: Re: [PATCH BlueZ 0/4] Add functionality to mesh-cfgclient tool

Patchset Applied
On Fri, 2020-01-17 at 20:22 -0800, Inga Stotland wrote:
> This set of patches adds more functionality to mesh configuratioon tool
> as well as a bit of code tightening.
>
>
> Inga Stotland (4):
> tools/mesh: Refactor code for generating model ID
> tools/mesh: Add length checks for rxed messages
> tools/mesh: Add support for Vendor Model App Get/List
> tools/mesh: Implement model group subscription commands
>
> tools/mesh/cfgcli.c | 233 ++++++++++++++++++++++++++++++--------------
> 1 file changed, 159 insertions(+), 74 deletions(-)
>