Skip to content

Commit 726e733

Browse files
committed
fix: parse multiple 16-bit and 32-bit Service UUIDs in BLE advertisement data
1 parent 0944dec commit 726e733

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

src/bluetooth_data_tools/gap.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ cdef cython.uint TYPE_COMPLETE_LOCAL_NAME
3333
cdef cython.uint TYPE_MANUFACTURER_SPECIFIC_DATA
3434
cdef cython.uint TYPE_16BIT_SERVICE_UUID_COMPLETE
3535
cdef cython.uint TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE
36+
cdef cython.uint TYPE_32BIT_SERVICE_UUID_COMPLETE
37+
cdef cython.uint TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE
3638
cdef cython.uint TYPE_128BIT_SERVICE_UUID_COMPLETE
3739
cdef cython.uint TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE
3840
cdef cython.uint TYPE_SERVICE_DATA

src/bluetooth_data_tools/gap.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ class BLEGAPType(IntEnum):
8686
TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE = (
8787
BLEGAPType.TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE.value
8888
)
89+
TYPE_32BIT_SERVICE_UUID_COMPLETE = BLEGAPType.TYPE_32BIT_SERVICE_UUID_COMPLETE.value
90+
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE = (
91+
BLEGAPType.TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE.value
92+
)
8993
TYPE_128BIT_SERVICE_UUID_COMPLETE = BLEGAPType.TYPE_128BIT_SERVICE_UUID_COMPLETE.value
9094
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE = (
9195
BLEGAPType.TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE.value
@@ -223,7 +227,24 @@ def _uncached_parse_advertisement_bytes(
223227
}:
224228
if service_uuids is _EMPTY_SERVICE_UUIDS:
225229
service_uuids = []
226-
service_uuids.append(_cached_uint16_bytes_as_uuid(gap_data[start:end]))
230+
# Parse multiple 16-bit UUIDs (each is 2 bytes)
231+
for i in range(start, end, 2):
232+
if i + 2 <= end:
233+
service_uuids.append(
234+
_cached_uint16_bytes_as_uuid(gap_data[i : i + 2])
235+
)
236+
elif gap_type_num in {
237+
TYPE_32BIT_SERVICE_UUID_COMPLETE,
238+
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE,
239+
}:
240+
if service_uuids is _EMPTY_SERVICE_UUIDS:
241+
service_uuids = []
242+
# Parse multiple 32-bit UUIDs (each is 4 bytes)
243+
for i in range(start, end, 4):
244+
if i + 4 <= end:
245+
service_uuids.append(
246+
_cached_uint32_bytes_as_uuid(gap_data[i : i + 4])
247+
)
227248
elif gap_type_num in {
228249
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
229250
TYPE_128BIT_SERVICE_UUID_COMPLETE,

tests/test_gap.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,54 @@ def test_parse_adv_data():
311311
adv = parse_advertisement_data(data)
312312

313313
assert adv.local_name == "M2_B1A8S10P"
314-
assert adv.service_uuids == ["0000fee7ffe0-0000-1000-8000-00805f9b34fb"]
314+
assert adv.service_uuids == [
315+
"0000ffe0-0000-1000-8000-00805f9b34fb",
316+
"0000fee7-0000-1000-8000-00805f9b34fb",
317+
]
315318
assert adv.service_data == {}
316319
assert adv.manufacturer_data == {2917: b"\x88\xa0\xc8G\x8c\xea\xd1\xc1"}
317320
assert adv.tx_power is None
318321

319322

323+
def test_parse_multiple_16bit_uuids():
324+
"""Test parsing advertisement data with 3 16-bit service UUIDs."""
325+
# Build advertisement data with 3 16-bit UUIDs
326+
# Length: 7 (1 byte type + 6 bytes for 3 UUIDs)
327+
# Type: 0x02 (16-bit service UUID more available)
328+
# UUIDs in little-endian: 0x1234 -> 3412, 0x5678 -> 7856, 0x9ABC -> BC9A
329+
data = [bytes.fromhex("070234127856BC9A")]
330+
adv = parse_advertisement_data(data)
331+
332+
assert adv.service_uuids == [
333+
"00001234-0000-1000-8000-00805f9b34fb",
334+
"00005678-0000-1000-8000-00805f9b34fb",
335+
"00009abc-0000-1000-8000-00805f9b34fb",
336+
]
337+
assert adv.local_name is None
338+
assert adv.service_data == {}
339+
assert adv.manufacturer_data == {}
340+
assert adv.tx_power is None
341+
342+
343+
def test_parse_multiple_32bit_uuids():
344+
"""Test parsing advertisement data with 2 32-bit service UUIDs."""
345+
# Build advertisement data with 2 32-bit UUIDs
346+
# Length: 9 (1 byte type + 8 bytes for 2 UUIDs)
347+
# Type: 0x04 (32-bit service UUID more available)
348+
# UUIDs in little-endian: 0x12345678 -> 78563412, 0x9ABCDEF0 -> F0DEBC9A
349+
data = [bytes.fromhex("090478563412F0DEBC9A")]
350+
adv = parse_advertisement_data(data)
351+
352+
assert adv.service_uuids == [
353+
"12345678-0000-1000-8000-00805f9b34fb",
354+
"9abcdef0-0000-1000-8000-00805f9b34fb",
355+
]
356+
assert adv.local_name is None
357+
assert adv.service_data == {}
358+
assert adv.manufacturer_data == {}
359+
assert adv.tx_power is None
360+
361+
320362
def test_parse_advertisement_data_zero_padded_scan_included():
321363
data = [
322364
b"\x02\x01\x06\t\xffY\x00\xfe\x024\x9e\xa6\xba\x00\x00\x00\x00\x00\x00\x00\x00"

0 commit comments

Comments
 (0)