@@ -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