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
|
## 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).
|
||||||
|
|||||||
@@ -58,3 +58,4 @@ done
|
|||||||
echo "## STARTING "
|
echo "## STARTING "
|
||||||
|
|
||||||
$BINARY
|
$BINARY
|
||||||
|
wc -c $BINARY
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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
@@ -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,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);
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user