# 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. Le chemin natif repose sur `web-transport-quinn`; le chemin client `wasm32-unknown-unknown` repose sur `web-transport-wasm` et l'API WebTransport du navigateur. Les deux conservent les erreurs publiques dans `game-realtime-transport-lib`. La frontière fiable 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 sur les chemins natif et navigateur 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`; - client WASM avec endpoint HTTPS, hash certificat SHA-256 explicite, établissement de session navigateur et ouverture du stream bidirectionnel primaire; - adaptation WASM du framing `u32` big-endian et des traits realtime, y compris FIN, reset/STOP et réception incrémentale. - capacité datagram WebTransport backend-spécifique sur les sessions native et navigateur, sans extension de `RealtimeConnection`. ## Chemin navigateur/WASM Pour `wasm32-unknown-unknown`, la crate remplace les dépendances natives Quinn/Tokio/rcgen par `web-transport-wasm`. Le build final reçoit `--cfg=web_sys_unstable_apis` uniquement pour cette cible via `.cargo/config.toml`, conformément à l'exigence actuelle des bindings WebTransport de `web-sys`. L'API client garde les mêmes noms de surface que le client natif : `WebTransportCertificateHash`, `WebTransportClientConfig`, `connect`, `WebTransportSession`, `WebTransportConnection`, `WebTransportSender` et `WebTransportReceiver`. Le navigateur ouvre toujours le stream bidirectionnel primaire côté client ; le serveur reste le backend Rust natif. Le pin SHA-256 est transmis à `WebTransportOptions.serverCertificateHashes`; aucune variante navigateur sans validation TLS n'est ajoutée. Le framing applicatif reste strictement identique au natif. À partir de `alpha.7`, les deadlines `connect_timeout`, `primary_stream_timeout` et `send_timeout` sont aussi matérialisées côté navigateur par des timers WASM. Une deadline navigateur doit tenir dans la plage `u32` millisecondes ; une valeur supérieure est rejetée comme `InvalidConfiguration`. Une réception idle reste volontairement sans timeout implicite, comme sur le chemin natif. Le smoke navigateur technique séparé prouve ensuite cette surface avec un vrai navigateur sans fallback WebSocket. ## Datagrams backend-spécifiques `WebTransportSession` expose `max_datagram_size()`, `send_datagram(...)` et `receive_datagram()` sur les chemins natif et navigateur. Cette surface reste volontairement propre au backend WebTransport : un datagram n'est ni fiable ni ordonné et ne peut donc pas satisfaire le contrat `RealtimeConnection`. La taille maximale dépend de la session et du chemin réseau. Le backend rejette localement un payload supérieur à `max_datagram_size()` avec `TransportErrorKind::MessageTooLarge`; cette valeur peut évoluer entre sessions et ne doit pas être traitée comme une constante produit. `receive_datagram()` n'impose aucun timeout. Le consommateur doit borner l'attente selon son scénario. Le smoke `game-realtime-webtransport-datagram-smoke` utilise une deadline locale précisément parce que la perte d'un datagram est un résultat autorisé par le protocole. Il vérifie un échange loopback dans les deux sens sans en déduire de garantie de livraison ou d'ordre. Le chemin navigateur compile la même capacité via `web-transport-wasm`; `alpha.9` ne réouvre pas le smoke navigateur interactif et limite la preuve runtime datagram au backend natif. ## 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. Sur les chemins natif et navigateur, 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. Côté navigateur, le timer est porté par `gloo-timers` et la future WebTransport abandonnée reste traitée selon la sémantique cancel-safe du wrapper amont. ## 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` sur les chemins natif et navigateur. 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 serveur WebTransport WASM ; - de fallback WebSocket dans le backend ; - 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é. Les dépendances navigateur sont target-specific et ne sont donc pas tirées par le backend natif. Réciproquement, `web-transport-quinn`, Tokio et rcgen ne sont pas requis pour construire la crate en `wasm32-unknown-unknown`.