Files
Martino Ferrari dca378b5ef initial commit
2026-04-11 16:30:18 +02:00

192 lines
7.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RMon Wire Protocol
## Transport
- **TCP** over an SSH local port-forward tunnel
- **Port**: configurable, default `7891`
- One persistent TCP connection per UI client
- Multiple clients can connect to the same agent simultaneously
## Framing
Every message (in both directions) uses the same length-prefix frame:
```
┌─────────────────────┬────────────────────────────┐
│ length : u32 LE │ payload : [u8; length] │
└─────────────────────┴────────────────────────────┘
```
- `length` is the byte count of the serialized payload (excludes the 4-byte length field itself)
- `payload` is a `bincode`-serialized `ClientMessage` (client→agent) or `AgentMessage` (agent→client)
- `bincode` configuration: little-endian, fixed-size integers, no trailing bytes
Maximum frame size: **16 MiB** (`length` values above this are a protocol error; close connection).
## Versioning
The `Handshake` / `HandshakeAck` exchange negotiates compatibility. If the agent does not support the client's version it sends `HandshakeAck { accepted: false }` and closes the connection. Both sides must be built from the same `rmon-common` version to guarantee `bincode` schema compatibility.
## Client → Agent Messages
```rust
enum ClientMessage {
/// Must be the first message sent. No other messages are valid before HandshakeAck.
Handshake {
protocol_version: u16, // current: 1
client_version: String,
},
/// Request the full list of signals the agent knows about.
/// Agent replies with one SignalList message.
ListSignals,
/// Subscribe to live data for the given signals.
/// Agent begins sending DataBatch messages for these signals.
/// `from_ns`: if Some, also request buffered/stored data from that timestamp forward
/// before live data begins.
Subscribe {
ids: Vec<SignalId>,
from_ns: Option<i64>,
},
/// Stop live delivery for these signals. Does not affect storage.
Unsubscribe {
ids: Vec<SignalId>,
},
/// Request stored historical data for a signal.
/// Agent replies with one or more HistoryChunk messages (done=true on last chunk).
/// `max_points`: agent may downsample to fit; 0 = no limit.
QueryHistory {
id: SignalId,
from_ns: i64,
to_ns: i64,
max_points: u32,
},
/// Start recording the given signals. Mode overrides any existing mode for these signals.
StartRecording {
ids: Vec<SignalId>,
mode: StorageMode,
},
/// Stop recording (data already written is kept).
StopRecording {
ids: Vec<SignalId>,
},
/// Request current agent status (version, uptime, source states).
GetStatus,
}
```
## Agent → Client Messages
```rust
enum AgentMessage {
/// Response to Handshake.
HandshakeAck {
protocol_version: u16,
agent_version: String,
accepted: bool,
reject_reason: Option<String>,
},
/// Full signal list, sent in response to ListSignals.
/// Sent unsolicited again if the signal list changes (source added/removed).
SignalList {
signals: Vec<SignalInfo>,
},
/// Live data batch for subscribed signals.
/// The agent batches samples collected since the last send (target: ~50ms intervals).
/// Multiple signals may be grouped into one message to reduce framing overhead.
DataBatch {
batches: Vec<SignalBatch>,
},
/// One chunk of a historical query response.
HistoryChunk {
id: SignalId,
samples: Vec<Sample>,
done: bool, // true on the final chunk for this query
},
/// Acknowledgement of StartRecording / StopRecording.
RecordingStatus {
id: SignalId,
recording: bool,
mode: Option<StorageMode>,
},
/// Response to GetStatus.
StatusReport {
agent_version: String,
hostname: String,
uptime_secs: u64,
sources: Vec<SourceStatus>,
},
/// Unrecoverable error for a specific operation. Connection remains open.
Error {
context: String, // e.g. "QueryHistory" or "Subscribe"
message: String,
},
}
struct SignalBatch {
id: SignalId,
samples: Vec<Sample>,
}
struct SourceStatus {
name: String,
source_type: SourceType,
connected: bool,
last_sample_ns: Option<i64>,
error: Option<String>,
}
```
## Session Lifecycle
```
Client Agent
│ │
│──── Handshake ────────────────────► │
│ │
│◄─── HandshakeAck (accepted=true) ─── │
│ │
│──── ListSignals ───────────────────► │
│◄─── SignalList ───────────────────── │
│ │
│──── Subscribe {ids, from_ns} ──────► │ ←── live data starts flowing
│◄═══ DataBatch (repeated) ═══════════ │
│ │
│──── QueryHistory ──────────────────► │
│◄─── HistoryChunk (done=false) ────── │
│◄─── HistoryChunk (done=false) ────── │
│◄─── HistoryChunk (done=true) ─────── │
│ │
│──── StartRecording ────────────────► │
│◄─── RecordingStatus ──────────────── │
│ │
│ [TCP close / SSH tunnel drops] │
│ │ ←── agent continues running,
data continues to be stored
```
## Batching Policy
The agent collects samples from all sources in a shared `tokio::broadcast` channel. A per-session task drains the channel and sends `DataBatch` messages at a target interval of **50 ms** (20 Hz UI update rate). If a batch would exceed 1 MiB, it is split and sent immediately.
High-frequency signals (e.g., 10 kHz UDP) are automatically decimated to 10× the batch rate (200 Hz) for live display; full-resolution data is still stored.
## Error Handling
- `Error` messages are non-fatal; the session continues
- If the agent cannot fulfill a `QueryHistory` (signal not found, time range not stored), it sends `Error` then continues
- If the TCP connection drops, the agent discards the session's subscriptions; no other sessions are affected
- The UI reconnects automatically (with exponential backoff) if the tunnel drops