Summary
In raw HCI mode, one noble LE Create Connection write can result in two controller connection attempts when the kernel L2CAP workaround reaches the controller but ultimately fails.
BluetoothHciSocket::Write() calls kernelConnectWorkArounds() and suppresses the original HCI command only when that function returns true:
if (this->_mode == HCI_CHANNEL_RAW && this->kernelConnectWorkArounds(buffer.Data(), buffer.Length())) {
return;
}
write(this->_socket, buffer.Data(), buffer.Length());
For a recognized LE connection command, kernelConnectWorkArounds() opens and synchronously connects a kernel L2CAP socket. If that connect later fails, it erases the socket and returns false:
l2socket_ptr->connect();
if (!l2socket_ptr->isConnected()) {
this->_l2sockets_connecting.erase(bdaddr_dst);
return false;
}
Returning false makes Write() send the original raw LE Create Connection command even when the L2CAP connect already caused the kernel/controller to perform a complete first attempt.
Capture evidence
The btmon capture attached to matter-js/matterjs-server#929 contains this exact shape:
- noble requests its pre-connect HCI reset
- an unlabeled/kernel
LE Create Connection uses the kernel-side 30–50 ms connection parameters
- that connection completes on handle 72 and then fails with
0x3e
- a process-attributed
MainThread LE Create Connection is sent with noble's 7.5–22.5 ms defaults
- the second connection completes on handle 71
- an application retry has already requested another reset, which is transmitted immediately after handle 71 becomes live
The first attempt also produces the expected kernel bookkeeping commands (LE Add Device To Accept List and LE Read Remote Used Features). Those commands are therefore not sufficient evidence that an unrelated BlueZ auto-connect stole the connection; they can be downstream of noble's L2CAP workaround itself.
Impact
- noble's JavaScript HCI layer observes events from both attempts for one logical connect
- a success-then-
0x3e sequence can consume/alter the JavaScript connection queue
- the application can retry while the raw fallback attempt is still pending
- noble's reset-before-first-connect behavior can then queue a reset that lands after the fallback connection succeeds
This likely explains the core capture behind stoprocent/noble#97, noble#98, and noble#102 more directly than an independent BlueZ connection race.
Proposed direction
The workaround needs to distinguish at least three outcomes:
- command not handled — send the original raw HCI command
- L2CAP setup failed before any controller attempt — raw fallback may be appropriate
- kernel/controller attempt started and later failed — do not start a second raw attempt
Useful first steps would be:
- expose or log the L2CAP failure stage and
errno
- avoid falling through once
connect() has initiated a controller attempt
- add a regression harness that asserts one logical connect produces only one
LE Create Connection
- validate on Raspberry Pi and a desktop Linux adapter because the workaround is historically load-bearing there
References
Summary
In raw HCI mode, one noble
LE Create Connectionwrite can result in two controller connection attempts when the kernel L2CAP workaround reaches the controller but ultimately fails.BluetoothHciSocket::Write()callskernelConnectWorkArounds()and suppresses the original HCI command only when that function returnstrue:For a recognized LE connection command,
kernelConnectWorkArounds()opens and synchronously connects a kernel L2CAP socket. If that connect later fails, it erases the socket and returnsfalse:Returning
falsemakesWrite()send the original rawLE Create Connectioncommand even when the L2CAP connect already caused the kernel/controller to perform a complete first attempt.Capture evidence
The
btmoncapture attached to matter-js/matterjs-server#929 contains this exact shape:LE Create Connectionuses the kernel-side 30–50 ms connection parameters0x3eMainThreadLE Create Connectionis sent with noble's 7.5–22.5 ms defaultsThe first attempt also produces the expected kernel bookkeeping commands (
LE Add Device To Accept ListandLE Read Remote Used Features). Those commands are therefore not sufficient evidence that an unrelated BlueZ auto-connect stole the connection; they can be downstream of noble's L2CAP workaround itself.Impact
0x3esequence can consume/alter the JavaScript connection queueThis likely explains the core capture behind stoprocent/noble#97, noble#98, and noble#102 more directly than an independent BlueZ connection race.
Proposed direction
The workaround needs to distinguish at least three outcomes:
Useful first steps would be:
errnoconnect()has initiated a controller attemptLE Create ConnectionReferences