Working network test

This commit is contained in:
Michael Mikovsky
2026-04-22 13:12:25 -06:00
parent cd301dea67
commit d0ca53f5c7
9 changed files with 536 additions and 52 deletions
+77 -10
View File
@@ -114,9 +114,17 @@ impl Cli {
///
/// # Returns
/// List of child node names
pub fn list_nodes(&self, path: Option<&str>) -> Result<Vec<String>, String> {
let path = path.unwrap_or(&self.current_path);
self.tree.list_nodes(path)
pub fn list_nodes(&mut self, path: Option<&str>) -> Result<Vec<String>, 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<Vec<crate::protocol::EndpointInfo>, 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<String> {
self.tree.list_leaves()
pub fn list_leaves(&mut self) -> Vec<String> {
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<crate::protocol::NodeInfo, String> {
self.tree.get_info(path)
pub fn get_info(&mut self, path: &str) -> Result<crate::protocol::NodeInfo, String> {
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.
+2
View File
@@ -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;
+106
View File
@@ -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<Box<crate::tree::Tree>>,
}
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<Box<crate::tree::Tree>>) -> 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<TreeResponse, String> {
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<u16> {
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
}
}
+32 -6
View File
@@ -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<Mutex<Tree>>) {
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<Mutex<Tree>>) {
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 {} => {
+112 -35
View File
@@ -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<String> {
let segments: Vec<String> = 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<EndpointInfo> {
let segments: Vec<String> = 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<EndpointInfo> {
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<String> {
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<Mutex<Box<dyn Endpoint>>>, &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<Vec<String>, 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<Vec<EndpointInfo>, 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<String> {
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<String> {
let segments: Vec<String> = 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<EndpointInfo> {
let segments: Vec<String> = 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<NodeInfo, String> {