diff options
| author | murilo ijanc | 2026-03-25 02:07:37 -0300 |
|---|---|---|
| committer | murilo ijanc | 2026-03-25 02:07:37 -0300 |
| commit | 7aff2e1d279a4e442b32f49ca0a0eca065355787 (patch) | |
| tree | bc987ece7eb78bb8375de1b20123ecd0f90472ba /src/protocol.rs | |
| download | tesseras-paste-7aff2e1d279a4e442b32f49ca0a0eca065355787.tar.gz | |
Initial commit: tesseras-paste decentralized pastebin
DHT-backed encrypted pastebin with two binaries (tp/tpd),
XChaCha20-Poly1305 encryption, content-addressed storage,
and Unix socket + HTTP interfaces.
Diffstat (limited to 'src/protocol.rs')
| -rw-r--r-- | src/protocol.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..d45cdd8 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,173 @@ +//! Unix socket protocol for daemon ↔ CLI. +//! +//! Simple line-oriented text protocol: +//! PUT <ttl_secs> <content>\n +//! PUTP <ttl_secs> <content>\n +//! GET <key>\n +//! DEL <key>\n +//! PIN <key>\n +//! UNPIN <key>\n +//! STATUS\n +//! SHUTDOWN\n +//! +//! Responses: +//! OK <data>\n +//! ERR <message>\n + +/// A parsed request received from the CLI over the Unix socket. +pub enum Request { + Put { + ttl_secs: u64, + content_b58: String, + encrypt: bool, + }, + Get { + key: String, + }, + Del { + key: String, + }, + Pin { + key: String, + }, + Unpin { + key: String, + }, + Status, + Shutdown, +} + +/// A response sent back to the CLI over the Unix socket. +pub enum Response { + Ok(String), + Err(String), +} + +/// Parse a single protocol line into a [`Request`]. +pub fn parse_request(line: &str) -> Result<Request, String> { + let line = line.trim(); + if line.is_empty() { + return Err("empty request".into()); + } + + let mut parts = line.splitn(3, ' '); + let cmd = parts.next().unwrap(); + + match cmd { + "PUT" | "PUTP" => { + let ttl_str = + parts.next().ok_or("PUT requires: PUT <ttl> <data>")?; + let content_b58 = + parts.next().ok_or("PUT requires: PUT <ttl> <data>")?; + let ttl_secs: u64 = + ttl_str.parse().map_err(|_| "invalid TTL number")?; + Ok(Request::Put { + ttl_secs, + content_b58: content_b58.to_string(), + encrypt: cmd == "PUT", + }) + } + "GET" => { + let key = parts.next().ok_or("GET requires: GET <key>")?; + Ok(Request::Get { + key: key.to_string(), + }) + } + "DEL" => { + let key = parts.next().ok_or("DEL requires: DEL <key>")?; + Ok(Request::Del { + key: key.to_string(), + }) + } + "PIN" => { + let key = parts.next().ok_or("PIN requires: PIN <key>")?; + Ok(Request::Pin { + key: key.to_string(), + }) + } + "UNPIN" => { + let key = parts.next().ok_or("UNPIN requires: UNPIN <key>")?; + Ok(Request::Unpin { + key: key.to_string(), + }) + } + "STATUS" => Ok(Request::Status), + "SHUTDOWN" => Ok(Request::Shutdown), + _ => Err(format!("unknown command: {cmd}")), + } +} + +/// Serialize a [`Response`] into a protocol line (`OK ...\n` or `ERR ...\n`). +pub fn format_response(r: &Response) -> String { + match r { + Response::Ok(data) => format!("OK {data}\n"), + Response::Err(msg) => format!("ERR {msg}\n"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_put() { + let r = parse_request("PUT 3600 deadbeef").unwrap(); + match r { + Request::Put { + ttl_secs, + content_b58, + encrypt, + } => { + assert_eq!(ttl_secs, 3600); + assert_eq!(content_b58, "deadbeef"); + assert!(encrypt); + } + _ => panic!("expected Put"), + } + } + + #[test] + fn parse_putp() { + let r = parse_request("PUTP 60 abc").unwrap(); + match r { + Request::Put { encrypt, .. } => assert!(!encrypt), + _ => panic!("expected Put"), + } + } + + #[test] + fn parse_get() { + let r = parse_request("GET abc123#key456").unwrap(); + match r { + Request::Get { key } => assert_eq!(key, "abc123#key456"), + _ => panic!("expected Get"), + } + } + + #[test] + fn parse_status() { + assert!(matches!(parse_request("STATUS").unwrap(), Request::Status)); + } + + #[test] + fn parse_empty_fails() { + assert!(parse_request("").is_err()); + } + + #[test] + fn parse_unknown_fails() { + assert!(parse_request("FOOBAR").is_err()); + } + + #[test] + fn format_ok() { + let r = format_response(&Response::Ok("hello".into())); + assert_eq!(r, "OK hello\n"); + } + + #[test] + fn format_err() { + let r = format_response(&Response::Err("oops".into())); + assert_eq!(r, "ERR oops\n"); + } +} |