diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport')
5 files changed, 71 insertions, 70 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/__init__.py b/packages/meshbay-node/src/meshbay_node/transport/__init__.py index e423e35..86dbf23 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/__init__.py +++ b/packages/meshbay-node/src/meshbay_node/transport/__init__.py @@ -15,8 +15,8 @@ Transport decision (2026-08-13, second security review): # QUIC transport (MNP v2) — requires aioquic>=1.0 try: - from .quic_server import QuicChunkServer, Denylist from .quic_client import QuicChunkClient + from .quic_server import Denylist, QuicChunkServer QUIC_AVAILABLE = True except ImportError: QuicChunkServer = None # type: ignore[assignment,misc] @@ -26,7 +26,7 @@ except ImportError: # WebRTC transport (browsers + native clients) — requires aiortc>=1.9 try: - from .webrtc_server import WebRTCTransport, WebRTCPeerSession + from .webrtc_server import WebRTCPeerSession, WebRTCTransport WEBRTC_AVAILABLE = True except ImportError: WebRTCTransport = None # type: ignore[assignment,misc] diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py index af87b70..273f225 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/quic_client.py +++ b/packages/meshbay-node/src/meshbay_node/transport/quic_client.py @@ -13,18 +13,14 @@ import base64 import logging import os import struct -from pathlib import Path -import jwt import msgpack -from aioquic.asyncio import connect, QuicConnectionProtocol +from aioquic.asyncio import QuicConnectionProtocol, connect from aioquic.quic.configuration import QuicConfiguration from aioquic.quic.events import QuicEvent, StreamDataReceived from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey - from meshbay_common import MNP_VERSION from meshbay_common.groupbox import PURPOSE_ACK, PURPOSE_INDEX, unseal -from meshbay_common.protocol import MNP, file_chunk_plaintext from meshbay_common.handshake import ( MNP_MIN_SUPPORTED, NONCE_LEN, @@ -36,6 +32,7 @@ from meshbay_common.handshake import ( quic_binding, verify_proof, ) +from meshbay_common.protocol import MNP, file_chunk_plaintext def _peer_cert_der(proto) -> bytes | None: diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py index 2a2b07a..036574b 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py @@ -24,17 +24,16 @@ import os import struct import uuid from pathlib import Path -from typing import Any, Callable +from typing import Any -import jwt import msgpack from aioquic.asyncio import QuicConnectionProtocol, serve from aioquic.quic.configuration import QuicConfiguration from aioquic.quic.events import QuicEvent, StreamDataReceived, StreamReset from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey - from meshbay_common import MNP_VERSION -from meshbay_node.roots import ROOT_NOT_SERVED, RootSet, entry_abs_path +from meshbay_common.crypto import pk_to_b64 +from meshbay_common.groupbox import PURPOSE_ACK, seal from meshbay_common.handshake import ( MNP_MIN_SUPPORTED, NONCE_LEN, @@ -48,10 +47,10 @@ from meshbay_common.handshake import ( quic_binding, verify_proof, ) -from meshbay_common.crypto import pk_to_b64 -from meshbay_common.groupbox import PURPOSE_ACK, seal from meshbay_common.protocol import MNP, file_chunk_wire + from meshbay_node.indexer import GroupIndex +from meshbay_node.roots import ROOT_NOT_SERVED, RootSet, entry_abs_path from meshbay_node.transport.wire import index_sync_message log = logging.getLogger(__name__) diff --git a/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py index 8d680ea..cfc93c7 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py +++ b/packages/meshbay-node/src/meshbay_node/transport/tls_cert.py @@ -10,11 +10,10 @@ the QUIC handshake proof (11.5.6), since QUIC has no DTLS fingerprint to bind to Certificate is generated once and cached at ~/.config/meshbay/node_tls.crt/.key. """ -import logging -import os -from pathlib import Path import datetime import ipaddress +import logging +from pathlib import Path from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization @@ -47,8 +46,8 @@ def generate_self_signed_cert( .issuer_name(issuer) .public_key(rsa_key.public_key()) .serial_number(x509.random_serial_number()) - .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) - .not_valid_after(datetime.datetime.now(datetime.timezone.utc) + .not_valid_before(datetime.datetime.now(datetime.UTC)) + .not_valid_after(datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=3650)) .add_extension( x509.SubjectAlternativeName([ diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index 8a5bbff..2bd1419 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -25,8 +25,6 @@ Signaling flow (handled externally by the hub): import asyncio import base64 import contextvars -import hashlib -import hmac import logging import os import re @@ -38,63 +36,53 @@ from pathlib import Path from typing import Any import blake3 -import jwt import msgpack -from aiortc import RTCPeerConnection, RTCSessionDescription, RTCDataChannel +from aiortc import RTCDataChannel, RTCPeerConnection, RTCSessionDescription from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, Ed25519PublicKey, ) - from meshbay_common import MNP_VERSION -from meshbay_common.handshake import ( - MNP_MIN_SUPPORTED, - NONCE_LEN, - ROLE_CLIENT, - ROLE_NODE, - HandshakeError, - authorize_token, - check_version, - handshake_transcript, - make_proof, - verify_proof, - webrtc_binding, -) from meshbay_common.adminop import ( ADMIN_CHALLENGE_TTL, + OP_APP_DIRECTORIES, + OP_APPS_ENABLED, + OP_CHAT_DIRECTORY, + OP_CHAT_EPOCH, + OP_CHAT_LINK_PREVIEW, OP_DIR_DELETE, OP_FILE_DELETE, + OP_GEK_ROTATE, + OP_GROUP_ATTACH, + OP_GROUP_DETACH, OP_INVITE_CREATE, OP_MEMBER_REVOKE, - OP_GEK_ROTATE, OP_MEMBER_UNPIN, - OP_APPS_ENABLED, + OP_MUSICBRAINZ_ENABLED, + OP_ROOT_ADD, + OP_ROOT_EJECT, + OP_ROOT_PLUG, + OP_ROOT_REMOVE, + OP_ROOT_UPDATE, + OP_SEARCH_LISTED, OP_SET_SCAN_SETTINGS, - OP_TRANSFER_LIMITS, OP_TMDB_CONFIG, OP_TMDB_ENABLED, OP_TMDB_OVERRIDE, OP_TMDB_REMATCH, - OP_MUSICBRAINZ_ENABLED, - OP_APP_DIRECTORIES, - OP_CHAT_DIRECTORY, - OP_CHAT_EPOCH, - OP_CHAT_LINK_PREVIEW, - OP_SEARCH_LISTED, - OP_ROOT_ADD, - OP_ROOT_REMOVE, - OP_ROOT_UPDATE, - OP_ROOT_EJECT, - OP_ROOT_PLUG, - OP_GROUP_ATTACH, - OP_GROUP_DETACH, + OP_TRANSFER_LIMITS, admin_transcript, ) +from meshbay_common.chatbox import ( + NONCE_LEN as CHAT_NONCE_LEN, +) +from meshbay_common.chatbox import ( + SIG_LEN as CHAT_SIG_LEN, +) from meshbay_common.crypto import pk_to_b64, wrap_gek_aes from meshbay_common.device import ( DEVICE_TTL, device_add_transcript, - device_code_hash, device_hello_transcript, device_request_transcript, ) @@ -104,44 +92,62 @@ from meshbay_common.groupbox import ( PURPOSE_ROSTER, seal, ) +from meshbay_common.handshake import ( + MNP_MIN_SUPPORTED, + NONCE_LEN, + ROLE_CLIENT, + ROLE_NODE, + HandshakeError, + authorize_token, + check_version, + handshake_transcript, + make_proof, + verify_proof, + webrtc_binding, +) from meshbay_common.join import ( JOIN_TTL, ROLE_MEMBER, ROLE_OPERATOR, join_transcript, ) -from meshbay_common.chatbox import ( - NONCE_LEN as CHAT_NONCE_LEN, - SIG_LEN as CHAT_SIG_LEN, -) from meshbay_common.protocol import ( MNP, + UPLOAD_PROBE_INDEX, chunk_ciphertext, file_chunk_wire, - UPLOAD_PROBE_INDEX, file_upload_ack_wire, file_upload_payload, ) -from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage -from meshbay_node.transport.wire import index_sync_message -from meshbay_node.indexer import GroupIndex -from meshbay_node.indexer.indexer import DirectoryIndexer + from meshbay_node import hwaccel, linkpreview, ops, platform from meshbay_node import transfers as transfers_mod from meshbay_node import uploads as uploads_mod -from meshbay_node.transfers import TransferSlots +from meshbay_node.chat import FORMAT_SEALED_V1, ReplayedMessage +from meshbay_node.indexer import GroupIndex +from meshbay_node.indexer.indexer import DirectoryIndexer + # Re-imported under its original name: every call site and existing test in # this module still refers to it as `_probe_video`. The implementation lives # in media_probe.py so the indexer package (imported just above) can call it # too, for index-time enrichment, without a circular import. from meshbay_node.media_probe import ( BROWSER_INCOMPATIBLE_VIDEO_CODECS, +) +from meshbay_node.media_probe import ( probe_video as _probe_video, ) from meshbay_node.roots import ( - ROOT_NOT_SERVED, RootSet, entry_abs_path, off_disk, SAFE_UPLOAD_NAME, safe_subdir, + ROOT_NOT_SERVED, + SAFE_UPLOAD_NAME, + RootSet, _free_name, + entry_abs_path, + off_disk, + safe_subdir, ) +from meshbay_node.transfers import TransferSlots +from meshbay_node.transport.wire import index_sync_message log = logging.getLogger(__name__) @@ -6160,7 +6166,7 @@ class WebRTCPeerSession: # total budget is unchanged. await asyncio.wait_for(self._stream_credit_evt.wait(), timeout=STREAM_CREDIT_POLL) - except asyncio.TimeoutError: + except TimeoutError: silent = time.monotonic() - self._stream_heard_at if silent >= STREAM_CREDIT_TIMEOUT: log.info("Stream stalled: nothing from peer=%s for %.0fs", @@ -6208,7 +6214,7 @@ class WebRTCPeerSession: await asyncio.wait_for(asyncio.shield(prev), timeout=15) log.info("stream: previous stream ended in %.1fs", time.monotonic() - t0) - except asyncio.TimeoutError: + except TimeoutError: log.warning("stream: previous stream STILL RUNNING after 15s") except Exception: pass # it failed on its own; the slot is free either way @@ -6851,7 +6857,7 @@ async def _transcode_audio_to_aac(file_path: Path) -> bytes: try: _, stderr = await asyncio.wait_for( proc.communicate(), timeout=AUDIO_TRANSCODE_TIMEOUT_SECS) - except asyncio.TimeoutError: + except TimeoutError: proc.kill() await proc.wait() raise RuntimeError(f"ffmpeg timed out after {AUDIO_TRANSCODE_TIMEOUT_SECS}s") @@ -6912,7 +6918,7 @@ async def _seek_lands_at(file_path: Path, t: float, map_args: list[str]) -> floa ) stdout, _ = await asyncio.wait_for( probe.communicate(), timeout=SEEK_PROBE_TIMEOUT_SECS) - except (asyncio.TimeoutError, OSError) as e: + except (TimeoutError, OSError) as e: log.warning("stream: seek probe failed at %.1fs: %r", t, e) return None finally: @@ -6967,7 +6973,7 @@ async def _extract_subtitle_to_webvtt(file_path: Path, ordinal: int, try: _, stderr = await asyncio.wait_for( proc.communicate(), timeout=timeout) - except asyncio.TimeoutError: + except TimeoutError: proc.kill() await proc.wait() raise RuntimeError(f"ffmpeg timed out after {timeout:.0f}s") @@ -7152,7 +7158,7 @@ class WebRTCTransport: Returns (answer_sdp, ice_candidates) to relay back via hub signaling. ICE candidates are embedded in the SDP (aiortc gathers before returning). """ - from aiortc import RTCIceServer, RTCConfiguration + from aiortc import RTCConfiguration, RTCIceServer # aiortc keeps only the first STUN entry it sees here; the actual # multi-server fan-out is done by transport/stun_multi, which patches |