6.5 KiB
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é
u32big-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(...)etWebTransportReceiver::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 :
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 unRESET_STREAMWebTransport ;WebTransportReceiver::abort(code)envoie unSTOP_SENDINGWebTransport ;- dropper un
WebTransportSenderencore actif provoque un reset explicite ; - dropper un
WebTransportReceiverencore 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.