Skip to content

Commit 5b148fd

Browse files
perf(gap): decode 16/32-bit UUIDs from int, skip bytes-slice alloc (#251)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent cad7021 commit 5b148fd

2 files changed

Lines changed: 37 additions & 20 deletions

File tree

src/bluetooth_data_tools/gap.pxd

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ cdef list _EMPTY_SERVICE_UUIDS
1010
cdef object from_bytes
1111
cdef object from_bytes_little
1212

13-
cdef object _cached_uint16_bytes_as_uuid
14-
cdef object _cached_uint32_bytes_as_uuid
13+
cdef object _cached_uint16_int_as_uuid
14+
cdef object _cached_uint32_int_as_uuid
1515
cdef object _cached_uint128_bytes_as_uuid
1616
cdef object _cached_parse_advertisement_data
1717

@@ -54,6 +54,7 @@ cpdef parse_advertisement_data(object data)
5454
end=cython.uint,
5555
i=cython.uint,
5656
tx_power_byte="unsigned char",
57+
uuid32_int=cython.uint,
5758
)
5859
cpdef _uncached_parse_advertisement_bytes(bytes gap_bytes)
5960

src/bluetooth_data_tools/gap.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,21 @@ def _uint128_bytes_as_uuid(uint128_bytes: bytes_) -> str:
123123

124124

125125
@lru_cache(maxsize=256)
126-
def _uint16_bytes_as_uuid(uuid16_bytes: bytes_) -> str:
127-
"""Convert a 16-bit UUID to a UUID str."""
128-
return f"0000{from_bytes_little(uuid16_bytes):04x}-{BLE_UUID}"
126+
def _uint16_int_as_uuid(uuid16_int: int) -> str:
127+
"""Convert a 16-bit UUID integer to a UUID str."""
128+
return f"0000{uuid16_int:04x}-{BLE_UUID}"
129129

130130

131-
_cached_uint16_bytes_as_uuid = _uint16_bytes_as_uuid
131+
_cached_uint16_int_as_uuid = _uint16_int_as_uuid
132132

133133

134134
@lru_cache(maxsize=256)
135-
def _uint32_bytes_as_uuid(uuid32_bytes: bytes_) -> str:
136-
"""Convert a 32-bit UUID to a UUID str."""
137-
return f"{from_bytes_little(uuid32_bytes):08x}-{BLE_UUID}"
135+
def _uint32_int_as_uuid(uuid32_int: int) -> str:
136+
"""Convert a 32-bit UUID integer to a UUID str."""
137+
return f"{uuid32_int:08x}-{BLE_UUID}"
138138

139139

140-
_cached_uint32_bytes_as_uuid = _uint32_bytes_as_uuid
140+
_cached_uint32_int_as_uuid = _uint32_int_as_uuid
141141

142142

143143
_EMPTY_MANUFACTURER_DATA: dict[int, bytes] = {}
@@ -230,24 +230,34 @@ def _uncached_parse_advertisement_bytes(
230230
}:
231231
if service_uuids is _EMPTY_SERVICE_UUIDS:
232232
service_uuids = []
233-
# Parse multiple 16-bit UUIDs (each is 2 bytes)
233+
# Parse multiple 16-bit UUIDs (each is 2 little-endian bytes).
234+
# Decode inline to an int and look up by int key to skip the
235+
# per-iteration bytes-slice allocation.
234236
for i in range(start, end, 2):
235237
if i + 2 <= end:
236238
service_uuids.append(
237-
_cached_uint16_bytes_as_uuid(gap_data[i : i + 2])
239+
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
238240
)
239241
elif gap_type_num in {
240242
TYPE_32BIT_SERVICE_UUID_COMPLETE,
241243
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE,
242244
}:
243245
if service_uuids is _EMPTY_SERVICE_UUIDS:
244246
service_uuids = []
245-
# Parse multiple 32-bit UUIDs (each is 4 bytes)
247+
# Parse multiple 32-bit UUIDs (each is 4 little-endian bytes).
246248
for i in range(start, end, 4):
247249
if i + 4 <= end:
248-
service_uuids.append(
249-
_cached_uint32_bytes_as_uuid(gap_data[i : i + 4])
250+
# Assemble via uint local: in Cython the shift-by-24 of an
251+
# unsigned char promotes to signed int and would yield a
252+
# negative value when bit 31 is set; assigning to a
253+
# cython.uint local recovers the unsigned 32-bit value.
254+
uuid32_int = (
255+
gap_data[i]
256+
| (gap_data[i + 1] << 8)
257+
| (gap_data[i + 2] << 16)
258+
| (gap_data[i + 3] << 24)
250259
)
260+
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
251261
elif gap_type_num in {
252262
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
253263
TYPE_128BIT_SERVICE_UUID_COMPLETE,
@@ -268,18 +278,24 @@ def _uncached_parse_advertisement_bytes(
268278
continue
269279
if service_data is _EMPTY_SERVICE_DATA:
270280
service_data = {}
271-
service_data[_cached_uint16_bytes_as_uuid(gap_data[start:splice_pos])] = (
272-
gap_data[splice_pos:end]
273-
)
281+
service_data[
282+
_cached_uint16_int_as_uuid(gap_data[start] | (gap_data[start + 1] << 8))
283+
] = gap_data[splice_pos:end]
274284
elif gap_type_num == TYPE_SERVICE_DATA_32BIT_UUID:
275285
splice_pos = start + 4
276286
if splice_pos > total_length or splice_pos > end:
277287
continue
278288
if service_data is _EMPTY_SERVICE_DATA:
279289
service_data = {}
280-
service_data[_cached_uint32_bytes_as_uuid(gap_data[start:splice_pos])] = (
281-
gap_data[splice_pos:end]
290+
uuid32_int = (
291+
gap_data[start]
292+
| (gap_data[start + 1] << 8)
293+
| (gap_data[start + 2] << 16)
294+
| (gap_data[start + 3] << 24)
282295
)
296+
service_data[_cached_uint32_int_as_uuid(uuid32_int)] = gap_data[
297+
splice_pos:end
298+
]
283299
elif gap_type_num == TYPE_SERVICE_DATA_128BIT_UUID:
284300
splice_pos = start + 16
285301
if splice_pos > total_length or splice_pos > end:

0 commit comments

Comments
 (0)