From 84fc8d885181768ddbd0c612442fa57e14b49979 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Wed, 28 Oct 2026 16:39:58 -0400 Subject: [PATCH] Bound HCI requests by their timeout instead of 10 reads sendRequest() ignored its timeout and gave up after reading 10 events. Its filter also passes LE meta events, so while the controller is scanning, advertising reports used up all 10 reads before the command's response arrived and the request failed with ETIMEDOUT. This made any HCI command unreliable during a scan, including the command that disables scanning. Wait for each event with poll() for the time remaining before the timeout, and keep reading until the matching response arrives or the timeout expires. --- .../BluetoothLinux/HCI/HCIFileDescriptor.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/BluetoothLinux/HCI/HCIFileDescriptor.swift b/Sources/BluetoothLinux/HCI/HCIFileDescriptor.swift index 32b7383..a94ac9a 100644 --- a/Sources/BluetoothLinux/HCI/HCIFileDescriptor.swift +++ b/Sources/BluetoothLinux/HCI/HCIFileDescriptor.swift @@ -65,13 +65,19 @@ internal extension Socket { // send command try await sendCommand(command, parameter: commandParameterData) - // retrieve data... - var attempts = 10 - while attempts > 0 { + // retrieve data until the timeout expires + // (a fixed number of reads is not enough while scanning, since LE advertising + // reports also pass the filter and can arrive before the command's response) + let deadline = ContinuousClock.now + .milliseconds(Int(timeout.rawValue)) + while ContinuousClock.now < deadline { + + // wait for the next event no longer than the remaining time + let remaining = (deadline - ContinuousClock.now).components + let milliseconds = Int(remaining.seconds) * 1000 + Int(remaining.attoseconds / 1_000_000_000_000_000) + guard try fileDescriptor.poll(for: [.read], timeout: max(milliseconds, 0)).contains(.read) else { + break + } - // decrement attempts - attempts -= 1 - // attempt to read let eventBuffer = try await read(HCIEventHeader.maximumSize) assert(eventBuffer.isEmpty == false, "No HCI event read")