Initial implementation: relay server + common protocol + client stub

This commit is contained in:
Helios 2026-03-02 18:03:46 +01:00
commit 7285a33cff
No known key found for this signature in database
GPG key ID: C8259547CD8309B5
17 changed files with 926 additions and 0 deletions

View file

@ -0,0 +1,118 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// Messages sent from the relay server to a connected client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerMessage {
/// Request a screenshot from the client
ScreenshotRequest { request_id: Uuid },
/// Execute a shell command on the client
ExecRequest {
request_id: Uuid,
command: String,
},
/// Simulate a mouse click
ClickRequest {
request_id: Uuid,
x: i32,
y: i32,
button: MouseButton,
},
/// Type text on the client
TypeRequest {
request_id: Uuid,
text: String,
},
/// Acknowledge a client message
Ack { request_id: Uuid },
/// Server-side error response
Error {
request_id: Option<Uuid>,
message: String,
},
}
/// Messages sent from the client to the relay server
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ClientMessage {
/// Client registers itself with optional display name
Hello { label: Option<String> },
/// Response to a screenshot request — base64-encoded PNG
ScreenshotResponse {
request_id: Uuid,
image_base64: String,
width: u32,
height: u32,
},
/// Response to an exec request
ExecResponse {
request_id: Uuid,
stdout: String,
stderr: String,
exit_code: i32,
},
/// Generic acknowledgement for click/type
Ack { request_id: Uuid },
/// Client error response
Error {
request_id: Uuid,
message: String,
},
}
/// Mouse button variants
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MouseButton {
Left,
Right,
Middle,
}
impl Default for MouseButton {
fn default() -> Self {
MouseButton::Left
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_server_message_serialization() {
let msg = ServerMessage::ExecRequest {
request_id: Uuid::nil(),
command: "echo hello".into(),
};
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("exec_request"));
assert!(json.contains("echo hello"));
}
#[test]
fn test_client_message_serialization() {
let msg = ClientMessage::Hello { label: Some("test-pc".into()) };
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("hello"));
assert!(json.contains("test-pc"));
}
#[test]
fn test_roundtrip() {
let msg = ClientMessage::ExecResponse {
request_id: Uuid::nil(),
stdout: "hello\n".into(),
stderr: String::new(),
exit_code: 0,
};
let json = serde_json::to_string(&msg).unwrap();
let decoded: ClientMessage = serde_json::from_str(&json).unwrap();
match decoded {
ClientMessage::ExecResponse { exit_code, .. } => assert_eq!(exit_code, 0),
_ => panic!("wrong variant"),
}
}
}