Skip to main content

st_protocol/
lib.rs

1//! ST Protocol - Binary Wire Protocol for Smart Tree Daemon
2//!
3//! A tight, 6502-inspired binary protocol using control ASCII (0x00-0x1F) as opcodes.
4//! No JSON in the core path. Every byte means something.
5//!
6//! ## Frame Format
7//!
8//! ```text
9//! ┌──────┬─────────────────┬──────┐
10//! │ verb │     payload     │ 0x00 │
11//! │ 1B   │   N bytes       │ END  │
12//! └──────┴─────────────────┴──────┘
13//! ```
14//!
15//! ## Escape Sequences
16//!
17//! - `0x1B 0x1B` = literal `0x1B` in payload
18//! - `0x1B 0x00` = literal `0x00` in payload
19//!
20//! ## Network Addressing
21//!
22//! Single byte prefix for routing:
23//! - `0x00` = local daemon (Unix socket)
24//! - `0x01-0x7F` = cached host index
25//! - `0x80-0xFE` = inline address (len = byte - 0x80)
26//! - `0xFF` = broadcast/discover
27
28#![cfg_attr(not(feature = "std"), no_std)]
29
30#[cfg(feature = "alloc")]
31extern crate alloc;
32
33#[cfg(feature = "std")]
34extern crate std as alloc;
35
36mod verb;
37mod frame;
38mod payload;
39mod address;
40mod error;
41mod auth;
42
43pub use verb::Verb;
44pub use frame::{Frame, FrameBuilder};
45pub use payload::{Payload, PayloadEncoder, PayloadDecoder};
46pub use address::{Address, AddressString, HostCache};
47pub use error::{ProtocolError, ProtocolResult};
48pub use auth::{AuthLevel, AuthBlock, SecurityContext, SessionId, Signature};
49pub use auth::{is_protected_path, path_auth_level, PROTECTED_PATHS};
50
51/// Protocol version
52pub const VERSION: u8 = 1;
53
54/// End of frame marker
55pub const END: u8 = 0x00;
56
57/// Escape byte
58pub const ESC: u8 = 0x1B;
59
60/// Maximum payload length (64KB)
61pub const MAX_PAYLOAD_LEN: usize = 65535;
62
63/// Maximum frame size including overhead
64pub const MAX_FRAME_SIZE: usize = MAX_PAYLOAD_LEN + 4;
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_ping_frame() {
72        // PING is just ENQ + END = 2 bytes
73        let frame = Frame::new(Verb::Ping, Payload::empty());
74        let encoded = frame.encode();
75        assert_eq!(encoded, vec![0x05, 0x00]);
76    }
77
78    #[test]
79    fn test_scan_frame() {
80        // SCAN /home/hue depth=3
81        let mut payload = Payload::new();
82        payload.push_str("/home/hue");
83        payload.push_byte(3); // depth
84
85        let frame = Frame::new(Verb::Scan, payload);
86        let encoded = frame.encode();
87
88        // Verify structure
89        assert_eq!(encoded[0], 0x01); // SCAN verb
90        assert_eq!(encoded[encoded.len() - 1], 0x00); // END marker
91    }
92
93    #[test]
94    fn test_escape_sequence() {
95        // Payload containing literal 0x00 and 0x1B
96        let mut payload = Payload::new();
97        payload.push_byte(0x00); // Should become 0x1B 0x00
98        payload.push_byte(0x1B); // Should become 0x1B 0x1B
99        payload.push_byte(0x42); // Normal byte
100
101        let encoded = payload.encode();
102        assert_eq!(encoded, vec![0x1B, 0x00, 0x1B, 0x1B, 0x42]);
103    }
104
105    #[test]
106    fn test_roundtrip() {
107        let original = Frame::new(Verb::Search, Payload::from_string("*.rs"));
108        let encoded = original.encode();
109        let decoded = Frame::decode(&encoded).unwrap();
110
111        assert_eq!(decoded.verb(), original.verb());
112        assert_eq!(decoded.payload().as_bytes(), original.payload().as_bytes());
113    }
114}