# game-realtime-webtransport-lib Backend WebTransport/QUIC candidat pour le realtime de `games.sasedev`. ## Responsabilité La crate possède le transport WebTransport concret sans introduire de sémantique gameplay, room, joueur, tick ou snapshot. Son chemin natif repose sur `web-transport-quinn` et conserve les erreurs publiques dans `game-realtime-transport-lib`. La frontière native disponible couvre désormais : - configuration client HTTPS avec pin SHA-256 exact ; - identité serveur X.509 DER + clé privée PKCS#8 DER injectables ; - génération locale d'une identité self-signed ECDSA P-256 à validité courte pour `localhost`, IPv4 loopback et IPv6 loopback ; - bind UDP/QUIC sur adresse explicite ou port éphémère ; - établissement HTTP/3 WebTransport client/server natif ; - sélection d'un unique stream bidirectionnel fiable comme chemin realtime principal ; - framing privé `u32` big-endian + payload binaire ; - limite de message configurable, 1 MiB par défaut, vérifiée avant allocation côté réception et avant écriture côté émission ; - deadlines configurables pour la connexion, l'ouverture/accept du stream primaire et un envoi complet ; - adaptation `RealtimeConnection` / `RealtimeSender` / `RealtimeReceiver` ; - FIN propre via `RealtimeSender::close()` ; - reset/STOP_SENDING backend-spécifiques via `WebTransportSender::abort(...)` et `WebTransportReceiver::abort(...)` ; - cancellation/drop terminale : un sender abandonné est reset plutôt que transformé implicitement en FIN ; - parseur de framing réception incrémental conservant son état si une future `receive()` est annulée ; - mapping stable des erreurs reset/close/session/protocole vers `TransportErrorKind` ; - tracing sous `games::realtime::webtransport`. ## TLS de développement `WebTransportServerIdentity::generate_loopback()` crée une identité en mémoire. La clé privée n'est ni écrite ni versionnée. Le certificat est valide sept jours, avec une petite marge de clock skew, et son SHA-256 est exposé à travers `WebTransportCertificateHash` afin que le client puisse utiliser le pinning fourni par `web-transport-quinn`. Une identité préexistante peut être injectée en DER avec `WebTransportServerIdentity::from_pkcs8_der(...)`. La compatibilité certificat/clé est alors vérifiée par le builder TLS au bind du serveur. Aucune option de désactivation globale de la vérification TLS n'est exposée. ## Stream fiable principal Une session WebTransport établie n'est pas encore le contrat realtime lui-même. Le client appelle `WebTransportSession::open_primary_connection()` ; le serveur appelle `WebTransportSession::accept_primary_connection()`. Le chemin logique devient ensuite : ```text one WebTransport session -> one primary bidirectional reliable stream -> u32 big-endian payload length -> payload bytes ``` `open_primary_connection()` écrit déjà l'en-tête de stream WebTransport requis par HTTP/3 avant de retourner. Le pair peut donc terminer `accept_primary_connection()` avant l'envoi de la première frame applicative ; aucun préambule propre à games.sasedev n'est nécessaire. `RealtimeConnection::split()` conserve la session WebTransport dans les deux moitiés afin que la session ne soit pas fermée au moment où l'objet connexion est consommé. ## Limites et deadlines `WebTransportConfig::default()` conserve la baseline de 1 MiB par message. La limite peut être réduite ou augmentée tant qu'elle reste strictement positive et représentable dans le champ de longueur `u32` du framing. Les deadlines configurables couvrent : - connexion client et réponse finale à une requête WebTransport déjà surfacée côté serveur ; - ouverture ou accept du stream bidirectionnel principal ; - écriture complète header + payload d'une frame. L'attente d'un nouveau pair sur le listener reste volontairement non bornée : un serveur inactif ne doit pas produire périodiquement une erreur uniquement parce qu'aucun client ne se présente. QUIC applique sa propre flow-control. Le backend n'ajoute pas une seconde file applicative : si un envoi reste bloqué par flow-control/réseau au-delà de `send_timeout`, l'opération retourne `TransportErrorKind::Timeout` et le stream est reset afin qu'une frame partiellement transmise ne puisse pas être suivie d'une nouvelle frame invalide. ## Lifecycle, abort et cancellation `RealtimeSender::close()` reste la fermeture propre de la direction d'émission et produit un FIN. À l'inverse : - `WebTransportSender::abort(code)` envoie un `RESET_STREAM` WebTransport ; - `WebTransportReceiver::abort(code)` envoie un `STOP_SENDING` WebTransport ; - dropper un `WebTransportSender` encore actif provoque un reset explicite ; - dropper un `WebTransportReceiver` encore actif provoque un stop explicite ; - annuler une future `send()` en cours provoque également un reset via une garde de cancellation. Une erreur terminale de lecture/écriture rend la moitié concernée indisponible pour une réutilisation silencieuse. La réception n'utilise plus une lecture exacte monolithique. Le header et le payload sont lus progressivement avec l'API de lecture cancel-safe de Quinn ; `header_read`/`payload_read` restent dans le receiver. Une future `receive()` annulée peut donc être relancée sans perdre les octets déjà consommés ni décaler le framing. ## Mapping d'erreurs Le backend distingue notamment : - payload hors limite -> `MessageTooLarge` ; - reset/STOP_SENDING valide -> `Aborted` ; - stream déjà fermé -> `Closed` ; - fermeture de session WebTransport explicite -> `Closed` ; - erreur de session/connexion non classée comme fermeture propre -> `Io` ; - reset/stop invalide ou framing tronqué -> `Protocol` ; - deadline dépassée -> `Timeout`. Une longueur entrante hors limite ou un framing tronqué provoque aussi l'arrêt de la direction de réception afin d'éviter de poursuivre sur un flux désynchronisé. ## Frontières actuelles La crate ne possède toujours pas : - d'API datagram transport-neutral ; - de chemin navigateur/WASM ; - de smoke executable public WebTransport ; - de fallback WebSocket ; - de benchmark WebSocket/WebTransport. Ces responsabilités restent réservées aux tranches suivantes du plan `0.3.5`. Le chemin natif s'exécute sous un runtime Tokio fourni par le consommateur ; la crate ne crée ni runtime ni thread privé. Le chemin navigateur/WASM est distinct : aucun `cfg` WASM ni dépendance navigateur n'est requis par le backend natif actuel.