This commit is contained in:
Michael Mikovsky
2026-02-16 13:50:20 -07:00
parent c9b0e6f88f
commit 01959ce440
24 changed files with 695 additions and 201 deletions
+44
View File
@@ -216,3 +216,47 @@ const C2_URL: &str = symbol!("https://C2Server/endpoint");
## License ## License
MIT / Apache-2.0 MIT / Apache-2.0
## Implementation Notes
### Obfuscation Macros
The `ush-obfuscate` crate provides two macros for string obfuscation:
- **`sym!()`** - For static strings that are used at runtime. Uses AES encryption.
- **`xor!()`** - For simple XOR obfuscation.
When the `obfuscate` feature is enabled, strings are encrypted at compile time. When disabled, they remain as plain strings.
### Using Sym Constants
To avoid redundant `sym!()` calls, define constants in `src/tree/symbols.rs`:
```rust
use crate::obfuscate::sym;
pub const MY_CONSTANT: &'static str = sym!("MyString");
```
Then use the constant in your code:
```rust
use unshell::tree::symbols::*;
json!({ KEY_NAME: "value" })
```
### Module Organization
- **`src/tree/`** - Core tree system (routing, components, messages)
- **`ush-payload/src/`** - Protocol implementations, TCP client/server, connection management
Protocol-specific and transport-specific code belongs in `ush-payload`, while the tree system handles hierarchical message routing only.
### Building for Size
```bash
./build.sh # Builds with minimize profile and strips debug sections
```
This produces a ~200KB binary (may vary with content).
+1
View File
@@ -58,3 +58,4 @@ done
echo "## STARTING " echo "## STARTING "
$BINARY $BINARY
wc -c $BINARY
+2 -3
View File
@@ -1,9 +1,8 @@
use serde_json::{Value, json}; use serde_json::{json, Value};
use crate::{ use crate::{
ModuleError, Result,
config::{ConfigStructField, InterfaceData, InterfaceStruct, TreeMessage}, config::{ConfigStructField, InterfaceData, InterfaceStruct, TreeMessage},
warn, warn, ModuleError, Result,
}; };
pub type ConfigStructKeys = Vec<ConfigStructField>; pub type ConfigStructKeys = Vec<ConfigStructField>;
+1 -1
View File
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ModuleError, Result, config::config_struct}; use crate::{config::config_struct, ModuleError, Result};
pub trait Tree { pub trait Tree {
fn is_folder() -> bool { fn is_folder() -> bool {
+41
View File
@@ -1,3 +1,44 @@
//! Error handling for unshell modules.
//!
//! This module defines the `ModuleError` enum which provides a unified
//! error type for all operations in the unshell framework.
//!
//! # Error Categories
//!
//! - **Tree errors**: Errors related to tree operations (not found, message errors)
//! - **Object errors**: Type and method validation errors
//! - **System errors**: Library loading, linking, cryptographic errors
//! - **Data errors**: Serialization/deserialization errors
//!
//! # Usage
//!
//! ```rust
//! use unshell::error::{ModuleError, Result};
//!
//! fn example() -> Result<i32> {
//! Err(ModuleError::Error("Something went wrong".into()))
//! }
//!
//! // Using ? operator with string conversion
//! fn example2() -> Result<i32> {
//! let err: ModuleError = "error message".into();
//! Err(err)
//! }
//! ```
//!
//! # Serialization
//!
//! ModuleError implements serde serialization, making it suitable for
//! cross-process communication and logging:
//!
//! ```rust
//! use unshell::error::ModuleError;
//! use serde_json;
//!
//! let error = ModuleError::TreeMessageError("Invalid path".to_string());
//! let json = serde_json::to_string(&error).unwrap();
//! ```
use std::fmt; use std::fmt;
pub type Result<T> = std::result::Result<T, ModuleError>; pub type Result<T> = std::result::Result<T, ModuleError>;
+79 -1
View File
@@ -1,3 +1,81 @@
//! Unshell - A modular, pluggable framework for endpoint agents.
//!
//! This library provides a tree-based hierarchical message routing system
//! for building modular, cross-platform endpoint agents. It follows the
//! philosophy that everything should be replaceable at runtime.
//!
//! # Architecture
//!
//! The core architecture consists of:
//! - **Tree Elements**: Hierarchical nodes that can send/receive messages
//! - **Components**: Extensible modules that can be registered at runtime
//! - **Protocols**: Swappable encoding/transport layers
//! - **Endpoints**: Root containers for tree elements
//!
//! # Core Concepts
//!
//! ## TreeElement Trait
//!
//! The foundation of the system. Any struct implementing `TreeElement` can
//! participate in the tree hierarchy and handle messages:
//!
//! ```rust
//! use serde_json::{json, Value};
//! use unshell::tree::TreeElement;
//!
//! struct MyElement;
//!
//! impl TreeElement for MyElement {
//! fn get_type(&self) -> Value {
//! json!("MyElement")
//! }
//!
//! fn send_message(&mut self, target: Value, message: Value) -> Value {
//! // Handle messages here
//! json!({"status": "ok"})
//! }
//! }
//! ```
//!
//! ## Component Trait
//!
//! Components extend the tree with dynamic, configurable modules:
//!
//! ```rust
//! use serde_json::Value;
//! use unshell::tree::component::Component;
//!
//! struct MyComponent;
//!
//! impl Component for MyComponent {
//! fn name(&self) -> &str { "my_component" }
//! fn status(&self) -> Value { json!({"active": true}) }
//! fn init(&mut self, config: Value) -> Result<(), String> { Ok(()) }
//! fn shutdown(&mut self) -> Result<(), String> { Ok(()) }
//! }
//! ```
//!
//! # Module Organization
//!
//! - `tree`: Core tree system (routing, components, messages)
//! - `config`: Configuration structures and parsing
//! - `logger`: Logging infrastructure
//! - `obfuscate`: Compile-time string obfuscation
//!
//! # Usage
//!
//! ```rust
//! use unshell::tree::{EndpointManager, TreeElement};
//! use serde_json::json;
//!
//! // Create an endpoint
//! let mut endpoint = EndpointManager::new("my-endpoint");
//!
//! // Send messages to tree elements
//! let response = endpoint.branch.send_message(json!("id"), json!(null));
//! println!("Endpoint ID: {}", response);
//! ```
#![no_main] #![no_main]
pub mod config; pub mod config;
@@ -12,5 +90,5 @@ pub use error::{ModuleError, Result};
pub use announcement::Announcement; pub use announcement::Announcement;
// Re-exports // Re-exports
pub use serde_json::{Value, json}; pub use serde_json::{json, Value};
pub use ush_obfuscate as obfuscate; pub use ush_obfuscate as obfuscate;
+6 -6
View File
@@ -1,14 +1,14 @@
#[macro_export] #[macro_export]
macro_rules! log { macro_rules! log {
($level:expr, $fmt:tt) => {{ ($level:expr, $fmt:tt) => {{
use $crate::obfuscate; use $crate::obfuscate::format_sym;
let log_result = obfuscate::format_obs!($fmt); let log_result = format_sym!($fmt);
$crate::logger::add_record( $crate::logger::add_record(
$level, $level,
#[cfg(feature = "log_debug")] #[cfg(feature = "log_debug")]
Some(String::from(obfuscate::file_symbol!())), Some(String::from($crate::obfuscate::file_symbol!())),
#[cfg(not(feature = "log_debug"))] #[cfg(not(feature = "log_debug"))]
None, None,
@@ -17,14 +17,14 @@ macro_rules! log {
); );
}}; }};
($level:expr, $fmt:tt, $($arg:expr),*) => {{ ($level:expr, $fmt:tt, $($arg:expr),*) => {{
use $crate::obfuscate; use $crate::obfuscate::format_sym;
let log_result = obfuscate::format_obs!($fmt, $($arg),*); let log_result = format_sym!($fmt, $($arg),*);
$crate::logger::add_record( $crate::logger::add_record(
$level, $level,
#[cfg(feature = "log_debug")] #[cfg(feature = "log_debug")]
Some(String::from(obfuscate::file_symbol!())), Some(String::from($crate::obfuscate::file_symbol!())),
#[cfg(not(feature = "log_debug"))] #[cfg(not(feature = "log_debug"))]
None, None,
+50 -1
View File
@@ -1,3 +1,52 @@
//! Logging infrastructure for unshell.
//!
//! This module provides a pluggable logging system with support for
//! compile-time feature flags to enable or disable logging.
//!
//! # Features
//!
//! - **Feature-gated**: Logging can be disabled at compile time for smaller binaries
//! - **Custom loggers**: Implement the `Logger` trait for custom output
//! - **Structured records**: Log records include level, location, time, and message
//! - **FFI support**: C-compatible setup function for external initialization
//!
//! # Usage
//!
//! ```rust
//! use unshell::logger::{LogLevel, Record, Logger};
//!
//! // Implement custom logger
//! struct MyLogger;
//!
//! impl Logger for MyLogger {
//! fn log(&self, record: Record) {
//! println!("[{:?}] {}", record.log_level, record.message);
//! }
//! }
//!
//! // Set as global logger
//! unshell::logger::set_logger(&MyLogger);
//! ```
//!
//! # Log Levels
//!
//! ```rust
//! use unshell::logger::LogLevel;
//!
//! let level = LogLevel::Debug;
//! let level = LogLevel::Info;
//! let level = LogLevel::Warn;
//! let level = LogLevel::Error;
//! ```
//!
//! # Feature Flags
//!
//! - `log`: Enable logging functionality
//! - `log_debug`: Enable debug-level logging
//!
//! When the `log` feature is disabled, the macros module is replaced with
//! no-op implementations to reduce binary size.
// Choose if the macros are enabled based on the feature setting // Choose if the macros are enabled based on the feature setting
#[cfg(feature = "log")] #[cfg(feature = "log")]
pub mod macros; pub mod macros;
@@ -9,8 +58,8 @@ mod pretty_logger;
use std::time::SystemTime; use std::time::SystemTime;
pub use pretty_logger::PrettyLogger;
pub use pretty_logger::log; pub use pretty_logger::log;
pub use pretty_logger::PrettyLogger;
static mut LOGGER: &dyn Logger = &DefaultLogger; static mut LOGGER: &dyn Logger = &DefaultLogger;
+45
View File
@@ -1,4 +1,49 @@
//! Branch - A TreeElement with child elements for hierarchical routing. //! Branch - A TreeElement with child elements for hierarchical routing.
//!
//! A Branch is a container node in the tree hierarchy that can hold multiple
//! child elements. It provides path-based message routing to traverse the
//! tree structure.
//!
//! # Path-Based Routing
//!
//! Messages can target elements using path notation:
//!
//! ```rust
//! use serde_json::json;
//! use unshell::tree::{Branch, TreeElement};
//!
//! let mut branch = Branch::new("parent");
//! // ... add children ...
//!
//! // Target a direct child
//! branch.send_message(json!("child-name"), json!("Command"));
//!
//! // Target a nested child using array path
//! branch.send_message(json!(["parent", "child", "grandchild"]), json!("Command"));
//! ```
//!
//! # Child Management
//!
//! ```rust
//! use unshell::tree::{Branch, TreeElement};
//! use serde_json::json;
//!
//! let mut branch = Branch::new("my-branch");
//!
//! // Add children
//! branch.add_child("child1", Box::new(ChildElement));
//! branch.add_child("child2", Box::new(AnotherElement));
//!
//! // Query children
//! let children = branch.send_message(json!(null), json!("GetChildren"));
//! // Returns: {"child1": "ChildElement", "child2": "AnotherElement"}
//! ```
//!
//! # Pivot Routing
//!
//! Branches support multi-hop communication for pivoting through networks.
//! A path like `["endpoint1", "connections", "peer1", "endpoint2"]` would
//! route a message through multiple endpoints to reach a final destination.
use std::collections::HashMap; use std::collections::HashMap;
+8 -2
View File
@@ -14,9 +14,10 @@ use serde_json::{json, Value};
use crate::tree::component::ComponentRegistry; use crate::tree::component::ComponentRegistry;
use crate::tree::queue::Queue; use crate::tree::queue::Queue;
use crate::tree::readonly::ReadOnly; use crate::tree::readonly::ReadOnly;
use crate::tree::symbols::{self, TYPE_CONNECTION, TYPE_ENDPOINT}; use crate::tree::symbols::{self, TYPE_ENDPOINT};
use crate::tree::{Branch, TreeElement}; use crate::tree::{Branch, TreeElement};
#[allow(dead_code)]
pub(crate) struct Connection { pub(crate) struct Connection {
id: String, id: String,
peer_id: String, peer_id: String,
@@ -24,6 +25,7 @@ pub(crate) struct Connection {
receiver: Receiver<Value>, receiver: Receiver<Value>,
} }
#[allow(dead_code)]
impl Connection { impl Connection {
pub(crate) fn new( pub(crate) fn new(
id: String, id: String,
@@ -62,11 +64,13 @@ impl TreeElement for Connections {
} }
} }
#[allow(dead_code)]
pub(crate) struct Connections { pub(crate) struct Connections {
connections: HashMap<String, Connection>, connections: HashMap<String, Connection>,
branch: Branch, branch: Branch,
} }
#[allow(dead_code)]
impl Connections { impl Connections {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
@@ -80,6 +84,7 @@ impl Connections {
} }
} }
#[allow(dead_code)]
pub(crate) fn create_channel_pair() -> ( pub(crate) fn create_channel_pair() -> (
(Sender<Value>, Receiver<Value>), (Sender<Value>, Receiver<Value>),
(Sender<Value>, Receiver<Value>), (Sender<Value>, Receiver<Value>),
@@ -128,7 +133,8 @@ impl EndpointManager {
&mut self.branch &mut self.branch
} }
pub fn add_connection(&mut self, id: String, peer_id: String) -> Connection { #[allow(dead_code)]
pub(crate) fn add_connection(&mut self, id: String, peer_id: String) -> Connection {
let ((tx_local, rx_remote), (tx_remote, rx_local)) = create_channel_pair(); let ((tx_local, rx_remote), (tx_remote, rx_local)) = create_channel_pair();
let conn_a = Connection::new(id.clone(), peer_id.clone(), tx_remote, rx_local); let conn_a = Connection::new(id.clone(), peer_id.clone(), tx_remote, rx_local);
+2
View File
@@ -218,6 +218,8 @@ impl Default for TreeMessage {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde_json::json;
use super::*; use super::*;
#[test] #[test]
+100 -1
View File
@@ -1,7 +1,106 @@
//! Tree system for hierarchical message routing between endpoints. //! Tree system for hierarchical message routing between endpoints.
//! //!
//! The tree provides a modular IPC mechanism where components expose //! The tree provides a modular IPC mechanism where components expose
//! a tree of messageable elements. Used for C2 communication and pivoting. //! a tree of messageable elements. This is the core communication
//! abstraction used throughout unshell.
//!
//! # Design Philosophy
//!
//! The tree system follows these principles:
//! - **Everything is a TreeElement**: Components, queues, variables all share the same interface
//! - **Path-based routing**: Messages target elements by path (e.g., "components/tcp-client")
//! - **Loose typing**: Actions and payloads are flexible JSON values
//! - **Namespaced actions**: Use dot notation (e.g., "rpc.call", "stream.data")
//!
//! # Core Traits
//!
//! ## TreeElement
//!
//! The fundamental trait that all tree nodes implement:
//!
//! ```rust
//! use serde_json::{json, Value};
//! use unshell::tree::TreeElement;
//!
//! struct MyNode {
//! value: i32,
//! }
//!
//! impl TreeElement for MyNode {
//! fn get_type(&self) -> Value {
//! json!("MyNode")
//! }
//!
//! fn send_message(&mut self, target: Value, message: Value) -> Value {
//! // Handle the message and return a response
//! json!({"received": true})
//! }
//! }
//! ```
//!
//! ## Component
//!
//! A trait for extensible modules that can be dynamically registered:
//!
//! ```rust
//! use serde_json::Value;
//! use unshell::tree::component::Component;
//!
//! struct MyComponent {
//! name: String,
//! }
//!
//! impl Component for MyComponent {
//! fn name(&self) -> &str { &self.name }
//! fn status(&self) -> Value { json!({"running": true}) }
//! fn init(&mut self, config: Value) -> Result<(), String> { Ok(()) }
//! fn shutdown(&mut self) -> Result<(), String> { Ok(()) }
//! }
//! ```
//!
//! # Message Format
//!
//! Messages in the tree follow a simple RPC-like pattern:
//!
//! ```rust
//! use serde_json::json;
//!
//! // Target a specific element
//! let target = json!("components/tcp-client");
//!
//! // Send an RPC-like message
//! let message = json!({
//! "method": "connect",
//! "params": {"address": "127.0.0.1", "port": 8080}
//! });
//! ```
//!
//! # Example: Creating an Endpoint
//!
//! ```rust
//! use unshell::tree::{EndpointManager, TreeElement};
//! use serde_json::json;
//!
//! // Create a new endpoint with id, logs, and components
//! let mut endpoint = EndpointManager::new("my-endpoint");
//!
//! // Query children
//! let children = endpoint.branch.send_message(json!(null), json!("GetChildren"));
//!
//! // Get the endpoint ID
//! let id = endpoint.branch.send_message(json!("id"), json!(null));
//! ```
//!
//! # Modules
//!
//! - `branch`: Branch nodes that contain child elements
//! - `component`: Component registration and lifecycle management
//! - `endpoint`: Root endpoint management
//! - `log`: Log handling
//! - `message`: TreeMessage serialization format
//! - `queue`: Message queue implementation
//! - `readonly`: Read-only variable wrappers
//! - `symbols`: String constants (often obfuscated)
pub mod branch; pub mod branch;
pub mod component; pub mod component;
+113 -20
View File
@@ -1,24 +1,117 @@
use crate::obfuscate::symbol; //! Symbol constants for the tree system.
//!
//! This module provides string constants used throughout the tree system.
//! These symbols are often obfuscated at compile-time to avoid static analysis.
//!
//! # Categories
//!
//! - **Type symbols**: Define element types (Endpoint, Queue, Connection)
//! - **Command symbols**: Message commands (Get, Poll, GetLength)
//! - **Error symbols**: Error messages (UnsupportedMethod, InvalidCommand)
//! - **Key symbols**: JSON keys (method, params, config, status)
//! - **Method symbols**: RPC methods (connect, disconnect, send, recv)
//! - **State symbols**: Common state values (config, protocols, unknown)
//!
//! # Usage
//!
//! ```rust
//! use crate::tree::symbols::{TYPE_ENDPOINT, CMD_GET_CHILDREN, ERR_UNSUPPORTED_METHOD};
//!
//! // These are used internally for tree communication
//! let endpoint_type = TYPE_ENDPOINT;
//! let get_children_cmd = CMD_GET_CHILDREN;
//! let error_msg = ERR_UNSUPPORTED_METHOD;
//! ```
//!
//! # Obfuscation
//!
//! When the `obfuscate` feature is enabled, these strings are encrypted
//! at compile-time using AES, making static analysis more difficult.
pub const LOGGER: &'static str = symbol!("Logger"); use crate::obfuscate::sym;
pub const TYPE_TREE: &'static str = symbol!("Tree"); pub const LOGGER: &'static str = sym!("Logger");
pub const TYPE_QUEUE: &'static str = symbol!("Queue");
pub const TYPE_ENDPOINT: &'static str = symbol!("Endpoint");
pub const TYPE_CONNECTIONS: &'static str = symbol!("Connections");
pub const TYPE_CONNECTION: &'static str = symbol!("Connection");
pub const CMD_GET: &'static str = symbol!("Get"); pub const TYPE_TREE: &'static str = sym!("Tree");
pub const CMD_POLL: &'static str = symbol!("Poll"); pub const TYPE_QUEUE: &'static str = sym!("Queue");
pub const CMD_GET_LENGTH: &'static str = symbol!("GetLength"); pub const TYPE_ENDPOINT: &'static str = sym!("Endpoint");
pub const CMD_GET_CHILDREN: &'static str = symbol!("GetChildren"); pub const TYPE_CONNECTIONS: &'static str = sym!("Connections");
pub const TYPE_CONNECTION: &'static str = sym!("Connection");
pub const ERR_UNSUPPORTED_METHOD: &'static str = symbol!("UnsupportedMethod"); pub const CMD_GET: &'static str = sym!("Get");
pub const ERR_INVALID_COMMAND: &'static str = symbol!("InvalidCommand"); pub const CMD_POLL: &'static str = sym!("Poll");
pub const ERR_INVALID_CHILD: &'static str = symbol!("InvalidChild"); pub const CMD_GET_LENGTH: &'static str = sym!("GetLength");
pub const ERR_INVALID_TARGET: &'static str = symbol!("InvalidTarget"); pub const CMD_GET_CHILDREN: &'static str = sym!("GetChildren");
pub const ERR_CHILD_NOT_FOUND: &'static str = symbol!("ChildNotFound");
pub const ERR_INVALID_PATH: &'static str = symbol!("InvalidPath"); pub const ERR_UNSUPPORTED_METHOD: &'static str = sym!("UnsupportedMethod");
pub const ERR_MISSING_ARGS: &'static str = symbol!("MissingArgs"); pub const ERR_INVALID_COMMAND: &'static str = sym!("InvalidCommand");
pub const ERR_INVALID_STATE: &'static str = symbol!("InvalidState"); pub const ERR_INVALID_CHILD: &'static str = sym!("InvalidChild");
pub const ERR_READONLY: &'static str = symbol!("ReadOnly"); pub const ERR_INVALID_TARGET: &'static str = sym!("InvalidTarget");
pub const ERR_CHILD_NOT_FOUND: &'static str = sym!("ChildNotFound");
pub const ERR_INVALID_PATH: &'static str = sym!("InvalidPath");
pub const ERR_MISSING_ARGS: &'static str = sym!("MissingArgs");
pub const ERR_INVALID_STATE: &'static str = sym!("InvalidState");
pub const ERR_READONLY: &'static str = sym!("ReadOnly");
pub const TYPE_TCP_CLIENT: &'static str = sym!("TCPClient");
pub const TYPE_TCP_SERVER: &'static str = sym!("TCPServer");
pub const KEY_METHOD: &'static str = sym!("method");
pub const KEY_PARAMS: &'static str = sym!("params");
pub const KEY_SUCCESS: &'static str = sym!("success");
pub const KEY_ERROR: &'static str = sym!("error");
pub const KEY_CONFIG: &'static str = sym!("config");
pub const KEY_STATUS: &'static str = sym!("status");
pub const KEY_ADDRESS: &'static str = sym!("address");
pub const KEY_PORT: &'static str = sym!("port");
pub const KEY_DATA: &'static str = sym!("data");
pub const KEY_SIZE: &'static str = sym!("size");
pub const KEY_CLIENT_ID: &'static str = sym!("client_id");
pub const KEY_BYTES_SENT: &'static str = sym!("bytes_sent");
pub const KEY_BYTES_RECEIVED: &'static str = sym!("bytes_received");
pub const KEY_CONNECTED: &'static str = sym!("connected");
pub const KEY_REMOTE_ADDRESS: &'static str = sym!("remote_address");
pub const KEY_LOCAL_ADDRESS: &'static str = sym!("local_address");
pub const KEY_PROTOCOLS: &'static str = sym!("protocols");
pub const KEY_TYPE: &'static str = sym!("type");
pub const KEY_NAME: &'static str = sym!("name");
pub const KEY_ID: &'static str = sym!("id");
pub const KEY_PEER: &'static str = sym!("peer");
pub const KEY_LISTENING: &'static str = sym!("listening");
pub const KEY_BIND_ADDRESS: &'static str = sym!("bind_address");
pub const KEY_ACTIVE_CONNECTIONS: &'static str = sym!("active_connections");
pub const KEY_TOTAL_CONNECTIONS: &'static str = sym!("total_connections");
pub const KEY_CLIENTS: &'static str = sym!("clients");
pub const METHOD_CONNECT: &'static str = sym!("connect");
pub const METHOD_DISCONNECT: &'static str = sym!("disconnect");
pub const METHOD_SEND: &'static str = sym!("send");
pub const METHOD_RECV: &'static str = sym!("recv");
pub const METHOD_STATUS: &'static str = sym!("status");
pub const METHOD_SET_PROTOCOLS: &'static str = sym!("set_protocols");
pub const METHOD_LISTEN: &'static str = sym!("listen");
pub const METHOD_START: &'static str = sym!("start");
pub const METHOD_STOP: &'static str = sym!("stop");
pub const METHOD_ACCEPT: &'static str = sym!("accept");
pub const METHOD_LIST_CLIENTS: &'static str = sym!("list_clients");
pub const CMD_CONNECT: &'static str = sym!("Connect");
pub const CMD_DISCONNECT: &'static str = sym!("Disconnect");
pub const CMD_STATUS: &'static str = sym!("Status");
pub const CMD_LISTEN: &'static str = sym!("Listen");
pub const CMD_START: &'static str = sym!("Start");
pub const CMD_STOP: &'static str = sym!("Stop");
pub const STR_STATE: &'static str = sym!("state");
pub const STR_CONFIG: &'static str = sym!("config");
pub const STR_PROTOCOLS: &'static str = sym!("protocols");
pub const STR_UNKNOWN: &'static str = sym!("unknown");
pub const STR_0_0_0_0: &'static str = sym!("0.0.0.0");
pub const ERR_MISSING_METHOD: &'static str = sym!("missing method");
pub const ERR_MISSING_DATA: &'static str = sym!("missing data");
pub const ERR_MISSING_CLIENT_ID: &'static str = sym!("missing client_id");
pub const ERR_MISSING_PROTOCOLS: &'static str = sym!("missing protocols");
pub const ERR_INVALID_CONFIG: &'static str = sym!("Invalid config: {}");
pub const ERR_INVALID_PROTOCOLS: &'static str = sym!("Invalid protocols: {}");
pub const ERR_UNKNOWN_METHOD: &'static str = sym!("unknown method: {}");
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::crypt::{STATIC_BYTE_MAP, hash}; use crate::crypt::{hash, STATIC_BYTE_MAP};
// Randomly mapped Base62 characters // Randomly mapped Base62 characters
pub struct Base62 { pub struct Base62 {
+12 -10
View File
@@ -24,18 +24,20 @@ use obfuscate as obs;
// String obfuscation // String obfuscation
#[proc_macro] #[proc_macro]
pub fn obs(input: TokenStream) -> TokenStream { pub fn xor(input: TokenStream) -> TokenStream {
obs::xor(input) obs::xor(input)
} }
#[proc_macro_attribute] /// Represents strings as a symbol.
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro]
obs::aes_fn_name(_attr, item) pub fn sym(input: TokenStream) -> TokenStream {
obs::aes_str(input)
} }
#[proc_macro] /// Represents function names as a symbol.
pub fn symbol(input: TokenStream) -> TokenStream { #[proc_macro_attribute]
obs::aes_str(input) pub fn sym_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
obs::aes_fn_name(_attr, item)
} }
#[proc_macro] #[proc_macro]
@@ -56,7 +58,7 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream {
// Return as a string literal // Return as a string literal
let output = quote! { let output = quote! {
obfuscate::symbol!(#concatted) sym!(#concatted)
}; };
// let output = quote! { // let output = quote! {
// #concatted // #concatted
@@ -65,7 +67,7 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream {
} }
#[proc_macro] #[proc_macro]
pub fn format_obs(input: TokenStream) -> TokenStream { pub fn format_sym(input: TokenStream) -> TokenStream {
let PrintlnArgs { format_str, args } = parse_macro_input!(input as PrintlnArgs); let PrintlnArgs { format_str, args } = parse_macro_input!(input as PrintlnArgs);
let segments = parse_format_string(&format_str); let segments = parse_format_string(&format_str);
@@ -83,7 +85,7 @@ pub fn format_obs(input: TokenStream) -> TokenStream {
match segment { match segment {
FormatSegment::Static(text) => { FormatSegment::Static(text) => {
parts.push(quote! { parts.push(quote! {
obfuscate::symbol!(#text).to_string() #text.to_string()
}); });
} }
FormatSegment::Dynamic(spec, idx) => { FormatSegment::Dynamic(spec, idx) => {
+1 -1
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input}; use syn::{parse_macro_input, ItemFn, LitStr};
pub fn xor(input: TokenStream) -> TokenStream { pub fn xor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr); let input = parse_macro_input!(input as LitStr);
+1 -1
View File
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use rand::rngs::SmallRng; use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use syn::{LitFloat, parse_macro_input}; use syn::{parse_macro_input, LitFloat};
// const MIN_TAGS: u32 = 1; // Maximum instructions per recursive block // const MIN_TAGS: u32 = 1; // Maximum instructions per recursive block
// const MAX_TAGS: u32 = 22; // Maximum instructions per recursive block // const MAX_TAGS: u32 = 22; // Maximum instructions per recursive block
+1 -1
View File
@@ -1,7 +1,7 @@
use getrandom::fill; use getrandom::fill;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{LitStr, parse_macro_input}; use syn::{parse_macro_input, LitStr};
/// XOR encrypt strings /// XOR encrypt strings
pub fn xor(input: TokenStream) -> TokenStream { pub fn xor(input: TokenStream) -> TokenStream {
@@ -1,7 +1,7 @@
use crate::crypt::{BACKUP_ENV_KEY, ENV_KEY_NAME, STATIC_IV, aes_encrypt::encrypt_aes_lines}; use crate::crypt::{aes_encrypt::encrypt_aes_lines, BACKUP_ENV_KEY, ENV_KEY_NAME, STATIC_IV};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ItemFn, LitStr, parse_macro_input}; use syn::{parse_macro_input, ItemFn, LitStr};
use crate::obfuscate::get_encryption_key; use crate::obfuscate::get_encryption_key;
+42
View File
@@ -1,4 +1,46 @@
//! Base64 encoding/decoding protocol. //! Base64 encoding/decoding protocol.
//!
//! This module provides two protocol implementations:
//! - `Base64Protocol`: Standard base64 encoding with URL-safe variant support
//! - `IdentityProtocol`: No-op pass-through protocol
//!
//! # Base64 Protocol
//!
//! The Base64 protocol wraps data in base64 encoding, useful for:
//! - Evading basic pattern detection
//! - Text-based transport encoding
//! - Legacy system compatibility
//!
//! ```rust
//! use ush_payload::protocols::{Base64Config, ProtocolConfig, ProtocolStack};
//!
//! let mut stack = ProtocolStack::new();
//! stack.push(&ProtocolConfig::Base64(Base64Config {
//! url_safe: false,
//! padding: true,
//! })).unwrap();
//!
//! let data = b"Hello";
//! let encoded = stack.encode(data).unwrap();
//! let decoded = stack.decode(&encoded).unwrap();
//! assert_eq!(decoded, data);
//! ```
//!
//! # Identity Protocol
//!
//! The identity protocol is a no-op pass-through. Useful as a placeholder
//! or when no encoding is needed.
//!
//! ```rust
//! use ush_payload::protocols::{ProtocolConfig, ProtocolStack};
//!
//! let mut stack = ProtocolStack::new();
//! stack.push(&ProtocolConfig::Identity).unwrap();
//!
//! let data = b"test";
//! let result = stack.encode(data).unwrap();
//! assert_eq!(result, data);
//! ```
use super::stack::{Base64Config, Protocol, ProtocolError}; use super::stack::{Base64Config, Protocol, ProtocolError};
use serde_json::Value; use serde_json::Value;
+1
View File
@@ -114,6 +114,7 @@ impl Protocol for HttpProtocol {
/// ///
/// This is a simple implementation for testing - in production you'd /// This is a simple implementation for testing - in production you'd
/// use a proper HTTP server. /// use a proper HTTP server.
#[allow(dead_code)]
pub struct HttpServer { pub struct HttpServer {
config: HttpConfig, config: HttpConfig,
} }
+1 -1
View File
@@ -228,7 +228,7 @@ impl ProtocolStack {
let p = WebSocketProtocol::new(cfg.clone()); let p = WebSocketProtocol::new(cfg.clone());
(Box::new(p) as Box<dyn Protocol>, "websocket".to_string()) (Box::new(p) as Box<dyn Protocol>, "websocket".to_string())
} }
ProtocolConfig::Custom { name, config } => { ProtocolConfig::Custom { name, config: _ } => {
return Err(ProtocolError::NotFound(format!( return Err(ProtocolError::NotFound(format!(
"Custom protocol '{}' not implemented", "Custom protocol '{}' not implemented",
name name
+66 -71
View File
@@ -15,7 +15,7 @@ use crate::protocols::{ProtocolConfig, ProtocolStack};
use crate::tcp::config::{ConnectionStatus, TcpClientConfig}; use crate::tcp::config::{ConnectionStatus, TcpClientConfig};
use unshell::tree::component::Component; use unshell::tree::component::Component;
use unshell::tree::message::TreeMessage; use unshell::tree::message::TreeMessage;
use unshell::tree::symbols; use unshell::tree::symbols::*;
use unshell::tree::{Branch, TreeElement}; use unshell::tree::{Branch, TreeElement};
/// TCP Client component with protocol stacking support. /// TCP Client component with protocol stacking support.
@@ -58,9 +58,9 @@ impl TcpClient {
pub fn with_config(name: impl Into<String>, config: TcpClientConfig) -> Self { pub fn with_config(name: impl Into<String>, config: TcpClientConfig) -> Self {
let name = name.into(); let name = name.into();
let mut branch = Branch::new("TCPClient"); let mut branch = Branch::new(TYPE_TCP_CLIENT);
let state_branch = Branch::new("state"); let state_branch = Branch::new(STR_STATE);
branch.add_child("state", Box::new(state_branch)); branch.add_child(STR_STATE, Box::new(state_branch));
Self { Self {
name: name.clone(), name: name.clone(),
@@ -196,88 +196,87 @@ impl TcpClient {
/// Get status as JSON /// Get status as JSON
pub fn get_status(&self) -> Value { pub fn get_status(&self) -> Value {
json!({ json!({
"connected": self.status.connected, KEY_CONNECTED: self.status.connected,
"remote_address": self.status.remote_address, KEY_REMOTE_ADDRESS: self.status.remote_address,
"local_address": self.status.local_address, KEY_LOCAL_ADDRESS: self.status.local_address,
"bytes_sent": self.status.bytes_sent, KEY_BYTES_SENT: self.status.bytes_sent,
"bytes_received": self.status.bytes_received, KEY_BYTES_RECEIVED: self.status.bytes_received,
"config": self.config, KEY_CONFIG: self.config,
"protocols": self.protocol_stack.to_configs(), KEY_PROTOCOLS: self.protocol_stack.to_configs(),
}) })
} }
/// Handle RPC call from message /// Handle RPC call from message
fn handle_rpc(&mut self, payload: &Value) -> Value { fn handle_rpc(&mut self, payload: &Value) -> Value {
let method = match payload.get("method").and_then(|m| m.as_str()) { let method = match payload.get(KEY_METHOD).and_then(|m| m.as_str()) {
Some(m) => m, Some(m) => m,
None => return json!({"success": false, "error": "missing method"}), None => return json!({KEY_SUCCESS: false, KEY_ERROR: ERR_MISSING_METHOD}),
}; };
let params = payload.get("params").cloned().unwrap_or(Value::Null); let params = payload.get(KEY_PARAMS).cloned().unwrap_or(Value::Null);
match method { match method {
"connect" => { METHOD_CONNECT => {
// Allow override of address/port if let Some(addr) = params.get(KEY_ADDRESS).and_then(|a| a.as_str()) {
if let Some(addr) = params.get("address").and_then(|a| a.as_str()) {
self.config.address = addr.to_string(); self.config.address = addr.to_string();
} }
if let Some(port) = params.get("port").and_then(|p| p.as_u64()) { if let Some(port) = params.get(KEY_PORT).and_then(|p| p.as_u64()) {
self.config.port = port as u16; self.config.port = port as u16;
} }
match self.connect() { match self.connect() {
Ok(_) => json!({"success": true, "status": self.status}), Ok(_) => json!({KEY_SUCCESS: true, KEY_STATUS: self.status}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
} }
} }
"disconnect" => match self.disconnect() { METHOD_DISCONNECT => match self.disconnect() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"send" => { METHOD_SEND => {
let data = params let data = params
.get("data") .get(KEY_DATA)
.and_then(|d| d.as_str()) .and_then(|d| d.as_str())
.map(|s| s.as_bytes().to_vec()); .map(|s| s.as_bytes().to_vec());
match data { match data {
Some(data) => match self.send_raw(&data) { Some(data) => match self.send_raw(&data) {
Ok(n) => json!({"success": true, "bytes_sent": n}), Ok(n) => json!({KEY_SUCCESS: true, KEY_BYTES_SENT: n}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
None => json!({"success": false, "error": "missing data"}), None => json!({KEY_SUCCESS: false, KEY_ERROR: ERR_MISSING_DATA}),
} }
} }
"recv" => { METHOD_RECV => {
let size = params let size = params
.get("size") .get(KEY_SIZE)
.and_then(|s| s.as_u64()) .and_then(|s| s.as_u64())
.map(|s| s as usize) .map(|s| s as usize)
.unwrap_or(4096); .unwrap_or(4096);
match self.recv_raw(size) { match self.recv_raw(size) {
Ok(data) => json!({ Ok(data) => json!({
"success": true, KEY_SUCCESS: true,
"data": String::from_utf8_lossy(&data), KEY_DATA: String::from_utf8_lossy(&data),
"bytes": data.len() "bytes": data.len()
}), }),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
} }
} }
"status" => self.get_status(), METHOD_STATUS => self.get_status(),
"set_protocols" => { METHOD_SET_PROTOCOLS => {
if let Some(protocols) = params.get("protocols") { if let Some(protocols) = params.get(KEY_PROTOCOLS) {
match serde_json::from_value(protocols.clone()) { match serde_json::from_value(protocols.clone()) {
Ok(p) => match self.set_protocols(p) { Ok(p) => match self.set_protocols(p) {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
Err(e) => json!({"success": false, "error": e.to_string()}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e.to_string()}),
} }
} else { } else {
json!({"success": false, "error": "missing protocols"}) json!({KEY_SUCCESS: false, KEY_ERROR: ERR_MISSING_PROTOCOLS})
} }
} }
_ => json!({"success": false, "error": format!("unknown method: {}", method)}), _ => json!({KEY_SUCCESS: false, KEY_ERROR: format!("unknown method: {}", method)}),
} }
} }
} }
@@ -292,8 +291,7 @@ impl Component for TcpClient {
} }
fn init(&mut self, config: Value) -> Result<(), String> { fn init(&mut self, config: Value) -> Result<(), String> {
// Support both legacy config and new format if let Some(client_config) = config.get(KEY_CONFIG) {
if let Some(client_config) = config.get("config") {
self.config = serde_json::from_value(client_config.clone()) self.config = serde_json::from_value(client_config.clone())
.map_err(|e| format!("Invalid config: {}", e))?; .map_err(|e| format!("Invalid config: {}", e))?;
} else { } else {
@@ -301,7 +299,7 @@ impl Component for TcpClient {
.map_err(|e| format!("Invalid config: {}", e))?; .map_err(|e| format!("Invalid config: {}", e))?;
} }
if let Some(protocols) = config.get("protocols") { if let Some(protocols) = config.get(KEY_PROTOCOLS) {
let p: Vec<ProtocolConfig> = serde_json::from_value(protocols.clone()) let p: Vec<ProtocolConfig> = serde_json::from_value(protocols.clone())
.map_err(|e| format!("Invalid protocols: {}", e))?; .map_err(|e| format!("Invalid protocols: {}", e))?;
self.set_protocols(p)?; self.set_protocols(p)?;
@@ -319,32 +317,30 @@ impl Component for TcpClient {
impl TreeElement for TcpClient { impl TreeElement for TcpClient {
fn get_type(&self) -> Value { fn get_type(&self) -> Value {
json!({ json!({
"type": "TCPClient", KEY_TYPE: TYPE_TCP_CLIENT,
"name": self.name, KEY_NAME: self.name,
}) })
} }
fn send_message(&mut self, target: Value, message: Value) -> Value { fn send_message(&mut self, target: Value, message: Value) -> Value {
match target { match target {
Value::Null => { Value::Null => {
// Check for RPC call format if message.get(KEY_METHOD).is_some() {
if message.get("method").is_some() {
return self.handle_rpc(&message); return self.handle_rpc(&message);
} }
// Legacy string commands
if let Some(cmd) = message.as_str() { if let Some(cmd) = message.as_str() {
match cmd { match cmd {
"Connect" => match self.connect() { CMD_CONNECT => match self.connect() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"Disconnect" => match self.disconnect() { CMD_DISCONNECT => match self.disconnect() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"Status" => self.get_status(), CMD_STATUS => self.get_status(),
symbols::CMD_GET_CHILDREN => { CMD_GET_CHILDREN => {
let children = self let children = self
.branch .branch
.children() .children()
@@ -353,38 +349,37 @@ impl TreeElement for TcpClient {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
json!(children) json!(children)
} }
_ => json!(symbols::ERR_UNSUPPORTED_METHOD), _ => json!(ERR_UNSUPPORTED_METHOD),
} }
} else if let Value::Object(obj) = message { } else if let Value::Object(obj) = message {
// Handle configuration changes if let Some(config) = obj.get(KEY_CONFIG) {
if let Some(config) = obj.get("config") {
match serde_json::from_value(config.clone()) { match serde_json::from_value(config.clone()) {
Ok(cfg) => { Ok(cfg) => {
self.config = cfg; self.config = cfg;
json!({"success": true}) json!({KEY_SUCCESS: true})
} }
Err(e) => json!({"success": false, "error": e.to_string()}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e.to_string()}),
} }
} else if obj.get("method").is_some() { } else if obj.get(KEY_METHOD).is_some() {
let payload = Value::Object(obj.clone()); let payload = Value::Object(obj.clone());
self.handle_rpc(&payload) self.handle_rpc(&payload)
} else { } else {
json!(symbols::ERR_INVALID_COMMAND) json!(ERR_INVALID_COMMAND)
} }
} else { } else {
json!(symbols::ERR_INVALID_COMMAND) json!(ERR_INVALID_COMMAND)
} }
} }
Value::String(subtarget) => match subtarget.as_str() { Value::String(subtarget) => match subtarget.as_str() {
"config" => json!(self.config), STR_CONFIG => json!(self.config),
"state" => json!({ STR_STATE => json!({
"connected": self.status.connected, KEY_CONNECTED: self.status.connected,
"remote": self.status.remote_address, "remote": self.status.remote_address,
}), }),
"protocols" => json!(self.protocol_stack.to_configs()), STR_PROTOCOLS => json!(self.protocol_stack.to_configs()),
_ => json!(symbols::ERR_CHILD_NOT_FOUND), _ => json!(ERR_CHILD_NOT_FOUND),
}, },
_ => json!(symbols::ERR_INVALID_TARGET), _ => json!(ERR_INVALID_TARGET),
} }
} }
} }
+75 -78
View File
@@ -16,7 +16,7 @@ use crate::protocols::{ProtocolConfig, ProtocolStack};
use crate::tcp::config::{ListenerStatus, TcpServerConfig}; use crate::tcp::config::{ListenerStatus, TcpServerConfig};
use unshell::tree::component::Component; use unshell::tree::component::Component;
use unshell::tree::message::TreeMessage; use unshell::tree::message::TreeMessage;
use unshell::tree::symbols; use unshell::tree::symbols::*;
use unshell::tree::{Branch, TreeElement}; use unshell::tree::{Branch, TreeElement};
/// A connected client managed by the server /// A connected client managed by the server
@@ -133,12 +133,12 @@ impl TcpServer {
name: name.clone(), name: name.clone(),
config, config,
protocols: Vec::new(), protocols: Vec::new(),
status: ListenerStatus::stopped("0.0.0.0", 0), status: ListenerStatus::stopped(STR_0_0_0_0, 0),
listener: None, listener: None,
clients: HashMap::new(), clients: HashMap::new(),
client_protocols: HashMap::new(), client_protocols: HashMap::new(),
total_connections: 0, total_connections: 0,
branch: Branch::new("TCPServer"), branch: Branch::new(TYPE_TCP_SERVER),
} }
} }
@@ -299,128 +299,127 @@ impl TcpServer {
let addr = client let addr = client
.lock() .lock()
.map(|c| c.peer_address().to_string()) .map(|c| c.peer_address().to_string())
.unwrap_or_else(|_| "unknown".to_string()); .unwrap_or_else(|_| STR_UNKNOWN.to_string());
json!({"id": id, "peer": addr}) json!({KEY_ID: id, KEY_PEER: addr})
}) })
.collect(); .collect();
json!({ json!({
"listening": self.status.listening, KEY_LISTENING: self.status.listening,
"bind_address": self.config.bind_address, KEY_BIND_ADDRESS: self.config.bind_address,
"port": self.config.port, KEY_PORT: self.config.port,
"active_connections": self.clients.len(), KEY_ACTIVE_CONNECTIONS: self.clients.len(),
"total_connections": self.total_connections, KEY_TOTAL_CONNECTIONS: self.total_connections,
"config": self.config, KEY_CONFIG: self.config,
"protocols": self.protocols, KEY_PROTOCOLS: self.protocols,
"clients": client_list, KEY_CLIENTS: client_list,
}) })
} }
/// Handle RPC call from message /// Handle RPC call from message
fn handle_rpc(&mut self, payload: &Value) -> Value { fn handle_rpc(&mut self, payload: &Value) -> Value {
let method = match payload.get("method").and_then(|m| m.as_str()) { let method = match payload.get(KEY_METHOD).and_then(|m| m.as_str()) {
Some(m) => m, Some(m) => m,
None => return json!({"success": false, "error": "missing method"}), None => return json!({KEY_SUCCESS: false, KEY_ERROR: ERR_MISSING_METHOD}),
}; };
let params = payload.get("params").cloned().unwrap_or(Value::Null); let params = payload.get(KEY_PARAMS).cloned().unwrap_or(Value::Null);
match method { match method {
"listen" | "start" => { METHOD_LISTEN | METHOD_START => {
if let Some(addr) = params.get("bind_address").and_then(|a| a.as_str()) { if let Some(addr) = params.get(KEY_BIND_ADDRESS).and_then(|a| a.as_str()) {
self.config.bind_address = addr.to_string(); self.config.bind_address = addr.to_string();
} }
if let Some(port) = params.get("port").and_then(|p| p.as_u64()) { if let Some(port) = params.get(KEY_PORT).and_then(|p| p.as_u64()) {
self.config.port = port as u16; self.config.port = port as u16;
} }
match self.listen() { match self.listen() {
Ok(_) => json!({"success": true, "status": self.status}), Ok(_) => json!({KEY_SUCCESS: true, KEY_STATUS: self.status}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
} }
} }
"stop" => match self.stop() { METHOD_STOP => match self.stop() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"accept" => { METHOD_ACCEPT => {
// Try to accept a pending connection
if let Some((id, stream)) = self.accept() { if let Some((id, stream)) = self.accept() {
self.register_client(id.clone(), stream); self.register_client(id.clone(), stream);
json!({"success": true, "client_id": id}) json!({KEY_SUCCESS: true, KEY_CLIENT_ID: id})
} else { } else {
json!({"success": true, "client_id": null}) json!({KEY_SUCCESS: true, KEY_CLIENT_ID: serde_json::Value::Null})
} }
} }
"send" => { METHOD_SEND => {
let client_id = params let client_id = params
.get("client_id") .get(KEY_CLIENT_ID)
.and_then(|c| c.as_str()) .and_then(|c| c.as_str())
.ok_or_else(|| json!({"error": "missing client_id"})); .ok_or_else(|| json!({KEY_ERROR: ERR_MISSING_CLIENT_ID}));
match client_id { match client_id {
Ok(id) => { Ok(id) => {
let data = params let data = params
.get("data") .get(KEY_DATA)
.and_then(|d| d.as_str()) .and_then(|d| d.as_str())
.map(|s| s.as_bytes().to_vec()); .map(|s| s.as_bytes().to_vec());
match data { match data {
Some(data) => match self.send_to(id, &data) { Some(data) => match self.send_to(id, &data) {
Ok(n) => json!({"success": true, "bytes_sent": n}), Ok(n) => json!({KEY_SUCCESS: true, KEY_BYTES_SENT: n}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
None => json!({"success": false, "error": "missing data"}), None => json!({KEY_SUCCESS: false, KEY_ERROR: ERR_MISSING_DATA}),
} }
} }
Err(e) => e, Err(e) => e,
} }
} }
"recv" => { METHOD_RECV => {
let client_id = params let client_id = params
.get("client_id") .get(KEY_CLIENT_ID)
.and_then(|c| c.as_str()) .and_then(|c| c.as_str())
.ok_or_else(|| json!({"error": "missing client_id"})); .ok_or_else(|| json!({KEY_ERROR: ERR_MISSING_CLIENT_ID}));
match client_id { match client_id {
Ok(id) => { Ok(id) => {
let size = params let size = params
.get("size") .get(KEY_SIZE)
.and_then(|s| s.as_u64()) .and_then(|s| s.as_u64())
.map(|s| s as usize) .map(|s| s as usize)
.unwrap_or(4096); .unwrap_or(4096);
match self.recv_from(id, size) { match self.recv_from(id, size) {
Ok(data) => json!({ Ok(data) => json!({
"success": true, KEY_SUCCESS: true,
"data": String::from_utf8_lossy(&data), KEY_DATA: String::from_utf8_lossy(&data),
"bytes": data.len() "bytes": data.len()
}), }),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
} }
} }
Err(e) => e, Err(e) => e,
} }
} }
"disconnect" => { METHOD_DISCONNECT => {
let client_id = params let client_id = params
.get("client_id") .get(KEY_CLIENT_ID)
.and_then(|c| c.as_str()) .and_then(|c| c.as_str())
.ok_or_else(|| json!({"error": "missing client_id"})); .ok_or_else(|| json!({KEY_ERROR: ERR_MISSING_CLIENT_ID}));
match client_id { match client_id {
Ok(id) => match self.disconnect_client(id) { Ok(id) => match self.disconnect_client(id) {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
Err(e) => e, Err(e) => e,
} }
} }
"status" => self.get_status(), METHOD_STATUS => self.get_status(),
"list_clients" => { METHOD_LIST_CLIENTS => {
let clients: Vec<Value> = self.clients.keys().map(|k| json!(k)).collect(); let clients: Vec<Value> = self.clients.keys().map(|k| json!(k)).collect();
json!({"success": true, "clients": clients}) json!({KEY_SUCCESS: true, KEY_CLIENTS: clients})
} }
_ => json!({"success": false, "error": format!("unknown method: {}", method)}), _ => json!({KEY_SUCCESS: false, KEY_ERROR: format!("unknown method: {}", method)}),
} }
} }
} }
@@ -435,7 +434,7 @@ impl Component for TcpServer {
} }
fn init(&mut self, config: Value) -> Result<(), String> { fn init(&mut self, config: Value) -> Result<(), String> {
if let Some(server_config) = config.get("config") { if let Some(server_config) = config.get(KEY_CONFIG) {
self.config = serde_json::from_value(server_config.clone()) self.config = serde_json::from_value(server_config.clone())
.map_err(|e| format!("Invalid config: {}", e))?; .map_err(|e| format!("Invalid config: {}", e))?;
} else { } else {
@@ -443,7 +442,7 @@ impl Component for TcpServer {
.map_err(|e| format!("Invalid config: {}", e))?; .map_err(|e| format!("Invalid config: {}", e))?;
} }
if let Some(protocols) = config.get("protocols") { if let Some(protocols) = config.get(KEY_PROTOCOLS) {
let p: Vec<ProtocolConfig> = serde_json::from_value(protocols.clone()) let p: Vec<ProtocolConfig> = serde_json::from_value(protocols.clone())
.map_err(|e| format!("Invalid protocols: {}", e))?; .map_err(|e| format!("Invalid protocols: {}", e))?;
self.set_protocols(p)?; self.set_protocols(p)?;
@@ -461,32 +460,30 @@ impl Component for TcpServer {
impl TreeElement for TcpServer { impl TreeElement for TcpServer {
fn get_type(&self) -> Value { fn get_type(&self) -> Value {
json!({ json!({
"type": "TCPServer", KEY_TYPE: TYPE_TCP_SERVER,
"name": self.name, KEY_NAME: self.name,
}) })
} }
fn send_message(&mut self, target: Value, message: Value) -> Value { fn send_message(&mut self, target: Value, message: Value) -> Value {
match target { match target {
Value::Null => { Value::Null => {
// Check for RPC call format if message.get(KEY_METHOD).is_some() {
if message.get("method").is_some() {
return self.handle_rpc(&message); return self.handle_rpc(&message);
} }
// Legacy string commands
if let Some(cmd) = message.as_str() { if let Some(cmd) = message.as_str() {
match cmd { match cmd {
"Listen" | "Start" => match self.listen() { CMD_LISTEN | CMD_START => match self.listen() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"Stop" => match self.stop() { CMD_STOP => match self.stop() {
Ok(_) => json!({"success": true}), Ok(_) => json!({KEY_SUCCESS: true}),
Err(e) => json!({"success": false, "error": e}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
}, },
"Status" => self.get_status(), CMD_STATUS => self.get_status(),
symbols::CMD_GET_CHILDREN => { CMD_GET_CHILDREN => {
let children = self let children = self
.branch .branch
.children() .children()
@@ -495,37 +492,37 @@ impl TreeElement for TcpServer {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
json!(children) json!(children)
} }
_ => json!(symbols::ERR_UNSUPPORTED_METHOD), _ => json!(ERR_UNSUPPORTED_METHOD),
} }
} else if let Value::Object(obj) = message { } else if let Value::Object(obj) = message {
if let Some(config) = obj.get("config") { if let Some(config) = obj.get(KEY_CONFIG) {
match serde_json::from_value(config.clone()) { match serde_json::from_value(config.clone()) {
Ok(cfg) => { Ok(cfg) => {
self.config = cfg; self.config = cfg;
json!({"success": true}) json!({KEY_SUCCESS: true})
} }
Err(e) => json!({"success": false, "error": e.to_string()}), Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e.to_string()}),
} }
} else if obj.get("method").is_some() { } else if obj.get(KEY_METHOD).is_some() {
let payload = Value::Object(obj.clone()); let payload = Value::Object(obj.clone());
self.handle_rpc(&payload) self.handle_rpc(&payload)
} else { } else {
json!(symbols::ERR_INVALID_COMMAND) json!(ERR_INVALID_COMMAND)
} }
} else { } else {
json!(symbols::ERR_INVALID_COMMAND) json!(ERR_INVALID_COMMAND)
} }
} }
Value::String(subtarget) => match subtarget.as_str() { Value::String(subtarget) => match subtarget.as_str() {
"config" => json!(self.config), STR_CONFIG => json!(self.config),
"status" => self.get_status(), KEY_STATUS => self.get_status(),
"clients" => { KEY_CLIENTS => {
let clients: Vec<Value> = self.clients.keys().map(|k| json!(k)).collect(); let clients: Vec<Value> = self.clients.keys().map(|k| json!(k)).collect();
json!(clients) json!(clients)
} }
_ => json!(symbols::ERR_CHILD_NOT_FOUND), _ => json!(ERR_CHILD_NOT_FOUND),
}, },
_ => json!(symbols::ERR_INVALID_TARGET), _ => json!(ERR_INVALID_TARGET),
} }
} }
} }