[Fix] IPC, cli, daemon

This commit is contained in:
Alex-Emmet 2026-07-21 23:05:34 +02:00
commit 56aad3a023
32 changed files with 1356 additions and 399 deletions

View file

@ -1,5 +1,14 @@
pub mod protocol;
pub mod transport;
pub use protocol::{ClientMessage, DaemonMessage, LogEntry, StateSnapshot};
pub use protocol::{
ClientMessage, DaemonMessage, HelloAck, LogEntry, StateSnapshot, MetricSample,
RequestEnvelope, ResponseEnvelope, ResponseResult, LocalRequest, IpcErrorCode,
ConnectionStatus, StartupPhase, LifecycleEvent,
};
pub use transport::{read_msg, write_msg};
/// Current IPC protocol version.
pub const PROTOCOL_VERSION: u16 = 2;
/// Minimum protocol version this daemon understands.
pub const MIN_PROTOCOL_VERSION: u16 = 2;

View file

@ -1,28 +1,123 @@
use serde::{Deserialize, Serialize};
// ---------------------------------------------------------------------------
// Client → Daemon
// ---------------------------------------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum ClientMessage {
Command { seq: u64, line: String },
Subscribe,
Hello { supported_versions: Vec<u16> },
Subscribe { log_classes: Vec<String>, metric_interval_ms: Option<u64> },
Request(RequestEnvelope),
Ping { seq: u64 },
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RequestEnvelope {
pub request_id: u64,
pub protocol_version: u16,
pub request: LocalRequest,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum LocalRequest {
GetStatus,
ListTasks,
ListUsers,
CreateUser { username: String },
RemoveUser { user_id: i64 },
ReconnectOmikron,
RotateIotaIdentity,
RestartDaemon,
StopDaemon,
}
// ---------------------------------------------------------------------------
// Daemon → Client
// ---------------------------------------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum DaemonMessage {
HelloAck(HelloAck),
LogEntry(LogEntry),
StateUpdate(StateSnapshot),
CommandResult {
seq: u64,
success: bool,
message: String,
},
Pong {
seq: u64,
},
MetricSample(MetricSample),
Response(ResponseEnvelope),
Pong { seq: u64 },
LifecycleEvent(LifecycleEvent),
Gap { skipped: u64 },
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HelloAck {
pub protocol_version: u16,
pub daemon_version: String,
pub instance_id: String,
pub startup_phase: StartupPhase,
pub capabilities: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ResponseEnvelope {
pub request_id: u64,
pub result: ResponseResult,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum ResponseResult {
Ok(String),
Error(IpcErrorCode),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum IpcErrorCode {
InvalidRequest,
NotFound,
Conflict,
StorageFailure,
OmikronUnavailable,
UnsupportedVersion,
NotReady,
Disconnected,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum LifecycleEvent {
StateChanged(ConnectionStatus),
Shutdown { reason: String },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionStatus {
Connected,
Reconnecting,
Degraded,
Disconnected,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StartupPhase {
Starting,
MigratingStorage,
LoadingUsers,
StartingServices,
Ready,
Degraded,
Stopping,
}
// ---------------------------------------------------------------------------
// Shared types
// ---------------------------------------------------------------------------
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LogEntry {
pub timestamp_ms: u128,
@ -40,3 +135,12 @@ pub struct StateSnapshot {
pub net_down: Vec<(f64, f64)>,
pub sys_info: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct MetricSample {
pub cpu: Option<f64>,
pub ram: Option<f64>,
pub ping: Option<f64>,
pub net_up: Option<f64>,
pub net_down: Option<f64>,
}

View file

@ -41,19 +41,20 @@ where
#[cfg(test)]
mod tests {
use super::{read_msg, write_msg};
use crate::protocol::ClientMessage;
use crate::protocol::{ClientMessage, LocalRequest, RequestEnvelope};
#[tokio::test]
async fn round_trips_framed_messages() {
let (mut writer, mut reader) = tokio::io::duplex(1024);
let message = ClientMessage::Command {
seq: 4,
line: "help".into(),
};
let message = ClientMessage::Request(RequestEnvelope {
request_id: 4,
protocol_version: 2,
request: LocalRequest::GetStatus,
});
write_msg(&mut writer, &message)
.await
.expect("write succeeds");
let received: ClientMessage = read_msg(&mut reader).await.expect("read succeeds");
assert!(matches!(received, ClientMessage::Command { seq: 4, line } if line == "help"));
assert!(matches!(received, ClientMessage::Request(req) if req.request_id == 4));
}
}