* [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service
@ 2025-07-01 14:22 Christian Eggers
2025-07-01 14:22 ` [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers
` (6 more replies)
0 siblings, 7 replies; 22+ messages in thread
From: Christian Eggers @ 2025-07-01 14:22 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland, Christian Eggers
This series adds initial support for the GATT Proxy Service feature.
This allows provision and management of MESH devices from mobile phones.
Before continuing, I would be happy to get some feedback about this initial
part (and about what has to be done in order to mainline it). Any help for
doing the missing features (e.g. relaying between GATT and ADV) would be
highly appreciated.
What seems to work:
- Provisioning from another Linux computer via meshctl (sometimes I need
to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>'
if it doesn't work on the first try).
- Reading the composition (composition-get in meshctl). It looks like
meshctl tries to connect to the GATT Proxy Service automatically after
provisioning, but I have to enter 'connect' several times in order to
get a connection. You also need to manually set the target unicast
address ('target' command in 'config' menu).
- Adding extra network keys (Advertising for GATT Proxy Service cycles
between different network ids every 3 seconds).
- Transferring and binding of application keys.
- Using the OnOff client in meshctl (you again have to set the unicast
address in the 'onoff' menu).
- Provisioning and configuration of an OnOff device from iOS (using
the SMART+ app from Ledvance). This app requires at least basic
support for proxy configuration messages (patch 4/4).
What maybe added later:
- Proper selection of the output interface (GATT vs. ADV) when sending
messages.
- Relaying of network messages / beacons between GATT and ADV
interfaces (in order to access further devices via the proxy).
Note: I don't use the test-join script, because python3-pygobject is
(currently) not available on my (embedded) system. Instead, I use a
custom C++ program for the 'Join' and 'Attach' D-Bus calls.
^ permalink raw reply [flat|nested] 22+ messages in thread* [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers @ 2025-07-01 14:22 ` Christian Eggers 2025-07-01 16:02 ` Initial support for GATT Proxy Service bluez.test.bot 2025-07-01 14:22 ` [RFC PATCH BlueZ 2/4] mesh: add support for provisioning via GATT Christian Eggers ` (5 subsequent siblings) 6 siblings, 1 reply; 22+ messages in thread From: Christian Eggers @ 2025-07-01 14:22 UTC (permalink / raw) To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland, Christian Eggers I cannot find a specific interval for sending unprovisioned device beacons in MshPRT_v1.1, section 3.10.2/5.2.1. The current interval of 500 ms seems to cause interferience with normal advertising messages, maybe this is controller dependent (seen on RTL8761BU). A beacon interval of 1000 ms should be sufficient for scanning (e.g. by a provisioner) while allowing normal advertising messages to be transmitted. --- mesh/prov-acceptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index 27d4964316b3..78304515ed16 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -793,7 +793,7 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, memcpy(beacon + 2, uuid, 16); /* Infinitely Beacon until Canceled, or Provisioning Starts */ - result = mesh_send_pkt(0, 500, beacon, len); + result = mesh_send_pkt(0, 1000, beacon, len); if (!result) goto error_fail; -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* RE: Initial support for GATT Proxy Service 2025-07-01 14:22 ` [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers @ 2025-07-01 16:02 ` bluez.test.bot 0 siblings, 0 replies; 22+ messages in thread From: bluez.test.bot @ 2025-07-01 16:02 UTC (permalink / raw) To: linux-bluetooth, ceggers [-- Attachment #1: Type: text/plain, Size: 38267 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=977765 ---Test result--- Test Summary: CheckPatch PENDING 0.24 seconds GitLint PENDING 0.44 seconds BuildEll PASS 20.29 seconds BluezMake FAIL 91.57 seconds MakeCheck FAIL 3208.10 seconds MakeDistcheck FAIL 58.50 seconds CheckValgrind FAIL 64.58 seconds CheckSmatch FAIL 194.78 seconds bluezmakeextell FAIL 102.96 seconds IncrementalBuild PENDING 0.31 seconds ScanBuild FAIL 241.03 seconds Details ############################## Test: CheckPatch - PENDING Desc: Run checkpatch.pl script Output: ############################## Test: GitLint - PENDING Desc: Run gitlint Output: ############################## Test: BluezMake - FAIL Desc: Build BlueZ Output: tools/mgmt-tester.c: In function ‘main’: tools/mgmt-tester.c:12907:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12907 | int main(int argc, char *argv[]) | ^~~~ mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... mesh/net.c: In function ‘send_msg_pkt_oneshot’: mesh/net.c:2285:27: error: variable ‘info’ set but not used [-Werror=unused-but-set-variable] 2285 | struct mesh_io_send_info info; | ^~~~ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net.o] Error 1 make: *** [Makefile:4701: all] Error 2 ############################## Test: MakeCheck - FAIL Desc: Run Bluez Make Check Output: unit/test-avdtp.c: In function ‘main’: unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 766 | int main(int argc, char *argv[]) | ^~~~ unit/test-avrcp.c: In function ‘main’: unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 989 | int main(int argc, char *argv[]) | ^~~~ mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make: *** [Makefile:12368: check] Error 2 ############################## Test: MakeDistcheck - FAIL Desc: Run Bluez Make Distcheck Output: Package cups was not found in the pkg-config search path. Perhaps you should add the directory containing `cups.pc' to the PKG_CONFIG_PATH environment variable No package 'cups' found ../../mesh/gatt-service.c: In function ‘chrc_write_value_call’: ../../mesh/gatt-service.c:303:53: error: parameter name omitted 303 | static struct l_dbus_message *chrc_write_value_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ ../../mesh/gatt-service.c: In function ‘chrc_acquire_notify_call’: ../../mesh/gatt-service.c:432:56: error: parameter name omitted 432 | static struct l_dbus_message *chrc_acquire_notify_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ ../../mesh/gatt-service.c: In function ‘adv_release_call’: ../../mesh/gatt-service.c:734:48: error: parameter name omitted 734 | static struct l_dbus_message *adv_release_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ make[2]: *** [Makefile:7885: mesh/gatt-service.o] Error 1 make[2]: *** Waiting for unfinished jobs.... make[1]: *** [Makefile:4701: all] Error 2 make: *** [Makefile:12289: distcheck] Error 1 ############################## Test: CheckValgrind - FAIL Desc: Run Bluez Make Check with Valgrind Output: tools/mgmt-tester.c: In function ‘main’: tools/mgmt-tester.c:12907:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12907 | int main(int argc, char *argv[]) | ^~~~ mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:12368: check] Error 2 ############################## Test: CheckSmatch - FAIL Desc: Run smatch tool with source Output: src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:830:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1323:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1354:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:317:25: warning: array of flexible structures src/shared/bap.c: note: in included file: ./src/shared/ascs.h:88:25: warning: array of flexible structures src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:830:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1323:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1354:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:317:25: warning: array of flexible structures src/shared/bap.c: note: in included file: ./src/shared/ascs.h:88:25: warning: array of flexible structures src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' tools/mesh-cfgtest.c:1453:17: warning: unknown escape sequence: '\%' tools/sco-tester.c: note: in included file: ./lib/bluetooth.h:232:15: warning: array of flexible structures ./lib/bluetooth.h:237:31: warning: array of flexible structures tools/bneptest.c:634:39: warning: unknown escape sequence: '\%' tools/seq2bseq.c:57:26: warning: Variable length array is used. tools/obex-client-tool.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' android/avctp.c:505:34: warning: Variable length array is used. android/avctp.c:556:34: warning: Variable length array is used. unit/test-avrcp.c:373:26: warning: Variable length array is used. unit/test-avrcp.c:398:26: warning: Variable length array is used. unit/test-avrcp.c:414:24: warning: Variable length array is used. android/avrcp-lib.c:1085:34: warning: Variable length array is used. android/avrcp-lib.c:1583:34: warning: Variable length array is used. android/avrcp-lib.c:1612:34: warning: Variable length array is used. android/avrcp-lib.c:1638:34: warning: Variable length array is used. mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4701: all] Error 2 ############################## Test: bluezmakeextell - FAIL Desc: Build Bluez with External ELL Output: mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4701: all] Error 2 ############################## Test: IncrementalBuild - PENDING Desc: Incremental build with the patches in the series Output: ############################## Test: ScanBuild - FAIL Desc: Run Scan Build Output: src/shared/gatt-client.c:451:21: warning: Use of memory after it is freed gatt_db_unregister(op->client->db, op->db_id); ^~~~~~~~~~ src/shared/gatt-client.c:696:2: warning: Use of memory after it is freed discovery_op_complete(op, false, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:996:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1102:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1296:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1361:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1636:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1641:2: warning: Use of memory after it is freed discover_all(op); ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2147:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2155:8: warning: Use of memory after it is freed discovery_op_ref(op), ^~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3180:2: warning: Use of memory after it is freed complete_write_long_op(req, success, 0, false); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3202:2: warning: Use of memory after it is freed request_unref(req); ^~~~~~~~~~~~~~~~~~ 12 warnings generated. src/shared/bap.c:1528:8: warning: Use of memory after it is freed bap = bt_bap_ref_safe(bap); ^~~~~~~~~~~~~~~~~~~~ src/shared/bap.c:2309:20: warning: Use of memory after it is freed return queue_find(stream->bap->streams, NULL, stream); ^~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/shared/gatt-client.c:451:21: warning: Use of memory after it is freed gatt_db_unregister(op->client->db, op->db_id); ^~~~~~~~~~ src/shared/gatt-client.c:696:2: warning: Use of memory after it is freed discovery_op_complete(op, false, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:996:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1102:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1296:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1361:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1636:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1641:2: warning: Use of memory after it is freed discover_all(op); ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2147:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2155:8: warning: Use of memory after it is freed discovery_op_ref(op), ^~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3180:2: warning: Use of memory after it is freed complete_write_long_op(req, success, 0, false); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3202:2: warning: Use of memory after it is freed request_unref(req); ^~~~~~~~~~~~~~~~~~ 12 warnings generated. tools/hciattach.c:817:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 10)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:865:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:887:8: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 10)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:909:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:930:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:974:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 6)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 warnings generated. src/shared/bap.c:1528:8: warning: Use of memory after it is freed bap = bt_bap_ref_safe(bap); ^~~~~~~~~~~~~~~~~~~~ src/shared/bap.c:2309:20: warning: Use of memory after it is freed return queue_find(stream->bap->streams, NULL, stream); ^~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/oui.c:50:2: warning: Value stored to 'hwdb' is never read hwdb = udev_hwdb_unref(hwdb); ^ ~~~~~~~~~~~~~~~~~~~~~ src/oui.c:53:2: warning: Value stored to 'udev' is never read udev = udev_unref(udev); ^ ~~~~~~~~~~~~~~~~ 2 warnings generated. tools/rfcomm.c:234:3: warning: Value stored to 'i' is never read i = execvp(cmdargv[0], cmdargv); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:234:7: warning: Null pointer passed to 1st parameter expecting 'nonnull' i = execvp(cmdargv[0], cmdargv); ^~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:354:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd' if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:497:14: warning: Assigned value is garbage or undefined req.channel = raddr.rc_channel; ^ ~~~~~~~~~~~~~~~~ tools/rfcomm.c:515:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd' if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 warnings generated. tools/hcidump.c:180:9: warning: Potential leak of memory pointed to by 'dp' if (fds[i].fd == sock) ^~~ tools/hcidump.c:248:17: warning: Assigned value is garbage or undefined dh->ts_sec = htobl(frm.ts.tv_sec); ^ ~~~~~~~~~~~~~~~~~~~~ tools/hcidump.c:326:9: warning: 1st function call argument is an uninitialized value if (be32toh(dp.flags) & 0x02) { ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:341:20: warning: 1st function call argument is an uninitialized value frm.data_len = be32toh(dp.len); ^~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:346:14: warning: 1st function call argument is an uninitialized value opcode = be32toh(dp.flags) & 0xffff; ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:384:17: warning: Assigned value is garbage or undefined frm.data_len = btohs(dh.len); ^ ~~~~~~~~~~~~~ tools/hcidump.c:394:11: warning: Assigned value is garbage or undefined frm.len = frm.data_len; ^ ~~~~~~~~~~~~ tools/hcidump.c:398:9: warning: 1st function call argument is an uninitialized value ts = be64toh(ph.ts); ^~~~~~~~~~~~~~ /usr/include/endian.h:51:22: note: expanded from macro 'be64toh' # define be64toh(x) __bswap_64 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:403:13: warning: 1st function call argument is an uninitialized value frm.in = be32toh(dp.flags) & 0x01; ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:408:11: warning: Assigned value is garbage or undefined frm.in = dh.in; ^ ~~~~~ tools/hcidump.c:437:7: warning: Null pointer passed to 1st parameter expecting 'nonnull' fd = open(file, open_flags, 0644); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 warnings generated. tools/ciptool.c:350:7: warning: 5th function call argument is an uninitialized value sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK)); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:300:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:338:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ 3 warnings generated. tools/sdptool.c:941:26: warning: Result of 'malloc' is converted to a pointer of type 'uint32_t', which is incompatible with sizeof operand type 'int' uint32_t *value_int = malloc(sizeof(int)); ~~~~~~~~~~ ^~~~~~ ~~~~~~~~~~~ tools/sdptool.c:980:4: warning: 1st function call argument is an uninitialized value free(allocArray[i]); ^~~~~~~~~~~~~~~~~~~ tools/sdptool.c:3777:2: warning: Potential leak of memory pointed to by 'si.name' return add_service(0, &si); ^~~~~~~~~~~~~~~~~~~~~~~~~~ tools/sdptool.c:4112:4: warning: Potential leak of memory pointed to by 'context.svc' return -1; ^~~~~~~~~ 4 warnings generated. tools/avtest.c:243:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:253:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:262:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:276:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:283:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:290:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:297:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:309:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:313:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:322:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:326:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:335:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:342:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:364:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:368:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:377:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:381:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:394:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:398:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:405:4: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:415:4: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:580:3: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:588:3: warning: Value stored to 'len' is never read len = write(sk, buf, invalid ? 2 : 3); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/avtest.c:602:3: warning: Value stored to 'len' is never read len = write(sk, buf, 4 + media_transport_size); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/avtest.c:615:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:625:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:637:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:652:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:664:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:673:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:680:3: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:716:2: warning: Value stored to 'len' is never read len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed)); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 warnings generated. tools/btproxy.c:836:15: warning: Null pointer passed to 1st parameter expecting 'nonnull' tcp_port = atoi(optarg); ^~~~~~~~~~~~ tools/btproxy.c:839:8: warning: Null pointer passed to 1st parameter expecting 'nonnull' if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) ^~~~~~~~~~~~~~ 2 warnings generated. tools/iso-tester.c:1902:7: warning: Potential leak of memory pointed to by 'addr' err = bind(sk, (struct sockaddr *) addr, sizeof(*addr) + ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/iso-tester.c:1910:7: warning: Potential leak of memory pointed to by 'addr' err = bind(sk, (struct sockaddr *) addr, sizeof(*addr)); ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. tools/create-image.c:76:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:84:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:92:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:105:2: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ 4 warnings generated. tools/check-selftest.c:42:3: warning: Value stored to 'ptr' is never read ptr = fgets(result, sizeof(result), fp); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. tools/gatt-service.c:294:2: warning: 2nd function call argument is an uninitialized value chr_write(chr, value, len); ^~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. tools/btgatt-client.c:1824:2: warning: Value stored to 'argv' is never read argv += optind; ^ ~~~~~~ 1 warning generated. tools/btgatt-server.c:1212:2: warning: Value stored to 'argv' is never read argv -= optind; ^ ~~~~~~ 1 warning generated. tools/obex-server-tool.c:133:13: warning: Null pointer passed to 1st parameter expecting 'nonnull' data->fd = open(name, O_WRONLY | O_CREAT | O_NOCTTY, 0600); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/obex-server-tool.c:192:13: warning: Null pointer passed to 1st parameter expecting 'nonnull' data->fd = open(name, O_RDONLY | O_NOCTTY, 0); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. tools/btpclientctl.c:402:3: warning: Value stored to 'bit' is never read bit = 0; ^ ~ tools/btpclientctl.c:1655:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull' memcpy(cp->data, ad_data, ad_len); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t' pElem = malloc(sizeof(uint16_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t' pElem = malloc(sizeof(uint32_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ 2 warnings generated. android/avrcp-lib.c:1968:3: warning: 1st function call argument is an uninitialized value g_free(text[i]); ^~~~~~~~~~~~~~~ 1 warning generated. profiles/audio/avdtp.c:893:25: warning: Use of memory after it is freed session->prio_queue = g_slist_remove(session->prio_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/audio/avdtp.c:900:24: warning: Use of memory after it is freed session->req_queue = g_slist_remove(session->req_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. profiles/audio/a2dp.c:375:8: warning: Use of memory after it is freed if (!cb->resume_cb) ^~~~~~~~~~~~~ profiles/audio/a2dp.c:3269:20: warning: Access to field 'starting' results in a dereference of a null pointer (loaded from variable 'stream') stream->starting = TRUE; ~~~~~~ ^ profiles/audio/a2dp.c:3272:8: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream') if (!stream->suspending && stream->suspend_timer) { ^~~~~~~~~~~~~~~~~~ profiles/audio/a2dp.c:3332:22: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream') stream->suspending = TRUE; ~~~~~~ ^ 4 warnings generated. profiles/audio/avrcp.c:1961:2: warning: Value stored to 'operands' is never read operands += sizeof(*pdu); ^ ~~~~~~~~~~~~ 1 warning generated. profiles/health/hdp.c:644:3: warning: Use of memory after it is freed hdp_tmp_dc_data_unref(dc_data); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/health/hdp.c:800:19: warning: Use of memory after it is freed path = g_strdup(chan->path); ^~~~~~~~~~ profiles/health/hdp.c:1779:6: warning: Use of memory after it is freed hdp_tmp_dc_data_ref(hdp_conn), ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/health/hdp.c:1836:30: warning: Use of memory after it is freed reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError", ^~~~~~~~~ 4 warnings generated. profiles/health/hdp_util.c:1052:2: warning: Use of memory after it is freed conn_data->func(conn_data->data, gerr); ^~~~~~~~~~~~~~~ 1 warning generated. attrib/gatt.c:970:2: warning: Potential leak of memory pointed to by 'long_write' return prepare_write(long_write); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. profiles/audio/bap.c:1024:18: warning: Access to field 'data' results in a dereference of a null pointer (loaded from field 'ep') bap_update_cigs(setup->ep->data); ^~~~~~~~~~~~~~~ 1 warning generated. src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t' pElem = malloc(sizeof(uint16_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t' pElem = malloc(sizeof(uint32_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ 2 warnings generated. src/sdp-client.c:353:14: warning: Access to field 'cb' results in a dereference of a null pointer (*ctxt)->cb = cb; ~~~~~~~~~~~~^~~~ 1 warning generated. src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:300:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:338:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ 3 warnings generated. src/gatt-database.c:1173:10: warning: Value stored to 'bits' during its initialization is never read uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING, ^~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. gobex/gobex-header.c:95:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull' memcpy(to, from, count); ^~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. gobex/gobex-transfer.c:423:7: warning: Use of memory after it is freed if (!g_slist_find(transfers, transfer)) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:886:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 886 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:904:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 904 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:7885: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4701: all] Error 2 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ 2/4] mesh: add support for provisioning via GATT 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers 2025-07-01 14:22 ` [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers @ 2025-07-01 14:22 ` Christian Eggers 2025-07-01 14:22 ` [RFC PATCH BlueZ 3/4] mesh: add GATT proxy service Christian Eggers ` (4 subsequent siblings) 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-01 14:22 UTC (permalink / raw) To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland, Christian Eggers ToDo: Consider moving dbus_client instance into separate singleton, so that no destruction/construction is necessary when switching from GATT provisioning service to GATT proxy service (see next commit). --- Makefile.mesh | 4 +- mesh/gatt-service.c | 1178 ++++++++++++++++++++++++++++++++++++++++++ mesh/gatt-service.h | 53 ++ mesh/pb-gatt.c | 173 +++++++ mesh/pb-gatt.h | 20 + mesh/prov-acceptor.c | 6 + 6 files changed, 1433 insertions(+), 1 deletion(-) create mode 100644 mesh/gatt-service.c create mode 100644 mesh/gatt-service.h create mode 100644 mesh/pb-gatt.c create mode 100644 mesh/pb-gatt.h diff --git a/Makefile.mesh b/Makefile.mesh index e4c9fa6a32e6..700d64fe9293 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -35,10 +35,12 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/prov-acceptor.c mesh/prov-initiator.c \ mesh/manager.h mesh/manager.c \ mesh/pb-adv.h mesh/pb-adv.c \ + mesh/pb-gatt.h mesh/pb-gatt.c \ mesh/keyring.h mesh/keyring.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ - mesh/mesh-defs.h + mesh/mesh-defs.h \ + mesh/gatt-service.h mesh/gatt-service.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/gatt-service.c b/mesh/gatt-service.c new file mode 100644 index 000000000000..8f3ace178f9d --- /dev/null +++ b/mesh/gatt-service.c @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> // memcpy(), strerror() +#include <sys/socket.h> // SOCK_SEQPACKET, SOCK_NONBLOCK, + // AF_UNIX, SOCK_CLOEXEC, MSG_NOSIGNAL, + // struct msghdr, + // socketpair(), sendmsg() +#include <sys/types.h> // struct iovec +#include <unistd.h> // close() + +#include <ell/dbus.h> +#include <ell/dbus-client.h> +#include <ell/dbus-service.h> +#include <ell/idle.h> +#include <ell/io.h> +#include <ell/log.h> +#include <ell/util.h> // L_ARRAY_SIZE(), + // l_new(), l_free() + +#include "mesh/dbus.h" // dbus_get_bus(), + // dbus_append_byte_array(), + // dbus_error() +#include "mesh/error.h" // MESH_ERROR_INVALID_ARGS +#include "mesh/util.h" // print_packet() +#include "mesh/gatt-service.h" + +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define BLUEZ_MESH_GATT_PATH BLUEZ_MESH_PATH "/gatt" +#define BLUEZ_MESH_SERVICE_PATH BLUEZ_MESH_GATT_PATH "/service" +#define BLUEZ_MESH_CHRC_DATA_IN_PATH BLUEZ_MESH_SERVICE_PATH "/data_in" +#define BLUEZ_MESH_CHRC_DATA_OUT_PATH BLUEZ_MESH_SERVICE_PATH "/data_out" +/* + * Advertising should NOT be handled by provisioning's object manager, so + * we cannot use a child element of BLUEZ_MESH_GATT_PATH. + */ +#define BLUEZ_MESH_GATT_ADV_PATH BLUEZ_MESH_PATH "/gatt_adv" + +#define GATT_MGR_IFACE "org.bluez.GattManager1" +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1" + +#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" +#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1" + +#define GATT_MTU 23 + +struct gatt_service; +struct characterstic +{ + const char *uuid; + const char * const *flags; + struct gatt_service *service; +}; + +enum write_value_type { + WRITE_VALUE_TYPE_COMMAND, + WRITE_VALUE_TYPE_REQUEST, + WRITE_VALUE_TYPE_RELIABLE +}; + +enum link_type { + LINK_TYPE_BR_EDR, + LINK_TYPE_LE +}; + +struct write_value_options { + const char *device; + enum link_type link; + enum write_value_type type; + uint16_t offset; + uint16_t mtu; + bool prepare_authorize; +}; + +struct acquire_notify_options { + const char *device; + enum link_type link; + uint16_t mtu; +}; + +/* MshPRT_v1.1, section 6.3.1, SAR field */ +enum proxy_pdu_sar { + PROXY_PDU_SAR_CMPLT_MSG = 0x00, + PROXY_PDU_SAR_1ST_SEG = 0x01, + PROXY_PDU_SAR_CONT_SEG = 0x02, + PROXY_PDU_SAR_LAST_SEG = 0x03, +}; + +struct gatt_service { + const char *svc_uuid; + uint8_t max_pdu_len; + + gatt_service_notify_acquired_cb notify_acquired_cb; + gatt_service_notify_stopped_cb notify_stopped_cb; + gatt_service_rx_cb rx_cb; + gatt_service_tx_cmplt_cb tx_cmplt_cb; + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb; + struct characterstic chrc_data_in; + struct characterstic chrc_data_out; + + struct l_dbus_client *dbus_client; + struct l_dbus_proxy *dbus_proxy_gatt_mgr; + struct l_dbus_proxy *dbus_proxy_le_adv_mgr; + + /* + * ToDo: Check whether acceptors timeout complies with MshPRT_v1.1, + * section 5.2.2 + */ + struct l_io *notify_io; + uint16_t mtu; + uint8_t *sar; + uint8_t *sar_out; + uint8_t msg_type; + uint8_t sar_len; + void *user_data; + + gatt_destroy_cb svc_deinit_cb; + gatt_destroy_cb adv_deinit_cb; + + gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct gatt_service *gatt_service = NULL; + +static bool notify_write(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + unsigned int remaining = (service->sar + service->sar_len) + - service->sar_out; + unsigned max_size = service->mtu - 5; + struct iovec iov[2]; + struct msghdr msg; + bool more = false; + uint8_t sar_type; + int i, count; + + /* Note: One extra byte is required for sar_type */ + if (service->sar_len < max_size) { + sar_type = PROXY_PDU_SAR_CMPLT_MSG; + count = service->sar_len; + } + else if (service->sar_out == service->sar) { + sar_type = PROXY_PDU_SAR_1ST_SEG; + count = max_size - 1; + more = true; + } + else if (remaining < max_size) { + sar_type = PROXY_PDU_SAR_LAST_SEG; + count = remaining; + } + else { + sar_type = PROXY_PDU_SAR_CONT_SEG; + count = max_size - 1; + more = true; + } + + sar_type <<= 6; + sar_type |= service->msg_type; + + l_info("remaining=%u, count=%u, sar_type=0x%02x", remaining, count, sar_type); + print_packet("notify_write", service->sar_out, count); + + iov[0].iov_base = &sar_type; + iov[0].iov_len = sizeof(sar_type); + iov[1].iov_base = service->sar_out; + iov[1].iov_len = count; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = L_ARRAY_SIZE(iov); + + if (sendmsg(l_io_get_fd(service->notify_io), &msg, MSG_NOSIGNAL) < 0) + l_error("Cannot write notification data: %s", strerror(errno)); + + service->sar_out += count; + + if (!more) + more = service->tx_cmplt_cb(service->user_data); + + return more; +} + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len) +{ + if (!service || gatt_service != service) + return; + + if (len > service->max_pdu_len) { + l_error("Frame too long"); + return; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + service->sar_out = service->sar; + service->msg_type = msg_type; + print_packet("TX", service->sar, service->sar_len); + l_io_set_write_handler(service->notify_io, notify_write, service, NULL); +} + +static bool svc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct gatt_service *service = user_data; + +// l_info("svc_uuid_getter"); + return l_dbus_message_builder_append_basic(builder, 's', + service->svc_uuid); +} + +static bool svc_primary_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + bool primary = true; + +// l_info("svc_primary_getter"); + return l_dbus_message_builder_append_basic(builder, 'b', &primary); +} + +static void setup_gatt_svc_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_property(iface, "UUID", 0, "s", svc_uuid_getter, NULL); + l_dbus_interface_property(iface, "Primary", 0, "b", svc_primary_getter, + NULL); +} + +static bool parse_write_value_options(struct l_dbus_message_iter *itr, + struct write_value_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->type = WRITE_VALUE_TYPE_COMMAND; + opts->offset = 0; + opts->mtu = 0; + opts->prepare_authorize = false; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "type")) { + const char *type; + + if (!l_dbus_message_iter_get_variant(&var, "s", &type)) + return false; + + if (!strcmp(type, "command")) + opts->type = WRITE_VALUE_TYPE_COMMAND; + else if (!strcmp(type, "request")) + opts->type = WRITE_VALUE_TYPE_REQUEST; + else if (!strcmp(type, "reliable")) + opts->type = WRITE_VALUE_TYPE_RELIABLE; + else + return false; + } else if (!strcmp(key, "offset")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->offset)) + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } else if (!strcmp(key, "prepare-authorize")) { + if (!l_dbus_message_iter_get_variant(&var, "b", + &opts->prepare_authorize)) + return false; + } + } + + return true; +} + +static struct l_dbus_message *chrc_write_value_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter iter_data, dict; + struct write_value_options opts; + enum proxy_pdu_sar sar; + uint8_t msg_type; + uint8_t *data; + uint32_t len; + int i; + + if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_data, &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_write_value_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > service->max_pdu_len) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + l_info("chrc_write_value_call(type=%u, offset=%u, mtu=%u)", opts.type, opts.offset, opts.mtu); + print_packet("WriteValue", data, len); + + if (len < 1) + return l_dbus_message_new_method_return(msg); + + sar = (data[0] >> 6) & 0x03; + msg_type = data[0] & 0x3F; + + switch (sar) { + case PROXY_PDU_SAR_CMPLT_MSG: + service->rx_cb(service->user_data, msg_type, data, len); + break; + + case PROXY_PDU_SAR_1ST_SEG: + if (len > service->max_pdu_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + break; + + case PROXY_PDU_SAR_CONT_SEG: + case PROXY_PDU_SAR_LAST_SEG: { + if (len - 1 > service->max_pdu_len - service->sar_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar + service->sar_len, + data + 1, len - 1); + service->sar_len += len - 1; + + if (sar == PROXY_PDU_SAR_LAST_SEG) { + uint8_t sar_len = service->sar_len; + + /* reused by gatt_service_tx */ + service->sar_len = 0; + service->rx_cb(service->user_data, msg_type, + service->sar, sar_len); + } + + break; + } + } + + return l_dbus_message_new_method_return(msg); +} + +static bool parse_acquire_notify_options(struct l_dbus_message_iter *itr, + struct acquire_notify_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->mtu = 0; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } + } + + return true; +} + +static void notify_disconnected(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + + if (service != gatt_service) + return; + + l_info("notify_disconnected"); + + if (!service->notify_io) + return; + + if (service->notify_stopped_cb) + service->notify_stopped_cb(service->user_data); +} + +static struct l_dbus_message *chrc_acquire_notify_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter dict; + struct acquire_notify_options opts; + struct l_dbus_message *reply; + int fds[2]; + + l_info("AcquireNotify"); + + if (!l_dbus_message_get_arguments(msg, "a{sv}", &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_acquire_notify_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (socketpair(AF_UNIX, + SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, fds) == -1) + return dbus_error(msg, MESH_ERROR_FAILED, + "Cannot create socket"); + + service->notify_io = l_io_new(fds[0]); + l_io_set_close_on_destroy(service->notify_io, true); + l_io_set_disconnect_handler(service->notify_io, notify_disconnected, + service, NULL); + service->mtu = opts.mtu; + l_info("AcquireNotify: mtu=%u", opts.mtu); + + if (service->notify_acquired_cb) + service->notify_acquired_cb(service->user_data); + + reply = l_dbus_message_new_method_return(msg); + + /* l_dbus_message_builder_append_basic() cannot append UNIX FDs */ + l_dbus_message_set_arguments(reply, "hq", fds[1], service->mtu); + /* + * file descriptor for bluetoothd has just been dup'ed and must be + * closed here in order to get disconnect event after GATT notifications + * notifications have been disabled. + */ + close(fds[1]); + + return reply; +} + +static bool chrc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + + const char *path = l_dbus_message_get_path(msg); + const char *interface = l_dbus_message_get_interface(msg); + const char *member = l_dbus_message_get_member(msg); + +// l_info("chrc_uuid_getter(path=%s, interface=%s, member=%s)", path, interface, member); + return l_dbus_message_builder_append_basic(builder, 's', chr->uuid); +} + +static bool chrc_service_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("chrc_service_getter"); + return l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_SERVICE_PATH); +} + +static bool chrc_notify_acquired_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const struct gatt_service *service = chr->service; + bool notifying = !!service->notify_io; + +// l_info("chrc_notify_acquired_getter"); + + return l_dbus_message_builder_append_basic(builder, 'b', ¬ifying); +} + +static bool chrc_flags_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const char * const *flag = chr->flags; + +// l_info("chrc_flags_getter"); + + l_dbus_message_builder_enter_array(builder, "s"); + + while (*flag) + l_dbus_message_builder_append_basic(builder, 's', *flag++); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static void setup_gatt_chrc_interface(struct l_dbus_interface *iface) +{ + /* Note: "ReadValue" method is not supported. */ + l_dbus_interface_method(iface, "WriteValue" , 0, chrc_write_value_call, + "", "aya{sv}", + "value", "options"); + l_dbus_interface_method(iface, "AcquireNotify", 0, + chrc_acquire_notify_call, + "hq", "a{sv}", + "fd", "mtu", + "options"); + l_dbus_interface_property(iface, "UUID" , 0, "s", chrc_uuid_getter, + NULL); + l_dbus_interface_property(iface, "Service", 0, "o", chrc_service_getter, + NULL); + l_dbus_interface_property(iface, "NotifyAcquired", 0, "b", + chrc_notify_acquired_getter, NULL); + l_dbus_interface_property(iface, "Flags" , 0, "as", chrc_flags_getter, + NULL); +} + +static void register_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of GATT application failed: %s", error); + } +} + +static bool gatt_svc_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, GATT_SERVICE_IFACE, + setup_gatt_svc_interface, + NULL, false)) { + l_error("Cannot register " GATT_SERVICE_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_register_interface(dbus, GATT_CHRC_IFACE, + setup_gatt_chrc_interface, + NULL, false)) { + l_error("Cannot register " GATT_CHRC_IFACE " interface"); + goto error_unregister_svc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE, service)) { + l_error("Cannot add GATT service"); + goto error_unregister_chrc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_in)) { + l_error("Cannot add GATT Data In characteristic"); + goto error_remove_svc; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_out)) { + l_error("Cannot add GATT Data Out characteristic"); + goto error_remove_data_in_chrc; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_data_out_chrc; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterApplication", + register_app_setup, + register_app_reply, + NULL, NULL)) { + l_error("Cannot register GATT application"); + goto error_disable_object_manager; + } + + return true; + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_data_out_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + +error_remove_data_in_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + +error_remove_svc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + +error_unregister_chrc_iface: + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + +error_unregister_svc_iface: + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + +error_return: + return false; +} + +static void unregister_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of GATT application failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + + if (service->svc_deinit_cb) + service->svc_deinit_cb(service); +} + +static void gatt_svc_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->svc_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_gatt_mgr, + "UnregisterApplication", + unregister_app_setup, + unregister_app_reply, + service, NULL)) { + l_error("Cannot unregister GATT application"); + } +} + +static struct l_dbus_message *adv_release_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + l_info("ADV Release"); + + return NULL; +} + +static bool adv_type_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("adv_type_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "peripheral"); +} + +static bool adv_svc_uuids_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_uuids_getter"); + l_dbus_message_builder_enter_array(builder, "s"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_svc_data_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_data_getter"); + l_dbus_message_builder_enter_array(builder, "{sv}"); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_enter_variant(builder, "ay"); + + if (!service->fill_adv_service_data_cb(service->user_data, builder)) + return false; + + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_local_name_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_local_name_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "Test"); +} + +static bool adv_duration_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_duration_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &duration); +} + +static bool adv_timeout_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t timeout = 1000; + +// l_info("adv_timeout_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &timeout); +} + +static bool adv_min_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t min_interval_ms = 1000; + +// l_info("adv_min_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &min_interval_ms); +} + +static bool adv_max_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t max_interval_ms = 1000; + +// l_info("adv_max_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &max_interval_ms); +} + +static void setup_le_adv_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Release", 0, adv_release_call, + "", ""); + l_dbus_interface_property(iface, "Type", 0, "s", + adv_type_getter, NULL); + l_dbus_interface_property(iface, "ServiceUUIDs", 0, "as", + adv_svc_uuids_getter, NULL); + l_dbus_interface_property(iface, "ServiceData", 0, "a{sv}", + adv_svc_data_getter, NULL); + l_dbus_interface_property(iface, "LocalName", 0, "s", + adv_local_name_getter, NULL); + l_dbus_interface_property(iface, "Duration", 0, "q", + adv_duration_getter, NULL); + l_dbus_interface_property(iface, "Timeout", 0, "q", + adv_timeout_getter, NULL); + l_dbus_interface_property(iface, "MinInterval", 0, "u", + adv_min_interval_getter, NULL); + l_dbus_interface_property(iface, "MaxInterval", 0, "u", + adv_max_interval_getter, NULL); +} + +static void register_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_adv_setup"); + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_GATT_ADV_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_adv_reply"); + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of LE advertising failed: %s", error); + } +} + +static bool gatt_adv_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, LE_ADVERTISEMENT_IFACE, + setup_le_adv_interface, + NULL, false)) { + l_error("Cannot register " LE_ADVERTISEMENT_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, + service)) { + l_error("Cannot add provisioner LE advertising service"); + goto error_unregister_le_adv_iface; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_ADV_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_le_adv; + } + + /* + * org.freedesktop.DBus.Properties is required for building + * propertiesChanged signals + */ + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES, NULL)) { + l_error("Cannot add LE advertising properties"); + goto error_disable_object_manager; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterAdvertisement", + register_adv_setup, + register_adv_reply, + NULL, NULL)) { + l_error("Cannot register LE advertisement"); + goto error_remove_properties_iface; + } + + return true; + +error_remove_properties_iface: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_le_adv: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + +error_unregister_le_adv_iface: + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + +error_return: + return false; +} + +static void unregister_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_adv_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_ADV_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_adv_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of LE advertisement failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + + if (service->adv_deinit_cb) + service->adv_deinit_cb(service); +} + +static void gatt_adv_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->adv_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_le_adv_mgr, + "UnregisterAdvertisement", + unregister_adv_setup, + unregister_adv_reply, + service, NULL)) { + l_error("Cannot unregister LE advertisement"); + } +} + +static void dbus_proxy_added(struct l_dbus_proxy *dbus_proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(dbus_proxy); + const char *path = l_dbus_proxy_get_path(dbus_proxy); + struct gatt_service *service = user_data; + + l_info("Proxy added: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) { + service->dbus_proxy_gatt_mgr = dbus_proxy; + gatt_svc_init(dbus_get_bus(), dbus_proxy, service); + } else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) { + service->dbus_proxy_le_adv_mgr = dbus_proxy; + gatt_adv_init(dbus_get_bus(), dbus_proxy, service); + } +} + +static void dbus_proxy_removed(struct l_dbus_proxy *proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + struct gatt_service *service = user_data; + + l_info("Proxy removed: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) + service->dbus_proxy_gatt_mgr = NULL; + else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) + service->dbus_proxy_le_adv_mgr = NULL; +} + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data) +{ + static const char *flags_data_in[] = {"write-without-response", NULL}; + static const char *flags_data_out[] = {"notify", NULL}; + + /* Only one GATT service may exist at a time (MshPRT_v1.1, chapter 7) */ + if (gatt_service) + return NULL; + + gatt_service = l_new(struct gatt_service, 1); + gatt_service->svc_uuid = svc_uuid; + gatt_service->max_pdu_len = max_pdu_len; + gatt_service->sar = l_malloc(max_pdu_len); + + gatt_service->notify_acquired_cb = notify_acquired_cb; + gatt_service->notify_stopped_cb = notify_stopped_cb; + gatt_service->rx_cb = rx_cb; + gatt_service->tx_cmplt_cb = tx_cmplt_cb; + gatt_service->fill_adv_service_data_cb = fill_adv_service_data_cb; + gatt_service->user_data = user_data; + gatt_service->mtu = GATT_MTU; + + gatt_service->chrc_data_in.uuid = chrc_data_in_uuid; + gatt_service->chrc_data_in.flags = flags_data_in; + gatt_service->chrc_data_in.service = gatt_service; + + gatt_service->chrc_data_out.uuid = chrc_data_out_uuid; + gatt_service->chrc_data_out.flags = flags_data_out; + gatt_service->chrc_data_out.service = gatt_service; + + gatt_service->dbus_client = l_dbus_client_new(dbus_get_bus(), + "org.bluez", "/org/bluez"); + + l_dbus_client_set_proxy_handlers(gatt_service->dbus_client, + dbus_proxy_added, + dbus_proxy_removed, + NULL, + gatt_service, NULL); + + return gatt_service; +} + +static void gatt_svc_destroy(void *user_data) +{ + struct gatt_service *service = user_data; + gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!gatt_service || gatt_service != service) + return; + + destroy_cb = service->destroy_cb; + destroy_data = service->destroy_data; + + l_dbus_client_destroy(service->dbus_client); + l_io_destroy(service->notify_io); + l_free(service->sar); + l_free(service); + gatt_service = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +static void gatt_svc_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + /* l_dbus_client_destroy() must not be called from dbus context */ + l_idle_oneshot(gatt_svc_destroy, service, NULL); +} + +static void gatt_adv_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + gatt_svc_deinit(service, gatt_svc_deinit_finished); +} + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data) +{ + if (!gatt_service || gatt_service != service) + return; + + /* avoid recursion */ + l_io_set_disconnect_handler(service->notify_io, NULL, NULL, NULL); + + service->destroy_cb = destroy_cb; + service->destroy_data = user_data; + gatt_adv_deinit(service, gatt_adv_deinit_finished); +} + +void gatt_service_adv_updated(struct gatt_service *service) +{ + if (!gatt_service || gatt_service != service) + return; + + l_dbus_property_changed(dbus_get_bus(), BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, "ServiceData"); +} diff --git a/mesh/gatt-service.h b/mesh/gatt-service.h new file mode 100644 index 000000000000..a37966ec15f6 --- /dev/null +++ b/mesh/gatt-service.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +/* MshPRT_v1.1, section 6.3.1 */ +enum proxy_msg_type { + PROXY_MSG_TYPE_NETWORK_PDU = 0x00, + PROXY_MSG_TYPE_MESH_BEACON = 0x01, + PROXY_MSG_TYPE_PROXY_CFG = 0x02, + PROXY_MSG_TYPE_PROV_PDU = 0x03 +}; + +typedef void (*gatt_service_notify_acquired_cb)(void *user_data); +typedef void (*gatt_service_notify_stopped_cb)(void *user_data); +typedef void (*gatt_service_rx_cb)(void *user_data, + enum proxy_msg_type messageType, + const void *data, uint16_t len); +typedef bool (*gatt_service_tx_cmplt_cb)(void *user_data); +typedef bool (*gatt_service_fill_adv_service_data_cb)(void *user_data, + struct l_dbus_message_builder *builder); + +typedef void (*gatt_destroy_cb)(void *user_data); + +struct gatt_service; + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data); + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data); + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len); +void gatt_service_adv_updated(struct gatt_service *service); diff --git a/mesh/pb-gatt.c b/mesh/pb-gatt.c new file mode 100644 index 000000000000..797dd7361ee1 --- /dev/null +++ b/mesh/pb-gatt.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <string.h> // memcpy() +#include <sys/types.h> // struct timeval [required by prov.h] + +#include <ell/dbus.h> +//#include <ell/log.h> +//#include <ell/timeout.h> +#include <ell/util.h> // l_new(), l_free() + +#include "mesh/gatt-service.h" +#include "mesh/net.h" // mesh_net_prov_caps, required by prov.h +#include "mesh/prov.h" // mesh_prov_open_func_t, + // mesh_prov_close_func_t, + // mesh_prov_receive_func_t +#include "mesh/provision.h" // PB_GATT +#include "mesh/pb-gatt.h" + +#define MESH_GATT_PROV_SVC_UUID "0x1827" +#define MESH_GATT_PROV_CHRC_DATA_IN "0x2ADB" +#define MESH_GATT_PROV_CHRC_DATA_OUT "0x2ADC" +#define MAX_PROXY_PROV_PDU_LEN 66 /* MshPRT_v1.1, section 7.1.3.1 / 7.1.3.2 */ + +struct pb_gatt_session { + mesh_prov_open_func_t open_cb; + mesh_prov_close_func_t close_cb; + mesh_prov_receive_func_t rx_cb; + mesh_prov_ack_func_t ack_cb; +// struct l_timeout *tx_timeout; + uint8_t uuid[16]; + uint16_t oob_info; + + struct gatt_service *gatt_service; + void *user_data; + + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct pb_gatt_session *pb_session = NULL; + +static void pb_gatt_tx(void *user_data, const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + gatt_service_tx(session->gatt_service, PROXY_MSG_TYPE_PROV_PDU, data, len); +} + +static void gatt_notify_acquired_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + /* + * MshPRT_v1.1, section 5.2.2: The link is opened on a PB-GATT + * bearer when the PB-GATT Client enables notifications. + */ + session->open_cb(session->user_data, pb_gatt_tx, session, PB_GATT); +} + +static void gatt_notify_stopped_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->close_cb(session->user_data, PROV_ERR_UNEXPECTED_ERR); +} + +static void gatt_rx_cb(void *user_data, enum proxy_msg_type msg_type, + const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + if (msg_type == PROXY_MSG_TYPE_PROV_PDU) + session->rx_cb(session->user_data, data + 1, len - 1); +} + +static bool gatt_tx_cmplt_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->ack_cb(session->user_data, 0 /* don't care */); + return false; +} + +static bool gatt_fill_adv_service_data_cb(void *user_data, + struct l_dbus_message_builder *builder) +{ + struct pb_gatt_session *session = user_data; + uint8_t oob_info[2]; + int i; + + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < sizeof(session->uuid); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(session->uuid[i])); + l_put_be16(session->oob_info, oob_info); + for (i = 0; i < sizeof(oob_info); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(oob_info[i])); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data) +{ + pb_session = l_new(struct pb_gatt_session, 1); + + pb_session->open_cb = open_cb; + pb_session->close_cb = close_cb; + pb_session->rx_cb = rx_cb; + pb_session->ack_cb = ack_cb; + + memcpy(pb_session->uuid, uuid, 16); + pb_session->user_data = user_data; + + pb_session->gatt_service = gatt_service_create(MESH_GATT_PROV_SVC_UUID, + MESH_GATT_PROV_CHRC_DATA_IN, + MESH_GATT_PROV_CHRC_DATA_OUT, + MAX_PROXY_PROV_PDU_LEN, + gatt_notify_acquired_cb, + gatt_notify_stopped_cb, + gatt_rx_cb, gatt_tx_cmplt_cb, + gatt_fill_adv_service_data_cb, + pb_session); + if (!pb_session->gatt_service) { + l_free(pb_session); + pb_session = NULL; + return false; + } + + return true; +} + +static void gatt_destroy_finished(void *user_data) +{ + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!pb_session || pb_session != user_data) + return; + + destroy_cb = pb_session->destroy_cb; + destroy_data = pb_session->destroy_data; + + l_free(pb_session); + pb_session = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data) +{ + if (!pb_session || pb_session->user_data != user_data) + return; + + pb_session->destroy_cb = destroy_cb; + pb_session->destroy_data = destroy_data; + gatt_service_destroy(pb_session->gatt_service, gatt_destroy_finished, + pb_session); +} diff --git a/mesh/pb-gatt.h b/mesh/pb-gatt.h new file mode 100644 index 000000000000..49d95974c4ae --- /dev/null +++ b/mesh/pb-gatt.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +typedef void (*pb_gatt_destroy_cb)(void *user_data); + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data); +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data); diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index 78304515ed16..f483c330953b 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -26,6 +26,7 @@ #include "mesh/provision.h" #include "mesh/remprv.h" #include "mesh/pb-adv.h" +#include "mesh/pb-gatt.h" #include "mesh/mesh.h" #include "mesh/agent.h" @@ -99,6 +100,7 @@ static void acceptor_free(void) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); + pb_gatt_unreg(prov, NULL, NULL); l_free(prov); prov = NULL; @@ -801,6 +803,10 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, /* Always register for PB-ADV */ result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); + + result = pb_gatt_reg(acp_prov_open, acp_prov_close, + acp_prov_rx, acp_prov_ack, uuid, + caps->oob_info, prov); } else { /* Run Device Key Refresh Procedure */ result = register_nppi_acceptor(acp_prov_open, acp_prov_close, -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ 3/4] mesh: add GATT proxy service 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers 2025-07-01 14:22 ` [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers 2025-07-01 14:22 ` [RFC PATCH BlueZ 2/4] mesh: add support for provisioning via GATT Christian Eggers @ 2025-07-01 14:22 ` Christian Eggers [not found] ` <CABUQxGwt8MjoYmx_8jF=A9qug24Dnx+rEX=5vDWP7+SGOERcMw@mail.gmail.com> 2025-07-01 14:22 ` [RFC PATCH BlueZ 4/4] mesh: initial support for proxy configuration messages via GATT Christian Eggers ` (3 subsequent siblings) 6 siblings, 1 reply; 22+ messages in thread From: Christian Eggers @ 2025-07-01 14:22 UTC (permalink / raw) To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland, Christian Eggers FIXME: net.c: determine whether data shall be sent via mesh_io or gatt_proxy --- Makefile.mesh | 3 +- mesh/gatt-proxy-svc.c | 294 ++++++++++++++++++++++++++++++++++++++++++ mesh/gatt-proxy-svc.h | 29 +++++ mesh/main.c | 3 + mesh/net-keys.c | 61 +++++++++ mesh/net-keys.h | 4 + mesh/net.c | 21 ++- mesh/net.h | 4 + mesh/prov-acceptor.c | 26 +++- 9 files changed, 440 insertions(+), 5 deletions(-) create mode 100644 mesh/gatt-proxy-svc.c create mode 100644 mesh/gatt-proxy-svc.h diff --git a/Makefile.mesh b/Makefile.mesh index 700d64fe9293..8a190f75de9d 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -40,7 +40,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h \ - mesh/gatt-service.h mesh/gatt-service.c + mesh/gatt-service.h mesh/gatt-service.c \ + mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/gatt-proxy-svc.c b/mesh/gatt-proxy-svc.c new file mode 100644 index 000000000000..e24543de718c --- /dev/null +++ b/mesh/gatt-proxy-svc.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> // memcpy() + +#include <ell/dbus.h> +#include <ell/queue.h> +#include <ell/timeout.h> +#include <ell/util.h> // ell_new(), l_free(), l_malloc() + +#include "mesh/gatt-service.h" +#include "mesh/mesh-io.h" // mesh_io_recv_func_t +#include "mesh/net.h" // mesh_net_attach_gatt(), + // mesh_net_detach_gatt() +#include "mesh/net-keys.h" // net_key_fill_adv_service_data(), + // net_key_get_next_id() +#include "mesh/util.h" // print_packet() +#include "mesh/gatt-proxy-svc.h" + +#define MESH_GATT_PROXY_SVC_UUID "0x1828" +#define MESH_GATT_PROXY_CHRC_DATA_IN "0x2ADD" +#define MESH_GATT_PROXY_CHRC_DATA_OUT "0x2ADE" + +#define MAX_PROXY_PDU_LEN 66 /* MshPRT_v1.1, section 7.2.2.2.7 */ + +struct gatt_io_reg { + mesh_io_recv_func_t cb; + void *user_data; + enum proxy_msg_type msg_type; +}; + +struct gatt_proxy_service { + struct gatt_service *gatt_service; + uint32_t current_adv_key_id; + struct l_timeout *adv_key_cycle_to; + bool txing; + struct l_queue *tx_deferred; + struct l_queue *rx_regs; +}; + +struct process_data { + enum proxy_msg_type msg_type; + const uint8_t *data; + uint8_t len; +}; + +static struct gatt_proxy_service *gatt_proxy_service; + +struct tx_deferred { + enum proxy_msg_type msg_type; + uint16_t len; + uint8_t data[]; +}; + +static struct gatt_io_reg *find_by_filter(struct l_queue *rx_regs, + enum proxy_msg_type msg_type) +{ + const struct l_queue_entry *entry; + + entry = l_queue_get_entries(rx_regs); + + for (; entry; entry = entry->next) { + struct gatt_io_reg *rx_reg = entry->data; + + if (rx_reg->msg_type == msg_type) + return rx_reg; + } + + return NULL; +} + +void gatt_proxy_service_register_recv_cb(struct gatt_proxy_service *gatt_proxy, + enum proxy_msg_type msg_type, + mesh_io_recv_func_t cb, + void *user_data) +{ + struct gatt_io_reg *rx_reg; + + if (gatt_proxy != gatt_proxy_service || !cb) + return; + + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); + + l_free(rx_reg); + l_queue_remove(gatt_proxy->rx_regs, rx_reg); + + rx_reg = l_malloc(sizeof(struct gatt_io_reg)); + rx_reg->cb = cb; + rx_reg->msg_type = msg_type; + rx_reg->user_data = user_data; + + l_queue_push_head(gatt_proxy->rx_regs, rx_reg); +} + +void +gatt_proxy_service_deregister_recv_cb(struct gatt_proxy_service *gatt_proxy, + enum proxy_msg_type msg_type) +{ + struct gatt_io_reg *rx_reg; + + if (gatt_proxy != gatt_proxy_service) + return; + + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); + + l_queue_remove(gatt_proxy->rx_regs, rx_reg); + l_free(rx_reg); +} + +void gatt_proxy_service_send(enum proxy_msg_type msg_type, + const void *data, uint8_t len) +{ + if (!gatt_proxy_service) + return; + + if (!gatt_proxy_service->txing) { + gatt_proxy_service->txing = true; + gatt_service_tx(gatt_proxy_service->gatt_service, msg_type, + data, len); + } else { + struct tx_deferred *tx_deferred; + +// print_packet("TX-Defer", data, len); + tx_deferred = l_malloc(len + sizeof(struct tx_deferred)); + tx_deferred->msg_type = msg_type; + tx_deferred->len = len; + memcpy(tx_deferred->data, data, len); + l_queue_push_tail(gatt_proxy_service->tx_deferred, tx_deferred); + } +} + +static void process_rx_callbacks(void *a, void *b) +{ + struct gatt_io_reg *rx_reg = a; + struct process_data *rx = b; + + if (rx->msg_type == rx_reg->msg_type) + rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); +} + +static void gatt_service_rx(void *user_data, enum proxy_msg_type msg_type, + const void *data, uint16_t len) +{ + struct gatt_proxy_service *gatt_proxy = user_data; + struct process_data rx = { + .msg_type = msg_type, + .data = data, + .len = len, + }; + + if (gatt_proxy != gatt_proxy_service) + return; + + print_packet("RX", data, len); + l_queue_foreach(gatt_proxy->rx_regs, process_rx_callbacks, &rx); +} + +static bool gatt_service_tx_cmplt(void *user_data) +{ + struct gatt_proxy_service *gatt_proxy = user_data; + struct tx_deferred *tx_deferred; + +// l_info("gatt_service_tx_cmplt"); + + if (gatt_proxy_service != gatt_proxy) + return false; + + if (!gatt_proxy->txing) + return false; + + gatt_proxy->txing = false; + + tx_deferred = l_queue_pop_head(gatt_proxy->tx_deferred); + if (!tx_deferred) + return false; + + gatt_proxy_service_send(tx_deferred->msg_type, + tx_deferred->data, tx_deferred->len); + l_free(tx_deferred); + return true; +} + +static bool gatt_service_fill_adv_service_data(void *user_data, + struct l_dbus_message_builder *builder) +{ + struct gatt_proxy_service *gatt_service = user_data; + + if (gatt_service != gatt_proxy_service) + return false; + + return net_key_fill_adv_service_data(gatt_service->current_adv_key_id, + builder); +} + +static void gatt_proxy_service_cycle_adv(struct l_timeout *timeout, + void *user_data) +{ + struct gatt_proxy_service *gatt_proxy = user_data; + uint32_t next_adv_key_id; + + if (gatt_proxy_service != gatt_proxy) + return; + + next_adv_key_id = net_key_get_next_id(gatt_proxy->current_adv_key_id); + if (!next_adv_key_id) + return; + + if (gatt_proxy->current_adv_key_id != next_adv_key_id) { + gatt_proxy->current_adv_key_id = next_adv_key_id; + gatt_service_adv_updated(gatt_proxy_service->gatt_service); + } + + l_timeout_modify(gatt_proxy->adv_key_cycle_to, 3); +} + +void gatt_proxy_service_set_current_adv_key(uint32_t id) +{ + if (!gatt_proxy_service) + return; + + gatt_proxy_service->current_adv_key_id = id; + gatt_service_adv_updated(gatt_proxy_service->gatt_service); +} + +void gatt_proxy_service_start(void) +{ + if (!gatt_proxy_service || gatt_proxy_service->gatt_service) + return; + + gatt_proxy_service->gatt_service = gatt_service_create( + MESH_GATT_PROXY_SVC_UUID, + MESH_GATT_PROXY_CHRC_DATA_IN, + MESH_GATT_PROXY_CHRC_DATA_OUT, + MAX_PROXY_PDU_LEN, + NULL, NULL, + gatt_service_rx, + gatt_service_tx_cmplt, + gatt_service_fill_adv_service_data, + gatt_proxy_service); + + gatt_proxy_service->adv_key_cycle_to = l_timeout_create(3, + gatt_proxy_service_cycle_adv, + gatt_proxy_service, NULL); + + mesh_net_attach_gatt(gatt_proxy_service); +} + +void gatt_proxy_service_stop(void) +{ + if (!gatt_proxy_service || !gatt_proxy_service->gatt_service) + return; + + mesh_net_detach_gatt(gatt_proxy_service); + l_timeout_remove(gatt_proxy_service->adv_key_cycle_to); + gatt_service_destroy(gatt_proxy_service->gatt_service, NULL, NULL); +} + +void gatt_proxy_service_create(void) +{ + if (gatt_proxy_service) + return; + + gatt_proxy_service = l_new(struct gatt_proxy_service, 1); + gatt_proxy_service->tx_deferred = l_queue_new(); + gatt_proxy_service->rx_regs = l_queue_new(); + + /* Check whether we have at least one key */ + if (!net_key_get_next_id(0)) + return; + + gatt_proxy_service_start(); +} + +void gatt_proxy_service_destroy(void) +{ + if (!gatt_proxy_service) + return; + + gatt_proxy_service_stop(); + + l_queue_destroy(gatt_proxy_service->rx_regs, l_free); + l_queue_destroy(gatt_proxy_service->tx_deferred, l_free); + l_free(gatt_proxy_service); + gatt_proxy_service = NULL; +} diff --git a/mesh/gatt-proxy-svc.h b/mesh/gatt-proxy-svc.h new file mode 100644 index 000000000000..5c640dc2c8ef --- /dev/null +++ b/mesh/gatt-proxy-svc.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdint.h> + +enum proxy_msg_type; +struct gatt_proxy_service; + +void gatt_proxy_service_create(void); +void gatt_proxy_service_destroy(void); +void gatt_proxy_service_start(void); +void gatt_proxy_service_stop(void); +void gatt_proxy_service_set_current_adv_key(uint32_t id); +void gatt_proxy_service_register_recv_cb(struct gatt_proxy_service *gatt_proxy, + enum proxy_msg_type msg_type, + mesh_io_recv_func_t cb, + void *user_data); +void +gatt_proxy_service_deregister_recv_cb(struct gatt_proxy_service *gatt_proxy, + enum proxy_msg_type msg_type); +void gatt_proxy_service_send(enum proxy_msg_type msg_type, + const void *data, uint8_t len); diff --git a/mesh/main.c b/mesh/main.c index 5b8af4bc7345..b423fe70b8bd 100644 --- a/mesh/main.c +++ b/mesh/main.c @@ -31,6 +31,7 @@ #include "mesh/crypto.h" #include "mesh/dbus.h" #include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/util.h" static const char *storage_dir; @@ -94,6 +95,8 @@ static void mesh_ready_callback(void *user_data, bool success) l_error("Failed to initialize mesh D-Bus resources"); l_main_quit(); } + + gatt_proxy_service_create(); } static void request_name_callback(struct l_dbus *dbus, bool success, diff --git a/mesh/net-keys.c b/mesh/net-keys.c index 42d498ce0ede..62ea4208af98 100644 --- a/mesh/net-keys.c +++ b/mesh/net-keys.c @@ -20,6 +20,7 @@ #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/net.h" #include "mesh/net-keys.h" @@ -29,6 +30,12 @@ /* This allows daemon to skip decryption on recently seen beacons */ #define BEACON_CACHE_MAX 10 +/* MshPRT_v1.1, section 7.2.2.2.1 */ +#define IDENTIFICATION_TYPE_NETWORK_ID 0x00 +#define IDENTIFICATION_TYPE_NODE_ID 0x01 +#define IDENTIFICATION_TYPE_PRV_NETWORK_ID 0x02 +#define IDENTIFICATION_TYPE_PRV_NODE_ID 0x03 + struct beacon_rx { uint8_t data[28]; uint32_t id; @@ -144,6 +151,9 @@ uint32_t net_key_add(const uint8_t flooding[16]) goto fail; key->id = ++last_flooding_id; + if (l_queue_isempty(keys)) + gatt_proxy_service_start(); + l_queue_push_tail(keys, key); return key->id; @@ -196,6 +206,9 @@ void net_key_unref(uint32_t id) l_timeout_remove(key->observe.timeout); l_queue_remove(keys, key); l_free(key); + + if (l_queue_isempty(keys)) + gatt_proxy_service_stop(); } } } @@ -663,6 +676,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, bool kr, bool ivu, return false; print_packet("Set SNB to", key->snb, 23); + gatt_proxy_service_set_current_adv_key(key->id); } l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); @@ -798,3 +812,50 @@ void net_key_cleanup(void) l_queue_destroy(beacons, l_free); beacons = NULL; } + +bool net_key_fill_adv_service_data(uint32_t id, + struct l_dbus_message_builder *builder) +{ + uint8_t identification_type = IDENTIFICATION_TYPE_NETWORK_ID; + struct net_key *key; + int i; + + key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + if (!key) + return false; + + l_dbus_message_builder_enter_array(builder, "y"); + l_dbus_message_builder_append_basic(builder, 'y', &identification_type); + + for (i = 0; i < sizeof(key->net_id); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(key->net_id[i])); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +uint32_t net_key_get_next_id(uint32_t id) +{ + const struct l_queue_entry *entry; + struct net_key *key; + bool found = false; + + /* Try to find next key (after the given key id) */ + for (entry = l_queue_get_entries(keys); entry; entry = entry->next) { + key = entry->data; + + if (!found) + if (key->id == id) + found = true; + else + return key->id; + } + + /* If not found, return id of first key */ + key = l_queue_peek_head(keys); + if (key) + return key->id; + + return 0; +} diff --git a/mesh/net-keys.h b/mesh/net-keys.h index e73812481ddb..5a9d7868ab48 100644 --- a/mesh/net-keys.h +++ b/mesh/net-keys.h @@ -37,3 +37,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu, void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); void net_key_beacon_disable(uint32_t id, bool mpb); uint32_t net_key_beacon_last_seen(uint32_t id); + +bool net_key_fill_adv_service_data(uint32_t id, + struct l_dbus_message_builder *builder); +uint32_t net_key_get_next_id(uint32_t id); diff --git a/mesh/net.c b/mesh/net.c index 1cf74c892ca0..a64cfbde6f4e 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -27,6 +27,8 @@ #include "mesh/net.h" #include "mesh/mesh-io.h" #include "mesh/friend.h" +#include "mesh/gatt-service.h" // PROXY_MSG_TYPE_NETWORK_PDU +#include "mesh/gatt-proxy-svc.h" // gatt_proxy_service_send() #include "mesh/mesh-config.h" #include "mesh/model.h" #include "mesh/appkey.h" @@ -2306,7 +2308,9 @@ static void send_msg_pkt_oneshot(void *user_data) /* No extra randomization when sending regular mesh messages */ info.u.gen.max_delay = DEFAULT_MIN_DELAY; - mesh_io_send(net->io, &info, tx->packet, tx->size); +// mesh_io_send(net->io, &info, tx->packet, tx->size); + gatt_proxy_service_send(PROXY_MSG_TYPE_NETWORK_PDU, + tx->packet + 1, tx->size - 1); l_free(tx); } @@ -3065,6 +3069,21 @@ struct mesh_io *mesh_net_detach(struct mesh_net *net) return io; } +void mesh_net_attach_gatt(struct gatt_proxy_service *gatt_proxy) +{ + gatt_proxy_service_register_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_NETWORK_PDU, + net_msg_recv, NULL); +} + +void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy) +{ +// mesh_io_send_cancel(net->io, &type, 1); + + gatt_proxy_service_deregister_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_NETWORK_PDU); +} + bool mesh_net_iv_index_update(struct mesh_net *net) { if (net->iv_upd_state != IV_UPD_NORMAL) diff --git a/mesh/net.h b/mesh/net.h index d385ba16efdd..1389e5c78d15 100644 --- a/mesh/net.h +++ b/mesh/net.h @@ -14,6 +14,8 @@ struct mesh_io; struct mesh_node; +struct mesh_io_recv_info; +struct gatt_proxy_service; #define DEV_ID 0 @@ -258,6 +260,8 @@ bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, uint32_t *net_key_id); bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io); struct mesh_io *mesh_net_detach(struct mesh_net *net); +void mesh_net_attach_gatt(struct gatt_proxy_service *gatt_proxy); +void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy); struct l_queue *mesh_net_get_app_keys(struct mesh_net *net); void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index f483c330953b..1d30f3593d3a 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -21,6 +21,8 @@ #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" +#include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/net.h" #include "mesh/prov.h" #include "mesh/provision.h" @@ -88,6 +90,11 @@ struct mesh_prov_acceptor { static struct mesh_prov_acceptor *prov = NULL; +static void gatt_unreg_finished(void *user_data) +{ + gatt_proxy_service_create(); +} + static void acceptor_free(void) { if (!prov) @@ -100,7 +107,7 @@ static void acceptor_free(void) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); - pb_gatt_unreg(prov, NULL, NULL); + pb_gatt_unreg(prov, gatt_unreg_finished, NULL); l_free(prov); prov = NULL; @@ -704,8 +711,13 @@ failure: cleanup: l_timeout_remove(prov->timeout); - /* Give PB Link 5 seconds to end session */ - prov->timeout = l_timeout_create(5, prov_to, prov, NULL); + if (prov->transport == PB_ADV) { + /* Give PB Link 5 seconds to end session */ + prov->timeout = l_timeout_create(5, prov_to, prov, NULL); + } else { + prov->timeout = NULL; + prov_to(NULL, prov); + } } static void acp_prov_ack(void *user_data, uint8_t msg_num) @@ -804,6 +816,14 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); + /* + * MeshPRT_v1.1, chapter 7: "A device may support the Mesh + * Provisioning Service or the Mesh Proxy Service or both. If + * both are supported, only one of these services shall be + * exposed in the GATT database at a time." + */ + gatt_proxy_service_destroy(); + result = pb_gatt_reg(acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, caps->oob_info, prov); -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
[parent not found: <CABUQxGwt8MjoYmx_8jF=A9qug24Dnx+rEX=5vDWP7+SGOERcMw@mail.gmail.com>]
* Re: [RFC PATCH BlueZ 3/4] mesh: add GATT proxy service [not found] ` <CABUQxGwt8MjoYmx_8jF=A9qug24Dnx+rEX=5vDWP7+SGOERcMw@mail.gmail.com> @ 2025-07-01 19:28 ` Christian Eggers 0 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-01 19:28 UTC (permalink / raw) To: Brian Gix; +Cc: linux-bluetooth, Inga Stotland Hi Brian, thanks for looking for my patches :-) On Tuesday, 1 July 2025, 20:52:35 CEST, Brian Gix wrote: > Hi Christian, > > I have not yet reviewed your patch set, but I do have a few > comments/questions to start off to learn the "theory of operation". > > GATT-Proxy mesh of course was always intended to be a temporary "Legacy > Support" feature. I think that the GATT specification was written in about 2017. It mentions "Legacy phones" where the OS does not allow to send arbitrary ADV messages. Has anything changed since then? > There should never need to be support for a BlueZ/Linux > based GATT Client, as the assumption is that if support for GATT-Proxy is > added, that it would be for the Server side only... I haven't thought about it yet... But as Linux is NOT a legacy system and all Mesh nodes need to support MESH-ADV, there should be no need for a GATT Proxy Client under Linux (exception: I use meshctl for testing my GATT Proxy Server) ... > A server that could accept incoming GATT/ATT-ACL connections and then > propagate GATT based mesh packets via ADV packets. That's what I am currently working on (although I haven't implemented the proxy's 'relaying' feature yet). > Once it is decided that a GATT Proxy server is a valid use case for the > mesh daemon, then it is also important the mesh daemon function as a fully > compliant client of the bluetoothd daemon. All ACL traffic *MUST* be > centralized in the bluetoothd daemon, with the mesh daemon appearing as a > standard dbus based client. Otherwise the concept of the GATT database of > all services (not just the GATT Mesh Proxy service) totally falls apart. I already expected that there must be a reason for a centralized GATT service per host. > The only possible exception to this is if it is written for a > multi-controler environment, where one BT controller is fully owned and > controlled by the mesh daemon, and used for GATT-Proxy messages only, and > the other BT controller(s) are shared with bluetoothd, and used for ADV > traffic. This is strongly discouraged however, because multi-controller > operation becomes fraught when there is not central control of the pool of > available controllers. And our main mission during mesh daemon development > was near-Zero impact on "classic bluetooth" operation. This is NOT what I intend (our embedded systems have only one Bluetooth controller, this must be sufficient). > So can you confirm that: > 1. You intend to add *GATT Server only* to the mesh daemon (no GATT clients > allowed)? Yes, I do not intend to implement a GATT client in bluetooth-meshd (although I mentioned this possibility in my previous answer to Luiz). > 2. That the GATT Server itself is *hosted* in bluetoothd itself, with no > ACL connections/L2CAP layer etc owned by the mesh daemon? Yes, I use the GATT server facility of bluetoothd in bluetooth-meshd (via D-Bus, not using non-documented interfaces). > 3. There is a valid use case (Mesh GATT Proxy Clients that cannot for some > reason issue Mesh ADV packets themselves)? Yes, my assumption is that mobile phones still don't support MESH ADV (at the the SMART+ app is a Mesh GATT Proxy Client). > Regards, > Brian Gix regards, Christian > > > On Tue, Jul 1, 2025 at 7:48 AM Christian Eggers <ceggers@arri.de> wrote: > > > FIXME: net.c: determine whether data shall be sent via mesh_io or > > gatt_proxy > > --- > > Makefile.mesh | 3 +- > > mesh/gatt-proxy-svc.c | 294 ++++++++++++++++++++++++++++++++++++++++++ > > mesh/gatt-proxy-svc.h | 29 +++++ > > mesh/main.c | 3 + > > mesh/net-keys.c | 61 +++++++++ > > mesh/net-keys.h | 4 + > > mesh/net.c | 21 ++- > > mesh/net.h | 4 + > > mesh/prov-acceptor.c | 26 +++- > > 9 files changed, 440 insertions(+), 5 deletions(-) > > create mode 100644 mesh/gatt-proxy-svc.c > > create mode 100644 mesh/gatt-proxy-svc.h > > > > diff --git a/Makefile.mesh b/Makefile.mesh > > index 700d64fe9293..8a190f75de9d 100644 > > --- a/Makefile.mesh > > +++ b/Makefile.mesh > > @@ -40,7 +40,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ > > mesh/rpl.h mesh/rpl.c \ > > mesh/prv-beacon.h mesh/prvbeac-server.c \ > > mesh/mesh-defs.h \ > > - mesh/gatt-service.h mesh/gatt-service.c > > + mesh/gatt-service.h mesh/gatt-service.c \ > > + mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c > > pkglibexec_PROGRAMS += mesh/bluetooth-meshd > > > > mesh/mesh.$(OBJEXT): ell/internal > > diff --git a/mesh/gatt-proxy-svc.c b/mesh/gatt-proxy-svc.c > > new file mode 100644 > > index 000000000000..e24543de718c > > --- /dev/null > > +++ b/mesh/gatt-proxy-svc.c > > @@ -0,0 +1,294 @@ > > +// SPDX-License-Identifier: LGPL-2.1-or-later > > +/* > > + * > > + * BlueZ - Bluetooth protocol stack for Linux > > + * > > + * Copyright (C) 2025 ARRI Lighting. All rights reserved. > > + * > > + * > > + */ > > + > > +#include <stdbool.h> > > +#include <stdlib.h> > > +#include <string.h> // memcpy() > > + > > +#include <ell/dbus.h> > > +#include <ell/queue.h> > > +#include <ell/timeout.h> > > +#include <ell/util.h> // ell_new(), l_free(), l_malloc() > > + > > +#include "mesh/gatt-service.h" > > +#include "mesh/mesh-io.h" // mesh_io_recv_func_t > > +#include "mesh/net.h" // mesh_net_attach_gatt(), > > + // mesh_net_detach_gatt() > > +#include "mesh/net-keys.h" // net_key_fill_adv_service_data(), > > + // net_key_get_next_id() > > +#include "mesh/util.h" // print_packet() > > +#include "mesh/gatt-proxy-svc.h" > > + > > +#define MESH_GATT_PROXY_SVC_UUID "0x1828" > > +#define MESH_GATT_PROXY_CHRC_DATA_IN "0x2ADD" > > +#define MESH_GATT_PROXY_CHRC_DATA_OUT "0x2ADE" > > + > > +#define MAX_PROXY_PDU_LEN 66 /* MshPRT_v1.1, section 7.2.2.2.7 */ > > + > > +struct gatt_io_reg { > > + mesh_io_recv_func_t cb; > > + void *user_data; > > + enum proxy_msg_type msg_type; > > +}; > > + > > +struct gatt_proxy_service { > > + struct gatt_service *gatt_service; > > + uint32_t current_adv_key_id; > > + struct l_timeout *adv_key_cycle_to; > > + bool txing; > > + struct l_queue *tx_deferred; > > + struct l_queue *rx_regs; > > +}; > > + > > +struct process_data { > > + enum proxy_msg_type msg_type; > > + const uint8_t *data; > > + uint8_t len; > > +}; > > + > > +static struct gatt_proxy_service *gatt_proxy_service; > > + > > +struct tx_deferred { > > + enum proxy_msg_type msg_type; > > + uint16_t len; > > + uint8_t data[]; > > +}; > > + > > +static struct gatt_io_reg *find_by_filter(struct l_queue *rx_regs, > > + enum proxy_msg_type > > msg_type) > > +{ > > + const struct l_queue_entry *entry; > > + > > + entry = l_queue_get_entries(rx_regs); > > + > > + for (; entry; entry = entry->next) { > > + struct gatt_io_reg *rx_reg = entry->data; > > + > > + if (rx_reg->msg_type == msg_type) > > + return rx_reg; > > + } > > + > > + return NULL; > > +} > > + > > +void gatt_proxy_service_register_recv_cb(struct gatt_proxy_service > > *gatt_proxy, > > + enum proxy_msg_type msg_type, > > + mesh_io_recv_func_t cb, > > + void *user_data) > > +{ > > + struct gatt_io_reg *rx_reg; > > + > > + if (gatt_proxy != gatt_proxy_service || !cb) > > + return; > > + > > + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); > > + > > + l_free(rx_reg); > > + l_queue_remove(gatt_proxy->rx_regs, rx_reg); > > + > > + rx_reg = l_malloc(sizeof(struct gatt_io_reg)); > > + rx_reg->cb = cb; > > + rx_reg->msg_type = msg_type; > > + rx_reg->user_data = user_data; > > + > > + l_queue_push_head(gatt_proxy->rx_regs, rx_reg); > > +} > > + > > +void > > +gatt_proxy_service_deregister_recv_cb(struct gatt_proxy_service > > *gatt_proxy, > > + enum proxy_msg_type > > msg_type) > > +{ > > + struct gatt_io_reg *rx_reg; > > + > > + if (gatt_proxy != gatt_proxy_service) > > + return; > > + > > + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); > > + > > + l_queue_remove(gatt_proxy->rx_regs, rx_reg); > > + l_free(rx_reg); > > +} > > + > > +void gatt_proxy_service_send(enum proxy_msg_type msg_type, > > + const void *data, uint8_t > > len) > > +{ > > + if (!gatt_proxy_service) > > + return; > > + > > + if (!gatt_proxy_service->txing) { > > + gatt_proxy_service->txing = true; > > + gatt_service_tx(gatt_proxy_service->gatt_service, msg_type, > > + data, len); > > + } else { > > + struct tx_deferred *tx_deferred; > > + > > +// print_packet("TX-Defer", data, len); > > + tx_deferred = l_malloc(len + sizeof(struct tx_deferred)); > > + tx_deferred->msg_type = msg_type; > > + tx_deferred->len = len; > > + memcpy(tx_deferred->data, data, len); > > + l_queue_push_tail(gatt_proxy_service->tx_deferred, > > tx_deferred); > > + } > > +} > > + > > +static void process_rx_callbacks(void *a, void *b) > > +{ > > + struct gatt_io_reg *rx_reg = a; > > + struct process_data *rx = b; > > + > > + if (rx->msg_type == rx_reg->msg_type) > > + rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); > > +} > > + > > +static void gatt_service_rx(void *user_data, enum proxy_msg_type msg_type, > > + const void *data, uint16_t > > len) > > +{ > > + struct gatt_proxy_service *gatt_proxy = user_data; > > + struct process_data rx = { > > + .msg_type = msg_type, > > + .data = data, > > + .len = len, > > + }; > > + > > + if (gatt_proxy != gatt_proxy_service) > > + return; > > + > > + print_packet("RX", data, len); > > + l_queue_foreach(gatt_proxy->rx_regs, process_rx_callbacks, &rx); > > +} > > + > > +static bool gatt_service_tx_cmplt(void *user_data) > > +{ > > + struct gatt_proxy_service *gatt_proxy = user_data; > > + struct tx_deferred *tx_deferred; > > + > > +// l_info("gatt_service_tx_cmplt"); > > + > > + if (gatt_proxy_service != gatt_proxy) > > + return false; > > + > > + if (!gatt_proxy->txing) > > + return false; > > + > > + gatt_proxy->txing = false; > > + > > + tx_deferred = l_queue_pop_head(gatt_proxy->tx_deferred); > > + if (!tx_deferred) > > + return false; > > + > > + gatt_proxy_service_send(tx_deferred->msg_type, > > + tx_deferred->data, > > tx_deferred->len); > > + l_free(tx_deferred); > > + return true; > > +} > > + > > +static bool gatt_service_fill_adv_service_data(void *user_data, > > + struct l_dbus_message_builder > > *builder) > > +{ > > + struct gatt_proxy_service *gatt_service = user_data; > > + > > + if (gatt_service != gatt_proxy_service) > > + return false; > > + > > + return > > net_key_fill_adv_service_data(gatt_service->current_adv_key_id, > > + builder); > > +} > > + > > +static void gatt_proxy_service_cycle_adv(struct l_timeout *timeout, > > + void *user_data) > > +{ > > + struct gatt_proxy_service *gatt_proxy = user_data; > > + uint32_t next_adv_key_id; > > + > > + if (gatt_proxy_service != gatt_proxy) > > + return; > > + > > + next_adv_key_id = > > net_key_get_next_id(gatt_proxy->current_adv_key_id); > > + if (!next_adv_key_id) > > + return; > > + > > + if (gatt_proxy->current_adv_key_id != next_adv_key_id) { > > + gatt_proxy->current_adv_key_id = next_adv_key_id; > > + gatt_service_adv_updated(gatt_proxy_service->gatt_service); > > + } > > + > > + l_timeout_modify(gatt_proxy->adv_key_cycle_to, 3); > > +} > > + > > +void gatt_proxy_service_set_current_adv_key(uint32_t id) > > +{ > > + if (!gatt_proxy_service) > > + return; > > + > > + gatt_proxy_service->current_adv_key_id = id; > > + gatt_service_adv_updated(gatt_proxy_service->gatt_service); > > +} > > + > > +void gatt_proxy_service_start(void) > > +{ > > + if (!gatt_proxy_service || gatt_proxy_service->gatt_service) > > + return; > > + > > + gatt_proxy_service->gatt_service = gatt_service_create( > > + MESH_GATT_PROXY_SVC_UUID, > > + MESH_GATT_PROXY_CHRC_DATA_IN, > > + MESH_GATT_PROXY_CHRC_DATA_OUT, > > + MAX_PROXY_PDU_LEN, > > + NULL, NULL, > > + gatt_service_rx, > > + gatt_service_tx_cmplt, > > + gatt_service_fill_adv_service_data, > > + gatt_proxy_service); > > + > > + gatt_proxy_service->adv_key_cycle_to = l_timeout_create(3, > > + > > gatt_proxy_service_cycle_adv, > > + gatt_proxy_service, NULL); > > + > > + mesh_net_attach_gatt(gatt_proxy_service); > > +} > > + > > +void gatt_proxy_service_stop(void) > > +{ > > + if (!gatt_proxy_service || !gatt_proxy_service->gatt_service) > > + return; > > + > > + mesh_net_detach_gatt(gatt_proxy_service); > > + l_timeout_remove(gatt_proxy_service->adv_key_cycle_to); > > + gatt_service_destroy(gatt_proxy_service->gatt_service, NULL, NULL); > > +} > > + > > +void gatt_proxy_service_create(void) > > +{ > > + if (gatt_proxy_service) > > + return; > > + > > + gatt_proxy_service = l_new(struct gatt_proxy_service, 1); > > + gatt_proxy_service->tx_deferred = l_queue_new(); > > + gatt_proxy_service->rx_regs = l_queue_new(); > > + > > + /* Check whether we have at least one key */ > > + if (!net_key_get_next_id(0)) > > + return; > > + > > + gatt_proxy_service_start(); > > +} > > + > > +void gatt_proxy_service_destroy(void) > > +{ > > + if (!gatt_proxy_service) > > + return; > > + > > + gatt_proxy_service_stop(); > > + > > + l_queue_destroy(gatt_proxy_service->rx_regs, l_free); > > + l_queue_destroy(gatt_proxy_service->tx_deferred, l_free); > > + l_free(gatt_proxy_service); > > + gatt_proxy_service = NULL; > > +} > > diff --git a/mesh/gatt-proxy-svc.h b/mesh/gatt-proxy-svc.h > > new file mode 100644 > > index 000000000000..5c640dc2c8ef > > --- /dev/null > > +++ b/mesh/gatt-proxy-svc.h > > @@ -0,0 +1,29 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * > > + * BlueZ - Bluetooth protocol stack for Linux > > + * > > + * Copyright (C) 2025 ARRI Lighting. All rights reserved. > > + * > > + * > > + */ > > + > > +#include <stdint.h> > > + > > +enum proxy_msg_type; > > +struct gatt_proxy_service; > > + > > +void gatt_proxy_service_create(void); > > +void gatt_proxy_service_destroy(void); > > +void gatt_proxy_service_start(void); > > +void gatt_proxy_service_stop(void); > > +void gatt_proxy_service_set_current_adv_key(uint32_t id); > > +void gatt_proxy_service_register_recv_cb(struct gatt_proxy_service > > *gatt_proxy, > > + enum proxy_msg_type msg_type, > > + mesh_io_recv_func_t cb, > > + void *user_data); > > +void > > +gatt_proxy_service_deregister_recv_cb(struct gatt_proxy_service > > *gatt_proxy, > > + enum proxy_msg_type > > msg_type); > > +void gatt_proxy_service_send(enum proxy_msg_type msg_type, > > + const void *data, uint8_t > > len); > > diff --git a/mesh/main.c b/mesh/main.c > > index 5b8af4bc7345..b423fe70b8bd 100644 > > --- a/mesh/main.c > > +++ b/mesh/main.c > > @@ -31,6 +31,7 @@ > > #include "mesh/crypto.h" > > #include "mesh/dbus.h" > > #include "mesh/mesh-io.h" > > +#include "mesh/gatt-proxy-svc.h" > > #include "mesh/util.h" > > > > static const char *storage_dir; > > @@ -94,6 +95,8 @@ static void mesh_ready_callback(void *user_data, bool > > success) > > l_error("Failed to initialize mesh D-Bus resources"); > > l_main_quit(); > > } > > + > > + gatt_proxy_service_create(); > > } > > > > static void request_name_callback(struct l_dbus *dbus, bool success, > > diff --git a/mesh/net-keys.c b/mesh/net-keys.c > > index 42d498ce0ede..62ea4208af98 100644 > > --- a/mesh/net-keys.c > > +++ b/mesh/net-keys.c > > @@ -20,6 +20,7 @@ > > #include "mesh/util.h" > > #include "mesh/crypto.h" > > #include "mesh/mesh-io.h" > > +#include "mesh/gatt-proxy-svc.h" > > #include "mesh/net.h" > > #include "mesh/net-keys.h" > > > > @@ -29,6 +30,12 @@ > > /* This allows daemon to skip decryption on recently seen beacons */ > > #define BEACON_CACHE_MAX 10 > > > > +/* MshPRT_v1.1, section 7.2.2.2.1 */ > > +#define IDENTIFICATION_TYPE_NETWORK_ID 0x00 > > +#define IDENTIFICATION_TYPE_NODE_ID 0x01 > > +#define IDENTIFICATION_TYPE_PRV_NETWORK_ID 0x02 > > +#define IDENTIFICATION_TYPE_PRV_NODE_ID 0x03 > > + > > struct beacon_rx { > > uint8_t data[28]; > > uint32_t id; > > @@ -144,6 +151,9 @@ uint32_t net_key_add(const uint8_t flooding[16]) > > goto fail; > > > > key->id = ++last_flooding_id; > > + if (l_queue_isempty(keys)) > > + gatt_proxy_service_start(); > > + > > l_queue_push_tail(keys, key); > > return key->id; > > > > @@ -196,6 +206,9 @@ void net_key_unref(uint32_t id) > > l_timeout_remove(key->observe.timeout); > > l_queue_remove(keys, key); > > l_free(key); > > + > > + if (l_queue_isempty(keys)) > > + gatt_proxy_service_stop(); > > } > > } > > } > > @@ -663,6 +676,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, > > bool kr, bool ivu, > > return false; > > > > print_packet("Set SNB to", key->snb, 23); > > + gatt_proxy_service_set_current_adv_key(key->id); > > } > > > > l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); > > @@ -798,3 +812,50 @@ void net_key_cleanup(void) > > l_queue_destroy(beacons, l_free); > > beacons = NULL; > > } > > + > > +bool net_key_fill_adv_service_data(uint32_t id, > > + struct l_dbus_message_builder > > *builder) > > +{ > > + uint8_t identification_type = IDENTIFICATION_TYPE_NETWORK_ID; > > + struct net_key *key; > > + int i; > > + > > + key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); > > + if (!key) > > + return false; > > + > > + l_dbus_message_builder_enter_array(builder, "y"); > > + l_dbus_message_builder_append_basic(builder, 'y', > > &identification_type); > > + > > + for (i = 0; i < sizeof(key->net_id); i++) > > + l_dbus_message_builder_append_basic(builder, 'y', > > + &(key->net_id[i])); > > + l_dbus_message_builder_leave_array(builder); > > + > > + return true; > > +} > > + > > +uint32_t net_key_get_next_id(uint32_t id) > > +{ > > + const struct l_queue_entry *entry; > > + struct net_key *key; > > + bool found = false; > > + > > + /* Try to find next key (after the given key id) */ > > + for (entry = l_queue_get_entries(keys); entry; entry = > > entry->next) { > > + key = entry->data; > > + > > + if (!found) > > + if (key->id == id) > > + found = true; > > + else > > + return key->id; > > + } > > + > > + /* If not found, return id of first key */ > > + key = l_queue_peek_head(keys); > > + if (key) > > + return key->id; > > + > > + return 0; > > +} > > diff --git a/mesh/net-keys.h b/mesh/net-keys.h > > index e73812481ddb..5a9d7868ab48 100644 > > --- a/mesh/net-keys.h > > +++ b/mesh/net-keys.h > > @@ -37,3 +37,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t > > iv_index, bool kr, bool ivu, > > void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); > > void net_key_beacon_disable(uint32_t id, bool mpb); > > uint32_t net_key_beacon_last_seen(uint32_t id); > > + > > +bool net_key_fill_adv_service_data(uint32_t id, > > + struct l_dbus_message_builder > > *builder); > > +uint32_t net_key_get_next_id(uint32_t id); > > diff --git a/mesh/net.c b/mesh/net.c > > index 1cf74c892ca0..a64cfbde6f4e 100644 > > --- a/mesh/net.c > > +++ b/mesh/net.c > > @@ -27,6 +27,8 @@ > > #include "mesh/net.h" > > #include "mesh/mesh-io.h" > > #include "mesh/friend.h" > > +#include "mesh/gatt-service.h" // PROXY_MSG_TYPE_NETWORK_PDU > > +#include "mesh/gatt-proxy-svc.h" // gatt_proxy_service_send() > > #include "mesh/mesh-config.h" > > #include "mesh/model.h" > > #include "mesh/appkey.h" > > @@ -2306,7 +2308,9 @@ static void send_msg_pkt_oneshot(void *user_data) > > /* No extra randomization when sending regular mesh messages */ > > info.u.gen.max_delay = DEFAULT_MIN_DELAY; > > > > - mesh_io_send(net->io, &info, tx->packet, tx->size); > > +// mesh_io_send(net->io, &info, tx->packet, tx->size); > > + gatt_proxy_service_send(PROXY_MSG_TYPE_NETWORK_PDU, > > + tx->packet + 1, tx->size - > > 1); > > l_free(tx); > > } > > > > @@ -3065,6 +3069,21 @@ struct mesh_io *mesh_net_detach(struct mesh_net > > *net) > > return io; > > } > > > > +void mesh_net_attach_gatt(struct gatt_proxy_service *gatt_proxy) > > +{ > > + gatt_proxy_service_register_recv_cb(gatt_proxy, > > + PROXY_MSG_TYPE_NETWORK_PDU, > > + net_msg_recv, NULL); > > +} > > + > > +void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy) > > +{ > > +// mesh_io_send_cancel(net->io, &type, 1); > > + > > + gatt_proxy_service_deregister_recv_cb(gatt_proxy, > > + > > PROXY_MSG_TYPE_NETWORK_PDU); > > +} > > + > > bool mesh_net_iv_index_update(struct mesh_net *net) > > { > > if (net->iv_upd_state != IV_UPD_NORMAL) > > diff --git a/mesh/net.h b/mesh/net.h > > index d385ba16efdd..1389e5c78d15 100644 > > --- a/mesh/net.h > > +++ b/mesh/net.h > > @@ -14,6 +14,8 @@ > > > > struct mesh_io; > > struct mesh_node; > > +struct mesh_io_recv_info; > > +struct gatt_proxy_service; > > > > #define DEV_ID 0 > > > > @@ -258,6 +260,8 @@ bool mesh_net_get_key(struct mesh_net *net, bool > > new_key, uint16_t idx, > > uint32_t > > *net_key_id); > > bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io); > > struct mesh_io *mesh_net_detach(struct mesh_net *net); > > +void mesh_net_attach_gatt(struct gatt_proxy_service *gatt_proxy); > > +void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy); > > struct l_queue *mesh_net_get_app_keys(struct mesh_net *net); > > > > void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, > > diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c > > index f483c330953b..1d30f3593d3a 100644 > > --- a/mesh/prov-acceptor.c > > +++ b/mesh/prov-acceptor.c > > @@ -21,6 +21,8 @@ > > #include "mesh/mesh-defs.h" > > #include "mesh/util.h" > > #include "mesh/crypto.h" > > +#include "mesh/mesh-io.h" > > +#include "mesh/gatt-proxy-svc.h" > > #include "mesh/net.h" > > #include "mesh/prov.h" > > #include "mesh/provision.h" > > @@ -88,6 +90,11 @@ struct mesh_prov_acceptor { > > > > static struct mesh_prov_acceptor *prov = NULL; > > > > +static void gatt_unreg_finished(void *user_data) > > +{ > > + gatt_proxy_service_create(); > > +} > > + > > static void acceptor_free(void) > > { > > if (!prov) > > @@ -100,7 +107,7 @@ static void acceptor_free(void) > > mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); > > > > pb_adv_unreg(prov); > > - pb_gatt_unreg(prov, NULL, NULL); > > + pb_gatt_unreg(prov, gatt_unreg_finished, NULL); > > > > l_free(prov); > > prov = NULL; > > @@ -704,8 +711,13 @@ failure: > > cleanup: > > l_timeout_remove(prov->timeout); > > > > - /* Give PB Link 5 seconds to end session */ > > - prov->timeout = l_timeout_create(5, prov_to, prov, NULL); > > + if (prov->transport == PB_ADV) { > > + /* Give PB Link 5 seconds to end session */ > > + prov->timeout = l_timeout_create(5, prov_to, prov, NULL); > > + } else { > > + prov->timeout = NULL; > > + prov_to(NULL, prov); > > + } > > } > > > > static void acp_prov_ack(void *user_data, uint8_t msg_num) > > @@ -804,6 +816,14 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, > > result = pb_adv_reg(false, acp_prov_open, acp_prov_close, > > acp_prov_rx, acp_prov_ack, uuid, > > prov); > > > > + /* > > + * MeshPRT_v1.1, chapter 7: "A device may support the Mesh > > + * Provisioning Service or the Mesh Proxy Service or both. > > If > > + * both are supported, only one of these services shall be > > + * exposed in the GATT database at a time." > > + */ > > + gatt_proxy_service_destroy(); > > + > > result = pb_gatt_reg(acp_prov_open, acp_prov_close, > > acp_prov_rx, acp_prov_ack, uuid, > > caps->oob_info, prov); > > -- > > 2.43.0 > > > > > ^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ 4/4] mesh: initial support for proxy configuration messages via GATT 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers ` (2 preceding siblings ...) 2025-07-01 14:22 ` [RFC PATCH BlueZ 3/4] mesh: add GATT proxy service Christian Eggers @ 2025-07-01 14:22 ` Christian Eggers 2025-07-01 15:09 ` [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers ` (2 subsequent siblings) 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-01 14:22 UTC (permalink / raw) To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland, Christian Eggers Implement reception and response for Proxy Configuration Messages (MshPRT_v1.1, section 6.6). ToDo: Apply configured filters. --- Makefile.mesh | 3 +- mesh/crypto.c | 6 +- mesh/crypto.h | 3 + mesh/net-keys.c | 58 +++++++++++++++++- mesh/net-keys.h | 3 + mesh/net.c | 56 +++++++++++++++++- mesh/proxy-cfg.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++ mesh/proxy-cfg.h | 17 ++++++ 8 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 mesh/proxy-cfg.c create mode 100644 mesh/proxy-cfg.h diff --git a/Makefile.mesh b/Makefile.mesh index 8a190f75de9d..d39bb17eca70 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -41,7 +41,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h \ mesh/gatt-service.h mesh/gatt-service.c \ - mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c + mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c \ + mesh/proxy-cfg.h mesh/proxy-cfg.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/crypto.c b/mesh/crypto.c index ab44baee3427..93c3af751e80 100644 --- a/mesh/crypto.c +++ b/mesh/crypto.c @@ -591,7 +591,7 @@ bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, return true; } -static bool network_header_parse(const uint8_t *packet, uint8_t packet_len, +bool mesh_crypto_network_header_parse(const uint8_t *packet, uint8_t packet_len, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src, uint16_t *dst) { @@ -631,7 +631,7 @@ bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, uint16_t this_dst; bool is_segmented; - if (!network_header_parse(packet, packet_len, + if (!mesh_crypto_network_header_parse(packet, packet_len, ctl, ttl, seq, src, &this_dst)) return false; @@ -836,7 +836,7 @@ bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, uint16_t src; uint16_t dst; - if (!network_header_parse(packet, packet_len, + if (!mesh_crypto_network_header_parse(packet, packet_len, &ctl, &ttl, &seq, &src, &dst)) return false; diff --git a/mesh/crypto.h b/mesh/crypto.h index 55789886eb0e..d66248f84cff 100644 --- a/mesh/crypto.h +++ b/mesh/crypto.h @@ -77,6 +77,9 @@ bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, bool *szmic, bool *relay, uint16_t *seqZero, uint8_t *segO, uint8_t *segN, const uint8_t **payload, uint8_t *payload_len); +bool mesh_crypto_network_header_parse(const uint8_t *packet, uint8_t packet_len, + bool *ctl, uint8_t *ttl, uint32_t *seq, + uint16_t *src, uint16_t *dst); bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, uint8_t *out, uint16_t payload_len, uint16_t src, uint16_t dst, uint8_t key_aid, diff --git a/mesh/net-keys.c b/mesh/net-keys.c index 62ea4208af98..4b2e14cabb01 100644 --- a/mesh/net-keys.c +++ b/mesh/net-keys.c @@ -76,6 +76,15 @@ struct net_key { bool ivu; }; +struct proxy_cfg_msg { + const uint8_t *data; + uint8_t len; + uint8_t *plain; + uint8_t plain_len; + uint32_t iv_index; + uint32_t key_id; +}; + static struct l_queue *beacons; static struct l_queue *keys; static uint32_t last_flooding_id; @@ -249,13 +258,36 @@ static void decrypt_net_pkt(void *a, void *b) if (result) { cache_id = key->id; - if (cache_plain[1] & 0x80) + if (cache_plain[1] & CTL) cache_plainlen = cache_len - 8; else cache_plainlen = cache_len - 4; } } +static void decrypt_proxy_cfg_msg(void *a, void *b) +{ + const struct net_key *key = a; + struct proxy_cfg_msg *proxy_cfg = b; + bool result; + + if (proxy_cfg->key_id || !key->ref_cnt || + (proxy_cfg->data[0] & 0x7f) != key->nid) + return; + + result = mesh_crypto_packet_decode(proxy_cfg->data, proxy_cfg->len, + true, + proxy_cfg->plain, + proxy_cfg->iv_index, + key->enc_key, + key->prv_key); + + if (result) { + proxy_cfg->key_id = key->id; + proxy_cfg->plain_len = proxy_cfg->len - 8; + } +} + uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len) { @@ -285,6 +317,30 @@ done: return cache_id; } +uint32_t net_key_decrypt_proxy_cfg_msg(uint32_t iv_index, + const uint8_t *pkt, size_t len, + uint8_t *plain, size_t *plain_len) +{ + struct proxy_cfg_msg proxy_cfg = { + .data = pkt, + .len = len, + .plain = plain, + .iv_index = iv_index, + }; + + /* MshPRT_v1.1, section 6.6: Proxy configuration messages have CTL=1 */ + if (!(pkt[1] & CTL)) + return 0; + + /* Try all network keys known to us */ + l_queue_foreach(keys, decrypt_proxy_cfg_msg, &proxy_cfg); + + if (proxy_cfg.key_id) + *plain_len = proxy_cfg.plain_len; + + return proxy_cfg.key_id; +} + bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); diff --git a/mesh/net-keys.h b/mesh/net-keys.h index 5a9d7868ab48..141543feace2 100644 --- a/mesh/net-keys.h +++ b/mesh/net-keys.h @@ -23,6 +23,9 @@ uint32_t net_key_frnd_add(uint32_t flooding_id, uint16_t lpn, uint16_t frnd, void net_key_unref(uint32_t id); uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len); +uint32_t net_key_decrypt_proxy_cfg_msg(uint32_t iv_index, + const uint8_t *pkt, size_t len, + uint8_t *plain, size_t *plain_len); bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len); uint32_t net_key_network_id(const uint8_t network[8]); uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, diff --git a/mesh/net.c b/mesh/net.c index a64cfbde6f4e..141ec4f5acb1 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -25,6 +25,7 @@ #include "mesh/net-keys.h" #include "mesh/node.h" #include "mesh/net.h" +#include "mesh/proxy-cfg.h" #include "mesh/mesh-io.h" #include "mesh/friend.h" #include "mesh/gatt-service.h" // PROXY_MSG_TYPE_NETWORK_PDU @@ -2352,7 +2353,7 @@ static enum _relay_advice packet_received(struct mesh_net *net, return RELAY_NONE; } - if (net_dst == 0) { + if (net_dst == UNASSIGNED_ADDRESS) { l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", net_dst, net_ctl, net_ttl); return RELAY_NONE; @@ -2523,6 +2524,34 @@ static void net_rx(void *net_ptr, void *user_data) } } +static void net_proxy_cfg_msg_rx(void *net_ptr, void *user_data) +{ + struct net_queue_data *data = user_data; + struct mesh_net *net = net_ptr; + uint8_t out[29]; + size_t out_size; + uint32_t net_key_id; + bool ivi_net = !!(net->iv_index & 1); + bool ivi_pkt = !!(data->data[0] & 0x80); + + /* if IVI flag differs, use previous IV Index */ + uint32_t iv_index = net->iv_index - (ivi_pkt ^ ivi_net); + + net_key_id = net_key_decrypt_proxy_cfg_msg(iv_index, + data->data, data->len, + out, &out_size); + + if (!net_key_id) + return; + + if (!data->seen) { + data->seen = true; + print_packet("RX: ProxyCfg [enc] :", data->data, data->len); + } + + proxy_cfg_msg_received(net, net_key_id, iv_index, out, out_size); +} + static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { @@ -2560,6 +2589,26 @@ static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, } } +static void +net_proxy_cfg_msg_recv(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + struct net_queue_data net_data = { + .info = info, + .data = data + 1, + .len = len - 1, + .relay_advice = RELAY_NONE, + .seen = false, + }; + + if (len < 9) + return; + + l_queue_foreach(nets, net_proxy_cfg_msg_rx, &net_data); + + /* Proxy configuration messages are not relayed */ +} + static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data) { struct mesh_net *net = user_data; @@ -3074,6 +3123,9 @@ void mesh_net_attach_gatt(struct gatt_proxy_service *gatt_proxy) gatt_proxy_service_register_recv_cb(gatt_proxy, PROXY_MSG_TYPE_NETWORK_PDU, net_msg_recv, NULL); + gatt_proxy_service_register_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_PROXY_CFG, + net_proxy_cfg_msg_recv, NULL); } void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy) @@ -3082,6 +3134,8 @@ void mesh_net_detach_gatt(struct gatt_proxy_service *gatt_proxy) gatt_proxy_service_deregister_recv_cb(gatt_proxy, PROXY_MSG_TYPE_NETWORK_PDU); + gatt_proxy_service_deregister_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_PROXY_CFG); } bool mesh_net_iv_index_update(struct mesh_net *net) diff --git a/mesh/proxy-cfg.c b/mesh/proxy-cfg.c new file mode 100644 index 000000000000..2cb23e0cc05b --- /dev/null +++ b/mesh/proxy-cfg.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> + +#include <ell/dbus.h> +#include <ell/log.h> +#include <ell/util.h> // l_put_be16() + +#include "mesh/crypto.h" // mesh_crypto_network_header_parse() +#include "mesh/mesh-io.h" // mesh_io_recv_func_t, required by gatt-proxy-svc.h +#include "mesh/gatt-service.h" // PROXY_MSG_TYPE_PROXY_CFG +#include "mesh/gatt-proxy-svc.h" // gatt_proxy_service_send() +#include "mesh/mesh-defs.h" // UNASSIGNED_ADDRESS +#include "mesh/net.h" // mesh_net_next_seq_num(), + // mesh_net_get_address() +#include "mesh/net-keys.h" // net_key_encrypt(), + // net_key_decrypt_proxy_cfg_msg() +#include "mesh/util.h" // print_packet() +#include "mesh/proxy-cfg.h" + +enum proxy_cfg_msg_op { + PROXY_CFG_MSG_OP_FILTER_SET_TYPE = 0x00, + PROXY_CFG_MSG_OP_FILTER_ADDRS_ADD = 0x01, + PROXY_CFG_MSG_OP_FILTER_ADDRS_REM = 0x02, + PROXY_CFG_MSG_OP_FILTER_STATUS = 0x03, +}; + +enum proxy_filter_type { + PROXY_FILTER_TYPE_ACCEPT_LIST = 0x00, + PROXY_FILTER_TYPE_REJECT_LIST = 0x00, +}; + +static enum proxy_filter_type filter_type = PROXY_FILTER_TYPE_ACCEPT_LIST; +static uint8_t num_filters = 0; + +void proxy_cfg_msg_received(struct mesh_net *net, uint32_t net_key_id, + uint32_t iv_index, + const uint8_t *data, uint8_t size) +{ + const uint8_t *msg; + uint8_t cfg_msg_len; + uint8_t net_ttl; + uint32_t net_seq; + uint16_t net_src, net_dst; + bool net_ctl; + uint8_t rsp[10]; + uint8_t rsp_len = 0; + + print_packet("RX: ProxyCfg [clr] :", data, size); + + if (!mesh_crypto_network_header_parse(data, size, &net_ctl, &net_ttl, + &net_seq, &net_src, &net_dst)) { + l_error("Failed to parse packet content"); + return; + } + + /* + * MshPRT_v1.1, section 6.6: + * - The CTL field shall be set to 1. [already checked] + * - The TTL field shall be set to 0. + * - The DST field shall be set to the unassigned address. + */ + if (net_dst != UNASSIGNED_ADDRESS || net_ttl) { + l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", + net_dst, net_ctl, net_ttl); + return; + } + + l_debug("RX: ProxyCfg %04x -> %04x : TTL 0x%02x : IV : %8.8x SEQ 0x%06x", + net_src, net_dst, net_ttl, iv_index, net_seq); + + msg = data + 9; + cfg_msg_len = size - 9; + + if (!cfg_msg_len) + return; + + /* process request */ + switch (msg[0]) { + case PROXY_CFG_MSG_OP_FILTER_SET_TYPE: + if (cfg_msg_len > 1) { + filter_type = msg[1]; + num_filters = 0; + } + break; + + case PROXY_CFG_MSG_OP_FILTER_ADDRS_ADD: + if (cfg_msg_len & 0x1) + num_filters += (cfg_msg_len - 1) / 2; + break; + + case PROXY_CFG_MSG_OP_FILTER_ADDRS_REM: + if (cfg_msg_len) + num_filters -= (num_filters >= ((cfg_msg_len - 1) / 2)) ? ((cfg_msg_len - 1) / 2) : num_filters; + break; + + default: + break; + } + + /* prepare response */ + switch (msg[0]) { + case PROXY_CFG_MSG_OP_FILTER_SET_TYPE: + case PROXY_CFG_MSG_OP_FILTER_ADDRS_ADD: + case PROXY_CFG_MSG_OP_FILTER_ADDRS_REM: + rsp[0] = PROXY_CFG_MSG_OP_FILTER_STATUS; + rsp[1] = filter_type; + l_put_be16(num_filters, &rsp[2]); + rsp_len = 4; + break; + + /* + * MshPRT_v1.1, section 6.7: + * Upon receiving a proxy configuration message with the Opcode + * field set to a value that is Reserved for Future Use, the + * Proxy Server shall ignore this message. + */ + default: + break; + } + + if (rsp_len) { + uint8_t pkt[30]; + uint8_t pkt_len; + + net_seq = mesh_net_next_seq_num(net); + if (!mesh_crypto_packet_build(true, 0/*TTL*/, net_seq, + mesh_net_get_address(net)/*src*/, + UNASSIGNED_ADDRESS/*dst*/, rsp[0], + false, 0, false, false, 0, 0, 0, + msg + 1, rsp_len - 1, pkt, &pkt_len)) + return; + + if (!net_key_encrypt(net_key_id, iv_index, pkt, pkt_len)) { + l_error("Failed to encode packet"); + return; + } + + gatt_proxy_service_send(PROXY_MSG_TYPE_PROXY_CFG, pkt, pkt_len); + } +} diff --git a/mesh/proxy-cfg.h b/mesh/proxy-cfg.h new file mode 100644 index 000000000000..658948ae7cbc --- /dev/null +++ b/mesh/proxy-cfg.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdint.h> + +struct mesh_net; + +void proxy_cfg_msg_received(struct mesh_net *net, uint32_t net_key_id, + uint32_t iv_index, + const uint8_t *data, uint8_t size); -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers ` (3 preceding siblings ...) 2025-07-01 14:22 ` [RFC PATCH BlueZ 4/4] mesh: initial support for proxy configuration messages via GATT Christian Eggers @ 2025-07-01 15:09 ` Christian Eggers 2025-07-01 16:07 ` Luiz Augusto von Dentz 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-01 15:09 UTC (permalink / raw) To: linux-bluetooth; +Cc: Brian Gix, Inga Stotland I forgot to mention, that the bluetooth-meshd must be built with the latest master of the ELL library (with my D-Bus patches). Otherwise, switching from the GATT provisioning service to the GATT proxy service will not work. On Tuesday, 1 July 2025, 16:22:19 CEST, Christian Eggers wrote: > This series adds initial support for the GATT Proxy Service feature. > This allows provision and management of MESH devices from mobile phones. > > Before continuing, I would be happy to get some feedback about this initial > part (and about what has to be done in order to mainline it). Any help for > doing the missing features (e.g. relaying between GATT and ADV) would be > highly appreciated. > > What seems to work: > - Provisioning from another Linux computer via meshctl (sometimes I need > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > if it doesn't work on the first try). > - Reading the composition (composition-get in meshctl). It looks like > meshctl tries to connect to the GATT Proxy Service automatically after > provisioning, but I have to enter 'connect' several times in order to > get a connection. You also need to manually set the target unicast > address ('target' command in 'config' menu). > - Adding extra network keys (Advertising for GATT Proxy Service cycles > between different network ids every 3 seconds). > - Transferring and binding of application keys. > - Using the OnOff client in meshctl (you again have to set the unicast > address in the 'onoff' menu). > - Provisioning and configuration of an OnOff device from iOS (using > the SMART+ app from Ledvance). This app requires at least basic > support for proxy configuration messages (patch 4/4). > > What maybe added later: > - Proper selection of the output interface (GATT vs. ADV) when sending > messages. > - Relaying of network messages / beacons between GATT and ADV > interfaces (in order to access further devices via the proxy). > > Note: I don't use the test-join script, because python3-pygobject is > (currently) not available on my (embedded) system. Instead, I use a > custom C++ program for the 'Join' and 'Attach' D-Bus calls. > ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers ` (4 preceding siblings ...) 2025-07-01 15:09 ` [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers @ 2025-07-01 16:07 ` Luiz Augusto von Dentz 2025-07-01 16:38 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers 6 siblings, 1 reply; 22+ messages in thread From: Luiz Augusto von Dentz @ 2025-07-01 16:07 UTC (permalink / raw) To: Christian Eggers; +Cc: linux-bluetooth, Brian Gix, Inga Stotland Hi Christian, On Tue, Jul 1, 2025 at 10:47 AM Christian Eggers <ceggers@arri.de> wrote: > > This series adds initial support for the GATT Proxy Service feature. > This allows provision and management of MESH devices from mobile phones. > > Before continuing, I would be happy to get some feedback about this initial > part (and about what has to be done in order to mainline it). Any help for > doing the missing features (e.g. relaying between GATT and ADV) would be > highly appreciated. > > What seems to work: > - Provisioning from another Linux computer via meshctl (sometimes I need > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > if it doesn't work on the first try). > - Reading the composition (composition-get in meshctl). It looks like > meshctl tries to connect to the GATT Proxy Service automatically after > provisioning, but I have to enter 'connect' several times in order to > get a connection. You also need to manually set the target unicast > address ('target' command in 'config' menu). > - Adding extra network keys (Advertising for GATT Proxy Service cycles > between different network ids every 3 seconds). > - Transferring and binding of application keys. > - Using the OnOff client in meshctl (you again have to set the unicast > address in the 'onoff' menu). > - Provisioning and configuration of an OnOff device from iOS (using > the SMART+ app from Ledvance). This app requires at least basic > support for proxy configuration messages (patch 4/4). Meshctl is sort of considered deprecated by meshd, it was created because at the time bluetoothd and meshd couldn't operate in the same controller so we need the GATT client to be used against bluetoothd, which is why the whole tools/mesh-gatt is sort of duplicated from mesh/ portion, but now that there is possible to have both bluetoothd and meshd we should probably move meshctl to be a client of meshd directly and then remove the whole tools/mesh-gatt/ directory. > What maybe added later: > - Proper selection of the output interface (GATT vs. ADV) when sending > messages. > - Relaying of network messages / beacons between GATT and ADV > interfaces (in order to access further devices via the proxy). Afaik the tools/mesh-gatt does already relay messages, but since it has been a long time I don't deal with mesh perhaps I don't remember it correctly so please have a look. > Note: I don't use the test-join script, because python3-pygobject is > (currently) not available on my (embedded) system. Instead, I use a > custom C++ program for the 'Join' and 'Attach' D-Bus calls. > -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service 2025-07-01 16:07 ` Luiz Augusto von Dentz @ 2025-07-01 16:38 ` Christian Eggers 2025-07-01 19:11 ` Luiz Augusto von Dentz 2025-07-01 19:17 ` Brian Gix 0 siblings, 2 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-01 16:38 UTC (permalink / raw) To: Luiz Augusto von Dentz; +Cc: linux-bluetooth, Brian Gix, Inga Stotland Hi Luiz, On Tuesday, 1 July 2025, 18:07:06 CEST, Luiz Augusto von Dentz wrote: > Hi Christian, > > On Tue, Jul 1, 2025 at 10:47 AM Christian Eggers <ceggers@arri.de> wrote: > > > > This series adds initial support for the GATT Proxy Service feature. > > This allows provision and management of MESH devices from mobile phones. > > > > Before continuing, I would be happy to get some feedback about this initial > > part (and about what has to be done in order to mainline it). Any help for > > doing the missing features (e.g. relaying between GATT and ADV) would be > > highly appreciated. > > > > What seems to work: > > - Provisioning from another Linux computer via meshctl (sometimes I need > > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > > if it doesn't work on the first try). > > - Reading the composition (composition-get in meshctl). It looks like > > meshctl tries to connect to the GATT Proxy Service automatically after > > provisioning, but I have to enter 'connect' several times in order to > > get a connection. You also need to manually set the target unicast > > address ('target' command in 'config' menu). > > - Adding extra network keys (Advertising for GATT Proxy Service cycles > > between different network ids every 3 seconds). > > - Transferring and binding of application keys. > > - Using the OnOff client in meshctl (you again have to set the unicast > > address in the 'onoff' menu). > > - Provisioning and configuration of an OnOff device from iOS (using > > the SMART+ app from Ledvance). This app requires at least basic > > support for proxy configuration messages (patch 4/4). > > Meshctl is sort of considered deprecated by meshd, it was created > because at the time bluetoothd and meshd couldn't operate in the same > controller so we need the GATT client to be used against bluetoothd, > which is why the whole tools/mesh-gatt is sort of duplicated from > mesh/ portion, but now that there is possible to have both bluetoothd > and meshd we should probably move meshctl to be a client of meshd > directly and then remove the whole tools/mesh-gatt/ directory. <Not sure whether I understand you correctly> I am working on the GATT Proxy Server (in bluetooth-meshd), whilst meshctl implements the GATT Proxy Client. Having a deprecated test program is much better than having nothing :-). Although I haven't checked for this, I guess that meshctl could become completely obsolete if GATT Proxy Client support was added to bluetooth-meshd. In this case, provision would be done by mesh-cfgclient regardless whether the link established by bluetooth-meshd is using ADV or GATT. My patch series mainly makes bluetooth-meshd a client of bluetoothd (which normally operate independently). As bluetoothd is (exclusively?) responsible for GATT and LE advertising, using these technologies in meshd requires it to use the D-Bus interfaces provided by bluetoothd. > > > What maybe added later: > > - Proper selection of the output interface (GATT vs. ADV) when sending > > messages. > > - Relaying of network messages / beacons between GATT and ADV > > interfaces (in order to access further devices via the proxy). > > Afaik the tools/mesh-gatt does already relay messages, but since it > has been a long time I don't deal with mesh perhaps I don't remember > it correctly so please have a look. I only had a short look, but I think that tools/mesh-gatt can only perform configuration of the 'relay' feature on a GATT Proxy Server (switch relaying on/off on a remote system). But probably I haven't got the big picture yet. > > > Note: I don't use the test-join script, because python3-pygobject is > > (currently) not available on my (embedded) system. Instead, I use a > > custom C++ program for the 'Join' and 'Attach' D-Bus calls. > > ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service 2025-07-01 16:38 ` Christian Eggers @ 2025-07-01 19:11 ` Luiz Augusto von Dentz 2025-07-01 19:17 ` Brian Gix 1 sibling, 0 replies; 22+ messages in thread From: Luiz Augusto von Dentz @ 2025-07-01 19:11 UTC (permalink / raw) To: Christian Eggers; +Cc: linux-bluetooth, Brian Gix, Inga Stotland Hi Christian, On Tue, Jul 1, 2025 at 12:38 PM Christian Eggers <ceggers@arri.de> wrote: > > Hi Luiz, > > On Tuesday, 1 July 2025, 18:07:06 CEST, Luiz Augusto von Dentz wrote: > > Hi Christian, > > > > On Tue, Jul 1, 2025 at 10:47 AM Christian Eggers <ceggers@arri.de> wrote: > > > > > > This series adds initial support for the GATT Proxy Service feature. > > > This allows provision and management of MESH devices from mobile phones. > > > > > > Before continuing, I would be happy to get some feedback about this initial > > > part (and about what has to be done in order to mainline it). Any help for > > > doing the missing features (e.g. relaying between GATT and ADV) would be > > > highly appreciated. > > > > > > What seems to work: > > > - Provisioning from another Linux computer via meshctl (sometimes I need > > > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > > > if it doesn't work on the first try). > > > - Reading the composition (composition-get in meshctl). It looks like > > > meshctl tries to connect to the GATT Proxy Service automatically after > > > provisioning, but I have to enter 'connect' several times in order to > > > get a connection. You also need to manually set the target unicast > > > address ('target' command in 'config' menu). > > > - Adding extra network keys (Advertising for GATT Proxy Service cycles > > > between different network ids every 3 seconds). > > > - Transferring and binding of application keys. > > > - Using the OnOff client in meshctl (you again have to set the unicast > > > address in the 'onoff' menu). > > > - Provisioning and configuration of an OnOff device from iOS (using > > > the SMART+ app from Ledvance). This app requires at least basic > > > support for proxy configuration messages (patch 4/4). > > > > Meshctl is sort of considered deprecated by meshd, it was created > > because at the time bluetoothd and meshd couldn't operate in the same > > controller so we need the GATT client to be used against bluetoothd, > > which is why the whole tools/mesh-gatt is sort of duplicated from > > mesh/ portion, but now that there is possible to have both bluetoothd > > and meshd we should probably move meshctl to be a client of meshd > > directly and then remove the whole tools/mesh-gatt/ directory. > > <Not sure whether I understand you correctly> > > I am working on the GATT Proxy Server (in bluetooth-meshd), whilst meshctl > implements the GATT Proxy Client. Having a deprecated test program is much > better than having nothing :-). Yep > Although I haven't checked for this, I guess that meshctl could become completely > obsolete if GATT Proxy Client support was added to bluetooth-meshd. In this > case, provision would be done by mesh-cfgclient regardless whether the > link established by bluetooth-meshd is using ADV or GATT. Yeah, we should probably integrate all the roles into mesh, now about mesh-cfgclient I wonder if we wouldn't be better off integrating it into bluetoothctl directly though, so one can control all daemon with that instead of resorting to different clients. > My patch series mainly makes bluetooth-meshd a client of bluetoothd (which > normally operate independently). As bluetoothd is (exclusively?) responsible > for GATT and LE advertising, using these technologies in meshd requires it > to use the D-Bus interfaces provided by bluetoothd. Yeah, that is intentional so we don't have to replicate the GATT and LE advertisement handling on meshd. > > > > > What maybe added later: > > > - Proper selection of the output interface (GATT vs. ADV) when sending > > > messages. > > > - Relaying of network messages / beacons between GATT and ADV > > > interfaces (in order to access further devices via the proxy). > > > > Afaik the tools/mesh-gatt does already relay messages, but since it > > has been a long time I don't deal with mesh perhaps I don't remember > > it correctly so please have a look. > > I only had a short look, but I think that tools/mesh-gatt can only perform > configuration of the 'relay' feature on a GATT Proxy Server (switch > relaying on/off on a remote system). But probably I haven't got the big > picture yet. > > > > > > Note: I don't use the test-join script, because python3-pygobject is > > > (currently) not available on my (embedded) system. Instead, I use a > > > custom C++ program for the 'Join' and 'Attach' D-Bus calls. > > > > > -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service 2025-07-01 16:38 ` Christian Eggers 2025-07-01 19:11 ` Luiz Augusto von Dentz @ 2025-07-01 19:17 ` Brian Gix 1 sibling, 0 replies; 22+ messages in thread From: Brian Gix @ 2025-07-01 19:17 UTC (permalink / raw) To: Christian Eggers; +Cc: Luiz Augusto von Dentz, linux-bluetooth, Inga Stotland On Tue, Jul 1, 2025 at 9:38 AM Christian Eggers <ceggers@arri.de> wrote: > > Hi Luiz, > > On Tuesday, 1 July 2025, 18:07:06 CEST, Luiz Augusto von Dentz wrote: > > Hi Christian, > > > > On Tue, Jul 1, 2025 at 10:47 AM Christian Eggers <ceggers@arri.de> wrote: > > > > > > This series adds initial support for the GATT Proxy Service feature. > > > This allows provision and management of MESH devices from mobile phones. > > > > > > Before continuing, I would be happy to get some feedback about this initial > > > part (and about what has to be done in order to mainline it). Any help for > > > doing the missing features (e.g. relaying between GATT and ADV) would be > > > highly appreciated. > > > > > > What seems to work: > > > - Provisioning from another Linux computer via meshctl (sometimes I need > > > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > > > if it doesn't work on the first try). > > > - Reading the composition (composition-get in meshctl). It looks like > > > meshctl tries to connect to the GATT Proxy Service automatically after > > > provisioning, but I have to enter 'connect' several times in order to > > > get a connection. You also need to manually set the target unicast > > > address ('target' command in 'config' menu). > > > - Adding extra network keys (Advertising for GATT Proxy Service cycles > > > between different network ids every 3 seconds). > > > - Transferring and binding of application keys. > > > - Using the OnOff client in meshctl (you again have to set the unicast > > > address in the 'onoff' menu). > > > - Provisioning and configuration of an OnOff device from iOS (using > > > the SMART+ app from Ledvance). This app requires at least basic > > > support for proxy configuration messages (patch 4/4). > > > > Meshctl is sort of considered deprecated by meshd, it was created > > because at the time bluetoothd and meshd couldn't operate in the same > > controller so we need the GATT client to be used against bluetoothd, > > which is why the whole tools/mesh-gatt is sort of duplicated from > > mesh/ portion, but now that there is possible to have both bluetoothd > > and meshd we should probably move meshctl to be a client of meshd > > directly and then remove the whole tools/mesh-gatt/ directory. > > <Not sure whether I understand you correctly> > > I am working on the GATT Proxy Server (in bluetooth-meshd), whilst meshctl > implements the GATT Proxy Client. Having a deprecated test program is much > better than having nothing :-). > > Although I haven't checked for this, I guess that meshctl could become completely > obsolete if GATT Proxy Client support was added to bluetooth-meshd. In this > case, provision would be done by mesh-cfgclient regardless whether the > link established by bluetooth-meshd is using ADV or GATT. Proxy-Client should never be added to bluetooth-meshd. As you point out, a deprecated GATT proxy client is probably "better than nothing" for testing a GATT proxy server. We had hoped that the need for GATT Proxy Mesh would dissipate over time, because point-to-point ACL connections within the mesh ecosystem is disruptive, and not really "mesh". > > My patch series mainly makes bluetooth-meshd a client of bluetoothd (which > normally operate independently). As bluetoothd is (exclusively?) responsible > for GATT and LE advertising, using these technologies in meshd requires it > to use the D-Bus interfaces provided by bluetoothd. This answers one of my questions from an earlier (perhaps suppressed) response I made... This is the correct architecture for adding a Proxy Server to bluetooth-meshd. I am officially a retiree now, but I will try to review this patch set this week, or next week at the latest. Between holiday trips. > > > > > > What maybe added later: > > > - Proper selection of the output interface (GATT vs. ADV) when sending > > > messages. > > > - Relaying of network messages / beacons between GATT and ADV > > > interfaces (in order to access further devices via the proxy). > > > > Afaik the tools/mesh-gatt does already relay messages, but since it > > has been a long time I don't deal with mesh perhaps I don't remember > > it correctly so please have a look. > > I only had a short look, but I think that tools/mesh-gatt can only perform > configuration of the 'relay' feature on a GATT Proxy Server (switch > relaying on/off on a remote system). But probably I haven't got the big > picture yet. > > > > > > Note: I don't use the test-join script, because python3-pygobject is > > > (currently) not available on my (embedded) system. Instead, I use a > > > custom C++ program for the 'Join' and 'Attach' D-Bus calls. > > > > > ^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 0/7] Initial support for GATT Proxy Service 2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers ` (5 preceding siblings ...) 2025-07-01 16:07 ` Luiz Augusto von Dentz @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers ` (6 more replies) 6 siblings, 7 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth Hi all, this is my 2nd RFC for adding GATT provisioning and GATT proxy service. These patches should work on top of my latest Mesh series I just sent: https://lore.kernel.org/linux-bluetooth/20250711173958.25689-1-ceggers@arri.de/ Please don't forget to update the Ell library to the latest master revision. Compared to the 1st RFC series, I have made a couple of cleanups and worked on the following features: - Proxy configuration messages / Proxy filtering - Propagation of Beacons via GATT - Relaying between ADV and GATT (after running 'relay-set' and 'proxy-set' in meshctl). After provisioning two nodes via GATT (with meshctl), I can connect to one of these nodes and control both from the 'onoff' client. So at least basic proxy relaying should work now. On Tuesday, 1 July 2025, 16:22:19 CEST, Christian Eggers wrote: > This series adds initial support for the GATT Proxy Service feature. > This allows provision and management of MESH devices from mobile phones. > > Before continuing, I would be happy to get some feedback about this initial > part (and about what has to be done in order to mainline it). Any help for > doing the missing features (e.g. relaying between GATT and ADV) would be > highly appreciated. > > What seems to work: > - Provisioning from another Linux computer via meshctl (sometimes I need > to repeatedly enter 'discover-unprovisioned on' and 'provision <uuid>' > if it doesn't work on the first try). > - Reading the composition (composition-get in meshctl). It looks like > meshctl tries to connect to the GATT Proxy Service automatically after > provisioning, but I have to enter 'connect' several times in order to > get a connection. You also need to manually set the target unicast > address ('target' command in 'config' menu). > - Adding extra network keys (Advertising for GATT Proxy Service cycles > between different network ids every 3 seconds). > - Transferring and binding of application keys. > - Using the OnOff client in meshctl (you again have to set the unicast > address in the 'onoff' menu). > - Provisioning and configuration of an OnOff device from iOS (using > the SMART+ app from Ledvance). This app requires at least basic > support for proxy configuration messages (patch 4/4). > > What maybe added later: > - Proper selection of the output interface (GATT vs. ADV) when sending > messages. > - Relaying of network messages / beacons between GATT and ADV > interfaces (in order to access further devices via the proxy). > > Note: I don't use the test-join script, because python3-pygobject is > (currently) not available on my (embedded) system. Instead, I use a > custom C++ program for the 'Join' and 'Attach' D-Bus calls. I just noticed that 'test-join' has been superseded by 'test-mesh'. I'll do some testing with this tool next week. Happy reviewing :-) Christian ^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 18:16 ` Initial support for GATT Proxy Service bluez.test.bot 2025-07-15 15:56 ` bluez.test.bot 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 2/7] mesh: add support for provisioning via GATT Christian Eggers ` (5 subsequent siblings) 6 siblings, 2 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers I cannot find a specific interval for sending unprovisioned device beacons in MshPRT_v1.1, section 3.10.2/5.2.1. The current interval of 500 ms seems to cause interferience with normal advertising messages, maybe this is controller dependent (seen on RTL8761BU). A beacon interval of 1000 ms should be sufficient for scanning (e.g. by a provisioner) while allowing normal advertising messages to be transmitted. --- mesh/prov-acceptor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index 0cedc227ad28..a83636280dd8 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -793,7 +793,7 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, memcpy(beacon + 2, uuid, 16); /* Infinitely Beacon until Canceled, or Provisioning Starts */ - result = mesh_send_pkt(0, 500, beacon, len); + result = mesh_send_pkt(0, 1000, beacon, len); if (!result) goto error_fail; -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* RE: Initial support for GATT Proxy Service 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers @ 2025-07-11 18:16 ` bluez.test.bot 2025-07-15 15:56 ` bluez.test.bot 1 sibling, 0 replies; 22+ messages in thread From: bluez.test.bot @ 2025-07-11 18:16 UTC (permalink / raw) To: linux-bluetooth, ceggers [-- Attachment #1: Type: text/plain, Size: 635 bytes --] This is an automated email and please do not reply to this email. Dear Submitter, Thank you for submitting the patches to the linux bluetooth mailing list. While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository. ----- Output ----- .git/rebase-apply/patch:395: trailing whitespace. .git/rebase-apply/patch:395: new blank line at EOF. + error: patch failed: mesh/net.c:192 error: mesh/net.c: patch does not apply hint: Use 'git am --show-current-patch' to see the failed patch Please resolve the issue and submit the patches again. --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 22+ messages in thread
* RE: Initial support for GATT Proxy Service 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers 2025-07-11 18:16 ` Initial support for GATT Proxy Service bluez.test.bot @ 2025-07-15 15:56 ` bluez.test.bot 1 sibling, 0 replies; 22+ messages in thread From: bluez.test.bot @ 2025-07-15 15:56 UTC (permalink / raw) To: linux-bluetooth, ceggers [-- Attachment #1: Type: text/plain, Size: 38388 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=981619 ---Test result--- Test Summary: CheckPatch PENDING 0.34 seconds GitLint PENDING 0.38 seconds BuildEll PASS 20.16 seconds BluezMake FAIL 2799.81 seconds MakeCheck FAIL 191.46 seconds MakeDistcheck FAIL 49.85 seconds CheckValgrind FAIL 123.25 seconds CheckSmatch FAIL 197.49 seconds bluezmakeextell FAIL 104.21 seconds IncrementalBuild PENDING 0.44 seconds ScanBuild FAIL 245.56 seconds Details ############################## Test: CheckPatch - PENDING Desc: Run checkpatch.pl script Output: ############################## Test: GitLint - PENDING Desc: Run gitlint Output: ############################## Test: BluezMake - FAIL Desc: Build BlueZ Output: tools/mgmt-tester.c: In function ‘main’: tools/mgmt-tester.c:12907:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12907 | int main(int argc, char *argv[]) | ^~~~ unit/test-avdtp.c: In function ‘main’: unit/test-avdtp.c:766:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 766 | int main(int argc, char *argv[]) | ^~~~ unit/test-avrcp.c: In function ‘main’: unit/test-avrcp.c:989:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 989 | int main(int argc, char *argv[]) | ^~~~ mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4039: all] Error 2 ############################## Test: MakeCheck - FAIL Desc: Run Bluez Make Check Output: mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make: *** [Makefile:10456: check] Error 2 ############################## Test: MakeDistcheck - FAIL Desc: Run Bluez Make Distcheck Output: Package cups was not found in the pkg-config search path. Perhaps you should add the directory containing `cups.pc' to the PKG_CONFIG_PATH environment variable No package 'cups' found ../../mesh/gatt-service.c: In function ‘chrc_write_value_call’: ../../mesh/gatt-service.c:310:53: error: parameter name omitted 310 | static struct l_dbus_message *chrc_write_value_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ ../../mesh/gatt-service.c: In function ‘chrc_acquire_notify_call’: ../../mesh/gatt-service.c:447:56: error: parameter name omitted 447 | static struct l_dbus_message *chrc_acquire_notify_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ ../../mesh/gatt-service.c: In function ‘adv_release_call’: ../../mesh/gatt-service.c:749:48: error: parameter name omitted 749 | static struct l_dbus_message *adv_release_call(struct l_dbus *, | ^~~~~~~~~~~~~~~ make[2]: *** [Makefile:6850: mesh/gatt-service.o] Error 1 make[2]: *** Waiting for unfinished jobs.... make[1]: *** [Makefile:4039: all] Error 2 make: *** [Makefile:10377: distcheck] Error 1 ############################## Test: CheckValgrind - FAIL Desc: Run Bluez Make Check with Valgrind Output: tools/mgmt-tester.c: In function ‘main’: tools/mgmt-tester.c:12907:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without 12907 | int main(int argc, char *argv[]) | ^~~~ mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:10456: check] Error 2 ############################## Test: CheckSmatch - FAIL Desc: Run smatch tool with source Output: src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:830:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1323:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1354:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:317:25: warning: array of flexible structures src/shared/bap.c: note: in included file: ./src/shared/ascs.h:88:25: warning: array of flexible structures src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' src/shared/crypto.c:271:21: warning: Variable length array is used. src/shared/crypto.c:272:23: warning: Variable length array is used. src/shared/gatt-helpers.c:768:31: warning: Variable length array is used. src/shared/gatt-helpers.c:830:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1323:31: warning: Variable length array is used. src/shared/gatt-helpers.c:1354:23: warning: Variable length array is used. src/shared/gatt-server.c:278:25: warning: Variable length array is used. src/shared/gatt-server.c:618:25: warning: Variable length array is used. src/shared/gatt-server.c:716:25: warning: Variable length array is used. src/shared/bap.c:317:25: warning: array of flexible structures src/shared/bap.c: note: in included file: ./src/shared/ascs.h:88:25: warning: array of flexible structures src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' tools/mesh-cfgtest.c:1453:17: warning: unknown escape sequence: '\%' tools/sco-tester.c: note: in included file: ./lib/bluetooth.h:232:15: warning: array of flexible structures ./lib/bluetooth.h:237:31: warning: array of flexible structures tools/bneptest.c:634:39: warning: unknown escape sequence: '\%' tools/seq2bseq.c:57:26: warning: Variable length array is used. tools/obex-client-tool.c: note: in included file (through /usr/include/readline/readline.h): /usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function' /usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction' /usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction' /usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction' unit/avctp.c:505:34: warning: Variable length array is used. unit/avctp.c:556:34: warning: Variable length array is used. unit/test-avrcp.c:373:26: warning: Variable length array is used. unit/test-avrcp.c:398:26: warning: Variable length array is used. unit/test-avrcp.c:414:24: warning: Variable length array is used. unit/avrcp-lib.c:1085:34: warning: Variable length array is used. unit/avrcp-lib.c:1583:34: warning: Variable length array is used. unit/avrcp-lib.c:1612:34: warning: Variable length array is used. unit/avrcp-lib.c:1638:34: warning: Variable length array is used. mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4039: all] Error 2 ############################## Test: bluezmakeextell - FAIL Desc: Build Bluez with External ELL Output: mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4039: all] Error 2 ############################## Test: IncrementalBuild - PENDING Desc: Incremental build with the patches in the series Output: ############################## Test: ScanBuild - FAIL Desc: Run Scan Build Output: src/shared/gatt-client.c:451:21: warning: Use of memory after it is freed gatt_db_unregister(op->client->db, op->db_id); ^~~~~~~~~~ src/shared/gatt-client.c:696:2: warning: Use of memory after it is freed discovery_op_complete(op, false, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:996:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1102:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1296:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1361:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1636:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1641:2: warning: Use of memory after it is freed discover_all(op); ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2147:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2155:8: warning: Use of memory after it is freed discovery_op_ref(op), ^~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3180:2: warning: Use of memory after it is freed complete_write_long_op(req, success, 0, false); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3202:2: warning: Use of memory after it is freed request_unref(req); ^~~~~~~~~~~~~~~~~~ 12 warnings generated. src/shared/bap.c:1528:8: warning: Use of memory after it is freed bap = bt_bap_ref_safe(bap); ^~~~~~~~~~~~~~~~~~~~ src/shared/bap.c:2310:20: warning: Use of memory after it is freed return queue_find(stream->bap->streams, NULL, stream); ^~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/shared/gatt-client.c:451:21: warning: Use of memory after it is freed gatt_db_unregister(op->client->db, op->db_id); ^~~~~~~~~~ src/shared/gatt-client.c:696:2: warning: Use of memory after it is freed discovery_op_complete(op, false, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:996:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1102:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1296:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1361:2: warning: Use of memory after it is freed discovery_op_complete(op, success, att_ecode); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1636:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:1641:2: warning: Use of memory after it is freed discover_all(op); ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2147:6: warning: Use of memory after it is freed if (read_db_hash(op)) { ^~~~~~~~~~~~~~~~ src/shared/gatt-client.c:2155:8: warning: Use of memory after it is freed discovery_op_ref(op), ^~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3180:2: warning: Use of memory after it is freed complete_write_long_op(req, success, 0, false); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ src/shared/gatt-client.c:3202:2: warning: Use of memory after it is freed request_unref(req); ^~~~~~~~~~~~~~~~~~ 12 warnings generated. tools/hciattach.c:817:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 10)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:865:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:887:8: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 10)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:909:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:930:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 4)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/hciattach.c:974:7: warning: Although the value stored to 'n' is used in the enclosing expression, the value is never actually read from 'n' if ((n = read_hci_event(fd, resp, 6)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 warnings generated. src/shared/bap.c:1528:8: warning: Use of memory after it is freed bap = bt_bap_ref_safe(bap); ^~~~~~~~~~~~~~~~~~~~ src/shared/bap.c:2310:20: warning: Use of memory after it is freed return queue_find(stream->bap->streams, NULL, stream); ^~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/oui.c:50:2: warning: Value stored to 'hwdb' is never read hwdb = udev_hwdb_unref(hwdb); ^ ~~~~~~~~~~~~~~~~~~~~~ src/oui.c:53:2: warning: Value stored to 'udev' is never read udev = udev_unref(udev); ^ ~~~~~~~~~~~~~~~~ 2 warnings generated. tools/rfcomm.c:234:3: warning: Value stored to 'i' is never read i = execvp(cmdargv[0], cmdargv); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:234:7: warning: Null pointer passed to 1st parameter expecting 'nonnull' i = execvp(cmdargv[0], cmdargv); ^~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:354:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd' if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/rfcomm.c:497:14: warning: Assigned value is garbage or undefined req.channel = raddr.rc_channel; ^ ~~~~~~~~~~~~~~~~ tools/rfcomm.c:515:8: warning: Although the value stored to 'fd' is used in the enclosing expression, the value is never actually read from 'fd' if ((fd = open(devname, O_RDONLY | O_NOCTTY)) < 0) { ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 warnings generated. tools/hcidump.c:180:9: warning: Potential leak of memory pointed to by 'dp' if (fds[i].fd == sock) ^~~ tools/hcidump.c:248:17: warning: Assigned value is garbage or undefined dh->ts_sec = htobl(frm.ts.tv_sec); ^ ~~~~~~~~~~~~~~~~~~~~ tools/hcidump.c:326:9: warning: 1st function call argument is an uninitialized value if (be32toh(dp.flags) & 0x02) { ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:341:20: warning: 1st function call argument is an uninitialized value frm.data_len = be32toh(dp.len); ^~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:346:14: warning: 1st function call argument is an uninitialized value opcode = be32toh(dp.flags) & 0xffff; ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:384:17: warning: Assigned value is garbage or undefined frm.data_len = btohs(dh.len); ^ ~~~~~~~~~~~~~ tools/hcidump.c:394:11: warning: Assigned value is garbage or undefined frm.len = frm.data_len; ^ ~~~~~~~~~~~~ tools/hcidump.c:398:9: warning: 1st function call argument is an uninitialized value ts = be64toh(ph.ts); ^~~~~~~~~~~~~~ /usr/include/endian.h:51:22: note: expanded from macro 'be64toh' # define be64toh(x) __bswap_64 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:403:13: warning: 1st function call argument is an uninitialized value frm.in = be32toh(dp.flags) & 0x01; ^~~~~~~~~~~~~~~~~ /usr/include/endian.h:46:22: note: expanded from macro 'be32toh' # define be32toh(x) __bswap_32 (x) ^~~~~~~~~~~~~~ tools/hcidump.c:408:11: warning: Assigned value is garbage or undefined frm.in = dh.in; ^ ~~~~~ tools/hcidump.c:437:7: warning: Null pointer passed to 1st parameter expecting 'nonnull' fd = open(file, open_flags, 0644); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 warnings generated. tools/ciptool.c:351:7: warning: 5th function call argument is an uninitialized value sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK)); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:300:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:338:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ 3 warnings generated. tools/sdptool.c:941:26: warning: Result of 'malloc' is converted to a pointer of type 'uint32_t', which is incompatible with sizeof operand type 'int' uint32_t *value_int = malloc(sizeof(int)); ~~~~~~~~~~ ^~~~~~ ~~~~~~~~~~~ tools/sdptool.c:980:4: warning: 1st function call argument is an uninitialized value free(allocArray[i]); ^~~~~~~~~~~~~~~~~~~ tools/sdptool.c:3777:2: warning: Potential leak of memory pointed to by 'si.name' return add_service(0, &si); ^~~~~~~~~~~~~~~~~~~~~~~~~~ tools/sdptool.c:4112:4: warning: Potential leak of memory pointed to by 'context.svc' return -1; ^~~~~~~~~ 4 warnings generated. tools/avtest.c:243:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:253:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:262:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:276:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:283:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:290:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:297:5: warning: Value stored to 'len' is never read len = write(sk, buf, ^ ~~~~~~~~~~~~~~ tools/avtest.c:309:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:313:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:322:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:326:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:335:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:342:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:364:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:368:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:377:5: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:381:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:394:5: warning: Value stored to 'len' is never read len = write(sk, buf, 4); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:398:5: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:405:4: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:415:4: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:580:3: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:588:3: warning: Value stored to 'len' is never read len = write(sk, buf, invalid ? 2 : 3); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/avtest.c:602:3: warning: Value stored to 'len' is never read len = write(sk, buf, 4 + media_transport_size); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/avtest.c:615:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:625:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:637:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:652:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:664:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:673:3: warning: Value stored to 'len' is never read len = write(sk, buf, 3); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:680:3: warning: Value stored to 'len' is never read len = write(sk, buf, 2); ^ ~~~~~~~~~~~~~~~~~ tools/avtest.c:716:2: warning: Value stored to 'len' is never read len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed)); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 32 warnings generated. tools/btproxy.c:836:15: warning: Null pointer passed to 1st parameter expecting 'nonnull' tcp_port = atoi(optarg); ^~~~~~~~~~~~ tools/btproxy.c:839:8: warning: Null pointer passed to 1st parameter expecting 'nonnull' if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) ^~~~~~~~~~~~~~ 2 warnings generated. tools/iso-tester.c:1857:7: warning: Potential leak of memory pointed to by 'addr' err = bind(sk, (struct sockaddr *) addr, sizeof(*addr) + ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/iso-tester.c:1865:7: warning: Potential leak of memory pointed to by 'addr' err = bind(sk, (struct sockaddr *) addr, sizeof(*addr)); ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. tools/create-image.c:76:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:84:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:92:3: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ tools/create-image.c:105:2: warning: Value stored to 'fd' is never read fd = -1; ^ ~~ 4 warnings generated. tools/check-selftest.c:42:3: warning: Value stored to 'ptr' is never read ptr = fgets(result, sizeof(result), fp); ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. tools/gatt-service.c:294:2: warning: 2nd function call argument is an uninitialized value chr_write(chr, value, len); ^~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. tools/btgatt-server.c:1212:2: warning: Value stored to 'argv' is never read argv -= optind; ^ ~~~~~~ 1 warning generated. tools/btgatt-client.c:1824:2: warning: Value stored to 'argv' is never read argv += optind; ^ ~~~~~~ 1 warning generated. tools/obex-server-tool.c:133:13: warning: Null pointer passed to 1st parameter expecting 'nonnull' data->fd = open(name, O_WRONLY | O_CREAT | O_NOCTTY, 0600); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tools/obex-server-tool.c:192:13: warning: Null pointer passed to 1st parameter expecting 'nonnull' data->fd = open(name, O_RDONLY | O_NOCTTY, 0); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. tools/btpclientctl.c:402:3: warning: Value stored to 'bit' is never read bit = 0; ^ ~ tools/btpclientctl.c:1655:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull' memcpy(cp->data, ad_data, ad_len); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t' pElem = malloc(sizeof(uint16_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t' pElem = malloc(sizeof(uint32_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ 2 warnings generated. unit/avrcp-lib.c:1968:3: warning: 1st function call argument is an uninitialized value g_free(text[i]); ^~~~~~~~~~~~~~~ 1 warning generated. unit/avdtp.c:756:25: warning: Use of memory after it is freed session->prio_queue = g_slist_remove(session->prio_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ unit/avdtp.c:763:24: warning: Use of memory after it is freed session->req_queue = g_slist_remove(session->req_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. profiles/audio/avdtp.c:893:25: warning: Use of memory after it is freed session->prio_queue = g_slist_remove(session->prio_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/audio/avdtp.c:900:24: warning: Use of memory after it is freed session->req_queue = g_slist_remove(session->req_queue, req); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 warnings generated. profiles/audio/a2dp.c:375:8: warning: Use of memory after it is freed if (!cb->resume_cb) ^~~~~~~~~~~~~ profiles/audio/a2dp.c:3283:20: warning: Access to field 'starting' results in a dereference of a null pointer (loaded from variable 'stream') stream->starting = TRUE; ~~~~~~ ^ profiles/audio/a2dp.c:3286:8: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream') if (!stream->suspending && stream->suspend_timer) { ^~~~~~~~~~~~~~~~~~ profiles/audio/a2dp.c:3346:22: warning: Access to field 'suspending' results in a dereference of a null pointer (loaded from variable 'stream') stream->suspending = TRUE; ~~~~~~ ^ 4 warnings generated. profiles/audio/avrcp.c:1961:2: warning: Value stored to 'operands' is never read operands += sizeof(*pdu); ^ ~~~~~~~~~~~~ 1 warning generated. profiles/health/hdp.c:644:3: warning: Use of memory after it is freed hdp_tmp_dc_data_unref(dc_data); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/health/hdp.c:800:19: warning: Use of memory after it is freed path = g_strdup(chan->path); ^~~~~~~~~~ profiles/health/hdp.c:1779:6: warning: Use of memory after it is freed hdp_tmp_dc_data_ref(hdp_conn), ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ profiles/health/hdp.c:1836:30: warning: Use of memory after it is freed reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError", ^~~~~~~~~ 4 warnings generated. profiles/health/hdp_util.c:1052:2: warning: Use of memory after it is freed conn_data->func(conn_data->data, gerr); ^~~~~~~~~~~~~~~ 1 warning generated. attrib/gatt.c:970:2: warning: Potential leak of memory pointed to by 'long_write' return prepare_write(long_write); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. profiles/audio/bap.c:1024:18: warning: Access to field 'data' results in a dereference of a null pointer (loaded from field 'ep') bap_update_cigs(setup->ep->data); ^~~~~~~~~~~~~~~ 1 warning generated. src/sdpd-request.c:211:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint16_t' pElem = malloc(sizeof(uint16_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ src/sdpd-request.c:239:13: warning: Result of 'malloc' is converted to a pointer of type 'char', which is incompatible with sizeof operand type 'uint32_t' pElem = malloc(sizeof(uint32_t)); ^~~~~~ ~~~~~~~~~~~~~~~~ 2 warnings generated. src/sdp-client.c:353:14: warning: Access to field 'cb' results in a dereference of a null pointer (*ctxt)->cb = cb; ~~~~~~~~~~~~^~~~ 1 warning generated. src/sdp-xml.c:126:10: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:300:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ src/sdp-xml.c:338:11: warning: Assigned value is garbage or undefined buf[1] = data[i + 1]; ^ ~~~~~~~~~~~ 3 warnings generated. src/gatt-database.c:1173:10: warning: Value stored to 'bits' during its initialization is never read uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING, ^~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. gobex/gobex-header.c:95:2: warning: Null pointer passed to 2nd parameter expecting 'nonnull' memcpy(to, from, count); ^~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. gobex/gobex-transfer.c:423:7: warning: Use of memory after it is freed if (!g_slist_find(transfers, transfer)) ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 warning generated. mesh/net-keys.c: In function ‘net_key_fill_adv_service_data’: mesh/net-keys.c:910:16: error: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Werror=sign-compare] 910 | for (i = 0; i < sizeof(key->net_id); i++) | ^ mesh/net-keys.c: In function ‘net_key_get_next_id’: mesh/net-keys.c:928:6: error: suggest explicit braces to avoid ambiguous ‘else’ [-Werror=dangling-else] 928 | if (!found) | ^ cc1: all warnings being treated as errors make[1]: *** [Makefile:6850: mesh/net-keys.o] Error 1 make[1]: *** Waiting for unfinished jobs.... make: *** [Makefile:4039: all] Error 2 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 2/7] mesh: add support for provisioning via GATT 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 3/7] mesh: add GATT proxy service Christian Eggers ` (4 subsequent siblings) 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers ToDo: Consider moving dbus_client instance into separate singleton, so that no destruction/construction is necessary when switching from GATT provisioning service to GATT proxy service (see next commit). --- Makefile.mesh | 4 +- mesh/gatt-service.c | 1193 ++++++++++++++++++++++++++++++++++++++++++ mesh/gatt-service.h | 54 ++ mesh/pb-gatt.c | 173 ++++++ mesh/pb-gatt.h | 20 + mesh/prov-acceptor.c | 6 + 6 files changed, 1449 insertions(+), 1 deletion(-) create mode 100644 mesh/gatt-service.c create mode 100644 mesh/gatt-service.h create mode 100644 mesh/pb-gatt.c create mode 100644 mesh/pb-gatt.h diff --git a/Makefile.mesh b/Makefile.mesh index e4c9fa6a32e6..700d64fe9293 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -35,10 +35,12 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/prov-acceptor.c mesh/prov-initiator.c \ mesh/manager.h mesh/manager.c \ mesh/pb-adv.h mesh/pb-adv.c \ + mesh/pb-gatt.h mesh/pb-gatt.c \ mesh/keyring.h mesh/keyring.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ - mesh/mesh-defs.h + mesh/mesh-defs.h \ + mesh/gatt-service.h mesh/gatt-service.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/gatt-service.c b/mesh/gatt-service.c new file mode 100644 index 000000000000..9fdf54d9031d --- /dev/null +++ b/mesh/gatt-service.c @@ -0,0 +1,1193 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> // memcpy(), strerror() +#include <sys/socket.h> // SOCK_SEQPACKET, SOCK_NONBLOCK, + // AF_UNIX, SOCK_CLOEXEC, MSG_NOSIGNAL, + // struct msghdr, + // socketpair(), sendmsg() +#include <sys/types.h> // struct iovec +#include <unistd.h> // close() + +#include <ell/dbus.h> +#include <ell/dbus-client.h> +#include <ell/dbus-service.h> +#include <ell/idle.h> +#include <ell/io.h> +#include <ell/log.h> +#include <ell/util.h> // L_ARRAY_SIZE(), + // l_new(), l_free() + +#include "mesh/dbus.h" // dbus_get_bus(), + // dbus_append_byte_array(), + // dbus_error() +#include "mesh/error.h" // MESH_ERROR_INVALID_ARGS +#include "mesh/util.h" // print_packet() +#include "mesh/gatt-service.h" + +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define BLUEZ_MESH_GATT_PATH BLUEZ_MESH_PATH "/gatt" +#define BLUEZ_MESH_SERVICE_PATH BLUEZ_MESH_GATT_PATH "/service" +#define BLUEZ_MESH_CHRC_DATA_IN_PATH BLUEZ_MESH_SERVICE_PATH "/data_in" +#define BLUEZ_MESH_CHRC_DATA_OUT_PATH BLUEZ_MESH_SERVICE_PATH "/data_out" +/* + * Advertising should NOT be handled by provisioning's object manager, so + * we cannot use a child element of BLUEZ_MESH_GATT_PATH. + */ +#define BLUEZ_MESH_GATT_ADV_PATH BLUEZ_MESH_PATH "/gatt_adv" + +#define GATT_MGR_IFACE "org.bluez.GattManager1" +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1" + +#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" +#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1" + +#define GATT_MTU 23 + +struct gatt_service; +struct characterstic +{ + const char *uuid; + const char * const *flags; + struct gatt_service *service; +}; + +enum write_value_type { + WRITE_VALUE_TYPE_COMMAND, + WRITE_VALUE_TYPE_REQUEST, + WRITE_VALUE_TYPE_RELIABLE +}; + +enum link_type { + LINK_TYPE_BR_EDR, + LINK_TYPE_LE +}; + +struct write_value_options { + const char *device; + enum link_type link; + enum write_value_type type; + uint16_t offset; + uint16_t mtu; + bool prepare_authorize; +}; + +struct acquire_notify_options { + const char *device; + enum link_type link; + uint16_t mtu; +}; + +/* MshPRT_v1.1, section 6.3.1, SAR field */ +#define PROXY_PDA_SAR_SHIFT 6 +#define PROXY_PDA_SAR_MASK 0x3 +enum proxy_pdu_sar { + PROXY_PDU_SAR_CMPLT_MSG = 0x00, + PROXY_PDU_SAR_1ST_SEG = 0x01, + PROXY_PDU_SAR_CONT_SEG = 0x02, + PROXY_PDU_SAR_LAST_SEG = 0x03, +}; + +struct gatt_service { + const char *svc_uuid; + uint8_t max_pdu_len; + + gatt_service_notify_acquired_cb notify_acquired_cb; + gatt_service_notify_stopped_cb notify_stopped_cb; + gatt_service_rx_cb rx_cb; + gatt_service_tx_cmplt_cb tx_cmplt_cb; + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb; + struct characterstic chrc_data_in; + struct characterstic chrc_data_out; + + struct l_dbus_client *dbus_client; + struct l_dbus_proxy *dbus_proxy_gatt_mgr; + struct l_dbus_proxy *dbus_proxy_le_adv_mgr; + + /* + * ToDo: Check whether acceptors timeout complies with MshPRT_v1.1, + * section 5.2.2 + */ + struct l_io *notify_io; + uint16_t mtu; + uint8_t *sar; + uint8_t *sar_out; + uint8_t msg_type; + uint8_t sar_len; + void *user_data; + + gatt_destroy_cb svc_deinit_cb; + gatt_destroy_cb adv_deinit_cb; + + gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct gatt_service *gatt_service = NULL; + +static bool notify_write(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + unsigned int remaining = (service->sar + service->sar_len) + - service->sar_out; + unsigned max_size = service->mtu - 5; + struct iovec iov[2]; + struct msghdr msg; + bool more = false; + uint8_t sar_type; + int i, count; + + /* Note: One extra byte is required for sar_type */ + if (service->sar_len < max_size) { + sar_type = PROXY_PDU_SAR_CMPLT_MSG; + count = service->sar_len; + } + else if (service->sar_out == service->sar) { + sar_type = PROXY_PDU_SAR_1ST_SEG; + count = max_size - 1; + more = true; + } + else if (remaining < max_size) { + sar_type = PROXY_PDU_SAR_LAST_SEG; + count = remaining; + } + else { + sar_type = PROXY_PDU_SAR_CONT_SEG; + count = max_size - 1; + more = true; + } + + sar_type <<= PROXY_PDA_SAR_SHIFT; + sar_type |= service->msg_type; + +// l_info("remaining=%u, count=%u, sar_type=0x%02x", remaining, count, sar_type); +// print_packet("notify_write", service->sar_out, count); + + iov[0].iov_base = &sar_type; + iov[0].iov_len = sizeof(sar_type); + iov[1].iov_base = service->sar_out; + iov[1].iov_len = count; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = L_ARRAY_SIZE(iov); + + if (sendmsg(l_io_get_fd(service->notify_io), &msg, MSG_NOSIGNAL) < 0) + l_error("Cannot write notification data: %s", strerror(errno)); + + service->sar_out += count; + + if (!more) + more = service->tx_cmplt_cb(service->user_data); + + return more; +} + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len) +{ + if (!service || gatt_service != service) + return; + + if (len > service->max_pdu_len) { + l_error("Frame too long"); + return; + } + + if (!service->notify_io) { + l_warn("Not connected, dropping TX message..."); + return; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + service->sar_out = service->sar; + service->msg_type = msg_type; + print_packet("TX-GATT", service->sar, service->sar_len); + l_io_set_write_handler(service->notify_io, notify_write, service, NULL); +} + +static bool svc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct gatt_service *service = user_data; + +// l_info("svc_uuid_getter"); + return l_dbus_message_builder_append_basic(builder, 's', + service->svc_uuid); +} + +static bool svc_primary_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + bool primary = true; + +// l_info("svc_primary_getter"); + return l_dbus_message_builder_append_basic(builder, 'b', &primary); +} + +static void setup_gatt_svc_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_property(iface, "UUID", 0, "s", svc_uuid_getter, NULL); + l_dbus_interface_property(iface, "Primary", 0, "b", svc_primary_getter, + NULL); +} + +static bool parse_write_value_options(struct l_dbus_message_iter *itr, + struct write_value_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->type = WRITE_VALUE_TYPE_COMMAND; + opts->offset = 0; + opts->mtu = 0; + opts->prepare_authorize = false; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "type")) { + const char *type; + + if (!l_dbus_message_iter_get_variant(&var, "s", &type)) + return false; + + if (!strcmp(type, "command")) + opts->type = WRITE_VALUE_TYPE_COMMAND; + else if (!strcmp(type, "request")) + opts->type = WRITE_VALUE_TYPE_REQUEST; + else if (!strcmp(type, "reliable")) + opts->type = WRITE_VALUE_TYPE_RELIABLE; + else + return false; + } else if (!strcmp(key, "offset")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->offset)) + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } else if (!strcmp(key, "prepare-authorize")) { + if (!l_dbus_message_iter_get_variant(&var, "b", + &opts->prepare_authorize)) + return false; + } + } + + return true; +} + +static struct l_dbus_message *chrc_write_value_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter iter_data, dict; + struct write_value_options opts; + enum proxy_pdu_sar sar; + uint8_t msg_type; + uint8_t *data; + uint32_t len; + int i; + + if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_data, &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_write_value_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > service->max_pdu_len) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + +// l_info("chrc_write_value_call(type=%u, offset=%u, mtu=%u)", opts.type, opts.offset, opts.mtu); +// print_packet("WriteValue", data, len); + + if (len < 1) + return l_dbus_message_new_method_return(msg); + + sar = (data[0] >> PROXY_PDA_SAR_SHIFT) & PROXY_PDA_SAR_MASK; + msg_type = data[0] & PROXY_MSG_TYPE_MASK; + + switch (sar) { + case PROXY_PDU_SAR_CMPLT_MSG: + print_packet("RX-GATT", data, len); + service->rx_cb(service->user_data, msg_type, data, len); + break; + + case PROXY_PDU_SAR_1ST_SEG: + if (len > service->max_pdu_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar, data, len); + service->sar_len = len; + break; + + case PROXY_PDU_SAR_CONT_SEG: + case PROXY_PDU_SAR_LAST_SEG: { + if (len - 1 > service->max_pdu_len - service->sar_len) { + l_debug("Length exceeded: %d", len); + break; + } + + memcpy(service->sar + service->sar_len, + data + 1, len - 1); + service->sar_len += len - 1; + + if (sar == PROXY_PDU_SAR_LAST_SEG) { + uint8_t sar_len = service->sar_len; + + /* reused by gatt_service_tx */ + service->sar_len = 0; + print_packet("RX-GATT", service->sar, sar_len); + service->rx_cb(service->user_data, msg_type, + service->sar, sar_len); + } + + break; + } + } + + return l_dbus_message_new_method_return(msg); +} + +static bool parse_acquire_notify_options(struct l_dbus_message_iter *itr, + struct acquire_notify_options *opts) +{ + const char *key; + struct l_dbus_message_iter var; + + opts->device = NULL; + opts->link = LINK_TYPE_BR_EDR; + opts->mtu = 0; + + while (l_dbus_message_iter_next_entry(itr, &key, &var)) { + if (!strcmp(key, "device")) { + if (!l_dbus_message_iter_get_variant(&var, "o", + &opts->device)) + return false; + } else if (!strcmp(key, "link")) { + const char *link; + + if (!l_dbus_message_iter_get_variant(&var, "s", &link)) + return false; + + if (!strcmp(link, "BR/EDR")) + opts->link = LINK_TYPE_BR_EDR; + else if (!strcmp(link, "LE")) + opts->link = LINK_TYPE_LE; + else + return false; + } else if (!strcmp(key, "mtu")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &opts->mtu)) + return false; + } + } + + return true; +} + +static void notify_disconnected(struct l_io *io, void *user_data) +{ + struct gatt_service *service = user_data; + + if (service != gatt_service) + return; + + l_debug("notify_disconnected"); + + if (!service->notify_io) + return; + + /* avoid recursion */ + l_io_set_disconnect_handler(service->notify_io, NULL, NULL, NULL); + + l_io_destroy(service->notify_io); + service->notify_io = NULL; + + if (service->notify_stopped_cb) + service->notify_stopped_cb(service->user_data); +} + +static struct l_dbus_message *chrc_acquire_notify_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + struct characterstic *chr = user_data; + struct gatt_service *service = chr->service; + struct l_dbus_message_iter dict; + struct acquire_notify_options opts; + struct l_dbus_message *reply; + int fds[2]; + + l_debug("AcquireNotify"); + + if (!l_dbus_message_get_arguments(msg, "a{sv}", &dict)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!parse_acquire_notify_options(&dict, &opts)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (socketpair(AF_UNIX, + SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, fds) == -1) + return dbus_error(msg, MESH_ERROR_FAILED, + "Cannot create socket"); + + service->notify_io = l_io_new(fds[0]); + l_io_set_close_on_destroy(service->notify_io, true); + l_io_set_disconnect_handler(service->notify_io, notify_disconnected, + service, NULL); + service->mtu = opts.mtu; + l_debug("AcquireNotify: mtu=%u", opts.mtu); + + if (service->notify_acquired_cb) + service->notify_acquired_cb(service->user_data); + + reply = l_dbus_message_new_method_return(msg); + + /* l_dbus_message_builder_append_basic() cannot append UNIX FDs */ + l_dbus_message_set_arguments(reply, "hq", fds[1], service->mtu); + /* + * file descriptor for bluetoothd has just been dup'ed and must be + * closed here in order to get disconnect event after GATT notifications + * notifications have been disabled. + */ + close(fds[1]); + + return reply; +} + +static bool chrc_uuid_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + + const char *path = l_dbus_message_get_path(msg); + const char *interface = l_dbus_message_get_interface(msg); + const char *member = l_dbus_message_get_member(msg); + +// l_info("chrc_uuid_getter(path=%s, interface=%s, member=%s)", path, interface, member); + return l_dbus_message_builder_append_basic(builder, 's', chr->uuid); +} + +static bool chrc_service_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("chrc_service_getter"); + return l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_SERVICE_PATH); +} + +static bool chrc_notify_acquired_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const struct gatt_service *service = chr->service; + bool notifying = !!service->notify_io; + +// l_info("chrc_notify_acquired_getter"); + + return l_dbus_message_builder_append_basic(builder, 'b', ¬ifying); +} + +static bool chrc_flags_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct characterstic *chr = user_data; + const char * const *flag = chr->flags; + +// l_info("chrc_flags_getter"); + + l_dbus_message_builder_enter_array(builder, "s"); + + while (*flag) + l_dbus_message_builder_append_basic(builder, 's', *flag++); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static void setup_gatt_chrc_interface(struct l_dbus_interface *iface) +{ + /* Note: "ReadValue" method is not supported. */ + l_dbus_interface_method(iface, "WriteValue" , 0, chrc_write_value_call, + "", "aya{sv}", + "value", "options"); + l_dbus_interface_method(iface, "AcquireNotify", 0, + chrc_acquire_notify_call, + "hq", "a{sv}", + "fd", "mtu", + "options"); + l_dbus_interface_property(iface, "UUID" , 0, "s", chrc_uuid_getter, + NULL); + l_dbus_interface_property(iface, "Service", 0, "o", chrc_service_getter, + NULL); + l_dbus_interface_property(iface, "NotifyAcquired", 0, "b", + chrc_notify_acquired_getter, NULL); + l_dbus_interface_property(iface, "Flags" , 0, "as", chrc_flags_getter, + NULL); +} + +static void register_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of GATT application failed: %s", error); + } +} + +static bool gatt_svc_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, GATT_SERVICE_IFACE, + setup_gatt_svc_interface, + NULL, false)) { + l_error("Cannot register " GATT_SERVICE_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_register_interface(dbus, GATT_CHRC_IFACE, + setup_gatt_chrc_interface, + NULL, false)) { + l_error("Cannot register " GATT_CHRC_IFACE " interface"); + goto error_unregister_svc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE, service)) { + l_error("Cannot add GATT service"); + goto error_unregister_chrc_iface; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_in)) { + l_error("Cannot add GATT Data In characteristic"); + goto error_remove_svc; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE, + &service->chrc_data_out)) { + l_error("Cannot add GATT Data Out characteristic"); + goto error_remove_data_in_chrc; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_data_out_chrc; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterApplication", + register_app_setup, + register_app_reply, + NULL, NULL)) { + l_error("Cannot register GATT application"); + goto error_disable_object_manager; + } + + return true; + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_data_out_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + +error_remove_data_in_chrc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + +error_remove_svc: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + +error_unregister_chrc_iface: + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + +error_unregister_svc_iface: + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + +error_return: + return false; +} + +static void unregister_app_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_app_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_app_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_app_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of GATT application failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_OUT_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_CHRC_DATA_IN_PATH, + GATT_CHRC_IFACE); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_SERVICE_PATH, + GATT_SERVICE_IFACE); + + l_dbus_unregister_interface(dbus, GATT_CHRC_IFACE); + l_dbus_unregister_interface(dbus, GATT_SERVICE_IFACE); + + if (service->svc_deinit_cb) + service->svc_deinit_cb(service); +} + +static void gatt_svc_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->svc_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_gatt_mgr, + "UnregisterApplication", + unregister_app_setup, + unregister_app_reply, + service, NULL)) { + l_error("Cannot unregister GATT application"); + } +} + +static struct l_dbus_message *adv_release_call(struct l_dbus *, + struct l_dbus_message *msg, + void *user_data) +{ + l_debug("ADV Release"); + + return NULL; +} + +static bool adv_type_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ +// l_info("adv_type_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "peripheral"); +} + +static bool adv_svc_uuids_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_uuids_getter"); + l_dbus_message_builder_enter_array(builder, "s"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_svc_data_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct gatt_service *service = user_data; + +// l_info("adv_svc_data_getter"); + l_dbus_message_builder_enter_array(builder, "{sv}"); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', service->svc_uuid); + l_dbus_message_builder_enter_variant(builder, "ay"); + + if (!service->fill_adv_service_data_cb(service->user_data, builder)) + return false; + + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool adv_local_name_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_local_name_getter"); + + return l_dbus_message_builder_append_basic(builder, 's', "Test"); +} + +static bool adv_duration_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t max_interval_ms = 1000; + uint16_t duration = 1 * max_interval_ms; + +// l_info("adv_duration_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &duration); +} + +static bool adv_timeout_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint16_t timeout = 1000; + +// l_info("adv_timeout_getter"); + + return l_dbus_message_builder_append_basic(builder, 'q', &timeout); +} + +static bool adv_min_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t min_interval_ms = 1000; + +// l_info("adv_min_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &min_interval_ms); +} + +static bool adv_max_interval_getter(struct l_dbus *dbus, + struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + uint32_t max_interval_ms = 1000; + +// l_info("adv_max_interval_getter"); + + return l_dbus_message_builder_append_basic(builder, 'u', + &max_interval_ms); +} + +static void setup_le_adv_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Release", 0, adv_release_call, + "", ""); + l_dbus_interface_property(iface, "Type", 0, "s", + adv_type_getter, NULL); + l_dbus_interface_property(iface, "ServiceUUIDs", 0, "as", + adv_svc_uuids_getter, NULL); + l_dbus_interface_property(iface, "ServiceData", 0, "a{sv}", + adv_svc_data_getter, NULL); + l_dbus_interface_property(iface, "LocalName", 0, "s", + adv_local_name_getter, NULL); + l_dbus_interface_property(iface, "Duration", 0, "q", + adv_duration_getter, NULL); + l_dbus_interface_property(iface, "Timeout", 0, "q", + adv_timeout_getter, NULL); + l_dbus_interface_property(iface, "MinInterval", 0, "u", + adv_min_interval_getter, NULL); + l_dbus_interface_property(iface, "MaxInterval", 0, "u", + adv_max_interval_getter, NULL); +} + +static void register_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("register_adv_setup"); + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', + BLUEZ_MESH_GATT_ADV_PATH); + + /* Options (empty) */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void register_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ +// l_info("register_adv_reply"); + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Registration of LE advertising failed: %s", error); + } +} + +static bool gatt_adv_init(struct l_dbus *dbus, struct l_dbus_proxy *dbus_proxy, + struct gatt_service *service) +{ + if (!l_dbus_register_interface(dbus, LE_ADVERTISEMENT_IFACE, + setup_le_adv_interface, + NULL, false)) { + l_error("Cannot register " LE_ADVERTISEMENT_IFACE " interface"); + goto error_return; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, + service)) { + l_error("Cannot add provisioner LE advertising service"); + goto error_unregister_le_adv_iface; + } + + if (!l_dbus_object_manager_enable(dbus, BLUEZ_MESH_GATT_ADV_PATH)) { + l_error("Cannot enable object manager"); + goto error_remove_le_adv; + } + + /* + * org.freedesktop.DBus.Properties is required for building + * propertiesChanged signals + */ + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES, NULL)) { + l_error("Cannot add LE advertising properties"); + goto error_disable_object_manager; + } + + if (!l_dbus_proxy_method_call(dbus_proxy, "RegisterAdvertisement", + register_adv_setup, + register_adv_reply, + NULL, NULL)) { + l_error("Cannot register LE advertisement"); + goto error_remove_properties_iface; + } + + return true; + +error_remove_properties_iface: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + +error_disable_object_manager: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + +error_remove_le_adv: + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + +error_unregister_le_adv_iface: + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + +error_return: + return false; +} + +static void unregister_adv_setup(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_builder *builder; + +// l_info("unregister_adv_setup"); + + builder = l_dbus_message_builder_new(msg); + + /* Object path */ + l_dbus_message_builder_append_basic(builder, 'o', BLUEZ_MESH_GATT_ADV_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void unregister_adv_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct gatt_service *service = user_data; + struct l_dbus *dbus = dbus_get_bus(); +// l_info("unregister_adv_reply"); + + if (l_dbus_message_is_error(result)) { + const char *error; + + l_dbus_message_get_error(result, &error, NULL); + + l_error("Unregistration of LE advertisement failed: %s", error); + } + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_PROPERTIES); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + L_DBUS_INTERFACE_OBJECT_MANAGER); + + l_dbus_object_remove_interface(dbus, BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE); + + l_dbus_unregister_interface(dbus, LE_ADVERTISEMENT_IFACE); + + if (service->adv_deinit_cb) + service->adv_deinit_cb(service); +} + +static void gatt_adv_deinit(struct gatt_service *service, gatt_destroy_cb cb) +{ + service->adv_deinit_cb = cb; + + if (!l_dbus_proxy_method_call(service->dbus_proxy_le_adv_mgr, + "UnregisterAdvertisement", + unregister_adv_setup, + unregister_adv_reply, + service, NULL)) { + l_error("Cannot unregister LE advertisement"); + } +} + +static void dbus_proxy_added(struct l_dbus_proxy *dbus_proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(dbus_proxy); + const char *path = l_dbus_proxy_get_path(dbus_proxy); + struct gatt_service *service = user_data; + + l_debug("Proxy added: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) { + service->dbus_proxy_gatt_mgr = dbus_proxy; + gatt_svc_init(dbus_get_bus(), dbus_proxy, service); + } else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) { + service->dbus_proxy_le_adv_mgr = dbus_proxy; + gatt_adv_init(dbus_get_bus(), dbus_proxy, service); + } +} + +static void dbus_proxy_removed(struct l_dbus_proxy *proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + struct gatt_service *service = user_data; + + l_debug("Proxy removed: %s (%s)", interface, path); + + if (!strcmp(interface, GATT_MGR_IFACE)) + service->dbus_proxy_gatt_mgr = NULL; + else if (!strcmp(interface, LE_ADVERTISING_MGR_IFACE)) + service->dbus_proxy_le_adv_mgr = NULL; +} + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data) +{ + static const char *flags_data_in[] = {"write-without-response", NULL}; + static const char *flags_data_out[] = {"notify", NULL}; + + /* Only one GATT service may exist at a time (MshPRT_v1.1, chapter 7) */ + if (gatt_service) + return NULL; + + gatt_service = l_new(struct gatt_service, 1); + gatt_service->svc_uuid = svc_uuid; + gatt_service->max_pdu_len = max_pdu_len; + gatt_service->sar = l_malloc(max_pdu_len); + + gatt_service->notify_acquired_cb = notify_acquired_cb; + gatt_service->notify_stopped_cb = notify_stopped_cb; + gatt_service->rx_cb = rx_cb; + gatt_service->tx_cmplt_cb = tx_cmplt_cb; + gatt_service->fill_adv_service_data_cb = fill_adv_service_data_cb; + gatt_service->user_data = user_data; + gatt_service->mtu = GATT_MTU; + + gatt_service->chrc_data_in.uuid = chrc_data_in_uuid; + gatt_service->chrc_data_in.flags = flags_data_in; + gatt_service->chrc_data_in.service = gatt_service; + + gatt_service->chrc_data_out.uuid = chrc_data_out_uuid; + gatt_service->chrc_data_out.flags = flags_data_out; + gatt_service->chrc_data_out.service = gatt_service; + + gatt_service->dbus_client = l_dbus_client_new(dbus_get_bus(), + "org.bluez", "/org/bluez"); + + l_dbus_client_set_proxy_handlers(gatt_service->dbus_client, + dbus_proxy_added, + dbus_proxy_removed, + NULL, + gatt_service, NULL); + + return gatt_service; +} + +static void gatt_svc_destroy(void *user_data) +{ + struct gatt_service *service = user_data; + gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!gatt_service || gatt_service != service) + return; + + destroy_cb = service->destroy_cb; + destroy_data = service->destroy_data; + + l_dbus_client_destroy(service->dbus_client); + l_io_destroy(service->notify_io); + l_free(service->sar); + l_free(service); + gatt_service = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +static void gatt_svc_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + /* l_dbus_client_destroy() must not be called from dbus context */ + l_idle_oneshot(gatt_svc_destroy, service, NULL); +} + +static void gatt_adv_deinit_finished(void *user_data) +{ + struct gatt_service *service = user_data; + + if (!gatt_service || gatt_service != service) + return; + + gatt_svc_deinit(service, gatt_svc_deinit_finished); +} + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data) +{ + if (!gatt_service || gatt_service != service) + return; + + /* avoid recursion */ + l_io_set_disconnect_handler(service->notify_io, NULL, NULL, NULL); + + service->destroy_cb = destroy_cb; + service->destroy_data = user_data; + gatt_adv_deinit(service, gatt_adv_deinit_finished); +} + +void gatt_service_adv_updated(struct gatt_service *service) +{ + if (!gatt_service || gatt_service != service) + return; + + l_dbus_property_changed(dbus_get_bus(), BLUEZ_MESH_GATT_ADV_PATH, + LE_ADVERTISEMENT_IFACE, "ServiceData"); +} diff --git a/mesh/gatt-service.h b/mesh/gatt-service.h new file mode 100644 index 000000000000..96b3eff2f3dc --- /dev/null +++ b/mesh/gatt-service.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +/* MshPRT_v1.1, section 6.3.1 */ +#define PROXY_MSG_TYPE_MASK 0x3F +enum proxy_msg_type { + PROXY_MSG_TYPE_NETWORK_PDU = 0x00, + PROXY_MSG_TYPE_MESH_BEACON = 0x01, + PROXY_MSG_TYPE_PROXY_CFG = 0x02, + PROXY_MSG_TYPE_PROV_PDU = 0x03 +}; + +typedef void (*gatt_service_notify_acquired_cb)(void *user_data); +typedef void (*gatt_service_notify_stopped_cb)(void *user_data); +typedef void (*gatt_service_rx_cb)(void *user_data, + enum proxy_msg_type messageType, + const void *data, uint16_t len); +typedef bool (*gatt_service_tx_cmplt_cb)(void *user_data); +typedef bool (*gatt_service_fill_adv_service_data_cb)(void *user_data, + struct l_dbus_message_builder *builder); + +typedef void (*gatt_destroy_cb)(void *user_data); + +struct gatt_service; + +struct gatt_service * +gatt_service_create( + const char *svc_uuid, + const char *chrc_data_in_uuid, + const char *chrc_data_out_uuid, + uint8_t max_pdu_len, + gatt_service_notify_acquired_cb notify_acquired_cb, + gatt_service_notify_stopped_cb notify_stopped_cb, + gatt_service_rx_cb rx_cb, + gatt_service_tx_cmplt_cb tx_cmplt_cb, + gatt_service_fill_adv_service_data_cb fill_adv_service_data_cb, + void *user_data); + +void gatt_service_destroy(struct gatt_service *service, + gatt_destroy_cb destroy_cb, void *user_data); + +void gatt_service_tx(struct gatt_service *service, uint8_t msg_type, + const void *data, uint16_t len); +void gatt_service_adv_updated(struct gatt_service *service); diff --git a/mesh/pb-gatt.c b/mesh/pb-gatt.c new file mode 100644 index 000000000000..797dd7361ee1 --- /dev/null +++ b/mesh/pb-gatt.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <string.h> // memcpy() +#include <sys/types.h> // struct timeval [required by prov.h] + +#include <ell/dbus.h> +//#include <ell/log.h> +//#include <ell/timeout.h> +#include <ell/util.h> // l_new(), l_free() + +#include "mesh/gatt-service.h" +#include "mesh/net.h" // mesh_net_prov_caps, required by prov.h +#include "mesh/prov.h" // mesh_prov_open_func_t, + // mesh_prov_close_func_t, + // mesh_prov_receive_func_t +#include "mesh/provision.h" // PB_GATT +#include "mesh/pb-gatt.h" + +#define MESH_GATT_PROV_SVC_UUID "0x1827" +#define MESH_GATT_PROV_CHRC_DATA_IN "0x2ADB" +#define MESH_GATT_PROV_CHRC_DATA_OUT "0x2ADC" +#define MAX_PROXY_PROV_PDU_LEN 66 /* MshPRT_v1.1, section 7.1.3.1 / 7.1.3.2 */ + +struct pb_gatt_session { + mesh_prov_open_func_t open_cb; + mesh_prov_close_func_t close_cb; + mesh_prov_receive_func_t rx_cb; + mesh_prov_ack_func_t ack_cb; +// struct l_timeout *tx_timeout; + uint8_t uuid[16]; + uint16_t oob_info; + + struct gatt_service *gatt_service; + void *user_data; + + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; +}; + +static struct pb_gatt_session *pb_session = NULL; + +static void pb_gatt_tx(void *user_data, const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + gatt_service_tx(session->gatt_service, PROXY_MSG_TYPE_PROV_PDU, data, len); +} + +static void gatt_notify_acquired_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + /* + * MshPRT_v1.1, section 5.2.2: The link is opened on a PB-GATT + * bearer when the PB-GATT Client enables notifications. + */ + session->open_cb(session->user_data, pb_gatt_tx, session, PB_GATT); +} + +static void gatt_notify_stopped_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->close_cb(session->user_data, PROV_ERR_UNEXPECTED_ERR); +} + +static void gatt_rx_cb(void *user_data, enum proxy_msg_type msg_type, + const void *data, uint16_t len) +{ + struct pb_gatt_session *session = user_data; + + if (msg_type == PROXY_MSG_TYPE_PROV_PDU) + session->rx_cb(session->user_data, data + 1, len - 1); +} + +static bool gatt_tx_cmplt_cb(void *user_data) +{ + struct pb_gatt_session *session = user_data; + + session->ack_cb(session->user_data, 0 /* don't care */); + return false; +} + +static bool gatt_fill_adv_service_data_cb(void *user_data, + struct l_dbus_message_builder *builder) +{ + struct pb_gatt_session *session = user_data; + uint8_t oob_info[2]; + int i; + + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < sizeof(session->uuid); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(session->uuid[i])); + l_put_be16(session->oob_info, oob_info); + for (i = 0; i < sizeof(oob_info); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(oob_info[i])); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data) +{ + pb_session = l_new(struct pb_gatt_session, 1); + + pb_session->open_cb = open_cb; + pb_session->close_cb = close_cb; + pb_session->rx_cb = rx_cb; + pb_session->ack_cb = ack_cb; + + memcpy(pb_session->uuid, uuid, 16); + pb_session->user_data = user_data; + + pb_session->gatt_service = gatt_service_create(MESH_GATT_PROV_SVC_UUID, + MESH_GATT_PROV_CHRC_DATA_IN, + MESH_GATT_PROV_CHRC_DATA_OUT, + MAX_PROXY_PROV_PDU_LEN, + gatt_notify_acquired_cb, + gatt_notify_stopped_cb, + gatt_rx_cb, gatt_tx_cmplt_cb, + gatt_fill_adv_service_data_cb, + pb_session); + if (!pb_session->gatt_service) { + l_free(pb_session); + pb_session = NULL; + return false; + } + + return true; +} + +static void gatt_destroy_finished(void *user_data) +{ + pb_gatt_destroy_cb destroy_cb; + void *destroy_data; + + if (!pb_session || pb_session != user_data) + return; + + destroy_cb = pb_session->destroy_cb; + destroy_data = pb_session->destroy_data; + + l_free(pb_session); + pb_session = NULL; + + if (destroy_cb) + destroy_cb(destroy_data); +} + +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data) +{ + if (!pb_session || pb_session->user_data != user_data) + return; + + pb_session->destroy_cb = destroy_cb; + pb_session->destroy_data = destroy_data; + gatt_service_destroy(pb_session->gatt_service, gatt_destroy_finished, + pb_session); +} diff --git a/mesh/pb-gatt.h b/mesh/pb-gatt.h new file mode 100644 index 000000000000..49d95974c4ae --- /dev/null +++ b/mesh/pb-gatt.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdint.h> + +typedef void (*pb_gatt_destroy_cb)(void *user_data); + +bool pb_gatt_reg(mesh_prov_open_func_t open_cb, mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + const uint8_t *uuid, uint16_t oob_info, void *user_data); +void pb_gatt_unreg(void *user_data, pb_gatt_destroy_cb destroy_cb, + void *destroy_data); diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index a83636280dd8..b3c538359a66 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -27,6 +27,7 @@ #include "mesh/provision.h" #include "mesh/remprv.h" #include "mesh/pb-adv.h" +#include "mesh/pb-gatt.h" #include "mesh/mesh.h" #include "mesh/agent.h" @@ -100,6 +101,7 @@ static void acceptor_free(void) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); + pb_gatt_unreg(prov, NULL, NULL); l_free(prov); prov = NULL; @@ -801,6 +803,10 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, /* Always register for PB-ADV */ result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); + + result = pb_gatt_reg(acp_prov_open, acp_prov_close, + acp_prov_rx, acp_prov_ack, uuid, + caps->oob_info, prov); } else { /* Run Device Key Refresh Procedure */ result = register_nppi_acceptor(acp_prov_open, acp_prov_close, -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 3/7] mesh: add GATT proxy service 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 2/7] mesh: add support for provisioning via GATT Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 4/7] mesh: gatt-proxy: support for proxy configuration messages and filtering Christian Eggers ` (3 subsequent siblings) 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers MeshPRT_v1.1, chapter 7 states that GATT provisioning service and GATT proxy service must NOT be active simultaneously. This patch only implements the GATT proxy itself (so that a GATT proxy client can exchange network messages with server). Relaying / retransmission of network messages and propagation of beacons will be handled in later patches. --- Makefile.mesh | 3 +- mesh/gatt-proxy-svc.c | 327 ++++++++++++++++++++++++++++++++++++++++++ mesh/gatt-proxy-svc.h | 28 ++++ mesh/main.c | 3 + mesh/net-keys.c | 61 ++++++++ mesh/net-keys.h | 4 + mesh/net.c | 72 +++++++++- mesh/net.h | 3 + mesh/prov-acceptor.c | 26 +++- 9 files changed, 518 insertions(+), 9 deletions(-) create mode 100644 mesh/gatt-proxy-svc.c create mode 100644 mesh/gatt-proxy-svc.h diff --git a/Makefile.mesh b/Makefile.mesh index 700d64fe9293..8a190f75de9d 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -40,7 +40,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/rpl.h mesh/rpl.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h \ - mesh/gatt-service.h mesh/gatt-service.c + mesh/gatt-service.h mesh/gatt-service.c \ + mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/gatt-proxy-svc.c b/mesh/gatt-proxy-svc.c new file mode 100644 index 000000000000..27cc22c0572a --- /dev/null +++ b/mesh/gatt-proxy-svc.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> // memcpy() + +#include <ell/dbus.h> +#include <ell/log.h> // l_warn() +#include <ell/queue.h> +#include <ell/timeout.h> +#include <ell/util.h> // ell_new(), l_free(), l_malloc() + +#include "mesh/gatt-service.h" +#include "mesh/mesh-io.h" // mesh_io_recv_func_t +#include "mesh/net.h" // mesh_net_attach_gatt(), + // mesh_net_detach_gatt() +#include "mesh/net-keys.h" // net_key_fill_adv_service_data(), + // net_key_get_next_id() +#include "mesh/util.h" // print_packet() +#include "mesh/gatt-proxy-svc.h" + +#define MESH_GATT_PROXY_SVC_UUID "0x1828" +#define MESH_GATT_PROXY_CHRC_DATA_IN "0x2ADD" +#define MESH_GATT_PROXY_CHRC_DATA_OUT "0x2ADE" + +#define MAX_PROXY_PDU_LEN 66 /* MshPRT_v1.1, section 7.2.2.2.7 */ + +struct gatt_io_reg { + mesh_io_recv_func_t cb; + void *user_data; + enum proxy_msg_type msg_type; +}; + +struct gatt_proxy_svc { + struct gatt_service *gatt_service; + uint32_t current_adv_key_id; + struct l_timeout *adv_key_cycle_to; + bool connected; + bool txing; + struct l_queue *tx_deferred; + struct l_queue *rx_regs; +}; + +struct process_data { + enum proxy_msg_type msg_type; + const uint8_t *data; + uint8_t len; +}; + +static struct gatt_proxy_svc *gatt_proxy_svc; + +struct tx_deferred { + enum proxy_msg_type msg_type; + uint16_t len; + uint8_t data[]; +}; + +static struct gatt_io_reg *find_by_filter(struct l_queue *rx_regs, + enum proxy_msg_type msg_type) +{ + const struct l_queue_entry *entry; + + entry = l_queue_get_entries(rx_regs); + + for (; entry; entry = entry->next) { + struct gatt_io_reg *rx_reg = entry->data; + + if (rx_reg->msg_type == msg_type) + return rx_reg; + } + + return NULL; +} + +void gatt_proxy_svc_register_recv_cb(struct gatt_proxy_svc *gatt_proxy, + enum proxy_msg_type msg_type, + mesh_io_recv_func_t cb, + void *user_data) +{ + struct gatt_io_reg *rx_reg; + + if (gatt_proxy != gatt_proxy_svc || !cb) + return; + + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); + + l_free(rx_reg); + l_queue_remove(gatt_proxy->rx_regs, rx_reg); + + rx_reg = l_malloc(sizeof(struct gatt_io_reg)); + rx_reg->cb = cb; + rx_reg->msg_type = msg_type; + rx_reg->user_data = user_data; + + l_queue_push_head(gatt_proxy->rx_regs, rx_reg); +} + +void gatt_proxy_svc_deregister_recv_cb(struct gatt_proxy_svc *gatt_proxy, + enum proxy_msg_type msg_type) +{ + struct gatt_io_reg *rx_reg; + + if (gatt_proxy != gatt_proxy_svc) + return; + + rx_reg = find_by_filter(gatt_proxy->rx_regs, msg_type); + + l_queue_remove(gatt_proxy->rx_regs, rx_reg); + l_free(rx_reg); +} + +static void gatt_proxy_svc_send(enum proxy_msg_type msg_type, const void *data, + uint8_t len) +{ + if (!gatt_proxy_svc) + return; + + if (!gatt_proxy_svc->connected) { + l_warn("Not connected, dropping TX message..."); + return; + } + + if (!gatt_proxy_svc->txing) { + gatt_proxy_svc->txing = true; + gatt_service_tx(gatt_proxy_svc->gatt_service, msg_type, + data, len); + } else { + struct tx_deferred *tx_deferred; + +// print_packet("TX-Defer", data, len); + tx_deferred = l_malloc(len + sizeof(struct tx_deferred)); + tx_deferred->msg_type = msg_type; + tx_deferred->len = len; + memcpy(tx_deferred->data, data, len); + l_queue_push_tail(gatt_proxy_svc->tx_deferred, tx_deferred); + } +} + +void gatt_proxy_svc_send_net(const void *data, uint8_t len) +{ + gatt_proxy_svc_send(PROXY_MSG_TYPE_NETWORK_PDU, data, len); +} + +static void gatt_service_notify_acquired(void *user_data) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + + if (gatt_proxy != gatt_proxy_svc) + return; + + gatt_proxy->connected = true; +} + +static void gatt_service_notify_stopped(void *user_data) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + + if (gatt_proxy != gatt_proxy_svc) + return; + + gatt_proxy->connected = false; + gatt_proxy->txing = false; + l_queue_clear(gatt_proxy->tx_deferred, l_free); +} + +static void process_rx_callbacks(void *a, void *b) +{ + struct gatt_io_reg *rx_reg = a; + struct process_data *rx = b; + + if (rx->msg_type == rx_reg->msg_type) + rx_reg->cb(rx_reg->user_data, NULL, rx->data, rx->len); +} + +static void gatt_service_rx(void *user_data, enum proxy_msg_type msg_type, + const void *data, uint16_t len) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + struct process_data rx = { + .msg_type = msg_type, + .data = data, + .len = len, + }; + + if (gatt_proxy != gatt_proxy_svc) + return; + + l_queue_foreach(gatt_proxy->rx_regs, process_rx_callbacks, &rx); +} + +static bool gatt_service_tx_cmplt(void *user_data) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + struct tx_deferred *tx_deferred; + +// l_info("gatt_service_tx_cmplt"); + + if (gatt_proxy_svc != gatt_proxy) + return false; + + if (!gatt_proxy->connected || !gatt_proxy->txing) + return false; + + gatt_proxy->txing = false; + + tx_deferred = l_queue_pop_head(gatt_proxy->tx_deferred); + if (!tx_deferred) + return false; + + gatt_proxy_svc_send(tx_deferred->msg_type, tx_deferred->data, + tx_deferred->len); + l_free(tx_deferred); + return true; +} + +static bool gatt_service_fill_adv_service_data(void *user_data, + struct l_dbus_message_builder *builder) +{ + struct gatt_proxy_svc *gatt_service = user_data; + + if (gatt_service != gatt_proxy_svc) + return false; + + return net_key_fill_adv_service_data(gatt_service->current_adv_key_id, + builder); +} + +static void gatt_proxy_svc_cycle_adv(struct l_timeout *timeout, + void *user_data) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + uint32_t next_adv_key_id; + + if (gatt_proxy_svc != gatt_proxy) + return; + + next_adv_key_id = net_key_get_next_id(gatt_proxy->current_adv_key_id); + if (!next_adv_key_id) + return; + + if (gatt_proxy->current_adv_key_id != next_adv_key_id) { + gatt_proxy->current_adv_key_id = next_adv_key_id; + gatt_service_adv_updated(gatt_proxy_svc->gatt_service); + } + + l_timeout_modify(gatt_proxy->adv_key_cycle_to, 3); +} + +void gatt_proxy_svc_set_current_adv_key(uint32_t id) +{ + if (!gatt_proxy_svc) + return; + + gatt_proxy_svc->current_adv_key_id = id; + gatt_service_adv_updated(gatt_proxy_svc->gatt_service); +} + +void gatt_proxy_svc_start(void) +{ + if (!gatt_proxy_svc || gatt_proxy_svc->gatt_service) + return; + + gatt_proxy_svc->gatt_service = gatt_service_create( + MESH_GATT_PROXY_SVC_UUID, + MESH_GATT_PROXY_CHRC_DATA_IN, + MESH_GATT_PROXY_CHRC_DATA_OUT, + MAX_PROXY_PDU_LEN, + gatt_service_notify_acquired, + gatt_service_notify_stopped, + gatt_service_rx, + gatt_service_tx_cmplt, + gatt_service_fill_adv_service_data, + gatt_proxy_svc); + + gatt_proxy_svc->adv_key_cycle_to = l_timeout_create(3, + gatt_proxy_svc_cycle_adv, + gatt_proxy_svc, NULL); + + mesh_net_attach_gatt(gatt_proxy_svc); +} + +void gatt_proxy_svc_stop(void) +{ + if (!gatt_proxy_svc || !gatt_proxy_svc->gatt_service) + return; + + mesh_net_detach_gatt(gatt_proxy_svc); + l_timeout_remove(gatt_proxy_svc->adv_key_cycle_to); + gatt_service_destroy(gatt_proxy_svc->gatt_service, NULL, NULL); +} + +void gatt_proxy_svc_create(void) +{ + if (gatt_proxy_svc) + return; + + gatt_proxy_svc = l_new(struct gatt_proxy_svc, 1); + gatt_proxy_svc->tx_deferred = l_queue_new(); + gatt_proxy_svc->rx_regs = l_queue_new(); + + /* Check whether we have at least one key */ + if (!net_key_get_next_id(0)) + return; + + gatt_proxy_svc_start(); +} + +void gatt_proxy_svc_destroy(void) +{ + if (!gatt_proxy_svc) + return; + + gatt_proxy_svc_stop(); + + l_queue_destroy(gatt_proxy_svc->rx_regs, l_free); + l_queue_destroy(gatt_proxy_svc->tx_deferred, l_free); + l_free(gatt_proxy_svc); + gatt_proxy_svc = NULL; +} diff --git a/mesh/gatt-proxy-svc.h b/mesh/gatt-proxy-svc.h new file mode 100644 index 000000000000..2b0c9d7ec21b --- /dev/null +++ b/mesh/gatt-proxy-svc.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdint.h> + +enum proxy_msg_type; +struct gatt_proxy_svc; + +void gatt_proxy_svc_create(void); +void gatt_proxy_svc_destroy(void); +void gatt_proxy_svc_start(void); +void gatt_proxy_svc_stop(void); +void gatt_proxy_svc_set_current_adv_key(uint32_t id); +void gatt_proxy_svc_register_recv_cb(struct gatt_proxy_svc *gatt_proxy, + enum proxy_msg_type msg_type, + mesh_io_recv_func_t cb, + void *user_data); +void gatt_proxy_svc_deregister_recv_cb(struct gatt_proxy_svc *gatt_proxy, + enum proxy_msg_type msg_type); +void gatt_proxy_svc_send_net(const void *data, uint8_t len); + diff --git a/mesh/main.c b/mesh/main.c index 5b8af4bc7345..079b3cbcad36 100644 --- a/mesh/main.c +++ b/mesh/main.c @@ -31,6 +31,7 @@ #include "mesh/crypto.h" #include "mesh/dbus.h" #include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/util.h" static const char *storage_dir; @@ -94,6 +95,8 @@ static void mesh_ready_callback(void *user_data, bool success) l_error("Failed to initialize mesh D-Bus resources"); l_main_quit(); } + + gatt_proxy_svc_create(); } static void request_name_callback(struct l_dbus *dbus, bool success, diff --git a/mesh/net-keys.c b/mesh/net-keys.c index 98e6d23d3f87..ab724f9cfa1e 100644 --- a/mesh/net-keys.c +++ b/mesh/net-keys.c @@ -22,6 +22,7 @@ #include "mesh/util.h" #include "mesh/crypto.h" #include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/net.h" #include "mesh/net-keys.h" @@ -31,6 +32,12 @@ /* This allows daemon to skip decryption on recently seen beacons */ #define BEACON_CACHE_MAX 10 +/* MshPRT_v1.1, section 7.2.2.2.1 */ +#define IDENTIFICATION_TYPE_NETWORK_ID 0x00 +#define IDENTIFICATION_TYPE_NODE_ID 0x01 +#define IDENTIFICATION_TYPE_PRV_NETWORK_ID 0x02 +#define IDENTIFICATION_TYPE_PRV_NODE_ID 0x03 + struct beacon_rx { uint8_t data[BEACON_LEN_MAX]; uint32_t id; @@ -146,6 +153,9 @@ uint32_t net_key_add(const uint8_t flooding[16]) goto fail; key->id = ++last_flooding_id; + if (l_queue_isempty(keys)) + gatt_proxy_svc_start(); + l_queue_push_tail(keys, key); return key->id; @@ -198,6 +208,9 @@ void net_key_unref(uint32_t id) l_timeout_remove(key->observe.timeout); l_queue_remove(keys, key); l_free(key); + + if (l_queue_isempty(keys)) + gatt_proxy_svc_stop(); } } } @@ -662,6 +675,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, bool kr, bool ivu, return false; print_packet("Set SNB to", key->snb, BEACON_LEN_SNB); + gatt_proxy_svc_set_current_adv_key(key->id); } l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); @@ -797,3 +811,50 @@ void net_key_cleanup(void) l_queue_destroy(beacons, l_free); beacons = NULL; } + +bool net_key_fill_adv_service_data(uint32_t id, + struct l_dbus_message_builder *builder) +{ + uint8_t identification_type = IDENTIFICATION_TYPE_NETWORK_ID; + struct net_key *key; + int i; + + key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + if (!key) + return false; + + l_dbus_message_builder_enter_array(builder, "y"); + l_dbus_message_builder_append_basic(builder, 'y', &identification_type); + + for (i = 0; i < sizeof(key->net_id); i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(key->net_id[i])); + l_dbus_message_builder_leave_array(builder); + + return true; +} + +uint32_t net_key_get_next_id(uint32_t id) +{ + const struct l_queue_entry *entry; + struct net_key *key; + bool found = false; + + /* Try to find next key (after the given key id) */ + for (entry = l_queue_get_entries(keys); entry; entry = entry->next) { + key = entry->data; + + if (!found) + if (key->id == id) + found = true; + else + return key->id; + } + + /* If not found, return id of first key */ + key = l_queue_peek_head(keys); + if (key) + return key->id; + + return 0; +} diff --git a/mesh/net-keys.h b/mesh/net-keys.h index 49e01132cbf2..512347521547 100644 --- a/mesh/net-keys.h +++ b/mesh/net-keys.h @@ -38,3 +38,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu, void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); void net_key_beacon_disable(uint32_t id, bool mpb); uint32_t net_key_beacon_last_seen(uint32_t id); + +bool net_key_fill_adv_service_data(uint32_t id, + struct l_dbus_message_builder *builder); +uint32_t net_key_get_next_id(uint32_t id); diff --git a/mesh/net.c b/mesh/net.c index b29e24f5d4a9..911f781ef75c 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -29,6 +29,8 @@ #include "mesh/net.h" #include "mesh/mesh-io.h" #include "mesh/friend.h" +#include "mesh/gatt-service.h" // PROXY_MSG_TYPE_NETWORK_PDU +#include "mesh/gatt-proxy-svc.h" // gatt_proxy_svc_send_net() #include "mesh/mesh-config.h" #include "mesh/model.h" #include "mesh/appkey.h" @@ -177,6 +179,7 @@ struct mesh_destination { }; struct net_queue_data { + struct gatt_proxy_svc *gatt_proxy; struct mesh_io_recv_info *info; struct mesh_net *net; const uint8_t *data; @@ -192,6 +195,7 @@ struct net_queue_data { struct oneshot_tx { struct mesh_net *net; uint16_t interval; + bool frnd; uint8_t cnt; uint8_t size; uint8_t packet[MESH_AD_MAX_LEN]; @@ -2288,17 +2292,30 @@ static void send_msg_pkt_oneshot(void *user_data) /* No extra randomization when sending regular mesh messages */ info.u.gen.max_delay = DEFAULT_MIN_DELAY; + /* + * MshPrt_v1.1, section 3.4.6.4 - Transmitting a network PDU + * If [...], and the Network PDU is secured using the friendship + * security credentials, the Network PDU shall be delivered to the + * advertising bearer network interface. + * If [...], and the Network PDU is not secured using the friendship + * security credentials, the Network PDU shall be delivered to all + * network interfaces. + */ mesh_io_send(net->io, &info, tx->packet, tx->size); + if (!tx->frnd) + gatt_proxy_svc_send_net(tx->packet + 1, tx->size - 1); l_free(tx); } static void send_msg_pkt(struct mesh_net *net, uint8_t cnt, uint16_t interval, - const uint8_t *packet, uint8_t size) + const uint8_t *packet, uint8_t size, + bool frnd) { struct oneshot_tx *tx = l_new(struct oneshot_tx, 1); tx->net = net; tx->interval = interval; + tx->frnd = frnd; tx->cnt = cnt; tx->size = size; memcpy(tx->packet, packet, size); @@ -2482,6 +2499,15 @@ static void net_rx(void *net_ptr, void *user_data) if (net_idx == NET_IDX_INVALID) return; + /* + * MshPRT_v1.1, section 3.4.5.1 - Interface input filter + * The input filter of the interface connected to the GATT bearer shall + * drop all Network PDUs that have been secured using the friendship + * security credentials. + */ + if (data->gatt_proxy && frnd) + return; + relay_advice = packet_received(net, net_key_id, net_idx, frnd, iv_index, out, out_size, rssi); if (relay_advice > data->relay_advice) { @@ -2506,7 +2532,9 @@ static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, { uint64_t hash; bool isNew; + struct gatt_proxy_svc *gatt_proxy = user_data; struct net_queue_data net_data = { + .gatt_proxy = gatt_proxy, .info = info, .data = data + 1, .len = len - 1, @@ -3047,6 +3075,21 @@ struct mesh_io *mesh_net_detach(struct mesh_net *net) return io; } +void mesh_net_attach_gatt(struct gatt_proxy_svc *gatt_proxy) +{ + gatt_proxy_svc_register_recv_cb(gatt_proxy, PROXY_MSG_TYPE_NETWORK_PDU, + net_msg_recv, + gatt_proxy); +} + +void mesh_net_detach_gatt(struct gatt_proxy_svc *gatt_proxy) +{ +// mesh_io_send_cancel(net->io, &type, 1); + + gatt_proxy_svc_deregister_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_NETWORK_PDU); +} + bool mesh_net_iv_index_update(struct mesh_net *net) { if (net->iv_upd_state != IV_UPD_NORMAL) @@ -3171,7 +3214,7 @@ static bool send_seg(struct mesh_net *net, uint8_t cnt, uint16_t interval, return false; } - send_msg_pkt(net, cnt, interval, packet, packet_len + 1); + send_msg_pkt(net, cnt, interval, packet, packet_len + 1, false); msg->last_seg = segO; @@ -3191,6 +3234,7 @@ void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, uint16_t seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & SEQ_ZERO_MASK; uint8_t segO = (hdr >> SEGO_HDR_SHIFT) & SEG_MASK; uint8_t segN = (hdr >> SEGN_HDR_SHIFT) & SEG_MASK; + bool frnd; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: @@ -3219,8 +3263,13 @@ void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, return; } + if (key_id_to_net_idx(net, net_key_id, &frnd) == NET_IDX_INVALID) { + l_error("Failed to determine friend security material"); + return; + } + send_msg_pkt(net, net->tx_cnt, net->tx_interval, packet, - packet_len + 1); + packet_len + 1, frnd); l_debug("TX: Friend Seg-%d %04x -> %04x : len %u) : TTL %d : SEQ %06x", segO, src, dst, packet_len, ttl, seq); @@ -3345,6 +3394,7 @@ void mesh_net_ack_send(struct mesh_net *net, uint32_t net_key_id, uint8_t data[7]; uint8_t pkt_len; uint8_t pkt[MESH_AD_MAX_LEN]; + bool frnd; /* * MshPRFv1.0.1 section 3.4.5.2, Interface output filter: @@ -3378,7 +3428,13 @@ void mesh_net_ack_send(struct mesh_net *net, uint32_t net_key_id, return; } - send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1); + if (key_id_to_net_idx(net, net_key_id, &frnd) == NET_IDX_INVALID) { + l_error("Failed to determine friend security material"); + return; + } + + send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1, + frnd); l_debug("TX: Friend ACK %04x -> %04x : len %u : TTL %d : SEQ %06x", src, dst, pkt_len, ttl, seq); @@ -3394,6 +3450,7 @@ void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, uint8_t pkt_len; uint8_t pkt[MESH_AD_MAX_LEN]; bool result = false; + bool frnd; if (!net->src_addr) return; @@ -3459,9 +3516,14 @@ void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, return; } + if (key_id_to_net_idx(net, net_key_id, &frnd) == NET_IDX_INVALID) { + l_error("Failed to determine friend security material"); + return; + } + if (!(IS_UNASSIGNED(dst))) send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, - pkt_len + 1); + pkt_len + 1, frnd); } int mesh_net_key_refresh_phase_set(struct mesh_net *net, uint16_t idx, diff --git a/mesh/net.h b/mesh/net.h index 5200beb2fada..af581478412c 100644 --- a/mesh/net.h +++ b/mesh/net.h @@ -14,6 +14,7 @@ struct mesh_io; struct mesh_node; +struct gatt_proxy_svc; #define DEV_ID 0 @@ -249,6 +250,8 @@ bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, uint32_t *net_key_id); bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io); struct mesh_io *mesh_net_detach(struct mesh_net *net); +void mesh_net_attach_gatt(struct gatt_proxy_svc *gatt_proxy); +void mesh_net_detach_gatt(struct gatt_proxy_svc *gatt_proxy); struct l_queue *mesh_net_get_app_keys(struct mesh_net *net); void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c index b3c538359a66..a374abb92083 100644 --- a/mesh/prov-acceptor.c +++ b/mesh/prov-acceptor.c @@ -22,6 +22,8 @@ #include "mesh/mesh-defs.h" #include "mesh/util.h" #include "mesh/crypto.h" +#include "mesh/mesh-io.h" +#include "mesh/gatt-proxy-svc.h" #include "mesh/net.h" #include "mesh/prov.h" #include "mesh/provision.h" @@ -89,6 +91,11 @@ struct mesh_prov_acceptor { static struct mesh_prov_acceptor *prov = NULL; +static void gatt_unreg_finished(void *user_data) +{ + gatt_proxy_svc_create(); +} + static void acceptor_free(void) { if (!prov) @@ -101,7 +108,7 @@ static void acceptor_free(void) mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); pb_adv_unreg(prov); - pb_gatt_unreg(prov, NULL, NULL); + pb_gatt_unreg(prov, gatt_unreg_finished, NULL); l_free(prov); prov = NULL; @@ -705,8 +712,13 @@ failure: cleanup: l_timeout_remove(prov->timeout); - /* Give PB Link 5 seconds to end session */ - prov->timeout = l_timeout_create(5, prov_to, prov, NULL); + if (prov->transport == PB_ADV) { + /* Give PB Link 5 seconds to end session */ + prov->timeout = l_timeout_create(5, prov_to, prov, NULL); + } else { + prov->timeout = NULL; + prov_to(NULL, prov); + } } static void acp_prov_ack(void *user_data, uint8_t msg_num) @@ -804,6 +816,14 @@ bool acceptor_start(uint8_t num_ele, uint8_t *uuid, result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, prov); + /* + * MeshPRT_v1.1, chapter 7: "A device may support the Mesh + * Provisioning Service or the Mesh Proxy Service or both. If + * both are supported, only one of these services shall be + * exposed in the GATT database at a time." + */ + gatt_proxy_svc_destroy(); + result = pb_gatt_reg(acp_prov_open, acp_prov_close, acp_prov_rx, acp_prov_ack, uuid, caps->oob_info, prov); -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 4/7] mesh: gatt-proxy: support for proxy configuration messages and filtering 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers ` (2 preceding siblings ...) 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 3/7] mesh: add GATT proxy service Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 5/7] mesh: main.conf/cfgmod-server: allow to enable proxy support Christian Eggers ` (2 subsequent siblings) 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers Implement reception / responses for Proxy Configuration Messages (MshPRT_v1.1, section 6.6) and proxy filtering (sections 6.4 and 6.7). --- Makefile.mesh | 3 +- mesh/crypto.c | 6 +- mesh/crypto.h | 3 + mesh/gatt-proxy-svc.c | 182 +++++++++++++++++++++++++++++++++++++++++- mesh/gatt-proxy-svc.h | 16 +++- mesh/net-keys.c | 56 +++++++++++++ mesh/net-keys.h | 3 + mesh/net.c | 104 +++++++++++++++++++++--- mesh/proxy-cfg.c | 168 ++++++++++++++++++++++++++++++++++++++ mesh/proxy-cfg.h | 19 +++++ 10 files changed, 541 insertions(+), 19 deletions(-) create mode 100644 mesh/proxy-cfg.c create mode 100644 mesh/proxy-cfg.h diff --git a/Makefile.mesh b/Makefile.mesh index 8a190f75de9d..d39bb17eca70 100644 --- a/Makefile.mesh +++ b/Makefile.mesh @@ -41,7 +41,8 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \ mesh/prv-beacon.h mesh/prvbeac-server.c \ mesh/mesh-defs.h \ mesh/gatt-service.h mesh/gatt-service.c \ - mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c + mesh/gatt-proxy-svc.h mesh/gatt-proxy-svc.c \ + mesh/proxy-cfg.h mesh/proxy-cfg.c pkglibexec_PROGRAMS += mesh/bluetooth-meshd mesh/mesh.$(OBJEXT): ell/internal diff --git a/mesh/crypto.c b/mesh/crypto.c index 451be81d2fb3..a98836349517 100644 --- a/mesh/crypto.c +++ b/mesh/crypto.c @@ -590,7 +590,7 @@ bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, return true; } -static bool network_header_parse(const uint8_t *packet, uint8_t packet_len, +bool mesh_crypto_network_header_parse(const uint8_t *packet, uint8_t packet_len, bool *ctl, uint8_t *ttl, uint32_t *seq, uint16_t *src, uint16_t *dst) { @@ -630,7 +630,7 @@ bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, uint16_t this_dst; bool is_segmented; - if (!network_header_parse(packet, packet_len, + if (!mesh_crypto_network_header_parse(packet, packet_len, ctl, ttl, seq, src, &this_dst)) return false; @@ -843,7 +843,7 @@ bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, uint16_t src; uint16_t dst; - if (!network_header_parse(packet, packet_len, + if (!mesh_crypto_network_header_parse(packet, packet_len, &ctl, &ttl, &seq, &src, &dst)) return false; diff --git a/mesh/crypto.h b/mesh/crypto.h index e4bbe4343223..43d4ffaa5023 100644 --- a/mesh/crypto.h +++ b/mesh/crypto.h @@ -74,6 +74,9 @@ bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, bool *szmic, bool *relay, uint16_t *seqZero, uint8_t *segO, uint8_t *segN, const uint8_t **payload, uint8_t *payload_len); +bool mesh_crypto_network_header_parse(const uint8_t *packet, uint8_t packet_len, + bool *ctl, uint8_t *ttl, uint32_t *seq, + uint16_t *src, uint16_t *dst); bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, uint8_t *out, uint16_t payload_len, uint16_t src, uint16_t dst, uint8_t key_aid, diff --git a/mesh/gatt-proxy-svc.c b/mesh/gatt-proxy-svc.c index 27cc22c0572a..4100578a9f0a 100644 --- a/mesh/gatt-proxy-svc.c +++ b/mesh/gatt-proxy-svc.c @@ -10,17 +10,21 @@ #include <stdbool.h> #include <stdlib.h> -#include <string.h> // memcpy() +#include <string.h> // memcpy(), memmove() #include <ell/dbus.h> #include <ell/log.h> // l_warn() #include <ell/queue.h> #include <ell/timeout.h> -#include <ell/util.h> // ell_new(), l_free(), l_malloc() +#include <ell/util.h> // ell_new(), l_free(), l_malloc(), + // L_ARRAY_SIZE +#include "mesh/mesh-defs.h" // UNASSIGNED_ADDRESS #include "mesh/gatt-service.h" #include "mesh/mesh-io.h" // mesh_io_recv_func_t -#include "mesh/net.h" // mesh_net_attach_gatt(), +#include "mesh/net.h" // PROXY_FILTER_ACCEPT_LIST, + // PROXY_FILTER_REJECT_LIST + // mesh_net_attach_gatt(), // mesh_net_detach_gatt() #include "mesh/net-keys.h" // net_key_fill_adv_service_data(), // net_key_get_next_id() @@ -47,6 +51,9 @@ struct gatt_proxy_svc { bool txing; struct l_queue *tx_deferred; struct l_queue *rx_regs; + uint8_t filter_type; + uint16_t filter_addrs[32]; + unsigned filter_count; }; struct process_data { @@ -117,6 +124,136 @@ void gatt_proxy_svc_deregister_recv_cb(struct gatt_proxy_svc *gatt_proxy, l_free(rx_reg); } +void gatt_proxy_svc_filter_set_type(struct gatt_proxy_svc *gatt_proxy, + uint8_t filter_type) +{ + if (!gatt_proxy || gatt_proxy != gatt_proxy_svc) + return; + + /* Behavior not specified in MshPRT, section 6.7 */ + if (filter_type != PROXY_FILTER_ACCEPT_LIST && + filter_type != PROXY_FILTER_REJECT_LIST) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If a Proxy Server receives a Set Filter Type message, it shall set + * the proxy filter type as requested in the message parameter, and it + * shall clear the proxy filter list. + */ + gatt_proxy->filter_type = filter_type; + gatt_proxy->filter_count = 0; +} + +void gatt_proxy_svc_filter_add(struct gatt_proxy_svc *gatt_proxy, + uint16_t addr) +{ + int i; + + if (!gatt_proxy || gatt_proxy != gatt_proxy_svc) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If the AddressArray field contains the unassigned address, the Proxy + * Server shall ignore that address. + */ + if (addr == UNASSIGNED_ADDRESS) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If the Proxy Server runs out of space in the proxy filter list, + * the Proxy Server shall not add these addresses. + */ + if (gatt_proxy->filter_count == L_ARRAY_SIZE(gatt_proxy->filter_addrs)) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If one or more addresses contained in the message are already in the + * list, the Proxy Server shall not add these addresses. + */ + for (i = 0; i < gatt_proxy->filter_count; i++) + if (gatt_proxy->filter_addrs[i] == addr) + return; + + gatt_proxy->filter_addrs[gatt_proxy->filter_count++] = addr; +} + +void gatt_proxy_svc_filter_remove(struct gatt_proxy_svc *gatt_proxy, + uint16_t addr) +{ + int i; + + if (!gatt_proxy || gatt_proxy != gatt_proxy_svc) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If the AddressArray field contains the unassigned address, the Proxy + * Server shall ignore that address. + */ + if (addr == UNASSIGNED_ADDRESS) + return; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If one or more addresses contained in the message were not in the + * list, the Proxy Server shall ignore these addresses. + */ + for (i = 0; i < gatt_proxy->filter_count; i++) + if (gatt_proxy->filter_addrs[i] == addr) + break; + + if (i == gatt_proxy->filter_count) + return; + + memmove(gatt_proxy->filter_addrs + i, gatt_proxy->filter_addrs + i + 1, + gatt_proxy->filter_count - i - 1); + gatt_proxy->filter_count--; +} + +unsigned gatt_proxy_svc_filter_count(struct gatt_proxy_svc *gatt_proxy, + uint8_t *filter_type) +{ + if (!gatt_proxy || gatt_proxy != gatt_proxy_svc) + return 0; + + *filter_type = gatt_proxy->filter_type; + + return gatt_proxy->filter_count; +} + +void gatt_proxy_svc_filter_pdu_rcvd(struct gatt_proxy_svc *gatt_proxy, + uint16_t src) +{ + if (!gatt_proxy || gatt_proxy != gatt_proxy_svc) + return; + + if (gatt_proxy->filter_type == PROXY_FILTER_ACCEPT_LIST) { + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If the proxy filter is an accept list filter, upon receiving + * a Proxy PDU containing a valid Network PDU from the Proxy + * Client, the Proxy Server shall add the unicast address + * contained in the SRC field of the Network PDU to the accept + * list. + */ + gatt_proxy_svc_filter_add(gatt_proxy, src); + } else { + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * If the proxy filter is a reject list filter, upon receiving a + * Proxy PDU containing a valid Network PDU from the Proxy + * Client, the Proxy Server shall remove the unicast address + * contained in the SRC field of the Network PDU from the reject + * list. + */ + gatt_proxy_svc_filter_remove(gatt_proxy, src); + } +} + static void gatt_proxy_svc_send(enum proxy_msg_type msg_type, const void *data, uint8_t len) { @@ -144,11 +281,40 @@ static void gatt_proxy_svc_send(enum proxy_msg_type msg_type, const void *data, } } -void gatt_proxy_svc_send_net(const void *data, uint8_t len) +void gatt_proxy_svc_send_net(uint16_t dst, const void *data, uint8_t len) { + int i; + + if (!gatt_proxy_svc) + return; + + /* + * MshPRT_v1.1, section 6.4 - Proxy filtering + * The output filter of the network interface (see Section 3.4.5) [...] + * can be configured by the Proxy Client. This allows the Proxy Client + * to explicitly request to receive only mesh messages with certain + * *destination* addresses. + */ + for (i = 0; i < gatt_proxy_svc->filter_count; i++) + if (gatt_proxy_svc->filter_addrs[i] == dst) + break; + + if (gatt_proxy_svc->filter_type == PROXY_FILTER_ACCEPT_LIST) { + if (i == gatt_proxy_svc->filter_count) // not found + return; + } else { /* PROXY_FILTER_REJECT_LIST */ + if (i != gatt_proxy_svc->filter_count) // found + return; + } + gatt_proxy_svc_send(PROXY_MSG_TYPE_NETWORK_PDU, data, len); } +void gatt_proxy_svc_send_proxy_cfg(const void *data, uint8_t len) +{ + gatt_proxy_svc_send(PROXY_MSG_TYPE_PROXY_CFG, data, len); +} + static void gatt_service_notify_acquired(void *user_data) { struct gatt_proxy_svc *gatt_proxy = user_data; @@ -157,6 +323,14 @@ static void gatt_service_notify_acquired(void *user_data) return; gatt_proxy->connected = true; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * Upon connection, the Proxy Server shall initialize the proxy filter + * as an accept list filter and the accept list shall be empty. + */ + gatt_proxy->filter_type = PROXY_FILTER_ACCEPT_LIST; + gatt_proxy->filter_count = 0; } static void gatt_service_notify_stopped(void *user_data) diff --git a/mesh/gatt-proxy-svc.h b/mesh/gatt-proxy-svc.h index 2b0c9d7ec21b..0abb85d7109f 100644 --- a/mesh/gatt-proxy-svc.h +++ b/mesh/gatt-proxy-svc.h @@ -24,5 +24,17 @@ void gatt_proxy_svc_register_recv_cb(struct gatt_proxy_svc *gatt_proxy, void *user_data); void gatt_proxy_svc_deregister_recv_cb(struct gatt_proxy_svc *gatt_proxy, enum proxy_msg_type msg_type); -void gatt_proxy_svc_send_net(const void *data, uint8_t len); - + +void gatt_proxy_svc_filter_set_type(struct gatt_proxy_svc *gatt_proxy, + uint8_t filter_type); +void gatt_proxy_svc_filter_add(struct gatt_proxy_svc *gatt_proxy, + uint16_t addr); +void gatt_proxy_svc_filter_remove(struct gatt_proxy_svc *gatt_proxy, + uint16_t addr); +unsigned gatt_proxy_svc_filter_count(struct gatt_proxy_svc *gatt_proxy, + uint8_t *filter_type); +void gatt_proxy_svc_filter_pdu_rcvd(struct gatt_proxy_svc *gatt_proxy, + uint16_t src); + +void gatt_proxy_svc_send_net(uint16_t dst, const void *data, uint8_t len); +void gatt_proxy_svc_send_proxy_cfg(const void *data, uint8_t len); diff --git a/mesh/net-keys.c b/mesh/net-keys.c index ab724f9cfa1e..1a2cd39421c1 100644 --- a/mesh/net-keys.c +++ b/mesh/net-keys.c @@ -78,6 +78,15 @@ struct net_key { bool ivu; }; +struct proxy_cfg_msg { + const uint8_t *data; + uint8_t len; + uint8_t *plain; + uint8_t plain_len; + uint32_t iv_index; + uint32_t key_id; +}; + static struct l_queue *beacons; static struct l_queue *keys; static uint32_t last_flooding_id; @@ -255,6 +264,29 @@ static void decrypt_net_pkt(void *a, void *b) } } +static void decrypt_proxy_cfg_msg(void *a, void *b) +{ + const struct net_key *key = a; + struct proxy_cfg_msg *proxy_cfg = b; + bool result; + + if (proxy_cfg->key_id || !key->ref_cnt || + (proxy_cfg->data[0] & 0x7f) != key->nid) + return; + + result = mesh_crypto_packet_decode(proxy_cfg->data, proxy_cfg->len, + true, + proxy_cfg->plain, + proxy_cfg->iv_index, + key->enc_key, + key->prv_key); + + if (result) { + proxy_cfg->key_id = key->id; + proxy_cfg->plain_len = proxy_cfg->len; + } +} + uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len) { @@ -284,6 +316,30 @@ done: return cache_id; } +uint32_t net_key_decrypt_proxy_cfg_msg(uint32_t iv_index, + const uint8_t *pkt, size_t len, + uint8_t *plain, size_t *plain_len) +{ + struct proxy_cfg_msg proxy_cfg = { + .data = pkt, + .len = len, + .plain = plain, + .iv_index = iv_index, + }; + + /* MshPRT_v1.1, section 6.6: Proxy configuration messages have CTL=1 */ + if (!(pkt[1] & CTL)) + return 0; + + /* Try all network keys known to us */ + l_queue_foreach(keys, decrypt_proxy_cfg_msg, &proxy_cfg); + + if (proxy_cfg.key_id) + *plain_len = proxy_cfg.plain_len; + + return proxy_cfg.key_id; +} + bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len) { struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); diff --git a/mesh/net-keys.h b/mesh/net-keys.h index 512347521547..b43157df29bc 100644 --- a/mesh/net-keys.h +++ b/mesh/net-keys.h @@ -26,6 +26,9 @@ uint32_t net_key_frnd_add(uint32_t flooding_id, uint16_t lpn, uint16_t frnd, void net_key_unref(uint32_t id); uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, uint8_t **plain, size_t *plain_len); +uint32_t net_key_decrypt_proxy_cfg_msg(uint32_t iv_index, + const uint8_t *pkt, size_t len, + uint8_t *plain, size_t *plain_len); bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len); uint32_t net_key_network_id(const uint8_t network[8]); uint32_t net_key_beacon(const uint8_t *data, uint16_t len, uint32_t *ivi, diff --git a/mesh/net.c b/mesh/net.c index 911f781ef75c..9a04aa5120c4 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -27,6 +27,7 @@ #include "mesh/net-keys.h" #include "mesh/node.h" #include "mesh/net.h" +#include "mesh/proxy-cfg.h" #include "mesh/mesh-io.h" #include "mesh/friend.h" #include "mesh/gatt-service.h" // PROXY_MSG_TYPE_NETWORK_PDU @@ -192,8 +193,20 @@ struct net_queue_data { bool seen; }; +struct net_queue_data_proxy_cfg { + struct gatt_proxy_svc *gatt_proxy; + struct mesh_net *net; + const uint8_t *data; + uint8_t *out; + size_t out_size; + uint32_t net_key_id; + uint32_t iv_index; + uint16_t len; +}; + struct oneshot_tx { struct mesh_net *net; + uint16_t net_dst; uint16_t interval; bool frnd; uint8_t cnt; @@ -2303,17 +2316,20 @@ static void send_msg_pkt_oneshot(void *user_data) */ mesh_io_send(net->io, &info, tx->packet, tx->size); if (!tx->frnd) - gatt_proxy_svc_send_net(tx->packet + 1, tx->size - 1); + gatt_proxy_svc_send_net(tx->net_dst, tx->packet + 1, + tx->size - 1); l_free(tx); } -static void send_msg_pkt(struct mesh_net *net, uint8_t cnt, uint16_t interval, +static void send_msg_pkt(struct mesh_net *net, uint16_t dst, uint8_t cnt, + uint16_t interval, const uint8_t *packet, uint8_t size, bool frnd) { struct oneshot_tx *tx = l_new(struct oneshot_tx, 1); tx->net = net; + tx->net_dst = dst; tx->interval = interval; tx->frnd = frnd; tx->cnt = cnt; @@ -2324,6 +2340,7 @@ static void send_msg_pkt(struct mesh_net *net, uint8_t cnt, uint16_t interval, } static enum _relay_advice packet_received(struct mesh_net *net, + struct gatt_proxy_svc *gatt_proxy, uint32_t net_key_id, uint16_t net_idx, bool frnd, uint32_t iv_index, const uint8_t *data, uint8_t size, int8_t rssi) @@ -2347,7 +2364,9 @@ static enum _relay_advice packet_received(struct mesh_net *net, return RELAY_NONE; } - if (net_dst == 0) { + gatt_proxy_svc_filter_pdu_rcvd(gatt_proxy, net_src); + + if (net_dst == UNASSIGNED_ADDRESS) { l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", net_dst, net_ctl, net_ttl); return RELAY_NONE; @@ -2508,7 +2527,8 @@ static void net_rx(void *net_ptr, void *user_data) if (data->gatt_proxy && frnd) return; - relay_advice = packet_received(net, net_key_id, net_idx, frnd, + relay_advice = packet_received(net, data->gatt_proxy, net_key_id, + net_idx, frnd, iv_index, out, out_size, rssi); if (relay_advice > data->relay_advice) { /* @@ -2527,6 +2547,48 @@ static void net_rx(void *net_ptr, void *user_data) } } +static void net_proxy_cfg_msg_rx(void *net_ptr, void *user_data) +{ + struct net_queue_data_proxy_cfg *data = user_data; + struct mesh_net *net = net_ptr; + uint8_t out[MESH_NET_MAX_PDU_LEN]; + size_t out_size; + uint32_t net_key_id; + uint16_t net_idx; + bool frnd; + bool ivi_net = !!(net->iv_index & 1); + bool ivi_pkt = !!(data->data[0] & 0x80); + + /* if IVI flag differs, use previous IV Index */ + uint32_t iv_index = net->iv_index - (ivi_pkt ^ ivi_net); + + net_key_id = net_key_decrypt_proxy_cfg_msg(iv_index, + data->data, data->len, + out, &out_size); + + if (!net_key_id) + return; + + net_idx = key_id_to_net_idx(net, net_key_id, &frnd); + + if (net_idx == NET_IDX_INVALID) + return; + + /* + * MshPRT_v1.1, section 3.4.5.1 - Interface input filter + * The input filter of the interface connected to the GATT bearer shall + * drop all Network PDUs that have been secured using the friendship + * security credentials. + */ + if (frnd) + return; + + print_packet("RX: ProxyCfg [enc] :", data->data, data->len); + + proxy_cfg_msg_received(data->gatt_proxy, net, net_key_id, iv_index, out, + out_size); +} + static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, const uint8_t *data, uint16_t len) { @@ -2566,6 +2628,25 @@ static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, } } +static void +net_proxy_cfg_msg_recv(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + struct gatt_proxy_svc *gatt_proxy = user_data; + struct net_queue_data_proxy_cfg net_data = { + .gatt_proxy = gatt_proxy, + .data = data + 1, + .len = len - 1, + }; + + if (len < 9) + return; + + l_queue_foreach(nets, net_proxy_cfg_msg_rx, &net_data); + + /* Proxy configuration messages are not relayed */ +} + static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data) { struct mesh_net *net = user_data; @@ -3080,6 +3161,9 @@ void mesh_net_attach_gatt(struct gatt_proxy_svc *gatt_proxy) gatt_proxy_svc_register_recv_cb(gatt_proxy, PROXY_MSG_TYPE_NETWORK_PDU, net_msg_recv, gatt_proxy); + gatt_proxy_svc_register_recv_cb(gatt_proxy, PROXY_MSG_TYPE_PROXY_CFG, + net_proxy_cfg_msg_recv, + gatt_proxy); } void mesh_net_detach_gatt(struct gatt_proxy_svc *gatt_proxy) @@ -3088,6 +3172,8 @@ void mesh_net_detach_gatt(struct gatt_proxy_svc *gatt_proxy) gatt_proxy_svc_deregister_recv_cb(gatt_proxy, PROXY_MSG_TYPE_NETWORK_PDU); + gatt_proxy_svc_deregister_recv_cb(gatt_proxy, + PROXY_MSG_TYPE_PROXY_CFG); } bool mesh_net_iv_index_update(struct mesh_net *net) @@ -3214,8 +3300,8 @@ static bool send_seg(struct mesh_net *net, uint8_t cnt, uint16_t interval, return false; } - send_msg_pkt(net, cnt, interval, packet, packet_len + 1, false); - + send_msg_pkt(net, msg->remote, cnt, interval, packet, packet_len + 1, + false); msg->last_seg = segO; return true; @@ -3268,7 +3354,7 @@ void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, return; } - send_msg_pkt(net, net->tx_cnt, net->tx_interval, packet, + send_msg_pkt(net, dst, net->tx_cnt, net->tx_interval, packet, packet_len + 1, frnd); l_debug("TX: Friend Seg-%d %04x -> %04x : len %u) : TTL %d : SEQ %06x", @@ -3433,7 +3519,7 @@ void mesh_net_ack_send(struct mesh_net *net, uint32_t net_key_id, return; } - send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1, + send_msg_pkt(net, dst, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1, frnd); l_debug("TX: Friend ACK %04x -> %04x : len %u : TTL %d : SEQ %06x", @@ -3522,7 +3608,7 @@ void mesh_net_transport_send(struct mesh_net *net, uint32_t net_key_id, } if (!(IS_UNASSIGNED(dst))) - send_msg_pkt(net, net->tx_cnt, net->tx_interval, pkt, + send_msg_pkt(net, dst, net->tx_cnt, net->tx_interval, pkt, pkt_len + 1, frnd); } diff --git a/mesh/proxy-cfg.c b/mesh/proxy-cfg.c new file mode 100644 index 000000000000..5a9439f4d941 --- /dev/null +++ b/mesh/proxy-cfg.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2025 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdbool.h> + +#include <ell/dbus.h> +#include <ell/log.h> +#include <ell/util.h> // l_get_be16(), l_put_be16() + +#include "src/shared/ad.h" + +#include "mesh/crypto.h" // mesh_crypto_network_header_parse() +#include "mesh/mesh-io.h" // mesh_io_recv_func_t, required by gatt-proxy-svc.h +#include "mesh/gatt-proxy-svc.h" // gatt_proxy_svc_filter_set_type(), + // gatt_proxy_svc_filter_add(), + // gatt_proxy_svc_filter_remove(), + // gatt_proxy_svc_filter_count(), + // gatt_proxy_svc_send() +#include "mesh/mesh-defs.h" // UNASSIGNED_ADDRESS +#include "mesh/net.h" // PROXY_OP_SET_FILTER_TYPE, + // PROXY_OP_FILTER_ADD, + // PROXY_OP_FILTER_DEL, + // PROXY_OP_FILTER_STATUS, + // mesh_net_next_seq_num(), + // mesh_net_get_address() +#include "mesh/net-keys.h" // net_key_encrypt(), + // net_key_decrypt_proxy_cfg_msg() +#include "mesh/util.h" // print_packet() +#include "mesh/proxy-cfg.h" + +void proxy_cfg_msg_received(struct gatt_proxy_svc *gatt_proxy, + struct mesh_net *net, + uint32_t net_key_id, uint32_t iv_index, + const uint8_t *data, uint8_t size) +{ + const uint8_t *msg; + uint8_t cfg_msg_len; + uint8_t net_ttl; + uint32_t net_seq; + uint16_t net_src, net_dst; + bool net_ctl; + uint8_t rsp[4]; // length of PROXY_OP_FILTER_STATUS + uint8_t rsp_len = 0; + + print_packet("RX: ProxyCfg [clr] :", data, size); + + if (!mesh_crypto_network_header_parse(data, size, &net_ctl, &net_ttl, + &net_seq, &net_src, &net_dst)) { + l_error("Failed to parse packet content"); + return; + } + + /* + * MshPRT_v1.1, section 6.6: + * - The CTL field shall be set to 1. [already checked] + * - The TTL field shall be set to 0. + * - The DST field shall be set to the unassigned address. + */ + if (net_dst != UNASSIGNED_ADDRESS || net_ttl) { + l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", + net_dst, net_ctl, net_ttl); + return; + } + + l_debug("RX: ProxyCfg %04x -> %04x : TTL 0x%02x : IV : %8.8x SEQ 0x%06x", + net_src, net_dst, net_ttl, iv_index, net_seq); + + msg = data + 9; + cfg_msg_len = size - 9 - 8 /* NetMIC */; + + if (!cfg_msg_len) + return; + + /* process request */ + switch (msg[0]) { + case PROXY_OP_SET_FILTER_TYPE: + if (cfg_msg_len >= 2) { + uint8_t filter_type; + + filter_type = msg[1]; + gatt_proxy_svc_filter_set_type(gatt_proxy, + filter_type); + } + break; + + case PROXY_OP_FILTER_ADD: + if (cfg_msg_len & 0x1) { + int num_filters = (cfg_msg_len - 1) / 2, i; + uint16_t addr; + + for (i = 0; i < num_filters; i++) { + addr = l_get_be16(msg + 2 * i + 1); + gatt_proxy_svc_filter_add(gatt_proxy, + addr); + } + } + break; + + case PROXY_OP_FILTER_DEL: + if (cfg_msg_len & 0x1) { + int num_filters = (cfg_msg_len - 1) / 2, i; + uint16_t addr; + + for (i = 0; i < num_filters; i++) { + addr = l_get_be16(msg + 2 * i + 1); + gatt_proxy_svc_filter_remove(gatt_proxy, + addr); + } + } + break; + + default: + break; + } + + /* prepare response */ + switch (msg[0]) { + case PROXY_OP_SET_FILTER_TYPE: + case PROXY_OP_FILTER_ADD: + case PROXY_OP_FILTER_DEL: { + uint8_t filter_type; + uint16_t num_filters = + gatt_proxy_svc_filter_count(gatt_proxy, + &filter_type); + rsp[0] = PROXY_OP_FILTER_STATUS; + rsp[1] = filter_type; + l_put_be16(num_filters, &rsp[2]); + rsp_len = 4; + break; + } + + /* + * MshPRT_v1.1, section 6.7: + * Upon receiving a proxy configuration message with the Opcode + * field set to a value that is Reserved for Future Use, the + * Proxy Server shall ignore this message. + */ + default: + break; + } + + if (rsp_len) { + uint8_t pkt[MESH_NET_MAX_PDU_LEN]; + uint8_t pkt_len; + + net_seq = mesh_net_next_seq_num(net); + if (!mesh_crypto_packet_build(true, 0/*TTL*/, net_seq, + mesh_net_get_address(net)/*src*/, + UNASSIGNED_ADDRESS/*dst*/, rsp[0], + false, 0, false, 0, 0, 0, + rsp + 1, rsp_len - 1, pkt, &pkt_len)) + return; + + if (!net_key_encrypt(net_key_id, iv_index, pkt, pkt_len)) { + l_error("Failed to encode packet"); + return; + } + + gatt_proxy_svc_send_proxy_cfg(pkt, pkt_len); + } +} diff --git a/mesh/proxy-cfg.h b/mesh/proxy-cfg.h new file mode 100644 index 000000000000..b41cbd512c05 --- /dev/null +++ b/mesh/proxy-cfg.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 ARRI Lighting. All rights reserved. + * + * + */ + +#include <stdint.h> + +struct gatt_proxy_svc; +struct mesh_net; + +void proxy_cfg_msg_received(struct gatt_proxy_svc *gatt_proxy, + struct mesh_net *net, + uint32_t net_key_id, uint32_t iv_index, + const uint8_t *data, uint8_t size); -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 5/7] mesh: main.conf/cfgmod-server: allow to enable proxy support 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers ` (3 preceding siblings ...) 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 4/7] mesh: gatt-proxy: support for proxy configuration messages and filtering Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 6/7] mesh: net: proxy relaying Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 7/7] mesh: gatt-proxy: propagate beacons Christian Eggers 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers The proxy settings in the config file and in the configuration server only affect the _relaying_ between GATT and ADV. Provisioning via PB-GATT and connecting to the GATT proxy service is always possible (MshPRT_v1.1, section 4.2.12). The actual relaying of network messages and beacons is implemented in the next patches. --- mesh/cfgmod-server.c | 9 +++++---- mesh/mesh-main.conf | 9 +++++++++ mesh/mesh.c | 14 +++++++++++++- mesh/mesh.h | 1 + mesh/node.c | 3 ++- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/mesh/cfgmod-server.c b/mesh/cfgmod-server.c index f9f80aff7920..5c0015033d00 100644 --- a/mesh/cfgmod-server.c +++ b/mesh/cfgmod-server.c @@ -405,7 +405,8 @@ static uint16_t cfg_relay_msg(struct mesh_node *node, const uint8_t *pkt, if (opcode == OP_CONFIG_RELAY_SET) { count = (pkt[1] & 0x7) + 1; interval = ((pkt[1] >> 3) + 1) * 10; - node_relay_mode_set(node, !!pkt[0], count, interval); + node_relay_mode_set(node, pkt[0] == MESH_MODE_ENABLED, count, + interval); } n = mesh_model_opcode_set(OP_CONFIG_RELAY_STATUS, msg); @@ -879,7 +880,7 @@ static bool cfg_srv_pkt(uint16_t src, uint16_t dst, uint16_t app_idx, break; case OP_CONFIG_RELAY_SET: - if (size != 2 || pkt[0] > 0x01) + if (size != 2 || pkt[0] > MESH_MODE_ENABLED) return true; /* Fall Through */ @@ -903,10 +904,10 @@ static bool cfg_srv_pkt(uint16_t src, uint16_t dst, uint16_t app_idx, break; case OP_CONFIG_PROXY_SET: - if (size != 1 || pkt[0] > 0x01) + if (size != 1 || pkt[0] > MESH_MODE_ENABLED) return true; - node_proxy_mode_set(node, !!pkt[0]); + node_proxy_mode_set(node, pkt[0] == MESH_MODE_ENABLED); /* Fall Through */ case OP_CONFIG_PROXY_GET: diff --git a/mesh/mesh-main.conf b/mesh/mesh-main.conf index aca9e6fa5a36..d917218fcf1a 100644 --- a/mesh/mesh-main.conf +++ b/mesh/mesh-main.conf @@ -16,6 +16,15 @@ # Defaults to true. #Relay = true +# Default setting for supporting proxy. The setting applies +# to all local nodes. +# If the value is true, then a configuration client can either enable or disable +# the proxy feature per local node. +# If the value is false, then the proxy feature cannot be configured for +# any local node. +# Defaults to true. +#Proxy = true + # Default setting for supporting Friendship. The setting applies # to all local nodes. # If the value is true, then a configuration client can either enable or disable diff --git a/mesh/mesh.c b/mesh/mesh.c index db77602d37da..5dbec2b319d0 100644 --- a/mesh/mesh.c +++ b/mesh/mesh.c @@ -85,7 +85,7 @@ static struct bt_mesh mesh = { .friend_support = true, .relay_support = true, .lpn_support = false, - .proxy_support = false, + .proxy_support = true, .crpl = DEFAULT_CRPL, .friend_queue_sz = DEFAULT_FRIEND_QUEUE_SZ, .initialized = false @@ -192,6 +192,11 @@ bool mesh_relay_supported(void) return mesh.relay_support; } +bool mesh_proxy_supported(void) +{ + return mesh.proxy_support; +} + bool mesh_friendship_supported(void) { return mesh.friend_support; @@ -231,6 +236,13 @@ static void parse_settings(const char *mesh_conf_fname) l_free(str); } + str = l_settings_get_string(settings, "General", "Proxy"); + if (str) { + if (!strcasecmp(str, "false")) + mesh.proxy_support = false; + l_free(str); + } + str = l_settings_get_string(settings, "General", "Friendship"); if (str) { if (!strcasecmp(str, "false")) diff --git a/mesh/mesh.h b/mesh/mesh.h index c30a8d1f08da..e293d4ba1613 100644 --- a/mesh/mesh.h +++ b/mesh/mesh.h @@ -40,6 +40,7 @@ const char *mesh_prov_status_str(uint8_t status); const char *mesh_get_storage_dir(void); bool mesh_beacon_enabled(void); bool mesh_relay_supported(void); +bool mesh_proxy_supported(void); bool mesh_friendship_supported(void); uint16_t mesh_get_crpl(void); uint8_t mesh_get_friend_queue_size(void); diff --git a/mesh/node.c b/mesh/node.c index 7f20e97ea230..18aad67bf8d0 100644 --- a/mesh/node.c +++ b/mesh/node.c @@ -209,7 +209,8 @@ uint8_t *node_uuid_get(struct mesh_node *node) static void set_defaults(struct mesh_node *node) { node->lpn = MESH_MODE_UNSUPPORTED; - node->proxy = MESH_MODE_UNSUPPORTED; + node->proxy = (mesh_proxy_supported()) ? MESH_MODE_DISABLED : + MESH_MODE_UNSUPPORTED; node->mpb = MESH_MODE_DISABLED; node->mpb_period = NET_MPB_REFRESH_DEFAULT; node->friend = (mesh_friendship_supported()) ? MESH_MODE_DISABLED : -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 6/7] mesh: net: proxy relaying 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers ` (4 preceding siblings ...) 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 5/7] mesh: main.conf/cfgmod-server: allow to enable proxy support Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 7/7] mesh: gatt-proxy: propagate beacons Christian Eggers 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers Implement retransmission of network messages according to MshPRT_v1.1, section 3.4.6.3, table 3.13. --- mesh/net.c | 52 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/mesh/net.c b/mesh/net.c index 9a04aa5120c4..97591a5323d1 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -183,10 +183,12 @@ struct net_queue_data { struct gatt_proxy_svc *gatt_proxy; struct mesh_io_recv_info *info; struct mesh_net *net; + uint16_t net_dst; const uint8_t *data; uint8_t *out; size_t out_size; enum _relay_advice relay_advice; + bool proxy_enable; uint32_t net_key_id; uint32_t iv_index; uint16_t len; @@ -761,12 +763,7 @@ bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable) if (!net) return false; - /* No support for proxy yet */ - if (enable) { - l_error("Proxy not supported!"); - return false; - } - + net->proxy_enable = enable; trigger_heartbeat(net, FEATURE_PROXY, enable); return true; } @@ -2343,7 +2340,8 @@ static enum _relay_advice packet_received(struct mesh_net *net, struct gatt_proxy_svc *gatt_proxy, uint32_t net_key_id, uint16_t net_idx, bool frnd, uint32_t iv_index, - const uint8_t *data, uint8_t size, int8_t rssi) + const uint8_t *data, uint8_t size, int8_t rssi, + uint16_t *dst) { const uint8_t *msg; uint8_t app_msg_len; @@ -2364,6 +2362,7 @@ static enum _relay_advice packet_received(struct mesh_net *net, return RELAY_NONE; } + *dst = net_dst; gatt_proxy_svc_filter_pdu_rcvd(gatt_proxy, net_src); if (net_dst == UNASSIGNED_ADDRESS) { @@ -2487,7 +2486,7 @@ static void net_rx(void *net_ptr, void *user_data) uint8_t *out; size_t out_size; uint32_t net_key_id; - uint16_t net_idx; + uint16_t net_idx, net_dst; int8_t rssi = 0; bool frnd; bool ivi_net = !!(net->iv_index & 1); @@ -2528,8 +2527,8 @@ static void net_rx(void *net_ptr, void *user_data) return; relay_advice = packet_received(net, data->gatt_proxy, net_key_id, - net_idx, frnd, - iv_index, out, out_size, rssi); + net_idx, frnd, iv_index, + out, out_size, rssi, &net_dst); if (relay_advice > data->relay_advice) { /* * If packet was encrypted with friendship credentials, @@ -2538,8 +2537,11 @@ static void net_rx(void *net_ptr, void *user_data) if (frnd && !mesh_net_get_key(net, false, net_idx, &net_key_id)) return; + data->net_dst = net_dst; data->iv_index = iv_index; data->relay_advice = relay_advice; + if (net->proxy_enable) + data->proxy_enable = true; data->net_key_id = net_key_id; data->net = net; data->out = out; @@ -2601,8 +2603,10 @@ static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, .data = data + 1, .len = len - 1, .relay_advice = RELAY_NONE, + .proxy_enable = false, .seen = false, }; + uint8_t ttl; if (len < 9) return; @@ -2616,16 +2620,28 @@ static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, l_queue_foreach(nets, net_rx, &net_data); - if (net_data.relay_advice == RELAY_ALWAYS || - net_data.relay_advice == RELAY_ALLOWED) { - uint8_t ttl = net_data.out[1] & TTL_MASK; + if (net_data.relay_advice != RELAY_ALWAYS && + net_data.relay_advice != RELAY_ALLOWED) + return; - net_data.out[1] &= ~TTL_MASK; - net_data.out[1] |= ttl - 1; - net_key_encrypt(net_data.net_key_id, net_data.iv_index, - net_data.out, net_data.out_size); + ttl = net_data.out[1] & TTL_MASK; + net_data.out[1] &= ~TTL_MASK; + net_data.out[1] |= ttl - 1; + net_key_encrypt(net_data.net_key_id, net_data.iv_index, + net_data.out, net_data.out_size); + /* + * Table 3.13 in MshPRT_v1.1, sec. 3.4.6.3 states that (if proxy is on): + * - Inbound messages from ADV shall be retransmitted to GATT. + * - Inbound messages from GATT shall be retransmitted to all bearers. + * For me it seems not to make much sense relaying incoming messages + * from GATT back to GATT. + */ + if ((gatt_proxy && net_data.proxy_enable) || !gatt_proxy) send_relay_pkt(net_data.net, net_data.out, net_data.out_size); - } + + if (!gatt_proxy && net_data.proxy_enable) + gatt_proxy_svc_send_net(net_data.net_dst, net_data.out, + net_data.out_size); } static void -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [RFC PATCH BlueZ v2 7/7] mesh: gatt-proxy: propagate beacons 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers ` (5 preceding siblings ...) 2025-07-11 17:56 ` [RFC PATCH BlueZ v2 6/7] mesh: net: proxy relaying Christian Eggers @ 2025-07-11 17:56 ` Christian Eggers 6 siblings, 0 replies; 22+ messages in thread From: Christian Eggers @ 2025-07-11 17:56 UTC (permalink / raw) To: Brian Gix, Inga Stotland, linux-bluetooth; +Cc: Christian Eggers MshPRT_v1.1, section 6.7 requires that: 1. Upon connection of a GATT proxy client to a GATT proxy server, the server sends a beacon for each known subnet to the client. 2. Upon processing a SNB or MPB with a new IV index or flags value, the GATT proxy server shall propagate the beacon to the client. 3. When the GATT proxy server is added to a new subnet, a beacon for that subnet shall be sent to the client. --- mesh/gatt-proxy-svc.c | 20 +++++++++++++++++++- mesh/gatt-proxy-svc.h | 1 + mesh/net-keys.c | 25 +++++++++++++++++++++++++ mesh/net-keys.h | 1 + mesh/net.c | 23 +++++++++++++++++++++++ mesh/net.h | 1 + 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/mesh/gatt-proxy-svc.c b/mesh/gatt-proxy-svc.c index 4100578a9f0a..020555bbfdb2 100644 --- a/mesh/gatt-proxy-svc.c +++ b/mesh/gatt-proxy-svc.c @@ -25,7 +25,8 @@ #include "mesh/net.h" // PROXY_FILTER_ACCEPT_LIST, // PROXY_FILTER_REJECT_LIST // mesh_net_attach_gatt(), - // mesh_net_detach_gatt() + // mesh_net_detach_gatt(), + // mesh_net_send_all_beacons_gatt() #include "mesh/net-keys.h" // net_key_fill_adv_service_data(), // net_key_get_next_id() #include "mesh/util.h" // print_packet() @@ -281,6 +282,11 @@ static void gatt_proxy_svc_send(enum proxy_msg_type msg_type, const void *data, } } +void gatt_proxy_svc_send_beacon(const void *data, uint8_t len) +{ + gatt_proxy_svc_send(PROXY_MSG_TYPE_MESH_BEACON, data, len); +} + void gatt_proxy_svc_send_net(uint16_t dst, const void *data, uint8_t len) { int i; @@ -331,6 +337,18 @@ static void gatt_service_notify_acquired(void *user_data) */ gatt_proxy->filter_type = PROXY_FILTER_ACCEPT_LIST; gatt_proxy->filter_count = 0; + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * Upon connection, [...] The Proxy Server shall send a mesh + * beacon for each known subnet to the Proxy Client, [...] + * + * MshPRT_v1.1, section 7.2.3.2.1 - Characteristic behavior + * [...] the client will enable notifications [...] to the + * Mesh Proxy Data Out Client Characteristic Configuration + * Descriptor after a connection is established. + */ + mesh_net_send_all_beacons_gatt(); } static void gatt_service_notify_stopped(void *user_data) diff --git a/mesh/gatt-proxy-svc.h b/mesh/gatt-proxy-svc.h index 0abb85d7109f..2784602160bb 100644 --- a/mesh/gatt-proxy-svc.h +++ b/mesh/gatt-proxy-svc.h @@ -36,5 +36,6 @@ unsigned gatt_proxy_svc_filter_count(struct gatt_proxy_svc *gatt_proxy, void gatt_proxy_svc_filter_pdu_rcvd(struct gatt_proxy_svc *gatt_proxy, uint16_t src); +void gatt_proxy_svc_send_beacon(const void *data, uint8_t len); void gatt_proxy_svc_send_net(uint16_t dst, const void *data, uint8_t len); void gatt_proxy_svc_send_proxy_cfg(const void *data, uint8_t len); diff --git a/mesh/net-keys.c b/mesh/net-keys.c index 1a2cd39421c1..918455b2200f 100644 --- a/mesh/net-keys.c +++ b/mesh/net-keys.c @@ -732,6 +732,18 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t ivi, bool kr, bool ivu, print_packet("Set SNB to", key->snb, BEACON_LEN_SNB); gatt_proxy_svc_set_current_adv_key(key->id); + + /* + * MshPRT_v1.1, section 6.7 - Proxy Server behavior + * Upon successfully processing a Secure Network Beacon or + * a Mesh Private beacon with a new value for the IV Index + * field or the Flags field, the Proxy Server shall send a + * mesh beacon to the Proxy Client, ... + * When the Proxy Server is added to a new subnet, the server + * shall send a mesh beacon for that subnet to the Proxy Client, + * ... + */ + gatt_proxy_svc_send_beacon(key->snb + 1, BEACON_LEN_SNB - 1); } l_debug("Set Beacon: IVI: %8.8x, IVU: %d, KR: %d", ivi, ivu, kr); @@ -850,6 +862,19 @@ void net_key_beacon_disable(uint32_t id, bool mpb) key->observe.timeout = NULL; } +void net_key_beacon_send_gatt(uint32_t id) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + + if (!key) + return; + + /* FIXME: How to determine that key actually contains a valid SNB? */ + if (key->snb && key->snb[0] == BT_AD_MESH_BEACON && + key->snb[1] == BEACON_TYPE_SNB && key->snb[2] == 0) + gatt_proxy_svc_send_beacon(key->snb + 1, BEACON_LEN_SNB - 1); +} + static void free_key(void *data) { struct net_key *key = data; diff --git a/mesh/net-keys.h b/mesh/net-keys.h index b43157df29bc..0d292ee41606 100644 --- a/mesh/net-keys.h +++ b/mesh/net-keys.h @@ -40,6 +40,7 @@ bool net_key_beacon_refresh(uint32_t id, uint32_t iv_index, bool kr, bool ivu, bool force); void net_key_beacon_enable(uint32_t id, bool mpb, uint8_t refresh_count); void net_key_beacon_disable(uint32_t id, bool mpb); +void net_key_beacon_send_gatt(uint32_t id); uint32_t net_key_beacon_last_seen(uint32_t id); bool net_key_fill_adv_service_data(uint32_t id, diff --git a/mesh/net.c b/mesh/net.c index 97591a5323d1..e4998a0924f6 100644 --- a/mesh/net.c +++ b/mesh/net.c @@ -2997,6 +2997,29 @@ void net_local_beacon(uint32_t net_key_id, uint32_t ivi, bool ivu, bool kr) l_queue_foreach(nets, process_beacon, &beacon_data); } +static void send_beacon_gatt(void *a, void *b) +{ + struct mesh_subnet *subnet = a; + + net_key_beacon_send_gatt(subnet->net_key_tx); +} + +static void send_all_beacons_gatt(void *a, void *b) +{ + struct mesh_net *net = a; + + l_queue_foreach(net->subnets, send_beacon_gatt, NULL); +} + +void mesh_net_send_all_beacons_gatt(void) +{ + /* + * Upon connection, [...] The Proxy Server shall send a mesh beacon + * for each known subnet to the Proxy Client, [...] + */ + l_queue_foreach(nets, send_all_beacons_gatt, NULL); +} + bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable) { if (!net) diff --git a/mesh/net.h b/mesh/net.h index af581478412c..bf537e20a57c 100644 --- a/mesh/net.h +++ b/mesh/net.h @@ -229,6 +229,7 @@ uint16_t mesh_net_get_address(struct mesh_net *net); bool mesh_net_register_unicast(struct mesh_net *net, uint16_t unicast, uint8_t num_ele); void net_local_beacon(uint32_t key_id, uint32_t ivi, bool ivu, bool kr); +void mesh_net_send_all_beacons_gatt(void); bool mesh_net_set_snb_mode(struct mesh_net *net, bool enable); bool mesh_net_set_mpb_mode(struct mesh_net *net, bool enabla, uint8_t period, bool init); -- 2.43.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
end of thread, other threads:[~2025-07-15 15:56 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-01 14:22 [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers
2025-07-01 14:22 ` [RFC PATCH BlueZ 1/4] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers
2025-07-01 16:02 ` Initial support for GATT Proxy Service bluez.test.bot
2025-07-01 14:22 ` [RFC PATCH BlueZ 2/4] mesh: add support for provisioning via GATT Christian Eggers
2025-07-01 14:22 ` [RFC PATCH BlueZ 3/4] mesh: add GATT proxy service Christian Eggers
[not found] ` <CABUQxGwt8MjoYmx_8jF=A9qug24Dnx+rEX=5vDWP7+SGOERcMw@mail.gmail.com>
2025-07-01 19:28 ` Christian Eggers
2025-07-01 14:22 ` [RFC PATCH BlueZ 4/4] mesh: initial support for proxy configuration messages via GATT Christian Eggers
2025-07-01 15:09 ` [RFC PATCH BlueZ 0/4] Initial support for GATT Proxy Service Christian Eggers
2025-07-01 16:07 ` Luiz Augusto von Dentz
2025-07-01 16:38 ` Christian Eggers
2025-07-01 19:11 ` Luiz Augusto von Dentz
2025-07-01 19:17 ` Brian Gix
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 0/7] " Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 1/7] mesh: acceptor: increase interval for unprovisioned device beacon Christian Eggers
2025-07-11 18:16 ` Initial support for GATT Proxy Service bluez.test.bot
2025-07-15 15:56 ` bluez.test.bot
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 2/7] mesh: add support for provisioning via GATT Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 3/7] mesh: add GATT proxy service Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 4/7] mesh: gatt-proxy: support for proxy configuration messages and filtering Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 5/7] mesh: main.conf/cfgmod-server: allow to enable proxy support Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 6/7] mesh: net: proxy relaying Christian Eggers
2025-07-11 17:56 ` [RFC PATCH BlueZ v2 7/7] mesh: gatt-proxy: propagate beacons Christian Eggers
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox