122 lines
8.6 KiB
Markdown
122 lines
8.6 KiB
Markdown
<!-- file: crates/common/game-realtime-webtransport-lib/README.md -->
|
|
<!-- version: 5 -->
|
|
|
|
# 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.
|
|
|
|
## 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.
|
|
|
|
## 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`.
|