Skip to content

Commit 2829b82

Browse files
bluetoothbotclaude
andcommitted
perf(gap): hoist per-iteration UUID length bounds check
For 16/32/128-bit Service UUID AD lists, the parse loop ran a `i + N <= end` check on every iteration to discard any malformed short tail (per Bluetooth-Devices#226). Compute the multiple-of-stride endpoint once before the loop instead, so the loop body has no per-iteration bounds branch. Malformed-tail behavior is preserved (the bits `(end - start) & ~(N-1)` truncate any 1..N-1 byte remainder). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b8f4b62 commit 2829b82

2 files changed

Lines changed: 32 additions & 28 deletions

File tree

src/bluetooth_data_tools/gap.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ cpdef parse_advertisement_data(object data)
5252
offset=cython.uint,
5353
start=cython.uint,
5454
end=cython.uint,
55+
safe_end=cython.uint,
5556
i=cython.uint,
5657
tx_power_byte="unsigned char",
5758
uuid32_int=cython.uint,

src/bluetooth_data_tools/gap.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -231,47 +231,50 @@ def _uncached_parse_advertisement_bytes(
231231
if service_uuids is _EMPTY_SERVICE_UUIDS:
232232
service_uuids = []
233233
# 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.
236-
for i in range(start, end, 2):
237-
if i + 2 <= end:
238-
service_uuids.append(
239-
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
240-
)
234+
# Truncate to a multiple-of-2 endpoint so the loop body has no
235+
# per-iteration bounds branch; any odd tail is dropped.
236+
safe_end = start + ((end - start) & ~1)
237+
for i in range(start, safe_end, 2):
238+
service_uuids.append(
239+
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
240+
)
241241
elif gap_type_num in {
242242
TYPE_32BIT_SERVICE_UUID_COMPLETE,
243243
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE,
244244
}:
245245
if service_uuids is _EMPTY_SERVICE_UUIDS:
246246
service_uuids = []
247247
# Parse multiple 32-bit UUIDs (each is 4 little-endian bytes).
248-
for i in range(start, end, 4):
249-
if i + 4 <= end:
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)
259-
)
260-
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
248+
# Truncate to a multiple-of-4 endpoint so the loop body has no
249+
# per-iteration bounds branch; any 1-3 byte tail is dropped.
250+
safe_end = start + ((end - start) & ~3)
251+
for i in range(start, safe_end, 4):
252+
# Assemble via uint local: in Cython the shift-by-24 of an
253+
# unsigned char promotes to signed int and would yield a
254+
# negative value when bit 31 is set; assigning to a
255+
# cython.uint local recovers the unsigned 32-bit value.
256+
uuid32_int = (
257+
gap_data[i]
258+
| (gap_data[i + 1] << 8)
259+
| (gap_data[i + 2] << 16)
260+
| (gap_data[i + 3] << 24)
261+
)
262+
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
261263
elif gap_type_num in {
262264
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
263265
TYPE_128BIT_SERVICE_UUID_COMPLETE,
264266
}:
265267
if service_uuids is _EMPTY_SERVICE_UUIDS:
266268
service_uuids = []
267-
# Parse multiple 128-bit UUIDs (each is 16 bytes). The AD length
268-
# may not be a clean multiple of 16 for malformed input — skip
269-
# any trailing remainder rather than emitting a truncated UUID.
270-
for i in range(start, end, 16):
271-
if i + 16 <= end:
272-
service_uuids.append(
273-
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
274-
)
269+
# Parse multiple 128-bit UUIDs (each is 16 bytes). Truncate to a
270+
# multiple-of-16 endpoint so the loop body has no per-iteration
271+
# bounds branch; any trailing remainder is dropped rather than
272+
# emitting a truncated UUID.
273+
safe_end = start + ((end - start) & ~15)
274+
for i in range(start, safe_end, 16):
275+
service_uuids.append(
276+
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
277+
)
275278
elif gap_type_num == TYPE_SERVICE_DATA:
276279
splice_pos = start + 2
277280
if splice_pos > total_length or splice_pos > end:

0 commit comments

Comments
 (0)