mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Docs
This commit is contained in:
@@ -216,3 +216,47 @@ const C2_URL: &str = symbol!("https://C2Server/endpoint");
|
||||
## License
|
||||
|
||||
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,9 +1,8 @@
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::{
|
||||
ModuleError, Result,
|
||||
config::{ConfigStructField, InterfaceData, InterfaceStruct, TreeMessage},
|
||||
warn,
|
||||
warn, ModuleError, Result,
|
||||
};
|
||||
|
||||
pub type ConfigStructKeys = Vec<ConfigStructField>;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ModuleError, Result, config::config_struct};
|
||||
use crate::{config::config_struct, ModuleError, Result};
|
||||
|
||||
pub trait Tree {
|
||||
fn is_folder() -> bool {
|
||||
|
||||
@@ -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;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ModuleError>;
|
||||
|
||||
+79
-1
@@ -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]
|
||||
|
||||
pub mod config;
|
||||
@@ -12,5 +90,5 @@ pub use error::{ModuleError, Result};
|
||||
pub use announcement::Announcement;
|
||||
|
||||
// Re-exports
|
||||
pub use serde_json::{Value, json};
|
||||
pub use serde_json::{json, Value};
|
||||
pub use ush_obfuscate as obfuscate;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $fmt:tt) => {{
|
||||
use $crate::obfuscate;
|
||||
let log_result = obfuscate::format_obs!($fmt);
|
||||
use $crate::obfuscate::format_sym;
|
||||
let log_result = format_sym!($fmt);
|
||||
|
||||
$crate::logger::add_record(
|
||||
$level,
|
||||
|
||||
#[cfg(feature = "log_debug")]
|
||||
Some(String::from(obfuscate::file_symbol!())),
|
||||
Some(String::from($crate::obfuscate::file_symbol!())),
|
||||
#[cfg(not(feature = "log_debug"))]
|
||||
None,
|
||||
|
||||
@@ -17,14 +17,14 @@ macro_rules! log {
|
||||
);
|
||||
}};
|
||||
($level:expr, $fmt:tt, $($arg:expr),*) => {{
|
||||
use $crate::obfuscate;
|
||||
let log_result = obfuscate::format_obs!($fmt, $($arg),*);
|
||||
use $crate::obfuscate::format_sym;
|
||||
let log_result = format_sym!($fmt, $($arg),*);
|
||||
|
||||
$crate::logger::add_record(
|
||||
$level,
|
||||
|
||||
#[cfg(feature = "log_debug")]
|
||||
Some(String::from(obfuscate::file_symbol!())),
|
||||
Some(String::from($crate::obfuscate::file_symbol!())),
|
||||
#[cfg(not(feature = "log_debug"))]
|
||||
None,
|
||||
|
||||
|
||||
+50
-1
@@ -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
|
||||
#[cfg(feature = "log")]
|
||||
pub mod macros;
|
||||
@@ -9,8 +58,8 @@ mod pretty_logger;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
pub use pretty_logger::PrettyLogger;
|
||||
pub use pretty_logger::log;
|
||||
pub use pretty_logger::PrettyLogger;
|
||||
|
||||
static mut LOGGER: &dyn Logger = &DefaultLogger;
|
||||
|
||||
|
||||
@@ -1,4 +1,49 @@
|
||||
//! 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;
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ use serde_json::{json, Value};
|
||||
use crate::tree::component::ComponentRegistry;
|
||||
use crate::tree::queue::Queue;
|
||||
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};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Connection {
|
||||
id: String,
|
||||
peer_id: String,
|
||||
@@ -24,6 +25,7 @@ pub(crate) struct Connection {
|
||||
receiver: Receiver<Value>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Connection {
|
||||
pub(crate) fn new(
|
||||
id: String,
|
||||
@@ -62,11 +64,13 @@ impl TreeElement for Connections {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct Connections {
|
||||
connections: HashMap<String, Connection>,
|
||||
branch: Branch,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Connections {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
@@ -80,6 +84,7 @@ impl Connections {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn create_channel_pair() -> (
|
||||
(Sender<Value>, Receiver<Value>),
|
||||
(Sender<Value>, Receiver<Value>),
|
||||
@@ -128,7 +133,8 @@ impl EndpointManager {
|
||||
&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 conn_a = Connection::new(id.clone(), peer_id.clone(), tx_remote, rx_local);
|
||||
|
||||
@@ -218,6 +218,8 @@ impl Default for TreeMessage {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
+100
-1
@@ -1,7 +1,106 @@
|
||||
//! Tree system for hierarchical message routing between endpoints.
|
||||
//!
|
||||
//! 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 component;
|
||||
|
||||
+113
-20
@@ -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 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 LOGGER: &'static str = sym!("Logger");
|
||||
|
||||
pub const CMD_GET: &'static str = symbol!("Get");
|
||||
pub const CMD_POLL: &'static str = symbol!("Poll");
|
||||
pub const CMD_GET_LENGTH: &'static str = symbol!("GetLength");
|
||||
pub const CMD_GET_CHILDREN: &'static str = symbol!("GetChildren");
|
||||
pub const TYPE_TREE: &'static str = sym!("Tree");
|
||||
pub const TYPE_QUEUE: &'static str = sym!("Queue");
|
||||
pub const TYPE_ENDPOINT: &'static str = sym!("Endpoint");
|
||||
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 ERR_INVALID_COMMAND: &'static str = symbol!("InvalidCommand");
|
||||
pub const ERR_INVALID_CHILD: &'static str = symbol!("InvalidChild");
|
||||
pub const ERR_INVALID_TARGET: &'static str = symbol!("InvalidTarget");
|
||||
pub const ERR_CHILD_NOT_FOUND: &'static str = symbol!("ChildNotFound");
|
||||
pub const ERR_INVALID_PATH: &'static str = symbol!("InvalidPath");
|
||||
pub const ERR_MISSING_ARGS: &'static str = symbol!("MissingArgs");
|
||||
pub const ERR_INVALID_STATE: &'static str = symbol!("InvalidState");
|
||||
pub const ERR_READONLY: &'static str = symbol!("ReadOnly");
|
||||
pub const CMD_GET: &'static str = sym!("Get");
|
||||
pub const CMD_POLL: &'static str = sym!("Poll");
|
||||
pub const CMD_GET_LENGTH: &'static str = sym!("GetLength");
|
||||
pub const CMD_GET_CHILDREN: &'static str = sym!("GetChildren");
|
||||
|
||||
pub const ERR_UNSUPPORTED_METHOD: &'static str = sym!("UnsupportedMethod");
|
||||
pub const ERR_INVALID_COMMAND: &'static str = sym!("InvalidCommand");
|
||||
pub const ERR_INVALID_CHILD: &'static str = sym!("InvalidChild");
|
||||
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,4 +1,4 @@
|
||||
use crate::crypt::{STATIC_BYTE_MAP, hash};
|
||||
use crate::crypt::{hash, STATIC_BYTE_MAP};
|
||||
|
||||
// Randomly mapped Base62 characters
|
||||
pub struct Base62 {
|
||||
|
||||
+12
-10
@@ -24,18 +24,20 @@ use obfuscate as obs;
|
||||
// String obfuscation
|
||||
|
||||
#[proc_macro]
|
||||
pub fn obs(input: TokenStream) -> TokenStream {
|
||||
pub fn xor(input: TokenStream) -> TokenStream {
|
||||
obs::xor(input)
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn obfuscated_symbol(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
obs::aes_fn_name(_attr, item)
|
||||
/// Represents strings as a symbol.
|
||||
#[proc_macro]
|
||||
pub fn sym(input: TokenStream) -> TokenStream {
|
||||
obs::aes_str(input)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn symbol(input: TokenStream) -> TokenStream {
|
||||
obs::aes_str(input)
|
||||
/// Represents function names as a symbol.
|
||||
#[proc_macro_attribute]
|
||||
pub fn sym_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
obs::aes_fn_name(_attr, item)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
@@ -56,7 +58,7 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream {
|
||||
|
||||
// Return as a string literal
|
||||
let output = quote! {
|
||||
obfuscate::symbol!(#concatted)
|
||||
sym!(#concatted)
|
||||
};
|
||||
// let output = quote! {
|
||||
// #concatted
|
||||
@@ -65,7 +67,7 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
#[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 segments = parse_format_string(&format_str);
|
||||
@@ -83,7 +85,7 @@ pub fn format_obs(input: TokenStream) -> TokenStream {
|
||||
match segment {
|
||||
FormatSegment::Static(text) => {
|
||||
parts.push(quote! {
|
||||
obfuscate::symbol!(#text).to_string()
|
||||
#text.to_string()
|
||||
});
|
||||
}
|
||||
FormatSegment::Dynamic(spec, idx) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{ItemFn, LitStr, parse_macro_input};
|
||||
use syn::{parse_macro_input, ItemFn, LitStr};
|
||||
|
||||
pub fn xor(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as LitStr);
|
||||
|
||||
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use rand::rngs::SmallRng;
|
||||
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 MAX_TAGS: u32 = 22; // Maximum instructions per recursive block
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use getrandom::fill;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{LitStr, parse_macro_input};
|
||||
use syn::{parse_macro_input, LitStr};
|
||||
|
||||
/// XOR encrypt strings
|
||||
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 quote::quote;
|
||||
use syn::{ItemFn, LitStr, parse_macro_input};
|
||||
use syn::{parse_macro_input, ItemFn, LitStr};
|
||||
|
||||
use crate::obfuscate::get_encryption_key;
|
||||
|
||||
|
||||
@@ -1,4 +1,46 @@
|
||||
//! 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 serde_json::Value;
|
||||
|
||||
@@ -114,6 +114,7 @@ impl Protocol for HttpProtocol {
|
||||
///
|
||||
/// This is a simple implementation for testing - in production you'd
|
||||
/// use a proper HTTP server.
|
||||
#[allow(dead_code)]
|
||||
pub struct HttpServer {
|
||||
config: HttpConfig,
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ impl ProtocolStack {
|
||||
let p = WebSocketProtocol::new(cfg.clone());
|
||||
(Box::new(p) as Box<dyn Protocol>, "websocket".to_string())
|
||||
}
|
||||
ProtocolConfig::Custom { name, config } => {
|
||||
ProtocolConfig::Custom { name, config: _ } => {
|
||||
return Err(ProtocolError::NotFound(format!(
|
||||
"Custom protocol '{}' not implemented",
|
||||
name
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::protocols::{ProtocolConfig, ProtocolStack};
|
||||
use crate::tcp::config::{ConnectionStatus, TcpClientConfig};
|
||||
use unshell::tree::component::Component;
|
||||
use unshell::tree::message::TreeMessage;
|
||||
use unshell::tree::symbols;
|
||||
use unshell::tree::symbols::*;
|
||||
use unshell::tree::{Branch, TreeElement};
|
||||
|
||||
/// TCP Client component with protocol stacking support.
|
||||
@@ -58,9 +58,9 @@ impl TcpClient {
|
||||
pub fn with_config(name: impl Into<String>, config: TcpClientConfig) -> Self {
|
||||
let name = name.into();
|
||||
|
||||
let mut branch = Branch::new("TCPClient");
|
||||
let state_branch = Branch::new("state");
|
||||
branch.add_child("state", Box::new(state_branch));
|
||||
let mut branch = Branch::new(TYPE_TCP_CLIENT);
|
||||
let state_branch = Branch::new(STR_STATE);
|
||||
branch.add_child(STR_STATE, Box::new(state_branch));
|
||||
|
||||
Self {
|
||||
name: name.clone(),
|
||||
@@ -196,88 +196,87 @@ impl TcpClient {
|
||||
/// Get status as JSON
|
||||
pub fn get_status(&self) -> Value {
|
||||
json!({
|
||||
"connected": self.status.connected,
|
||||
"remote_address": self.status.remote_address,
|
||||
"local_address": self.status.local_address,
|
||||
"bytes_sent": self.status.bytes_sent,
|
||||
"bytes_received": self.status.bytes_received,
|
||||
"config": self.config,
|
||||
"protocols": self.protocol_stack.to_configs(),
|
||||
KEY_CONNECTED: self.status.connected,
|
||||
KEY_REMOTE_ADDRESS: self.status.remote_address,
|
||||
KEY_LOCAL_ADDRESS: self.status.local_address,
|
||||
KEY_BYTES_SENT: self.status.bytes_sent,
|
||||
KEY_BYTES_RECEIVED: self.status.bytes_received,
|
||||
KEY_CONFIG: self.config,
|
||||
KEY_PROTOCOLS: self.protocol_stack.to_configs(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle RPC call from message
|
||||
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,
|
||||
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 {
|
||||
"connect" => {
|
||||
// Allow override of address/port
|
||||
if let Some(addr) = params.get("address").and_then(|a| a.as_str()) {
|
||||
METHOD_CONNECT => {
|
||||
if let Some(addr) = params.get(KEY_ADDRESS).and_then(|a| a.as_str()) {
|
||||
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;
|
||||
}
|
||||
|
||||
match self.connect() {
|
||||
Ok(_) => json!({"success": true, "status": self.status}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(_) => json!({KEY_SUCCESS: true, KEY_STATUS: self.status}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
}
|
||||
}
|
||||
"disconnect" => match self.disconnect() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
METHOD_DISCONNECT => match self.disconnect() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"send" => {
|
||||
METHOD_SEND => {
|
||||
let data = params
|
||||
.get("data")
|
||||
.get(KEY_DATA)
|
||||
.and_then(|d| d.as_str())
|
||||
.map(|s| s.as_bytes().to_vec());
|
||||
|
||||
match data {
|
||||
Some(data) => match self.send_raw(&data) {
|
||||
Ok(n) => json!({"success": true, "bytes_sent": n}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(n) => json!({KEY_SUCCESS: true, KEY_BYTES_SENT: n}),
|
||||
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
|
||||
.get("size")
|
||||
.get(KEY_SIZE)
|
||||
.and_then(|s| s.as_u64())
|
||||
.map(|s| s as usize)
|
||||
.unwrap_or(4096);
|
||||
match self.recv_raw(size) {
|
||||
Ok(data) => json!({
|
||||
"success": true,
|
||||
"data": String::from_utf8_lossy(&data),
|
||||
KEY_SUCCESS: true,
|
||||
KEY_DATA: String::from_utf8_lossy(&data),
|
||||
"bytes": data.len()
|
||||
}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
}
|
||||
}
|
||||
"status" => self.get_status(),
|
||||
"set_protocols" => {
|
||||
if let Some(protocols) = params.get("protocols") {
|
||||
METHOD_STATUS => self.get_status(),
|
||||
METHOD_SET_PROTOCOLS => {
|
||||
if let Some(protocols) = params.get(KEY_PROTOCOLS) {
|
||||
match serde_json::from_value(protocols.clone()) {
|
||||
Ok(p) => match self.set_protocols(p) {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
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 {
|
||||
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> {
|
||||
// Support both legacy config and new format
|
||||
if let Some(client_config) = config.get("config") {
|
||||
if let Some(client_config) = config.get(KEY_CONFIG) {
|
||||
self.config = serde_json::from_value(client_config.clone())
|
||||
.map_err(|e| format!("Invalid config: {}", e))?;
|
||||
} else {
|
||||
@@ -301,7 +299,7 @@ impl Component for TcpClient {
|
||||
.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())
|
||||
.map_err(|e| format!("Invalid protocols: {}", e))?;
|
||||
self.set_protocols(p)?;
|
||||
@@ -319,32 +317,30 @@ impl Component for TcpClient {
|
||||
impl TreeElement for TcpClient {
|
||||
fn get_type(&self) -> Value {
|
||||
json!({
|
||||
"type": "TCPClient",
|
||||
"name": self.name,
|
||||
KEY_TYPE: TYPE_TCP_CLIENT,
|
||||
KEY_NAME: self.name,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_message(&mut self, target: Value, message: Value) -> Value {
|
||||
match target {
|
||||
Value::Null => {
|
||||
// Check for RPC call format
|
||||
if message.get("method").is_some() {
|
||||
if message.get(KEY_METHOD).is_some() {
|
||||
return self.handle_rpc(&message);
|
||||
}
|
||||
|
||||
// Legacy string commands
|
||||
if let Some(cmd) = message.as_str() {
|
||||
match cmd {
|
||||
"Connect" => match self.connect() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
CMD_CONNECT => match self.connect() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"Disconnect" => match self.disconnect() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
CMD_DISCONNECT => match self.disconnect() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"Status" => self.get_status(),
|
||||
symbols::CMD_GET_CHILDREN => {
|
||||
CMD_STATUS => self.get_status(),
|
||||
CMD_GET_CHILDREN => {
|
||||
let children = self
|
||||
.branch
|
||||
.children()
|
||||
@@ -353,38 +349,37 @@ impl TreeElement for TcpClient {
|
||||
.collect::<Vec<_>>();
|
||||
json!(children)
|
||||
}
|
||||
_ => json!(symbols::ERR_UNSUPPORTED_METHOD),
|
||||
_ => json!(ERR_UNSUPPORTED_METHOD),
|
||||
}
|
||||
} else if let Value::Object(obj) = message {
|
||||
// Handle configuration changes
|
||||
if let Some(config) = obj.get("config") {
|
||||
if let Some(config) = obj.get(KEY_CONFIG) {
|
||||
match serde_json::from_value(config.clone()) {
|
||||
Ok(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());
|
||||
self.handle_rpc(&payload)
|
||||
} else {
|
||||
json!(symbols::ERR_INVALID_COMMAND)
|
||||
json!(ERR_INVALID_COMMAND)
|
||||
}
|
||||
} else {
|
||||
json!(symbols::ERR_INVALID_COMMAND)
|
||||
json!(ERR_INVALID_COMMAND)
|
||||
}
|
||||
}
|
||||
Value::String(subtarget) => match subtarget.as_str() {
|
||||
"config" => json!(self.config),
|
||||
"state" => json!({
|
||||
"connected": self.status.connected,
|
||||
STR_CONFIG => json!(self.config),
|
||||
STR_STATE => json!({
|
||||
KEY_CONNECTED: self.status.connected,
|
||||
"remote": self.status.remote_address,
|
||||
}),
|
||||
"protocols" => json!(self.protocol_stack.to_configs()),
|
||||
_ => json!(symbols::ERR_CHILD_NOT_FOUND),
|
||||
STR_PROTOCOLS => json!(self.protocol_stack.to_configs()),
|
||||
_ => json!(ERR_CHILD_NOT_FOUND),
|
||||
},
|
||||
_ => json!(symbols::ERR_INVALID_TARGET),
|
||||
_ => json!(ERR_INVALID_TARGET),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::protocols::{ProtocolConfig, ProtocolStack};
|
||||
use crate::tcp::config::{ListenerStatus, TcpServerConfig};
|
||||
use unshell::tree::component::Component;
|
||||
use unshell::tree::message::TreeMessage;
|
||||
use unshell::tree::symbols;
|
||||
use unshell::tree::symbols::*;
|
||||
use unshell::tree::{Branch, TreeElement};
|
||||
|
||||
/// A connected client managed by the server
|
||||
@@ -133,12 +133,12 @@ impl TcpServer {
|
||||
name: name.clone(),
|
||||
config,
|
||||
protocols: Vec::new(),
|
||||
status: ListenerStatus::stopped("0.0.0.0", 0),
|
||||
status: ListenerStatus::stopped(STR_0_0_0_0, 0),
|
||||
listener: None,
|
||||
clients: HashMap::new(),
|
||||
client_protocols: HashMap::new(),
|
||||
total_connections: 0,
|
||||
branch: Branch::new("TCPServer"),
|
||||
branch: Branch::new(TYPE_TCP_SERVER),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,128 +299,127 @@ impl TcpServer {
|
||||
let addr = client
|
||||
.lock()
|
||||
.map(|c| c.peer_address().to_string())
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
json!({"id": id, "peer": addr})
|
||||
.unwrap_or_else(|_| STR_UNKNOWN.to_string());
|
||||
json!({KEY_ID: id, KEY_PEER: addr})
|
||||
})
|
||||
.collect();
|
||||
|
||||
json!({
|
||||
"listening": self.status.listening,
|
||||
"bind_address": self.config.bind_address,
|
||||
"port": self.config.port,
|
||||
"active_connections": self.clients.len(),
|
||||
"total_connections": self.total_connections,
|
||||
"config": self.config,
|
||||
"protocols": self.protocols,
|
||||
"clients": client_list,
|
||||
KEY_LISTENING: self.status.listening,
|
||||
KEY_BIND_ADDRESS: self.config.bind_address,
|
||||
KEY_PORT: self.config.port,
|
||||
KEY_ACTIVE_CONNECTIONS: self.clients.len(),
|
||||
KEY_TOTAL_CONNECTIONS: self.total_connections,
|
||||
KEY_CONFIG: self.config,
|
||||
KEY_PROTOCOLS: self.protocols,
|
||||
KEY_CLIENTS: client_list,
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle RPC call from message
|
||||
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,
|
||||
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 {
|
||||
"listen" | "start" => {
|
||||
if let Some(addr) = params.get("bind_address").and_then(|a| a.as_str()) {
|
||||
METHOD_LISTEN | METHOD_START => {
|
||||
if let Some(addr) = params.get(KEY_BIND_ADDRESS).and_then(|a| a.as_str()) {
|
||||
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;
|
||||
}
|
||||
|
||||
match self.listen() {
|
||||
Ok(_) => json!({"success": true, "status": self.status}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(_) => json!({KEY_SUCCESS: true, KEY_STATUS: self.status}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
}
|
||||
}
|
||||
"stop" => match self.stop() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
METHOD_STOP => match self.stop() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"accept" => {
|
||||
// Try to accept a pending connection
|
||||
METHOD_ACCEPT => {
|
||||
if let Some((id, stream)) = self.accept() {
|
||||
self.register_client(id.clone(), stream);
|
||||
json!({"success": true, "client_id": id})
|
||||
json!({KEY_SUCCESS: true, KEY_CLIENT_ID: id})
|
||||
} 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
|
||||
.get("client_id")
|
||||
.get(KEY_CLIENT_ID)
|
||||
.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 {
|
||||
Ok(id) => {
|
||||
let data = params
|
||||
.get("data")
|
||||
.get(KEY_DATA)
|
||||
.and_then(|d| d.as_str())
|
||||
.map(|s| s.as_bytes().to_vec());
|
||||
|
||||
match data {
|
||||
Some(data) => match self.send_to(id, &data) {
|
||||
Ok(n) => json!({"success": true, "bytes_sent": n}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(n) => json!({KEY_SUCCESS: true, KEY_BYTES_SENT: n}),
|
||||
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,
|
||||
}
|
||||
}
|
||||
"recv" => {
|
||||
METHOD_RECV => {
|
||||
let client_id = params
|
||||
.get("client_id")
|
||||
.get(KEY_CLIENT_ID)
|
||||
.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 {
|
||||
Ok(id) => {
|
||||
let size = params
|
||||
.get("size")
|
||||
.get(KEY_SIZE)
|
||||
.and_then(|s| s.as_u64())
|
||||
.map(|s| s as usize)
|
||||
.unwrap_or(4096);
|
||||
match self.recv_from(id, size) {
|
||||
Ok(data) => json!({
|
||||
"success": true,
|
||||
"data": String::from_utf8_lossy(&data),
|
||||
KEY_SUCCESS: true,
|
||||
KEY_DATA: String::from_utf8_lossy(&data),
|
||||
"bytes": data.len()
|
||||
}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
}
|
||||
}
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
"disconnect" => {
|
||||
METHOD_DISCONNECT => {
|
||||
let client_id = params
|
||||
.get("client_id")
|
||||
.get(KEY_CLIENT_ID)
|
||||
.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 {
|
||||
Ok(id) => match self.disconnect_client(id) {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
"status" => self.get_status(),
|
||||
"list_clients" => {
|
||||
METHOD_STATUS => self.get_status(),
|
||||
METHOD_LIST_CLIENTS => {
|
||||
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> {
|
||||
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())
|
||||
.map_err(|e| format!("Invalid config: {}", e))?;
|
||||
} else {
|
||||
@@ -443,7 +442,7 @@ impl Component for TcpServer {
|
||||
.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())
|
||||
.map_err(|e| format!("Invalid protocols: {}", e))?;
|
||||
self.set_protocols(p)?;
|
||||
@@ -461,32 +460,30 @@ impl Component for TcpServer {
|
||||
impl TreeElement for TcpServer {
|
||||
fn get_type(&self) -> Value {
|
||||
json!({
|
||||
"type": "TCPServer",
|
||||
"name": self.name,
|
||||
KEY_TYPE: TYPE_TCP_SERVER,
|
||||
KEY_NAME: self.name,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_message(&mut self, target: Value, message: Value) -> Value {
|
||||
match target {
|
||||
Value::Null => {
|
||||
// Check for RPC call format
|
||||
if message.get("method").is_some() {
|
||||
if message.get(KEY_METHOD).is_some() {
|
||||
return self.handle_rpc(&message);
|
||||
}
|
||||
|
||||
// Legacy string commands
|
||||
if let Some(cmd) = message.as_str() {
|
||||
match cmd {
|
||||
"Listen" | "Start" => match self.listen() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
CMD_LISTEN | CMD_START => match self.listen() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"Stop" => match self.stop() {
|
||||
Ok(_) => json!({"success": true}),
|
||||
Err(e) => json!({"success": false, "error": e}),
|
||||
CMD_STOP => match self.stop() {
|
||||
Ok(_) => json!({KEY_SUCCESS: true}),
|
||||
Err(e) => json!({KEY_SUCCESS: false, KEY_ERROR: e}),
|
||||
},
|
||||
"Status" => self.get_status(),
|
||||
symbols::CMD_GET_CHILDREN => {
|
||||
CMD_STATUS => self.get_status(),
|
||||
CMD_GET_CHILDREN => {
|
||||
let children = self
|
||||
.branch
|
||||
.children()
|
||||
@@ -495,37 +492,37 @@ impl TreeElement for TcpServer {
|
||||
.collect::<Vec<_>>();
|
||||
json!(children)
|
||||
}
|
||||
_ => json!(symbols::ERR_UNSUPPORTED_METHOD),
|
||||
_ => json!(ERR_UNSUPPORTED_METHOD),
|
||||
}
|
||||
} 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()) {
|
||||
Ok(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());
|
||||
self.handle_rpc(&payload)
|
||||
} else {
|
||||
json!(symbols::ERR_INVALID_COMMAND)
|
||||
json!(ERR_INVALID_COMMAND)
|
||||
}
|
||||
} else {
|
||||
json!(symbols::ERR_INVALID_COMMAND)
|
||||
json!(ERR_INVALID_COMMAND)
|
||||
}
|
||||
}
|
||||
Value::String(subtarget) => match subtarget.as_str() {
|
||||
"config" => json!(self.config),
|
||||
"status" => self.get_status(),
|
||||
"clients" => {
|
||||
STR_CONFIG => json!(self.config),
|
||||
KEY_STATUS => self.get_status(),
|
||||
KEY_CLIENTS => {
|
||||
let clients: Vec<Value> = self.clients.keys().map(|k| json!(k)).collect();
|
||||
json!(clients)
|
||||
}
|
||||
_ => json!(symbols::ERR_CHILD_NOT_FOUND),
|
||||
_ => json!(ERR_CHILD_NOT_FOUND),
|
||||
},
|
||||
_ => json!(symbols::ERR_INVALID_TARGET),
|
||||
_ => json!(ERR_INVALID_TARGET),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user