diff --git a/apps/link/call.py b/apps/link/call.py index 63afb6e15..cf7c5859c 100644 --- a/apps/link/call.py +++ b/apps/link/call.py @@ -79,7 +79,9 @@ def _relative_time(iso: str | None) -> str: @app.command() def pair( - device_label: str = typer.Option(..., "--device-label", help="Label for the phone being paired"), + device_label: str = typer.Option( + ..., "--device-label", help="Label for the phone being paired" + ), convey_host: str = typer.Option( "", "--convey-host", @@ -133,7 +135,9 @@ def pair( # the pair route fired but we missed the device (rare). nonce_entry = _nonces().peek(value) if nonce_entry and nonce_entry.used: - typer.echo("Pair request completed; device should appear in `sol call link list`.") + typer.echo( + "Pair request completed; device should appear in `sol call link list`." + ) raise typer.Exit(0) typer.echo("Timed out. Pair code expired.") raise typer.Exit(2) @@ -158,7 +162,9 @@ def list_devices() -> None: @app.command() def unpair( - target: str = typer.Argument(..., help="Device label or fingerprint (sha256:)"), + target: str = typer.Argument( + ..., help="Device label or fingerprint (sha256:)" + ), ) -> None: """Revoke a paired device. Next reconnect from that device fails at TLS handshake.""" authorized = _authorized() diff --git a/apps/link/routes.py b/apps/link/routes.py index bfd56995d..ff3a3c888 100644 --- a/apps/link/routes.py +++ b/apps/link/routes.py @@ -34,7 +34,12 @@ from cryptography.hazmat.primitives import serialization from flask import Blueprint, jsonify, request from think.link.auth import AuthorizedClients, ClientEntry -from think.link.ca import generate_nonce, load_or_generate_ca, mint_attestation, sign_csr +from think.link.ca import ( + generate_nonce, + load_or_generate_ca, + mint_attestation, + sign_csr, +) from think.link.nonces import NonceStore from think.link.paths import ( LinkState, diff --git a/think/link/auth.py b/think/link/auth.py index 3ec1d6c37..55ef055c7 100644 --- a/think/link/auth.py +++ b/think/link/auth.py @@ -111,7 +111,9 @@ class AuthorizedClients: self._entries = current return True - def touch_last_seen(self, fingerprint: str, *, now: dt.datetime | None = None) -> bool: + def touch_last_seen( + self, fingerprint: str, *, now: dt.datetime | None = None + ) -> bool: """Update last_seen_at for a paired device. Returns False if not paired.""" ts = (now or dt.datetime.now(dt.UTC)).strftime("%Y-%m-%dT%H:%M:%SZ") with self._lock: diff --git a/think/link/ca.py b/think/link/ca.py index dbe1b98b2..c808d79c7 100644 --- a/think/link/ca.py +++ b/think/link/ca.py @@ -136,7 +136,9 @@ def sign_csr( now = dt.datetime.now(dt.UTC) cert = ( x509.CertificateBuilder() - .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, device_label)])) + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, device_label)]) + ) .issuer_name(ca.cert.subject) .public_key(pub) .serial_number(x509.random_serial_number()) diff --git a/think/link/framing.py b/think/link/framing.py index 38ec86501..f1052ecae 100644 --- a/think/link/framing.py +++ b/think/link/framing.py @@ -145,7 +145,9 @@ def build_reset(stream_id: int, reason: int = RESET_UNSPECIFIED) -> Frame: def build_window(stream_id: int, credit: int) -> Frame: if not 0 <= credit <= 0xFFFFFFFF: raise ProtocolError(f"window credit out of range: {credit}") - return Frame(stream_id=stream_id, flags=FLAG_WINDOW, payload=credit.to_bytes(4, "big")) + return Frame( + stream_id=stream_id, flags=FLAG_WINDOW, payload=credit.to_bytes(4, "big") + ) def validate_flags(flags: int) -> None: diff --git a/think/link/mux.py b/think/link/mux.py index 20435befc..7cdf197a7 100644 --- a/think/link/mux.py +++ b/think/link/mux.py @@ -157,7 +157,9 @@ class Multiplexer: await self._emit(build_reset(frame.stream_id, RESET_PROTOCOL_ERROR)) return if len(self._streams) >= MAX_CONCURRENT_STREAMS: - await self._emit(build_reset(frame.stream_id, RESET_STREAM_LIMIT_EXCEEDED)) + await self._emit( + build_reset(frame.stream_id, RESET_STREAM_LIMIT_EXCEEDED) + ) return state = self._open_stream(frame.stream_id) if frame.payload: diff --git a/think/link/relay_client.py b/think/link/relay_client.py index 356c5bfca..f11d6e2d1 100644 --- a/think/link/relay_client.py +++ b/think/link/relay_client.py @@ -156,10 +156,7 @@ class RelayClient: task.cancel() if self._tunnels: await asyncio.gather( - *( - asyncio.shield(t) - for t in self._tunnels.values() - ), + *(asyncio.shield(t) for t in self._tunnels.values()), return_exceptions=True, ) self._tunnels.clear() @@ -193,7 +190,9 @@ class RelayClient: name=f"link-tunnel-{tunnel_id}", ) self._tunnels[tunnel_id] = task - task.add_done_callback(lambda _t, tid=tunnel_id: self._tunnels.pop(tid, None)) + task.add_done_callback( + lambda _t, tid=tunnel_id: self._tunnels.pop(tid, None) + ) async def _handle_tunnel(self, tunnel_id: str) -> None: assert self._account_token is not None @@ -207,7 +206,9 @@ class RelayClient: ) as ws: await self._pump_tunnel(ws, tunnel_id) except ConnectionClosed as exc: - log.info("tunnel %s closed: code=%s reason=%s", tunnel_id, exc.code, exc.reason) + log.info( + "tunnel %s closed: code=%s reason=%s", tunnel_id, exc.code, exc.reason + ) except TlsError as exc: log.info("tunnel %s TLS rejected: %s", tunnel_id, exc) except Exception as exc: # noqa: BLE001 @@ -251,7 +252,9 @@ class RelayClient: nonlocal fingerprint_touched try: async for frame in ws: - inbound = frame if isinstance(frame, bytes) else frame.encode("utf-8") + inbound = ( + frame if isinstance(frame, bytes) else frame.encode("utf-8") + ) outbound, plaintext = drive_tls(tls, inbound=inbound) if outbound: await ws.send(outbound) diff --git a/think/link/wsgi_bridge.py b/think/link/wsgi_bridge.py index d7c2cf720..64d849944 100644 --- a/think/link/wsgi_bridge.py +++ b/think/link/wsgi_bridge.py @@ -92,6 +92,7 @@ async def serve_request( ) -> Callable[[bytes], None]: response_state.status_line = status response_state.headers = list(headers) + # WSGI returns a write() callable, but we use the iterable instead. def write(_data: bytes) -> None: raise RuntimeError("write() callable not supported; return iterable") @@ -105,10 +106,10 @@ async def serve_request( None, lambda: wsgi_app(environ, start_response) ) except Exception: - log.exception( - "tunnel %s stream %s: wsgi app raised", tunnel_id, stream_id + log.exception("tunnel %s stream %s: wsgi app raised", tunnel_id, stream_id) + await _write_simple( + writer, 500, "internal server error", b"internal server error\n" ) - await _write_simple(writer, 500, "internal server error", b"internal server error\n") meta.status = 500 meta.response_bytes = _byte_count_for_simple(b"internal server error\n") return meta @@ -141,7 +142,11 @@ async def serve_request( meta.response_bytes += sent # Stream body. Iterate in a thread (WSGI iterables can block). - iterator = iter(result) if not isinstance(result, (bytes, bytearray)) else iter([bytes(result)]) + iterator = ( + iter(result) + if not isinstance(result, (bytes, bytearray)) + else iter([bytes(result)]) + ) async def next_chunk() -> bytes | None: def _pull() -> bytes | None: @@ -226,7 +231,10 @@ async def _read_request(reader: asyncio.StreamReader) -> _Request: headers_map = {k.lower(): v for k, v in headers} body = b"" - if "transfer-encoding" in headers_map and headers_map["transfer-encoding"].lower() == "chunked": + if ( + "transfer-encoding" in headers_map + and headers_map["transfer-encoding"].lower() == "chunked" + ): body = await _read_chunked(reader) else: cl_raw = headers_map.get("content-length", "0")