mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Working network test
This commit is contained in:
Generated
+7
@@ -444,6 +444,13 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no-alloc-network-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ members = [
|
|||||||
|
|
||||||
# Libraries
|
# Libraries
|
||||||
"ush-obfuscate",
|
"ush-obfuscate",
|
||||||
"base62",
|
"base62", "no-alloc-network-test",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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<i32, NetError> {
|
||||||
|
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::<sockaddr_in>() 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<i32, NetError> {
|
||||||
|
let mut client_addr: sockaddr_in = unsafe { zeroed() };
|
||||||
|
let mut client_len: socklen_t = std::mem::size_of::<sockaddr_in>() 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<const N: usize> {
|
||||||
|
data: [u8; N],
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> SmallStr<N> {
|
||||||
|
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<const N: usize> Default for SmallStr<N> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
-10
@@ -114,9 +114,17 @@ impl Cli {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// List of child node names
|
/// List of child node names
|
||||||
pub fn list_nodes(&self, path: Option<&str>) -> Result<Vec<String>, String> {
|
pub fn list_nodes(&mut self, path: Option<&str>) -> Result<Vec<String>, String> {
|
||||||
let path = path.unwrap_or(&self.current_path);
|
let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone());
|
||||||
self.tree.list_nodes(path)
|
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.
|
/// List endpoints at a path.
|
||||||
@@ -124,21 +132,80 @@ impl Cli {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `path` - Optional path (defaults to current path)
|
/// * `path` - Optional path (defaults to current path)
|
||||||
pub fn list_endpoints(
|
pub fn list_endpoints(
|
||||||
&self,
|
&mut self,
|
||||||
path: Option<&str>,
|
path: Option<&str>,
|
||||||
) -> Result<Vec<crate::protocol::EndpointInfo>, String> {
|
) -> Result<Vec<crate::protocol::EndpointInfo>, String> {
|
||||||
let path = path.unwrap_or(&self.current_path);
|
let path = path.map(|p| p.to_string()).unwrap_or_else(|| self.current_path.clone());
|
||||||
self.tree.list_endpoints(path)
|
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.
|
/// List all leaf paths.
|
||||||
pub fn list_leaves(&self) -> Vec<String> {
|
pub fn list_leaves(&mut self) -> Vec<String> {
|
||||||
self.tree.list_leaves()
|
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.
|
/// Get info about a node.
|
||||||
pub fn get_info(&self, path: &str) -> Result<crate::protocol::NodeInfo, String> {
|
pub fn get_info(&mut self, path: &str) -> Result<crate::protocol::NodeInfo, String> {
|
||||||
self.tree.get_info(path)
|
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.
|
/// Execute a command locally on the tree.
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
//! # Leaves Module
|
//! # Leaves Module
|
||||||
|
|
||||||
|
pub mod proxy;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
pub mod tty;
|
pub mod tty;
|
||||||
|
|
||||||
|
pub use proxy::ProxyEndpoint;
|
||||||
pub use shell::RemoteShell;
|
pub use shell::RemoteShell;
|
||||||
pub use tty::TTY;
|
pub use tty::TTY;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,16 @@
|
|||||||
//! This module provides the server functionality for handling incoming connections.
|
//! This module provides the server functionality for handling incoming connections.
|
||||||
|
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
FrameHeader, FrameType, TreeRequest, TreeResponse, TcpTransport, Transport,
|
FrameHeader, FrameType, Handshake, TreeRequest, TreeResponse, TcpTransport, Transport,
|
||||||
make_response, make_handshake_ack,
|
make_response, make_handshake_ack,
|
||||||
};
|
};
|
||||||
use crate::tree::Tree;
|
use crate::tree::Tree;
|
||||||
use crate::leaves::{RemoteShell, TTY};
|
use crate::leaves::{ProxyEndpoint, RemoteShell, TTY};
|
||||||
use std::sync::{Arc, Mutex};
|
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.
|
/// 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 tree = Arc::new(Mutex::new(Tree::new()));
|
||||||
{
|
{
|
||||||
let mut tree = tree.lock().unwrap();
|
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("/shell", Box::new(RemoteShell::new("shell")));
|
||||||
tree.add_endpoint("/tty", Box::new(TTY::new("tty")));
|
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
|
/// * `transport` - The TCP transport for the connection
|
||||||
/// * `tree` - Shared access to the tree
|
/// * `tree` - Shared access to the tree
|
||||||
pub fn handle_connection(mut transport: TcpTransport, tree: Arc<Mutex<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,
|
Ok(h) => h,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("recv error: {:?}", 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");
|
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");
|
transport.send_frame(&ack_header, Some(&ack_payload)).expect("send failed");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -168,11 +194,11 @@ pub fn handle_frame(
|
|||||||
|
|
||||||
let response = match request {
|
let response = match request {
|
||||||
TreeRequest::ListNodes {} => {
|
TreeRequest::ListNodes {} => {
|
||||||
let names = tree.list_nodes(dst_path).unwrap_or_default();
|
let names = tree.list_nodes_at(dst_path);
|
||||||
TreeResponse::NodeList { names }
|
TreeResponse::NodeList { names }
|
||||||
}
|
}
|
||||||
TreeRequest::ListEndpoints {} => {
|
TreeRequest::ListEndpoints {} => {
|
||||||
let endpoints = tree.list_endpoints(dst_path).unwrap_or_default();
|
let endpoints = tree.list_endpoints_at(dst_path);
|
||||||
TreeResponse::EndpointList { endpoints }
|
TreeResponse::EndpointList { endpoints }
|
||||||
}
|
}
|
||||||
TreeRequest::ListLeaves {} => {
|
TreeRequest::ListLeaves {} => {
|
||||||
|
|||||||
+109
-32
@@ -63,6 +63,52 @@ impl Node {
|
|||||||
self.children.keys().cloned().collect()
|
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
|
/// Get all endpoints at this node and in children
|
||||||
pub fn endpoint_names(&self) -> Vec<EndpointInfo> {
|
pub fn endpoint_names(&self) -> Vec<EndpointInfo> {
|
||||||
let mut endpoints = Vec::new();
|
let mut endpoints = Vec::new();
|
||||||
@@ -99,7 +145,11 @@ impl Node {
|
|||||||
for (name, child) in &self.children {
|
for (name, child) in &self.children {
|
||||||
let mut child_leaves = child.leaf_paths();
|
let mut child_leaves = child.leaf_paths();
|
||||||
for path in &mut child_leaves {
|
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.push(path.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +171,7 @@ impl Node {
|
|||||||
/// Tree structure for routing - contains the root node.
|
/// Tree structure for routing - contains the root node.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Tree {
|
pub struct Tree {
|
||||||
root: Node,
|
pub root: Node,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Tree {
|
impl fmt::Debug for Tree {
|
||||||
@@ -186,7 +236,7 @@ impl Tree {
|
|||||||
/// Returns the endpoint and the matched path
|
/// Returns the endpoint and the matched path
|
||||||
pub fn find_handler(&self, path: &str) -> Option<(Arc<Mutex<Box<dyn Endpoint>>>, &str)> {
|
pub fn find_handler(&self, path: &str) -> Option<(Arc<Mutex<Box<dyn Endpoint>>>, &str)> {
|
||||||
if path == "/" {
|
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);
|
let segments = path_segments(path);
|
||||||
@@ -207,45 +257,72 @@ impl Tree {
|
|||||||
current.endpoint.as_ref().map(|e| (e.clone(), handler_path))
|
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> {
|
pub fn list_nodes(&self, path: &str) -> Result<Vec<String>, String> {
|
||||||
let (_, matched_path) = self.find_handler(path)
|
// Use direct traversal - works without endpoint
|
||||||
.ok_or_else(|| format!("path not found: {}", path))?;
|
let names = self.root.list_nodes_at(path);
|
||||||
|
Ok(names)
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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> {
|
pub fn list_endpoints(&self, path: &str) -> Result<Vec<EndpointInfo>, String> {
|
||||||
let (_, matched_path) = self.find_handler(path)
|
let endpoints = self.root.list_endpoints_at(path);
|
||||||
.ok_or_else(|| format!("path not found: {}", path))?;
|
Ok(endpoints)
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List all leaf paths in the tree
|
/// List all leaf paths in the tree.
|
||||||
pub fn list_leaves(&self) -> Vec<String> {
|
pub fn list_leaves(&self) -> Vec<String> {
|
||||||
self.root.leaf_paths()
|
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
|
/// Get information about a node at the given path
|
||||||
pub fn get_info(&self, path: &str) -> Result<NodeInfo, String> {
|
pub fn get_info(&self, path: &str) -> Result<NodeInfo, String> {
|
||||||
let segments = path_segments(path);
|
let segments = path_segments(path);
|
||||||
|
|||||||
Reference in New Issue
Block a user