Skip to content

Commit 3985a70

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 a64a854 commit 3985a70

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
@@ -53,6 +53,7 @@ cpdef parse_advertisement_data(object data)
5353
offset=cython.uint,
5454
start=cython.uint,
5555
end=cython.uint,
56+
safe_end=cython.uint,
5657
i=cython.uint,
5758
tx_power_byte="unsigned char",
5859
uuid32_int=cython.uint,

src/bluetooth_data_tools/gap.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -251,47 +251,50 @@ def _uncached_parse_advertisement_bytes(
251251
if service_uuids is _EMPTY_SERVICE_UUIDS:
252252
service_uuids = []
253253
# Parse multiple 16-bit UUIDs (each is 2 little-endian bytes).
254-
# Decode inline to an int and look up by int key to skip the
255-
# per-iteration bytes-slice allocation.
256-
for i in range(start, end, 2):
257-
if i + 2 <= end:
258-
service_uuids.append(
259-
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
260-
)
254+
# Truncate to a multiple-of-2 endpoint so the loop body has no
255+
# per-iteration bounds branch; any odd tail is dropped.
256+
safe_end = start + ((end - start) & ~1)
257+
for i in range(start, safe_end, 2):
258+
service_uuids.append(
259+
_cached_uint16_int_as_uuid(gap_data[i] | (gap_data[i + 1] << 8))
260+
)
261261
elif gap_type_num in {
262262
TYPE_32BIT_SERVICE_UUID_COMPLETE,
263263
TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE,
264264
}:
265265
if service_uuids is _EMPTY_SERVICE_UUIDS:
266266
service_uuids = []
267267
# Parse multiple 32-bit UUIDs (each is 4 little-endian bytes).
268-
for i in range(start, end, 4):
269-
if i + 4 <= end:
270-
# Assemble via uint local: in Cython the shift-by-24 of an
271-
# unsigned char promotes to signed int and would yield a
272-
# negative value when bit 31 is set; assigning to a
273-
# cython.uint local recovers the unsigned 32-bit value.
274-
uuid32_int = (
275-
gap_data[i]
276-
| (gap_data[i + 1] << 8)
277-
| (gap_data[i + 2] << 16)
278-
| (gap_data[i + 3] << 24)
279-
)
280-
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
268+
# Truncate to a multiple-of-4 endpoint so the loop body has no
269+
# per-iteration bounds branch; any 1-3 byte tail is dropped.
270+
safe_end = start + ((end - start) & ~3)
271+
for i in range(start, safe_end, 4):
272+
# Assemble via uint local: in Cython the shift-by-24 of an
273+
# unsigned char promotes to signed int and would yield a
274+
# negative value when bit 31 is set; assigning to a
275+
# cython.uint local recovers the unsigned 32-bit value.
276+
uuid32_int = (
277+
gap_data[i]
278+
| (gap_data[i + 1] << 8)
279+
| (gap_data[i + 2] << 16)
280+
| (gap_data[i + 3] << 24)
281+
)
282+
service_uuids.append(_cached_uint32_int_as_uuid(uuid32_int))
281283
elif gap_type_num in {
282284
TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
283285
TYPE_128BIT_SERVICE_UUID_COMPLETE,
284286
}:
285287
if service_uuids is _EMPTY_SERVICE_UUIDS:
286288
service_uuids = []
287-
# Parse multiple 128-bit UUIDs (each is 16 bytes). The AD length
288-
# may not be a clean multiple of 16 for malformed input — skip
289-
# any trailing remainder rather than emitting a truncated UUID.
290-
for i in range(start, end, 16):
291-
if i + 16 <= end:
292-
service_uuids.append(
293-
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
294-
)
289+
# Parse multiple 128-bit UUIDs (each is 16 bytes). Truncate to a
290+
# multiple-of-16 endpoint so the loop body has no per-iteration
291+
# bounds branch; any trailing remainder is dropped rather than
292+
# emitting a truncated UUID.
293+
safe_end = start + ((end - start) & ~15)
294+
for i in range(start, safe_end, 16):
295+
service_uuids.append(
296+
_cached_uint128_bytes_as_uuid(gap_data[i : i + 16])
297+
)
295298
elif gap_type_num == TYPE_SERVICE_DATA:
296299
splice_pos = start + 2
297300
if splice_pos > end:

0 commit comments

Comments
 (0)