# Notes techniques

> HTML: https://remote.ivjn.us/docs/fr/technical/

Ce document décrit les mécanismes essentiels du code actuel. Les tableaux complets des champs des messages, des formats de trames et de l'ordre des échanges figurent dans le [protocole réseau](/docs/fr/websocket-protocol.md).

## Opérateurs et appareils se connectent à exchange en sortie

Les appareils domestiques et des succursales d’entreprise sont généralement derrière un NAT : exchange ne peut pas les joindre directement. Les deux côtés ouvrent donc un WebSocket vers exchange :

- L'appareil se connecte à `wss://<主机>/ws/device` (`ws://` pour le débogage en clair) et transmet son jeton d'appareil dans la première trame pour s'enregistrer.
- L'opérateur se connecte à `wss://<主机>/ws/operator` et utilise son jeton opérateur lors de la négociation.

exchange assure uniquement l'authentification, le routage et la traçabilité. `--listen` ou `REMOTE_LISTEN` détermine l'adresse d'écoute ; le proxy inverse en amont termine TLS.

## Deux espaces d'identifiants de session

L'opérateur et l'appareil maintiennent chacun leurs identifiants de session, sans conflit entre les deux espaces. L'opérateur peut joindre son propre identifiant à la requête sans attendre que le serveur en attribue un avant d'envoyer les trames suivantes. Les identifiants côté appareil sont alloués séparément sur sa connexion actuelle, en augmentant à partir de 1. Deux opérateurs utilisant tous deux leur identifiant local 1 obtiennent ainsi des identifiants différents sur l'appareil.

L'identifiant 0 est réservé aux messages de connexion et n'est attribué à aucune session. Un identifiant opérateur nul ou déjà utilisé sur cette connexion fait immédiatement échouer l'enregistrement, sans écriture dans la table de routage.

La table de traduction se trouve dans `crates/exchange/src/hub/session_table.rs`. `hub.rs` associe le résultat aux connexions : côté appareil, « identifiant appareil → identifiant opérateur » ; côté opérateur, « identifiant opérateur → (connexion appareil, identifiant appareil) ».

## Seul l'identifiant de session est réécrit au transfert

Les octets PTY et stdout/stderr des commandes utilisent des trames binaires. Leur en-tête de 9 octets contient 1 octet de type de flux, 4 octets de `session_id`, 4 octets de longueur de charge utile, puis la charge brute. Le format est défini dans `crates/proto/src/frame.rs`.

exchange remplace uniquement `session_id` lors du transfert :

- Opérateur vers appareil : `crates/exchange/src/ws/operator.rs` utilise `DataFrame::with_session_id` pour substituer l'identifiant côté appareil, puis place les octets encodés dans la file d'envoi de l'appareil.
- Appareil vers opérateur : `handle_data` dans `crates/exchange/src/ws/device.rs` remplace l'identifiant appareil par celui de l'opérateur. Le commentaire du code désigne précisément cette réécriture.

La charge utile passe sans modification. Le routage ne dépend pas du contenu des frappes PTY ou de la sortie des commandes ; les trames dont la session est introuvable sont abandonnées. Les messages de contrôle sont des trames texte JSON : à l'ouverture et à la fermeture, leurs identifiants sont également traduits vers l'espace du destinataire, en conservant les champs métier.

## Une reconnexion d'appareil remplace la connexion précédente

Lorsqu'un même appareil se reconnecte, la nouvelle connexion remplace l'ancienne dans la table des appareils en ligne ; l'appelant doit fermer l'ancienne. Rejeter la nouvelle connexion comme doublon ne convient pas : une ancienne connexion à moitié ouverte pourrait rester dans la table et obliger l'appareil à attendre son expiration.

Chaque connexion d'appareil reçoit un `epoch` (UUID) dans `Hub::register_device`. Le nettoyage utilise `Hub::unregister_device` : l'appareil n'est retiré et marqué hors ligne que si l'epoch enregistré correspond à celui du nettoyage. Si le nettoyage de l'ancienne connexion arrive après l'enregistrement de la nouvelle, les epoch diffèrent, la fonction renvoie une liste vide et la nouvelle connexion reste dans la table.

## Files d'envoi bornées et fermeture en cas de saturation

Chaque WebSocket dispose d'une file de capacité 512, créée par `channel` dans `crates/exchange/src/hub/outbound.rs`. Le routage s’effectue sous le verrou de la table des connexions ; seule la méthode `Sender::try_send` est utilisée afin d’éviter une attente du destinataire pendant ce verrouillage.

Lorsque la file est pleine, `try_send` renvoie `SendError::Lagging` et active le signal de fermeture. Le message de contrôle `Close` ne passe pas dans la file : il active ce même signal, ce qui permet de fermer même une connexion dont la file est bloquée. Les boucles de connexion appareil et opérateur attendent `sender.closed()` dans `tokio::select!` (`crates/exchange/src/ws/device.rs`, `crates/exchange/src/ws/operator.rs`). Dès le signal, la boucle de transfert se termine, puis désenregistre l'appareil selon son epoch ou nettoie les sessions de l'opérateur. Si le destinataire a déjà abandonné son récepteur, l'erreur est `SendError::Closed`, distincte d'un consommateur trop lent.

## Jetons composés d’un préfixe et d’un secret, dont seul le hachage argon2 est stocké

Le texte en clair a la forme `<prefix>.<secret>`. `split_token` dans `crates/exchange/src/auth.rs` sépare au premier `.` ; un préfixe ou un secret vide constitue une erreur de format.

À l'émission, le préfixe représente 8 octets aléatoires en hexadécimal, et le secret 32 octets aléatoires en base64url. La base ne conserve que deux colonnes :

- `token_prefix` : en clair, avec un index unique, pour une recherche lors de la vérification.
- `token_hash` : chaîne PHC argon2id du secret, produite par `hash_secret`.

Appareils et comptes opérateur utilisent ce format. La vérification retrouve le hachage par le préfixe, puis compare le secret avec `verify_secret`. Le texte en clair est renvoyé à l'appelant uniquement lors de l'émission ; il ne peut pas être reconstitué depuis la base.

Les paramètres argon2 sont 8 Mio de mémoire, 2 itérations et un parallélisme de 1, inférieurs aux valeurs par défaut de la bibliothèque. Le secret contient 256 bits aléatoires : un hachage lent n'apporte pas de protection pratique supplémentaire contre la force brute, mais accroît la latence des négociations lors de reconnexions simultanées. Ces paramètres ne conviennent qu'à de tels jetons aléatoires.

## Documents associés

- [Développement](/docs/fr/development.md)
- [Utilisation](/docs/fr/getting-started.md)
- [Protocole réseau](/docs/fr/websocket-protocol.md)
- Guide de déploiement dans `docs/deployment.md` du dépôt (adresses de serveur, non publié sur ce site).
