This commit is contained in:
parent
2126a142f4
commit
c148314742
17 changed files with 541 additions and 70 deletions
|
|
@ -18,10 +18,14 @@ mtp = { path = "/path/to/mtp", features = ["client", "crypto"] }
|
|||
|
||||
```rust
|
||||
use mtp::client::{ClientConfig, ClientTlsConfig};
|
||||
use std::time::Duration;
|
||||
|
||||
let config = ClientConfig::new("https://host.example.com:4433")
|
||||
.with_tls(ClientTlsConfig::SystemRoots)
|
||||
.with_client_id(0);
|
||||
.with_client_id(0)
|
||||
.with_ping_interval(Duration::from_secs(5))
|
||||
.with_max_missed_pings(3)
|
||||
.with_ping_timestamp(true);
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|
|
@ -30,6 +34,9 @@ let config = ClientConfig::new("https://host.example.com:4433")
|
|||
| `tls` | `ClientTlsConfig` | `SystemRoots` or `PinnedPem(pem_bytes)` |
|
||||
| `client_id` | `u64` | Client identifier (ignored during `auth_register`) |
|
||||
| `description` | `Option<String>` | Optional label sent during handshake (e.g. `"phone"`) |
|
||||
| `ping_interval` | `Duration` | Interval between Ping frames; zero disables pings |
|
||||
| `max_missed_pings` | `usize` | Unanswered Ping frames allowed before the connection closes |
|
||||
| `ping_timestamp` | `bool` | Adds a `Timestamp` entry to each Ping frame |
|
||||
| `auth_timeout` | `Duration` (crypto) | Authentication handshake timeout (default 30s) |
|
||||
|
||||
### TLS Certificate Handling
|
||||
|
|
@ -74,6 +81,46 @@ pub struct MTPConnection {
|
|||
- `description` -- the label sent during handshake (set via `ClientConfig::with_description`)
|
||||
- `client_id` -- the confirmed/assigned client identifier (crypto only)
|
||||
|
||||
When `ping_interval` is non-zero, MTP sends Ping frames in the background and
|
||||
consumes their Pong responses before application message handling. `get_ping()`
|
||||
returns the round-trip duration of the latest matched Pong, or `None` until a
|
||||
Pong arrives. A connection closes when the configured unanswered Ping limit is
|
||||
reached.
|
||||
|
||||
### Ping-Pong
|
||||
|
||||
Ping/Pong is part of the protocol, not just a transport keepalive. Each Ping
|
||||
frame is matched against a Pong with the same frame id, and the client uses the
|
||||
response to update `get_ping()`. If the host does not answer within the
|
||||
configured limit, the connection closes.
|
||||
|
||||
Enable it in `ClientConfig`, then inspect the latest round-trip time on the
|
||||
connection. Pings start after the connection has been established; `None` is
|
||||
normal until the first matching Pong arrives.
|
||||
|
||||
```rust
|
||||
use mtp::client::{ClientConfig, MTPClient};
|
||||
use std::time::Duration;
|
||||
|
||||
let config = ClientConfig::new("https://host.example.com:4433")
|
||||
.with_client_id(42)
|
||||
.with_ping_interval(Duration::from_secs(5))
|
||||
.with_max_missed_pings(3)
|
||||
.with_ping_timestamp(true);
|
||||
|
||||
let conn = MTPClient::connect(config).await?;
|
||||
|
||||
if let Some(round_trip) = conn.get_ping() {
|
||||
println!("latest MTP round trip: {round_trip:?}");
|
||||
}
|
||||
```
|
||||
|
||||
The client consumes the Pong frames used by this loop, so they are not returned
|
||||
by `conn.receiver.receive()`. Set `ping_interval` to `Duration::ZERO` (the
|
||||
default) to disable protocol pings. `max_missed_pings` is the number of
|
||||
outstanding Ping frames allowed before the client closes the connection; use a
|
||||
host with automatic Pong responses, or provide an equivalent responder.
|
||||
|
||||
### Unauthenticated Connect
|
||||
|
||||
```rust
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ let config = HostConfig::new(
|
|||
| `port` | `u16` | Listen port |
|
||||
| `tls_fullchain` | `Vec<u8>` | PEM-encoded TLS certificate chain |
|
||||
| `tls_key` | `Vec<u8>` | PEM-encoded TLS private key |
|
||||
| `send_pongs` | `bool` | Sends a Pong for each received Ping (default `true`) |
|
||||
| `authentication_policy` | `AuthenticationPolicy` (crypto) | `ForceAuthentication`, `AllowAuthentication`, or `Unauthenticated` |
|
||||
| `host_keyring` | `Keyring` (crypto) | Host's signing and KEM keys |
|
||||
| `get_existing_user` | `Fn(u64) -> Pin<Box<dyn Future<Output = Option<PublicKeyBundle>> + Send>> + Send + Sync` (crypto) | Async lookup callback for login |
|
||||
|
|
@ -77,6 +78,35 @@ let config = HostConfig::new(ip, port, cert, key);
|
|||
The host requires a TLS certificate. For development, generate a self-signed
|
||||
certificate using `rcgen`. For production, use a CA-signed certificate.
|
||||
|
||||
### Ping-Pong
|
||||
|
||||
The host handles protocol Ping/Pong automatically unless you disable it with
|
||||
`with_pongs(false)`. Enable the default responder explicitly when constructing
|
||||
the host if you want to make the choice visible in application configuration:
|
||||
|
||||
```rust
|
||||
let config = HostConfig::new(ip, port, cert, key)
|
||||
.with_pongs(true);
|
||||
```
|
||||
|
||||
For every received Ping, the responder sends a Pong with the same frame id and
|
||||
copies the optional `Timestamp` data entry. Ping and Pong frames handled this
|
||||
way are not delivered by `conn.receiver.receive()`. This lets native clients
|
||||
use `ClientConfig::with_ping_interval` and `MTPConnection::get_ping()` without
|
||||
adding application-level handlers.
|
||||
|
||||
Disable it only when the application needs to handle Ping frames itself:
|
||||
|
||||
```rust
|
||||
let config = HostConfig::new(ip, port, cert, key)
|
||||
.with_pongs(false);
|
||||
```
|
||||
|
||||
With automatic responses disabled, Ping frames are delivered through the normal
|
||||
receiver and the application is responsible for sending a compatible Pong (the
|
||||
same frame id, and normally the Ping's `Timestamp`) if it wants clients to
|
||||
continue their protocol ping loop.
|
||||
|
||||
## Accepting Connections
|
||||
|
||||
```rust
|
||||
|
|
|
|||
|
|
@ -149,6 +149,39 @@ If hashes are omitted, the browser uses its normal TLS root store.
|
|||
`maxMessageSize` caps inbound and outbound MTP frames before buffering/sending.
|
||||
`authTimeoutMs` bounds connect/login/register promises at the SDK layer.
|
||||
|
||||
## Streams
|
||||
|
||||
The browser client uses one WebTransport session per `MTPClient` instance.
|
||||
`send()`, `request()`, and `subscribe()` all operate over that session; the SDK
|
||||
does not expose browser stream objects directly.
|
||||
|
||||
Use the normal message APIs to send and receive over that session:
|
||||
|
||||
```typescript
|
||||
const client = await MTPClient.create({ url, hostPublicKey });
|
||||
await client.connect();
|
||||
|
||||
const unsubscribe = client.subscribe("SomeType", (message) => {
|
||||
console.log(message.data);
|
||||
});
|
||||
|
||||
await client.send("SomeType", { value: "hello" });
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
Internally, each outbound MTP frame is written to a new WebTransport
|
||||
unidirectional stream as a four-byte big-endian length followed by the frame,
|
||||
then that stream is closed. Incoming frames are read from the session's
|
||||
incoming unidirectional streams. The reader accepts both one-frame streams and
|
||||
native peers that place several frames on a persistent stream, so browser and
|
||||
native clients interoperate without stream configuration.
|
||||
|
||||
The SDK deliberately owns stream lifetime and framing. Do not create browser
|
||||
streams for MTP frames yourself through the SDK. For direct generated bindings,
|
||||
use `client.raw.client` or import `WasmClient` from `mtp/raw`; a `WasmClient`
|
||||
still owns one active WebTransport session, so create another instance for an
|
||||
independent connection.
|
||||
|
||||
## Sending, Requests, Subscriptions, And Pings
|
||||
|
||||
`send` accepts either a typed message or a prebuilt raw frame:
|
||||
|
|
|
|||
Loading…
Reference in a new issue