Add TreeTest

This commit is contained in:
Michael Mikovsky
2026-04-22 10:03:24 -06:00
parent fcb3b2be17
commit 1af134104e
14 changed files with 2891 additions and 115 deletions
+7
View File
@@ -0,0 +1,7 @@
//! # Protocol Module
pub mod types;
pub mod transport;
pub use types::*;
pub use transport::*;
+241
View File
@@ -0,0 +1,241 @@
//! # Transport Layer
//!
//! This module provides the Transport trait and TCP implementation.
//! Uses a simple length-prefixed framing: [u32 header_len][header bytes][u32 payload_len][payload bytes]
use crate::protocol::types::*;
use std::net::{TcpStream, TcpListener};
use std::io::{Read, Write, Error};
pub trait Transport: Sized {
type Error: std::fmt::Debug;
/// Send a frame (header + optional payload)
fn send_frame(&mut self, header: &FrameHeader, payload: Option<&[u8]>) -> Result<(), Self::Error>;
/// Receive a frame
fn recv_frame(&mut self) -> Result<(FrameHeader, Vec<u8>), Self::Error>;
/// Close the transport
#[allow(dead_code)]
fn close(&mut self) -> Result<(), Self::Error>;
}
#[derive(Debug)]
pub enum TransportError {
ConnectionClosed,
InvalidFrame(String),
Io(String),
}
impl std::fmt::Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransportError::ConnectionClosed => write!(f, "connection closed"),
TransportError::InvalidFrame(s) => write!(f, "invalid frame: {}", s),
TransportError::Io(s) => write!(f, "I/O error: {}", s),
}
}
}
impl From<Error> for TransportError {
fn from(e: Error) -> Self { TransportError::Io(e.to_string()) }
}
/// TCP transport implementation
pub struct TcpTransport {
stream: TcpStream,
}
impl TcpTransport {
pub fn new(stream: TcpStream) -> Self {
// Set timeouts for safety
stream.set_read_timeout(Some(std::time::Duration::from_secs(30))).ok();
stream.set_write_timeout(Some(std::time::Duration::from_secs(30))).ok();
Self { stream }
}
/// Connect to a remote address
pub fn connect(addr: &str) -> Result<Self, TransportError> {
let stream = TcpStream::connect(addr)?;
Ok(Self::new(stream))
}
/// Create a listening socket
pub fn listen(addr: &str) -> Result<std::net::TcpListener, TransportError> {
let listener = TcpListener::bind(addr)?;
listener.set_nonblocking(false)?;
Ok(listener)
}
/// Accept an incoming connection
pub fn accept(listener: &std::net::TcpListener) -> Result<Self, TransportError> {
let stream = listener.accept()?.0;
Ok(Self::new(stream))
}
/// Get peer address
pub fn peer_addr(&self) -> Result<std::net::SocketAddr, std::io::Error> {
self.stream.peer_addr()
}
/// Read exactly n bytes
fn read_exact(&mut self, mut n: usize) -> Result<Vec<u8>, TransportError> {
let mut buf = Vec::with_capacity(n);
while n > 0 {
let mut chunk = vec![0u8; n];
let read = self.stream.read(&mut chunk).map_err(|e| TransportError::Io(e.to_string()))?;
if read == 0 {
return Err(TransportError::ConnectionClosed);
}
buf.extend_from_slice(&chunk[..read]);
n -= read;
}
Ok(buf)
}
}
impl Transport for TcpTransport {
type Error = TransportError;
fn send_frame(&mut self, header: &FrameHeader, payload: Option<&[u8]>) -> Result<(), Self::Error> {
// Serialize header using rkyv
let header_bytes = header.to_bytes();
let header_len = header_bytes.len() as u32;
// Get payload bytes
let payload_bytes = payload.unwrap_or(&[]);
let payload_len = payload_bytes.len() as u32;
// Build frame: [u32 header_len][header][u32 payload_len][payload]
let mut frame = Vec::with_capacity(4 + header_len as usize + 4 + payload_len as usize);
frame.extend_from_slice(&header_len.to_le_bytes());
frame.extend_from_slice(&header_bytes);
frame.extend_from_slice(&payload_len.to_le_bytes());
frame.extend_from_slice(payload_bytes);
self.stream.write_all(&frame).map_err(|e| TransportError::Io(e.to_string()))?;
self.stream.flush().map_err(|e| TransportError::Io(e.to_string()))?;
Ok(())
}
fn recv_frame(&mut self) -> Result<(FrameHeader, Vec<u8>), Self::Error> {
// Read header length
let header_len_bytes = self.read_exact(4)?;
let header_len = u32::from_le_bytes(header_len_bytes.try_into().unwrap()) as usize;
// Read header
let header_bytes = self.read_exact(header_len)?;
let header = FrameHeader::from_bytes(&header_bytes).map_err(|e| TransportError::InvalidFrame(e))?;
// Read payload length
let payload_len_bytes = self.read_exact(4)?;
let payload_len = u32::from_le_bytes(payload_len_bytes.try_into().unwrap()) as usize;
// Read payload
let payload = if payload_len > 0 {
self.read_exact(payload_len)?
} else {
Vec::new()
};
Ok((header, payload))
}
fn close(&mut self) -> Result<(), Self::Error> {
self.stream.shutdown(std::net::Shutdown::Both).map_err(|e| TransportError::Io(e.to_string()))?;
Ok(())
}
}
// =============================================================================
// Frame builder functions
// =============================================================================
/// Create a request frame
pub fn make_request(dst_path: &str, src_path: &str, request_id: u64, request: &TreeRequest) -> (FrameHeader, Vec<u8>) {
let header = FrameHeader {
frame_type: FrameType::Request,
dst_path: Some(dst_path.to_string()),
src_path: src_path.to_string(),
request_id: Some(request_id),
stream_id: None,
};
let payload = request.to_bytes();
(header, payload)
}
/// Create a response frame
pub fn make_response(src_path: &str, request_id: u64, response: &TreeResponse) -> (FrameHeader, Vec<u8>) {
let header = FrameHeader {
frame_type: FrameType::Response,
dst_path: None,
src_path: src_path.to_string(),
request_id: Some(request_id),
stream_id: None,
};
let payload = response.to_bytes();
(header, payload)
}
/// Create a stream open frame
pub fn make_stream_open(dst_path: &str, src_path: &str, request_id: u64) -> FrameHeader {
FrameHeader {
frame_type: FrameType::StreamOpen,
dst_path: Some(dst_path.to_string()),
src_path: src_path.to_string(),
request_id: Some(request_id),
stream_id: None,
}
}
/// Create a stream data frame
pub fn make_stream_data(stream_id: u16, data: &[u8]) -> (FrameHeader, Vec<u8>) {
let header = FrameHeader {
frame_type: FrameType::StreamData,
dst_path: None,
src_path: String::new(),
request_id: None,
stream_id: Some(stream_id),
};
(header, data.to_vec())
}
/// Create a stream close frame
pub fn make_stream_close(stream_id: u16) -> FrameHeader {
FrameHeader {
frame_type: FrameType::StreamClose,
dst_path: None,
src_path: String::new(),
request_id: None,
stream_id: Some(stream_id),
}
}
/// Create a handshake frame
pub fn make_handshake(registered_paths: Vec<String>) -> (FrameHeader, Vec<u8>) {
let handshake = Handshake { registered_paths };
let payload = handshake.to_bytes();
let header = FrameHeader {
frame_type: FrameType::Handshake,
dst_path: None,
src_path: String::new(),
request_id: None,
stream_id: None,
};
(header, payload)
}
/// Create a handshake ack frame
pub fn make_handshake_ack(accepted: bool, assigned_base_path: &str) -> (FrameHeader, Vec<u8>) {
let ack = HandshakeAck {
accepted,
assigned_base_path: assigned_base_path.to_string()
};
let payload = ack.to_bytes();
let header = FrameHeader {
frame_type: FrameType::HandshakeAck,
dst_path: None,
src_path: String::new(),
request_id: None,
stream_id: None,
};
(header, payload)
}
+162
View File
@@ -0,0 +1,162 @@
//! # Protocol Types
//!
//! This module defines the core types for the UnShell protocol.
//! Uses rkyv for zero-copy serialization.
use rkyv::{Archive, Serialize, Deserialize};
use std::string::String;
use std::vec::Vec;
const BUFFER_SIZE: usize = 4096;
/// Frame type enum - distinguishes between different frame kinds
#[derive(Archive, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FrameType {
Request = 0x01,
Response = 0x02,
StreamOpen = 0x03,
StreamData = 0x04,
StreamClose = 0x05,
Handshake = 0x10,
HandshakeAck = 0x11,
}
impl FrameType {
#[allow(dead_code)]
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0x01 => Some(Self::Request),
0x02 => Some(Self::Response),
0x03 => Some(Self::StreamOpen),
0x04 => Some(Self::StreamData),
0x05 => Some(Self::StreamClose),
0x10 => Some(Self::Handshake),
0x11 => Some(Self::HandshakeAck),
_ => None,
}
}
}
/// Frame header - the metadata sent before each payload
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct FrameHeader {
pub frame_type: FrameType,
pub dst_path: Option<String>,
pub src_path: String,
pub request_id: Option<u64>,
pub stream_id: Option<u16>,
}
impl FrameHeader {
pub fn to_bytes(&self) -> Vec<u8> {
rkyv::to_bytes::<FrameHeader, BUFFER_SIZE>(self).unwrap().into_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
unsafe { rkyv::from_bytes_unchecked(bytes) }.map_err(|e| e.to_string())
}
}
/// Tree request - operations on the tree
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub enum TreeRequest {
ListNodes {},
ListEndpoints {},
ListLeaves {},
GetInfo { path: String },
Exec { cmd: String },
StreamOpen { path: String },
Resize { rows: u16, cols: u16 },
}
impl TreeRequest {
pub fn to_bytes(&self) -> Vec<u8> {
rkyv::to_bytes::<TreeRequest, BUFFER_SIZE>(self).unwrap().into_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
unsafe { rkyv::from_bytes_unchecked(bytes) }.map_err(|e| e.to_string())
}
}
/// Tree response - results from tree operations
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub enum TreeResponse {
NodeList { names: Vec<String> },
EndpointList { endpoints: Vec<EndpointInfo> },
LeafList { leaves: Vec<String> },
NodeInfo { info: NodeInfo },
ExecOutput { exit_code: i32, stdout: Vec<u8>, stderr: Vec<u8> },
StreamOpened { stream_id: u16 },
}
impl TreeResponse {
pub fn to_bytes(&self) -> Vec<u8> {
rkyv::to_bytes::<TreeResponse, BUFFER_SIZE>(self).unwrap().into_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
unsafe { rkyv::from_bytes_unchecked(bytes) }.map_err(|e| e.to_string())
}
}
/// Information about an endpoint
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct EndpointInfo {
pub name: String,
pub path: String,
pub endpoint_type: EndpointType,
}
/// Type of endpoint
#[derive(Archive, Serialize, Deserialize, Debug, Clone, Copy)]
#[repr(u8)]
pub enum EndpointType {
Leaf = 0x01,
Proxy = 0x02,
Stream = 0x03,
}
/// Information about a node in the tree
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct NodeInfo {
pub path: String,
pub is_leaf: bool,
pub has_children: bool,
pub endpoints: Vec<String>,
}
/// Handshake message - sent when connecting
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct Handshake {
pub registered_paths: Vec<String>,
}
impl Handshake {
pub fn to_bytes(&self) -> Vec<u8> {
rkyv::to_bytes::<Handshake, BUFFER_SIZE>(self).unwrap().into_vec()
}
#[allow(dead_code)]
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
unsafe { rkyv::from_bytes_unchecked(bytes) }.map_err(|e| e.to_string())
}
}
/// Handshake acknowledgement - router's response to handshake
#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
pub struct HandshakeAck {
pub accepted: bool,
pub assigned_base_path: String,
}
impl HandshakeAck {
pub fn to_bytes(&self) -> Vec<u8> {
rkyv::to_bytes::<HandshakeAck, BUFFER_SIZE>(self).unwrap().into_vec()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
unsafe { rkyv::from_bytes_unchecked(bytes) }.map_err(|e| e.to_string())
}
}