# Technische Beschreibung

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

Dieses Dokument beschreibt zentrale Mechanismen des aktuellen Codes. Vollständige Nachrichtenfelder, Frameformate und Handshake-Abläufe stehen im [Wire-Protokoll](/docs/de/websocket-protocol.md).

## Operator und Gerät verbinden sich ausgehend mit exchange

Geräte in Heimnetzen und Unternehmensfilialen liegen meist hinter NAT und sind für exchange nicht direkt erreichbar. Deshalb bauen beide Seiten WebSocket-Verbindungen zu exchange auf:

- Das Gerät verbindet sich mit `wss://<主机>/ws/device` (bei unverschlüsseltem Debugging `ws://`) und registriert sich im ersten Frame mit dem Gerätetoken.
- Der Operator verbindet sich mit `wss://<主机>/ws/operator`; der Handshake verwendet das Operatortoken.

exchange übernimmt Authentifizierung, Routing und Auditprotokollierung. `--listen` oder `REMOTE_LISTEN` bestimmt die Listenadresse; TLS terminiert am vorgeschalteten Reverse Proxy.

## Zwei Sitzungsnummernräume

Operator und Gerät verwalten eigene Sitzungsnummern, ohne den Nummernraum der anderen Seite zu belegen. Ein Operator kann seine Nummer bereits in der Anfrage senden und muss nicht auf eine Serverzuweisung warten. Geräteseitige Nummern werden für die aktuelle Geräteverbindung separat ab 1 aufsteigend vergeben. Verwenden zwei Operatoren lokal beide Nummer 1, entstehen auf dem Gerät unterschiedliche Nummern.

Nummer 0 bleibt verbindungsweiten Nachrichten vorbehalten. Eine Registrierung mit Operatornummer 0 oder einer auf dieser Operatorverbindung bereits belegten Nummer schlägt direkt fehl und verändert die Routingtabelle nicht.

Die Übersetzungstabelle liegt in `crates/exchange/src/hub/session_table.rs`. `hub.rs` ordnet die Ergebnisse konkreten Verbindungen zu: geräteseitig „Gerätenummer → Operatornummer“, operatorseitig „Operatornummer → (Geräteverbindung, Gerätenummer)“.

## Weiterleitung ändert nur die Sitzungsnummer

PTY-Bytes und stdout/stderr von Befehlen verwenden Binärframes. Der Header umfasst 9 Bytes: 1 Byte Streamtyp, 4 Bytes `session_id` und 4 Bytes Nutzdatenlänge, gefolgt von rohen Nutzdaten. Das Format ist in `crates/proto/src/frame.rs` definiert.

exchange ersetzt beim Weiterleiten ausschließlich `session_id`:

- Operator → Gerät: `crates/exchange/src/ws/operator.rs` ersetzt die Nummer mit `DataFrame::with_session_id` durch die Gerätenummer und legt die kodierten Bytes in die Geräte-Sendequeue.
- Gerät → Operator: `handle_data` in `crates/exchange/src/ws/device.rs` ersetzt die Gerätenummer durch die eigene Operatornummer. Genau diese Änderung beschreibt der Quelltextkommentar.

Die Nutzdaten bleiben unverändert. Routingentscheidungen hängen nicht vom Inhalt der PTY-Eingaben oder Befehlsausgaben ab. Ohne passende Sitzung wird der Frame verworfen. Steuernachrichten sind JSON-Textframes; auch beim Öffnen und Beenden wird die Nummer auf den Nummernraum der Gegenseite abgebildet, während fachliche Felder unverändert bleiben.

## Wiederverbindung ersetzt die alte Geräteverbindung

Verbindet sich dasselbe Gerät erneut, wird die neue Verbindung in die Onlinetabelle eingetragen und ersetzt die alte, deren Schließung dem Aufrufer überlassen wird. Das Ablehnen vermeintlich doppelter Verbindungen wäre falsch: Eine halboffene alte Verbindung könnte noch in der Tabelle hängen und das Gerät bis zu deren Timeout blockieren.

Jede Geräteverbindung erhält in `Hub::register_device` eine `epoch` (UUID). Beim Aufräumen über `Hub::unregister_device` wird das Gerät nur entfernt und offline gesetzt, wenn die gespeicherte epoch mit der epoch dieses Aufräumvorgangs übereinstimmt. Wird eine alte Verbindung erst nach Registrierung der neuen bereinigt, stimmen die Werte nicht überein: Die Funktion liefert eine leere Liste und die neue Verbindung bleibt eingetragen.

## Begrenzte Sendequeues schließen überlastete Verbindungen

Jeder WebSocket besitzt eine Queue mit Kapazität 512, erzeugt durch `channel` in `crates/exchange/src/hub/outbound.rs`. Da der Routingpfad die Verbindungstabelle gesperrt hält, verwendet die Zustellung ausschließlich `Sender::try_send` und wartet innerhalb der Sperre nicht auf die Gegenseite.

Bei voller Queue liefert `try_send` `SendError::Lagging` und setzt das Schließsignal. Auch die Steuernachricht `Close` setzt nur dieses Signal, ohne die Queue zu benutzen. So lässt sich eine Verbindung selbst bei blockierter Queue beenden. Geräte- und Operator-Eingangsschleifen warten in `tokio::select!` auf `sender.closed()` (`crates/exchange/src/ws/device.rs`, `crates/exchange/src/ws/operator.rs`). Beim Signal endet die Weiterleitung; danach wird das Gerät anhand seiner epoch abgemeldet beziehungsweise werden die Operatorsitzungen bereinigt. Hat die Gegenseite den Empfänger bereits verworfen, wird stattdessen `SendError::Closed` geliefert, um diesen Fall von zu langsamer Verarbeitung zu unterscheiden.

## Tokens bestehen aus Präfix und Geheimnis; gespeichert wird nur argon2

Das Klartextformat lautet `<prefix>.<secret>`. `split_token` in `crates/exchange/src/auth.rs` trennt am ersten `.`. Ein leeres Präfix oder Geheimnis ist ein Formatfehler.

Bei Ausstellung besteht das Präfix aus 8 zufälligen Bytes in Hexdarstellung, das Geheimnis aus 32 zufälligen Bytes in base64url. Die Datenbank speichert nur zwei Spalten:

- `token_prefix`: Klartext mit eindeutigem Index für genau eine Suche bei der Prüfung
- `token_hash`: durch `hash_secret` erzeugter argon2id-PHC-String des Geheimnisses

Geräte und Operatorkonten verwenden dasselbe Format. Zur Prüfung wird der Hash über das Präfix geladen und das Geheimnis mit `verify_secret` verglichen. Der vollständige Klartext wird nur bei Ausstellung zurückgegeben und lässt sich aus der Datenbank nicht rekonstruieren.

Die argon2-Parameter betragen 8 MiB Speicher, 2 Iterationen und Parallelität 1 und liegen unter den Bibliotheksstandards. Das Geheimnis ist ein zufälliger 256-Bit-Wert; langsames Hashing bringt hier keinen zusätzlichen Schutz gegen Durchprobieren, würde aber gleichzeitige Geräte-Wiederverbindungen durch Handshake-Latenz belasten. Diese Parameter eignen sich ausschließlich für solche Zufallstokens.

## Weitere Dokumentation

- [Entwicklung](/docs/de/development.md)
- [Bedienung](/docs/de/getting-started.md)
- [Wire-Protokoll](/docs/de/websocket-protocol.md)
- Bereitstellungsanleitung im Repository unter `docs/deployment.md` (mit Serveradressen, auf dieser Website nicht öffentlich)
