diff --git a/Cargo.lock b/Cargo.lock index 217ee09..6f68d5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,6 +1702,7 @@ dependencies = [ "parking_lot 0.12.5", "pyo3", "pythonize", + "sandd-protocol", "serde", "serde_json", "tokio", @@ -1725,6 +1726,7 @@ dependencies = [ "filetime", "futures-util", "portable-pty", + "sandd-protocol", "serde", "serde_json", "sysinfo", @@ -1739,6 +1741,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "sandd-protocol" +version = "0.0.0" +dependencies = [ + "base64", + "serde", + "serde_json", +] + [[package]] name = "schannel" version = "0.1.29" diff --git a/Cargo.toml b/Cargo.toml index 70f3efa..ea69564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["server", "sandd"] +members = ["protocol", "server", "sandd"] resolver = "2" [workspace.dependencies] diff --git a/hack/docker/Dockerfile.alpine b/hack/docker/Dockerfile.alpine index 3fe6294..3ba2f6b 100644 --- a/hack/docker/Dockerfile.alpine +++ b/hack/docker/Dockerfile.alpine @@ -13,6 +13,7 @@ RUN apk add --no-cache \ # Copy workspace files COPY Cargo.toml Cargo.lock ./ +COPY protocol/ ./protocol/ COPY sandd/ ./sandd/ COPY server/ ./server/ diff --git a/hack/docker/Dockerfile.debian b/hack/docker/Dockerfile.debian index 2328b06..a9a98f4 100644 --- a/hack/docker/Dockerfile.debian +++ b/hack/docker/Dockerfile.debian @@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \ # Copy workspace files COPY Cargo.toml Cargo.lock ./ +COPY protocol/ ./protocol/ COPY sandd/ ./sandd/ COPY server/ ./server/ diff --git a/hack/docker/Dockerfile.rocky b/hack/docker/Dockerfile.rocky index 8545554..1ce9003 100644 --- a/hack/docker/Dockerfile.rocky +++ b/hack/docker/Dockerfile.rocky @@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y \ # Copy workspace files COPY Cargo.toml Cargo.lock ./ +COPY protocol/ ./protocol/ COPY sandd/ ./sandd/ COPY server/ ./server/ diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml new file mode 100644 index 0000000..ed3324c --- /dev/null +++ b/protocol/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sandd-protocol" +version = "0.0.0" +edition = "2021" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +base64 = "0.22" diff --git a/server/src/protocol.rs b/protocol/src/lib.rs similarity index 97% rename from server/src/protocol.rs rename to protocol/src/lib.rs index 3cc38ca..33fbdfe 100644 --- a/server/src/protocol.rs +++ b/protocol/src/lib.rs @@ -1,10 +1,21 @@ +// Shared protocol between daemon and server + use serde::{Deserialize, Serialize}; -/// Protocol messages exchanged between agent and daemon +/// Snapshot metadata (shared between daemon and server) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SnapshotInfo { + pub id: String, + pub created_at: u64, // Unix timestamp in seconds + pub message: String, + pub tags: Vec, + pub file_count: usize, + pub total_size: u64, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum Message { - // Connection management Register { daemon_id: String, metadata: DaemonMetadata, @@ -15,8 +26,6 @@ pub enum Message { }, Heartbeat, Pong, - - // Command execution (simple mode) ExecuteCommand { request_id: String, command: String, @@ -38,8 +47,6 @@ pub enum Message { request_id: String, error: String, }, - - // Interactive session (PTY mode) NewSession { session_id: String, rows: u16, @@ -74,14 +81,12 @@ pub enum Message { session_id: String, exit_code: i32, }, - - // File transfer FileUploadStart { request_id: String, path: String, total_size: u64, #[serde(default)] - mode: Option, // Unix file permissions + mode: Option, }, FileUploadChunk { request_id: String, @@ -109,7 +114,6 @@ pub enum Message { request_id: String, error: String, }, - // Snapshot operations CreateSnapshot { request_id: String, @@ -138,7 +142,7 @@ pub enum Message { }, SnapshotList { request_id: String, - snapshots: Vec, + snapshots: Vec, }, FindSnapshotByTag { request_id: String, @@ -150,7 +154,7 @@ pub enum Message { }, SnapshotDetails { request_id: String, - snapshot: Option, + snapshot: Option, }, DeleteSnapshot { request_id: String, @@ -163,8 +167,6 @@ pub enum Message { request_id: String, error: String, }, - - // Error handling Error { message: String, #[serde(default)] @@ -184,14 +186,13 @@ pub struct DaemonMetadata { } fn default_timeout() -> u64 { - 300 // 5 minutes + 300 } fn default_term() -> String { "xterm-256color".to_string() } -// Base64 encoding for binary data in JSON mod base64_bytes { use serde::{Deserialize, Deserializer, Serializer}; @@ -209,7 +210,6 @@ mod base64_bytes { .map_err(serde::de::Error::custom) } } - #[cfg(test)] mod tests { use super::*; diff --git a/sandd/Cargo.toml b/sandd/Cargo.toml index 8d2974b..cbac446 100644 --- a/sandd/Cargo.toml +++ b/sandd/Cargo.toml @@ -28,6 +28,7 @@ name = "snapshot_real_project" path = "../examples/snapshot_real_project.rs" [dependencies] +sandd-protocol = { path = "../protocol" } tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/sandd/src/main.rs b/sandd/src/main.rs index 377d18d..fc1f873 100644 --- a/sandd/src/main.rs +++ b/sandd/src/main.rs @@ -1,5 +1,5 @@ mod executor; -mod protocol; +// Use shared protocol crate mod session; pub mod snapshot; @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; use clap::Parser; use executor::CommandExecutor; use futures_util::{SinkExt, StreamExt}; -use protocol::Message; +use sandd_protocol::Message; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -148,7 +148,7 @@ async fn connect_and_serve( let (mut ws_tx, mut ws_rx) = ws_stream.split(); // Gather system metadata - let metadata = protocol::DaemonMetadata { + let metadata = sandd_protocol::DaemonMetadata { hostname: System::host_name().unwrap_or_else(|| "unknown".to_string()), platform: std::env::consts::OS.to_string(), arch: std::env::consts::ARCH.to_string(), diff --git a/sandd/src/protocol.rs b/sandd/src/protocol.rs deleted file mode 100644 index b49c41b..0000000 --- a/sandd/src/protocol.rs +++ /dev/null @@ -1,202 +0,0 @@ -// Re-export the protocol from server crate for consistency -// In production, you'd want a shared protocol crate - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum Message { - Register { - daemon_id: String, - metadata: DaemonMetadata, - }, - RegisterAck { - success: bool, - message: String, - }, - Heartbeat, - Pong, - ExecuteCommand { - request_id: String, - command: String, - #[serde(default = "default_timeout")] - timeout_secs: u64, - #[serde(default)] - env: std::collections::HashMap, - #[serde(default)] - cwd: Option, - }, - CommandOutput { - request_id: String, - stdout: String, - stderr: String, - exit_code: i32, - duration_ms: u64, - }, - CommandError { - request_id: String, - error: String, - }, - NewSession { - session_id: String, - rows: u16, - cols: u16, - #[serde(default = "default_term")] - term: String, - }, - SessionStarted { - session_id: String, - success: bool, - error: Option, - }, - SessionInput { - session_id: String, - #[serde(with = "base64_bytes")] - data: Vec, - }, - SessionOutput { - session_id: String, - #[serde(with = "base64_bytes")] - data: Vec, - }, - SessionResize { - session_id: String, - rows: u16, - cols: u16, - }, - SessionClose { - session_id: String, - }, - SessionExit { - session_id: String, - exit_code: i32, - }, - FileUploadStart { - request_id: String, - path: String, - total_size: u64, - #[serde(default)] - mode: Option, - }, - FileUploadChunk { - request_id: String, - #[serde(with = "base64_bytes")] - data: Vec, - offset: u64, - }, - FileUploadComplete { - request_id: String, - success: bool, - error: Option, - }, - FileDownloadStart { - request_id: String, - path: String, - }, - FileDownloadChunk { - request_id: String, - #[serde(with = "base64_bytes")] - data: Vec, - offset: u64, - is_last: bool, - }, - FileDownloadError { - request_id: String, - error: String, - }, - // Snapshot operations - CreateSnapshot { - request_id: String, - workspace: String, - message: Option, - tags: Option>, - }, - SnapshotCreated { - request_id: String, - snapshot_id: String, - file_count: usize, - total_size: u64, - }, - RestoreSnapshot { - request_id: String, - snapshot_id: String, - destination: String, - }, - SnapshotRestored { - request_id: String, - file_count: usize, - }, - ListSnapshots { - request_id: String, - tags: Option>, - }, - SnapshotList { - request_id: String, - snapshots: Vec, - }, - FindSnapshotByTag { - request_id: String, - tag: String, - }, - GetSnapshot { - request_id: String, - snapshot_id: String, - }, - SnapshotDetails { - request_id: String, - snapshot: Option, - }, - DeleteSnapshot { - request_id: String, - snapshot_id: String, - }, - SnapshotDeleted { - request_id: String, - }, - SnapshotError { - request_id: String, - error: String, - }, - Error { - message: String, - #[serde(default)] - recoverable: bool, - }, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DaemonMetadata { - pub hostname: String, - pub platform: String, - pub arch: String, - #[serde(default)] - pub version: String, - #[serde(default)] - pub labels: std::collections::HashMap, -} - -fn default_timeout() -> u64 { - 300 -} - -fn default_term() -> String { - "xterm-256color".to_string() -} - -mod base64_bytes { - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(v: &Vec, s: S) -> Result { - use base64::Engine; - let base64 = base64::engine::general_purpose::STANDARD.encode(v); - s.serialize_str(&base64) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { - use base64::Engine; - let base64 = String::deserialize(d)?; - base64::engine::general_purpose::STANDARD - .decode(base64.as_bytes()) - .map_err(serde::de::Error::custom) - } -} diff --git a/sandd/src/session.rs b/sandd/src/session.rs index ca267bf..7a24542 100644 --- a/sandd/src/session.rs +++ b/sandd/src/session.rs @@ -1,4 +1,4 @@ -use crate::protocol::Message; +use sandd_protocol::Message; use anyhow::{anyhow, Result}; use futures_util::SinkExt; use portable_pty::{native_pty_system, CommandBuilder, PtySize, PtySystem}; diff --git a/sandd/src/snapshot/types.rs b/sandd/src/snapshot/types.rs index 27697f9..1ae727b 100644 --- a/sandd/src/snapshot/types.rs +++ b/sandd/src/snapshot/types.rs @@ -1,12 +1,15 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; +// Re-export SnapshotInfo from shared protocol +pub use sandd_protocol::SnapshotInfo; + pub type SnapshotId = String; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Snapshot { pub id: SnapshotId, - pub created_at: u64, // Unix timestamp in seconds + pub created_at: u64, // Unix timestamp in seconds pub tree: String, pub message: String, pub tags: Vec, @@ -15,16 +18,6 @@ pub struct Snapshot { pub total_size: u64, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SnapshotInfo { - pub id: SnapshotId, - pub created_at: u64, // Unix timestamp in seconds - pub message: String, - pub tags: Vec, - pub file_count: usize, - pub total_size: u64, -} - impl From for SnapshotInfo { fn from(snapshot: Snapshot) -> Self { Self { diff --git a/server/Cargo.toml b/server/Cargo.toml index bad910b..1183871 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -12,6 +12,7 @@ name = "sandbox_server" crate-type = ["cdylib", "rlib"] [dependencies] +sandd-protocol = { path = "../protocol" } tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/server/src/lib.rs b/server/src/lib.rs index 0abf94b..d788f31 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -2,7 +2,7 @@ #![allow(dead_code)] #![allow(non_local_definitions)] -mod protocol; +// Use shared protocol crate mod registry; mod server; @@ -18,7 +18,7 @@ use tokio::sync::oneshot; use tracing_subscriber; use uuid::Uuid; -use protocol::Message; +use sandd_protocol::Message; use registry::DaemonRegistry; use server::SandboxServer; diff --git a/server/src/registry.rs b/server/src/registry.rs index fd8cda2..dd28977 100644 --- a/server/src/registry.rs +++ b/server/src/registry.rs @@ -1,4 +1,4 @@ -use crate::protocol::{DaemonMetadata, Message}; +use sandd_protocol::{DaemonMetadata, Message}; use anyhow::{anyhow, Result}; use dashmap::DashMap; use std::sync::atomic::{AtomicU64, Ordering}; diff --git a/server/src/server.rs b/server/src/server.rs index 324145b..38c765f 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1,4 +1,4 @@ -use crate::protocol::Message; +use sandd_protocol::Message; use crate::registry::{DaemonConnection, DaemonRegistry}; use anyhow::{Context, Result}; use axum::{