Skip to content

Commit 14d34e8

Browse files
committed
aioble/l2cap: Convert OSError EINVAL to L2CAPDisconnectedError
When the BLE peer disconnects during an L2CAP transfer, ble.l2cap_send(), ble.l2cap_recvinto(), and ble.l2cap_disconnect() raise OSError with errno EINVAL because the connection handle or CID is no longer valid. Callers catch L2CAPDisconnectedError (now a DeviceDisconnectedError subclass) for clean disconnect handling, but the raw OSError was not being converted — it fell through to generic exception handlers. Wrap all three call sites: - send(): catch OSError EINVAL, raise L2CAPDisconnectedError - recvinto(): catch OSError EINVAL, raise L2CAPDisconnectedError - disconnect(): catch OSError EINVAL, set _cid = None, return cleanly
1 parent a51c737 commit 14d34e8

File tree

1 file changed

+29
-9
lines changed
  • micropython/bluetooth/aioble/aioble

1 file changed

+29
-9
lines changed

micropython/bluetooth/aioble/aioble/l2cap.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from micropython import const
55

66
import asyncio
7+
import errno
78

89
from .core import ble, log_error, register_irq_handler
910
from .device import DeviceConnection, DeviceDisconnectedError
@@ -118,10 +119,17 @@ async def recvinto(self, buf, timeout_ms=None):
118119
self._assert_connected()
119120

120121
# Extract up to len(buf) bytes from the channel buffer.
121-
n = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, buf)
122+
try:
123+
n = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, buf)
122124

123-
# Check if there's still remaining data in the channel buffers.
124-
self._data_ready = ble.l2cap_recvinto(self._connection._conn_handle, self._cid, None) > 0
125+
# Check if there's still remaining data in the channel buffers.
126+
self._data_ready = (
127+
ble.l2cap_recvinto(self._connection._conn_handle, self._cid, None) > 0
128+
)
129+
except OSError as e:
130+
if e.errno == errno.EINVAL:
131+
raise L2CAPDisconnectedError()
132+
raise
125133

126134
return n
127135

@@ -141,11 +149,16 @@ async def send(self, buf, timeout_ms=None, chunk_size=None):
141149
await self.flush(timeout_ms)
142150
# l2cap_send returns True if you can send immediately.
143151
self._assert_connected()
144-
self._stalled = not ble.l2cap_send(
145-
self._connection._conn_handle,
146-
self._cid,
147-
mv[offset : offset + chunk_size],
148-
)
152+
try:
153+
self._stalled = not ble.l2cap_send(
154+
self._connection._conn_handle,
155+
self._cid,
156+
mv[offset : offset + chunk_size],
157+
)
158+
except OSError as e:
159+
if e.errno == errno.EINVAL:
160+
raise L2CAPDisconnectedError()
161+
raise
149162
offset += chunk_size
150163

151164
async def flush(self, timeout_ms=None):
@@ -161,7 +174,14 @@ async def disconnect(self, timeout_ms=1000):
161174
return
162175

163176
# Wait for the cid to be cleared by the disconnect IRQ.
164-
ble.l2cap_disconnect(self._connection._conn_handle, self._cid)
177+
try:
178+
ble.l2cap_disconnect(self._connection._conn_handle, self._cid)
179+
except OSError as e:
180+
if e.errno == errno.EINVAL:
181+
# Channel already closed by peer — treat as disconnected.
182+
self._cid = None
183+
return
184+
raise
165185
await self.disconnected(timeout_ms)
166186

167187
async def disconnected(self, timeout_ms=1000):

0 commit comments

Comments
 (0)