Skip to content

Commit 879723a

Browse files
committed
feat: use "raw" query parameter to drop BPP protocol when security is disabled for backward compatibility
1 parent 9d5261b commit 879723a

4 files changed

Lines changed: 244 additions & 84 deletions

File tree

src/arduino/app_peripherals/camera/websocket_camera.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ class WebSocketCamera(BaseCamera):
3838
- BMP
3939
- TIFF
4040
41-
Communication with the WebSocket server is supported in three security modes:
42-
- Security disabled (empty secret)
43-
- Authenticated (secret + encrypt=False) - HMAC-SHA256
44-
- Authenticated + Encrypted (secret + encrypt=True) - ChaCha20-Poly1305
41+
Communication uses the BPP (Binary Peripheral Protocol) in three security modes:
42+
- Security disabled (secret=None) - BPP with no authentication
43+
- Authenticated (secret + encrypt=False) - BPP with HMAC-SHA256
44+
- Authenticated + Encrypted (secret + encrypt=True) - BPP with ChaCha20-Poly1305
4545
46-
In security-disabled mode, clients must send raw image bytes directly.
47-
In other security modes, data must be serialized using the BPP protocol.
46+
By default, all modes use BPP framing. When security is disabled (secret=None),
47+
clients can opt out of BPP by connecting with the "raw=true" URL query parameter,
48+
allowing them to send raw image bytes directly without BPP wrapping. This parameter
49+
is silently ignored when security is enabled.
4850
4951
When connecting, clients can specify a "client_name" parameter in the URL query string
5052
to identify themselves. This name will be sanitized to allow only alphanumeric chars,
@@ -93,9 +95,10 @@ def __init__(
9395
logger.warning("Encryption is redundant over TLS connections, disabling encryption.")
9496
encrypt = False
9597

96-
self.codec = BPPCodec(secret, encrypt) if secret is not None else None
98+
self.codec = BPPCodec(secret or "", encrypt)
9799
self.secret = secret
98100
self.encrypt = encrypt
101+
self._client_raw = False
99102
self.logger = logger
100103
self.name = self.__class__.__name__
101104

@@ -210,8 +213,9 @@ async def _start_server(self, future: Future) -> None:
210213

211214
async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
212215
"""Handle a connected WebSocket client. Only one client allowed at a time."""
213-
# Extract and sanitize client_name from URL parameters
216+
# Extract URL parameters: client_name and raw mode opt-in
214217
client_name = "Unknown"
218+
client_raw = False
215219
if conn.request:
216220
try:
217221
parsed_path = urlparse(conn.request.path)
@@ -222,10 +226,17 @@ async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
222226
sanitized = "".join(c for c in raw_name if c.isalnum() or c in " -_")[:64]
223227
if sanitized:
224228
client_name = sanitized
229+
# Allow raw (no BPP) mode only when security is disabled
230+
if "raw" in query_params and query_params["raw"][0].lower() == "true":
231+
if self.secret is None:
232+
client_raw = True
233+
else:
234+
self.logger.warning("Client requested raw mode but security is enabled, ignoring.")
225235
except Exception as e:
226-
self.logger.debug(f"Failed to extract client_name from URL parameters: {e}")
236+
self.logger.debug(f"Failed to extract URL parameters: {e}")
227237
finally:
228238
self.name = client_name
239+
self._client_raw = client_raw
229240

230241
client_addr = f"{conn.remote_address[0]}:{conn.remote_address[1]}"
231242

@@ -287,6 +298,7 @@ async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
287298
async with self._client_lock:
288299
if self._client == conn:
289300
self._client = None
301+
self._client_raw = False
290302
self._set_status("disconnected", {"client_address": client_addr, "client_name": client_name})
291303
self.logger.debug(f"Client removed: {client_addr}")
292304

@@ -299,7 +311,7 @@ def _parse_message(self, message: websockets.Data) -> np.ndarray | None:
299311
self.logger.warning(f"Failed to decode string message using base64: {e}")
300312
return None
301313

302-
if self.codec:
314+
if not self._client_raw:
303315
decoded = self.codec.decode(message)
304316
if decoded is None:
305317
self.logger.warning("Failed to decode message")
@@ -364,7 +376,7 @@ async def _send_to_client(self, message: bytes | str, client: websockets.ServerC
364376
if isinstance(message, str):
365377
message = message.encode()
366378

367-
data = self.codec.encode(message) if self.codec else message
379+
data = message if self._client_raw else self.codec.encode(message)
368380

369381
# Keep a ref to current client to avoid locking
370382
client = client or self._client

src/arduino/app_peripherals/microphone/websocket_microphone.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ class WebSocketMicrophone(BaseMicrophone):
3232
The client must encode the audio data in PCM format and must respect the
3333
sample rate, channels, format, and chunk size specified during initialization.
3434
35-
Communication with the WebSocket server is supported in three security modes:
36-
- Security disabled (empty secret)
37-
- Authenticated (secret + encrypt=False) - HMAC-SHA256
38-
- Authenticated + Encrypted (secret + encrypt=True) - ChaCha20-Poly1305
35+
Communication uses the BPP (Binary Peripheral Protocol) in three security modes:
36+
- Security disabled (secret=None) - BPP with no authentication
37+
- Authenticated (secret + encrypt=False) - BPP with HMAC-SHA256
38+
- Authenticated + Encrypted (secret + encrypt=True) - BPP with ChaCha20-Poly1305
3939
40-
In security-disabled mode, clients must send raw PCM bytes directly.
41-
In other security modes, data must be serialized using the BPP protocol.
40+
By default, all modes use BPP framing. When security is disabled (secret=None),
41+
clients can opt out of BPP by connecting with the "raw=true" URL query parameter,
42+
allowing them to send raw PCM bytes directly without BPP wrapping. This parameter
43+
is silently ignored when security is enabled.
4244
4345
When connecting, clients can specify a "client_name" parameter in the URL query string
4446
to identify themselves. This name will be sanitized to allow only alphanumeric chars,
@@ -100,9 +102,10 @@ def __init__(
100102
logger.warning("Encryption is redundant over TLS connections, disabling encryption.")
101103
encrypt = False
102104

103-
self.codec = BPPCodec(secret, encrypt) if secret is not None else None
105+
self.codec = BPPCodec(secret or "", encrypt)
104106
self.secret = secret
105107
self.encrypt = encrypt
108+
self._client_raw = False
106109
self.logger = logger
107110
self.name = self.__class__.__name__
108111

@@ -217,8 +220,9 @@ async def _start_server(self, future: Future) -> None:
217220

218221
async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
219222
"""Handle a connected WebSocket client. Only one client allowed at a time."""
220-
# Extract and sanitize client_name from URL parameters
223+
# Extract URL parameters: client_name and raw mode opt-in
221224
client_name = "Unknown"
225+
client_raw = False
222226
if conn.request:
223227
try:
224228
parsed_path = urlparse(conn.request.path)
@@ -229,10 +233,17 @@ async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
229233
sanitized = "".join(c for c in raw_name if c.isalnum() or c in " -_")[:64]
230234
if sanitized:
231235
client_name = sanitized
236+
# Allow raw (no BPP) mode only when security is disabled
237+
if "raw" in query_params and query_params["raw"][0].lower() == "true":
238+
if self.secret is None:
239+
client_raw = True
240+
else:
241+
self.logger.warning("Client requested raw mode but security is enabled, ignoring.")
232242
except Exception as e:
233-
self.logger.debug(f"Failed to extract client_name from URL parameters: {e}")
243+
self.logger.debug(f"Failed to extract URL parameters: {e}")
234244
finally:
235245
self.name = client_name
246+
self._client_raw = client_raw
236247

237248
client_addr = f"{conn.remote_address[0]}:{conn.remote_address[1]}"
238249

@@ -301,6 +312,7 @@ async def _ws_handler(self, conn: websockets.ServerConnection) -> None:
301312
async with self._client_lock:
302313
if self._client == conn:
303314
self._client = None
315+
self._client_raw = False
304316
self._set_status("disconnected", {"client_address": client_addr, "client_name": client_name})
305317
self.logger.debug(f"Client removed: {client_addr}")
306318

@@ -313,7 +325,7 @@ def _parse_message(self, message: str | bytes) -> np.ndarray | None:
313325
self.logger.warning(f"Failed to decode string message using base64: {e}")
314326
return None
315327

316-
if self.codec:
328+
if not self._client_raw:
317329
decoded = self.codec.decode(message)
318330
if decoded is None:
319331
self.logger.warning("Failed to decode message")
@@ -376,7 +388,7 @@ async def _send_to_client(self, message: bytes | str, client: websockets.ServerC
376388
if isinstance(message, str):
377389
message = message.encode()
378390

379-
data = self.codec.encode(message) if self.codec else message
391+
data = message if self._client_raw else self.codec.encode(message)
380392

381393
# Keep a ref to current client to avoid locking
382394
client = client or self._client

0 commit comments

Comments
 (0)