Updated Reserved entry order. Made DataType ID changes easier in future (this MAY NOT happen again once in use).
4.8 KiB
Connector
This file documents the connection and version negotiation logic.
Registry
The registry module provides a multi-version Registry used by the host for version negotiation. Accessed through the mtp facade (requires the host feature):
use mtp::codec::registry::Registry;
let registry = Registry::builtin(); // loads all TypeMaps from config
// Check if a version is supported
assert!(registry.supports(&Version(1, 0)));
// Find highest mutual version for a client
let client_versions = &[Version(0, 0), Version(1, 0)];
let negotiated = registry.negotiate(client_versions);
assert_eq!(negotiated, Some(Version(1, 0)));
// Look up a version's TypeMap
let tm = registry.get(&Version(2, 0)).unwrap();
The Registry::builtin() constructor uses the TypeMap::vX_Y() methods generated from the config.
Host
The host creates a QUIC server, manages the registry, and handles version negotiation with each connecting client.
Initialization
The host binds to the address from the mtp_BIND environment variable (defaults to ::) on the specified port:
use mtp::host::{MTPHost, HostConfig};
let config = HostConfig {
port: 4433,
tls_fullchain: std::fs::read("cert.pem")?,
tls_key: std::fs::read("key.pem")?,
};
let mut host = MTPHost::new(config).await?;
Accepting Connections with Version Negotiation
while let Some(conn) = host.accept().await {
// conn.version is the negotiated version
// conn.codec is a VersionedCodec scoped to that version
// conn.sender / conn.receiver for raw CommunicationValue I/O
let msg = conn.receiver.receive().await?;
}
The host's accept() method:
- Accepts a QUIC connection
- If authentication is required (crypto feature): performs login/register handshake
- Reads the first
CommunicationValue(always encoded with reserved type IDs) - Extracts the client's protocol version from
DataType::Version(wire ID 3) - Calls
registry.negotiate(&[client_version]) - Returns
Noneif the version is unsupported - Returns an
MTPConnectionwith the negotiated version otherwise
Login/Register Handshake
When require_authentication is set, the parties run a mutually-authenticated
challenge-response. The client speaks first with an unsigned hello:
- Login (
CommunicationType::Identification, ID 15): version, client ID - Register (
CommunicationType::Register, ID 17): version, public keys
The host then issues a fresh random server_challenge in a signed Challenge
(CommunicationType::Challenge, ID 21, carrying ServerNonce). The client signs
that challenge, binding its id (login) or public keys (register), and returns a
ChallengeResponse (ID 22). The host verifies the proof against the challenge it
issued and sends a signed final response, which the client verifies.
Because the client's proof covers the host-issued server_challenge (a one-time
value held only on the accepting task's stack), a captured proof cannot be
replayed on another connection. All signed payloads are domain-separated; see
mtp::crypto::auth.
Client
The client connects to a host and uses a single compiled-in protocol version.
use mtp::client::{MTPClient, ClientConfig};
let config = ClientConfig {
url: "https://host.example.com:4433".into(),
server_cert: None, // or Some(cert_pem_bytes)
};
// Connect (unauthenticated, existing client)
let conn = MTPClient::connect(config, 8765).await?;
// Authenticated login
let conn = MTPClient::auth_connect(config, 8765, keys, host_pk).await?;
// Registration (new client)
let conn = MTPClient::auth_register(config, keys, host_pk).await?;
The client's PROTOCOL_VERSION constant is set by protocol_version in type-maps.yaml and baked in at compile time. The client never imports the registry crate; it only uses mtp::type_map for enum types and mtp::codec for encoding.
Version Negotiation Flow
Client (v2.0) Host (v0.0, v1.0, v2.0)
| |
| QUIC connect |
|----------------------->|
| |
| CommValue{ Ident. } |
| Version -> "2.0" |
| Id -> 8765 |
| (unsigned hello; auth |
| challenge follows) |
|----------------------->|
| | registry.negotiate(&[Version(2,0)])
| | -> Some(Version(2,0))
| |
| Response |
|<-----------------------| (uses v2.0 TypeMap for encoding)
| Status, Nonces, |
| Signature |
| |
| (subsequent messages |
| use v2.0 TypeMap) |
If the client sends an unsupported version (e.g. v3.0 when the host only knows up to v2.0), negotiate returns None and the connection is closed.