diff --git a/Cargo.lock b/Cargo.lock index 4ae491f..c85e92d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,6 +444,13 @@ dependencies = [ "libc", ] +[[package]] +name = "no-alloc-network-test" +version = "0.1.0" +dependencies = [ + "libc", +] + [[package]] name = "num-traits" version = "0.2.19" diff --git a/Cargo.toml b/Cargo.toml index f397e5a..7319f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ # Libraries "ush-obfuscate", - "base62", + "base62", "no-alloc-network-test", ] resolver = "2" diff --git a/no-alloc-network-test/Cargo.toml b/no-alloc-network-test/Cargo.toml new file mode 100644 index 0000000..cfd5eb2 --- /dev/null +++ b/no-alloc-network-test/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "no-alloc-network-test" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +include.workspace = true + +[dependencies] +libc = "0.2" + +[lints] +workspace = true \ No newline at end of file diff --git a/no-alloc-network-test/src/main.rs b/no-alloc-network-test/src/main.rs new file mode 100644 index 0000000..4f935da --- /dev/null +++ b/no-alloc-network-test/src/main.rs @@ -0,0 +1,185 @@ +//! # TCP Network Stack using Raw Syscalls +//! +//! A TCP server using raw syscalls via libc - no std::net, no smoltcp. +//! +//! ## Usage +//! ```bash +//! cargo run +//! nc 127.0.0.1 1337 +//! ``` + +use libc::{accept, bind, listen, socket, sockaddr_in, socklen_t, read, write, close, AF_INET, SOCK_STREAM}; +use core::mem::zeroed; + +const PORT: u16 = 1337; +const BACKLOG: i32 = 128; + +fn main() -> Result<(), NetError> { + let server_fd = create_socket()?; + bind_socket(server_fd, PORT)?; + listen_socket(server_fd, BACKLOG)?; + + println!("TCP Server listening on port {}", PORT); + println!("Connect with: nc 127.0.0.1 {}", PORT); + + let mut counter: u32 = 0; + + loop { + match accept_client(server_fd) { + Ok(client_fd) => { + counter += 1; + let message = make_packet(counter); + + unsafe { + let _ = write(client_fd, message.as_bytes().as_ptr() as *const libc::c_void, message.len()); + } + + let mut buf = [0u8; 1024]; + loop { + let n = unsafe { + read(client_fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) + }; + if n <= 0 { + break; + } + } + + unsafe { + close(client_fd); + } + } + Err(_) => continue, + } + } +} + +fn create_socket() -> Result { + let fd = unsafe { + socket(AF_INET, SOCK_STREAM, 0) + }; + if fd < 0 { + return Err(NetError::DeviceError); + } + Ok(fd) +} + +fn bind_socket(fd: i32, port: u16) -> Result<(), NetError> { + let addr = build_sockaddr(port); + let result = unsafe { + bind(fd, &addr as *const sockaddr_in as *const libc::sockaddr, std::mem::size_of::() as socklen_t) + }; + if result < 0 { + return Err(NetError::BindFailed); + } + Ok(()) +} + +fn listen_socket(fd: i32, backlog: i32) -> Result<(), NetError> { + let result = unsafe { listen(fd, backlog) }; + if result < 0 { + return Err(NetError::BindFailed); + } + Ok(()) +} + +fn accept_client(server_fd: i32) -> Result { + let mut client_addr: sockaddr_in = unsafe { zeroed() }; + let mut client_len: socklen_t = std::mem::size_of::() as socklen_t; + + let client_fd = unsafe { + accept(server_fd, &mut client_addr as *mut sockaddr_in as *mut libc::sockaddr, &mut client_len) + }; + + if client_fd < 0 { + return Err(NetError::NoConnection); + } + Ok(client_fd) +} + +fn build_sockaddr(port: u16) -> sockaddr_in { + sockaddr_in { + sin_family: libc::AF_INET as u16, + sin_port: port.to_be(), + sin_addr: libc::in_addr { + s_addr: u32::from_le_bytes([127, 0, 0, 1]), + }, + sin_zero: [0; 8], + } +} + +#[derive(Debug, Clone, Copy)] +pub enum NetError { + NoConnection, + BindFailed, + DeviceError, +} + +impl core::fmt::Display for NetError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::NoConnection => write!(f, "No connection"), + Self::BindFailed => write!(f, "Bind failed"), + Self::DeviceError => write!(f, "Device error"), + } + } +} + +fn make_packet(n: u32) -> SmallStr<16> { + let mut result = SmallStr::<16>::new(); + result.push_slice(b"Packet #"); + push_number(&mut result, n); + result.push(b'\n'); + result +} + +fn push_number(s: &mut SmallStr<16>, n: u32) { + if n == 0 { + s.push(b'0'); + return; + } + let mut digits = [0u8; 10]; + let mut len = 0; + let mut num = n; + while num > 0 { + digits[len] = b'0' + (num % 10) as u8; + len += 1; + num /= 10; + } + for i in (0..len).rev() { + s.push(digits[i]); + } +} + +pub struct SmallStr { + data: [u8; N], + len: usize, +} + +impl SmallStr { + pub const fn new() -> Self { + Self { data: [0u8; N], len: 0 } + } + pub fn push(&mut self, byte: u8) { + if self.len < N { + self.data[self.len] = byte; + self.len += 1; + } + } + pub fn push_slice(&mut self, bytes: &[u8]) { + for &byte in bytes { + self.push(byte); + } + } + pub fn as_bytes(&self) -> &[u8] { + &self.data[..self.len] + } + pub fn len(&self) -> usize { + self.len + } +} + +impl Default for SmallStr { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/ush-treetest/src/cli/cli.rs b/ush-treetest/src/cli/cli.rs index 546f0ad..2bdbf95 100644 --- a/ush-treetest/src/cli/cli.rs +++ b/ush-treetest/src/cli/cli.rs @@ -114,9 +114,17 @@ impl Cli { /// /// # Returns /// List of child node names - pub fn list_nodes(&self, path: Option<&str>) -> Result, String> { - let path = path.unwrap_or(&self.current_path); - self.tree.list_nodes(path) + pub fn list_nodes(&mut self, path: Option<&str>) -> Result, String> { + let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone()); + if self.is_connected() && self.is_remote_path(&path) { + let response = self.send_request(&path, &TreeRequest::ListNodes {})?; + match response { + TreeResponse::NodeList { names } => Ok(names), + _ => Err("unexpected response type".to_string()), + } + } else { + self.tree.list_nodes(&path) + } } /// List endpoints at a path. @@ -124,21 +132,80 @@ impl Cli { /// # Arguments /// * `path` - Optional path (defaults to current path) pub fn list_endpoints( - &self, + &mut self, path: Option<&str>, ) -> Result, String> { - let path = path.unwrap_or(&self.current_path); - self.tree.list_endpoints(path) + let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone()); + if self.is_connected() && self.is_remote_path(&path) { + let response = self.send_request(&path, &TreeRequest::ListEndpoints {})?; + match response { + TreeResponse::EndpointList { endpoints } => Ok(endpoints), + _ => Err("unexpected response type".to_string()), + } + } else { + self.tree.list_endpoints(&path) + } } /// List all leaf paths. - pub fn list_leaves(&self) -> Vec { - self.tree.list_leaves() + pub fn list_leaves(&mut self) -> Vec { + if self.is_connected() { + let response = match self.send_request("/", &TreeRequest::ListLeaves {}) { + Ok(r) => r, + Err(_) => return self.tree.list_leaves(), + }; + match response { + TreeResponse::LeafList { leaves } => leaves.into_iter().map(|p| self.normalize_path(&p)).collect(), + _ => self.tree.list_leaves(), + } + } else { + self.tree.list_leaves() + } } /// Get info about a node. - pub fn get_info(&self, path: &str) -> Result { - self.tree.get_info(path) + pub fn get_info(&mut self, path: &str) -> Result { + let path_owned = path.to_string(); + if self.is_connected() && self.is_remote_path(&path_owned) { + let response = self.send_request(&path_owned, &TreeRequest::GetInfo { path: path_owned.clone() })?; + match response { + TreeResponse::NodeInfo { info } => Ok(info), + _ => Err("unexpected response type".to_string()), + } + } else { + self.tree.get_info(path) + } + } + + /// Check if a path is a remote path (not local). + /// + /// A path is remote if it's not the base_path assigned to this client. + fn is_remote_path(&self, path: &str) -> bool { + path != self.base_path && !path.starts_with(&self.base_path) + } + + /// Normalize a path by removing double slashes. + /// + /// # Example + /// ``` + /// assert_eq!(normalize_path("//shell"), "/shell"); + /// assert_eq!(normalize_path("/foo//bar"), "/foo/bar"); + /// ``` + fn normalize_path(&self, path: &str) -> String { + let mut result = String::new(); + let mut prev_slash = false; + for c in path.chars() { + if c == '/' { + if !prev_slash { + result.push(c); + } + prev_slash = true; + } else { + result.push(c); + prev_slash = false; + } + } + result } /// Execute a command locally on the tree. diff --git a/ush-treetest/src/leaves/mod.rs b/ush-treetest/src/leaves/mod.rs index 5e15fc9..c3fd49b 100644 --- a/ush-treetest/src/leaves/mod.rs +++ b/ush-treetest/src/leaves/mod.rs @@ -1,7 +1,9 @@ //! # Leaves Module +pub mod proxy; pub mod shell; pub mod tty; +pub use proxy::ProxyEndpoint; pub use shell::RemoteShell; pub use tty::TTY; \ No newline at end of file diff --git a/ush-treetest/src/leaves/proxy.rs b/ush-treetest/src/leaves/proxy.rs new file mode 100644 index 0000000..77b6c3d --- /dev/null +++ b/ush-treetest/src/leaves/proxy.rs @@ -0,0 +1,106 @@ +//! # Proxy Endpoint +//! +//! This module provides a proxy endpoint that routes to child nodes for ls/info operations. + +#[allow(unused_imports)] +use crate::protocol::{TreeRequest, TreeResponse, EndpointType}; +#[allow(unused_imports)] +use crate::tree::Endpoint; +use std::string::String; + +/// A proxy endpoint that routes to children for ls/info operations. +/// +/// This endpoint is used at the root ("/") to allow traversal to child endpoints +/// like /shell and /tty when there's no direct handler at the root. +#[derive(Debug)] +pub struct ProxyEndpoint { + name: String, + #[allow(dead_code)] + tree: Option>, +} + +impl ProxyEndpoint { + /// Create a new proxy endpoint. + /// + /// # Arguments + /// * `name` - The name of this endpoint (typically "proxy") + /// * `tree` - Optional tree to proxy to + /// + /// # Example + /// ``` + /// let proxy = ProxyEndpoint::new("proxy", None); + /// ``` + #[allow(dead_code)] + pub fn new(name: &str, _tree: Option>) -> Self { + Self { + name: name.to_string(), + tree: None, + } + } + + /// Create a proxy endpoint with an empty tree. + pub fn new_empty(name: &str) -> Self { + Self { + name: name.to_string(), + tree: None, + } + } +} + +impl crate::tree::Endpoint for ProxyEndpoint { + fn handle_request(&mut self, request: &TreeRequest, _src_path: &str) -> Result { + match request { + TreeRequest::ListNodes { .. } => { + if let Some(ref tree) = self.tree { + let names = tree.list_nodes("/").unwrap_or_default(); + Ok(TreeResponse::NodeList { names }) + } else { + Ok(TreeResponse::NodeList { names: vec![] }) + } + } + TreeRequest::ListEndpoints { .. } => { + if let Some(ref tree) = self.tree { + let endpoints = tree.list_endpoints("/").unwrap_or_default(); + Ok(TreeResponse::EndpointList { endpoints }) + } else { + Ok(TreeResponse::EndpointList { endpoints: vec![] }) + } + } + TreeRequest::ListLeaves { .. } => { + if let Some(ref tree) = self.tree { + let leaves = tree.list_leaves(); + Ok(TreeResponse::LeafList { leaves }) + } else { + Ok(TreeResponse::LeafList { leaves: vec![] }) + } + } + TreeRequest::GetInfo { path } => { + if let Some(ref tree) = self.tree { + let info = tree.get_info(path)?; + Ok(TreeResponse::NodeInfo { info }) + } else { + Err("no tree available".to_string()) + } + } + _ => Err("unsupported request on proxy".to_string()), + } + } + + fn on_stream_open(&mut self, stream_id: u16, _src_path: &str) -> Option { + Some(stream_id) + } + + fn on_stream_data(&mut self, _stream_id: u16, _data: &[u8]) -> bool { + false + } + + fn on_stream_close(&mut self, _stream_id: u16) {} + + fn endpoint_type(&self) -> EndpointType { + EndpointType::Proxy + } + + fn name(&self) -> &str { + &self.name + } +} \ No newline at end of file diff --git a/ush-treetest/src/server.rs b/ush-treetest/src/server.rs index fa0c39f..bb000b1 100644 --- a/ush-treetest/src/server.rs +++ b/ush-treetest/src/server.rs @@ -3,12 +3,16 @@ //! This module provides the server functionality for handling incoming connections. use crate::protocol::{ - FrameHeader, FrameType, TreeRequest, TreeResponse, TcpTransport, Transport, + FrameHeader, FrameType, Handshake, TreeRequest, TreeResponse, TcpTransport, Transport, make_response, make_handshake_ack, }; use crate::tree::Tree; -use crate::leaves::{RemoteShell, TTY}; +use crate::leaves::{ProxyEndpoint, RemoteShell, TTY}; use std::sync::{Arc, Mutex}; +use std::sync::atomic::{AtomicU32, Ordering}; + +/// Global client counter for assigning unique base paths. +static CLIENT_COUNT: AtomicU32 = AtomicU32::new(0); /// Default listening address for the server. /// @@ -38,6 +42,7 @@ pub fn run_server(addr: &str) -> ! { let tree = Arc::new(Mutex::new(Tree::new())); { let mut tree = tree.lock().unwrap(); + tree.add_endpoint("/", Box::new(ProxyEndpoint::new_empty("proxy"))); tree.add_endpoint("/shell", Box::new(RemoteShell::new("shell"))); tree.add_endpoint("/tty", Box::new(TTY::new("tty"))); } @@ -70,7 +75,7 @@ pub fn run_server(addr: &str) -> ! { /// * `transport` - The TCP transport for the connection /// * `tree` - Shared access to the tree pub fn handle_connection(mut transport: TcpTransport, tree: Arc>) { - let (header, _payload) = match transport.recv_frame() { + let (header, payload) = match transport.recv_frame() { Ok(h) => h, Err(e) => { log::error!("recv error: {:?}", e); @@ -85,7 +90,28 @@ pub fn handle_connection(mut transport: TcpTransport, tree: Arc>) { log::info!("Client connected"); - let (ack_header, ack_payload) = make_handshake_ack(true, "/client"); + let base_path = if payload.is_empty() { + let client_num = CLIENT_COUNT.fetch_add(1, Ordering::SeqCst); + format!("/client_{}", client_num) + } else { + let handshake = match Handshake::from_bytes(&payload) { + Ok(h) => h, + Err(e) => { + log::error!("handshake parse error: {}", e); + return; + } + }; + let client_num = CLIENT_COUNT.fetch_add(1, Ordering::SeqCst); + if handshake.registered_paths.is_empty() { + format!("/client_{}", client_num) + } else { + handshake.registered_paths.first().cloned().unwrap_or_else(|| { + format!("/client_{}", client_num) + }) + } + }; + + let (ack_header, ack_payload) = make_handshake_ack(true, &base_path); transport.send_frame(&ack_header, Some(&ack_payload)).expect("send failed"); loop { @@ -168,11 +194,11 @@ pub fn handle_frame( let response = match request { TreeRequest::ListNodes {} => { - let names = tree.list_nodes(dst_path).unwrap_or_default(); + let names = tree.list_nodes_at(dst_path); TreeResponse::NodeList { names } } TreeRequest::ListEndpoints {} => { - let endpoints = tree.list_endpoints(dst_path).unwrap_or_default(); + let endpoints = tree.list_endpoints_at(dst_path); TreeResponse::EndpointList { endpoints } } TreeRequest::ListLeaves {} => { diff --git a/ush-treetest/src/tree/mod.rs b/ush-treetest/src/tree/mod.rs index 8d2e810..1d16212 100644 --- a/ush-treetest/src/tree/mod.rs +++ b/ush-treetest/src/tree/mod.rs @@ -63,6 +63,52 @@ impl Node { self.children.keys().cloned().collect() } + /// List child nodes at a given path - traverses directly without find_handler + /// + /// Works even without endpoint at the path (like Linux directories). + /// + /// # Arguments + /// * `path` - The path to list children at (e.g., "/" or "/shell") + /// + /// # Returns + /// List of child node names, or empty list if path not found + pub fn list_nodes_at(&self, path: &str) -> Vec { + let segments: Vec = path + .split('/') + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + let mut current = self; + for seg in &segments { + if let Some(child) = current.children.get(seg) { + current = child; + } else { + return vec![]; + } + } + current.child_names() + } + + /// List endpoints at a given path - traverses directly without find_handler + /// + /// Works even without endpoint at the path (like Linux directories). + pub fn list_endpoints_at(&self, path: &str) -> Vec { + let segments: Vec = path + .split('/') + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + let mut current = self; + for seg in &segments { + if let Some(child) = current.children.get(seg) { + current = child; + } else { + return vec![]; + } + } + current.endpoint_names() + } + /// Get all endpoints at this node and in children pub fn endpoint_names(&self) -> Vec { let mut endpoints = Vec::new(); @@ -91,19 +137,23 @@ impl Node { /// Get all leaf paths (nodes with endpoint but no children) pub fn leaf_paths(&self) -> Vec { let mut paths = Vec::new(); - + if self.endpoint.is_some() && self.children.is_empty() { paths.push(self.path.clone()); } - + for (name, child) in &self.children { let mut child_leaves = child.leaf_paths(); for path in &mut child_leaves { - *path = format!("{}/{}", self.path, name); + *path = if self.path == "/" { + format!("/{}", name) + } else { + format!("{}/{}", self.path, name) + }; paths.push(path.clone()); } } - + paths } @@ -121,7 +171,7 @@ impl Node { /// Tree structure for routing - contains the root node. #[allow(dead_code)] pub struct Tree { - root: Node, + pub root: Node, } impl fmt::Debug for Tree { @@ -186,7 +236,7 @@ impl Tree { /// Returns the endpoint and the matched path pub fn find_handler(&self, path: &str) -> Option<(Arc>>, &str)> { if path == "/" { - return self.root.endpoint.as_ref().map(|e| (e.clone(), "")); + return self.root.endpoint.as_ref().map(|e| (e.clone(), "/")); } let segments = path_segments(path); @@ -207,44 +257,71 @@ impl Tree { current.endpoint.as_ref().map(|e| (e.clone(), handler_path)) } - /// List child nodes at a given path + /// List child nodes at a given path using direct tree traversal. + /// + /// Unlike `find_handler()`, this works even without an endpoint at the path, + /// making "/" and other directories traversable like Linux directories. + /// + /// # Example + /// ``` + /// let tree = Tree::new(); + /// tree.add_endpoint("/shell", Box::new(RemoteShell::new("shell"))); + /// let names = tree.list_nodes("/").unwrap(); // ["shell"] + /// ``` pub fn list_nodes(&self, path: &str) -> Result, String> { - let (_, matched_path) = self.find_handler(path) - .ok_or_else(|| format!("path not found: {}", path))?; - - let segments = path_segments(matched_path); - let mut current = &self.root; - - for segment in &segments { - if let Some(child) = current.children.get(segment) { - current = child; - } - } - - Ok(current.child_names()) + // Use direct traversal - works without endpoint + let names = self.root.list_nodes_at(path); + Ok(names) } - /// List all endpoints at a given path + /// List all endpoints at a given path. + /// + /// Works even without endpoint at the path. pub fn list_endpoints(&self, path: &str) -> Result, String> { - let (_, matched_path) = self.find_handler(path) - .ok_or_else(|| format!("path not found: {}", path))?; - - let segments = path_segments(matched_path); - let mut current = &self.root; - - for segment in &segments { - if let Some(child) = current.children.get(segment) { - current = child; - } - } - - Ok(current.endpoint_names()) + let endpoints = self.root.list_endpoints_at(path); + Ok(endpoints) } - /// List all leaf paths in the tree + /// List all leaf paths in the tree. pub fn list_leaves(&self) -> Vec { self.root.leaf_paths() } + + /// List child nodes at a given path - traverses directly without find_handler + pub fn list_nodes_at(&self, path: &str) -> Vec { + let segments: Vec = path + .split('/') + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + let mut current = &self.root; + for seg in &segments { + if let Some(child) = current.children.get(seg) { + current = child; + } else { + return vec![]; + } + } + current.child_names() + } + + /// List endpoints at a given path - traverses directly without find_handler + pub fn list_endpoints_at(&self, path: &str) -> Vec { + let segments: Vec = path + .split('/') + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + let mut current = &self.root; + for seg in &segments { + if let Some(child) = current.children.get(seg) { + current = child; + } else { + return vec![]; + } + } + current.endpoint_names() + } /// Get information about a node at the given path pub fn get_info(&self, path: &str) -> Result {