From 989b5057d072e5802675bacde7e8e3b7187a808b Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:05:43 -0700 Subject: [PATCH] Add Docs --- README.md | 158 ++++++++++++++++++++++++++++++- src/announcement.rs | 20 +++- src/config/config_struct.rs | 33 +++++++ src/config/config_struct_list.rs | 18 ++++ src/config/mod.rs | 50 +++++++++- src/config/tree.rs | 56 ++++++++++- src/logger/macros.rs | 33 +++++++ src/logger/macros_disabled.rs | 22 +++++ src/logger/pretty_logger.rs | 34 +++++++ src/tree/log.rs | 26 ++++- src/tree/queue.rs | 42 ++++++++ src/tree/readonly.rs | 49 ++++++++++ ush-obfuscate/src/lib.rs | 54 ++++++++++- 13 files changed, 578 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a99f181..374dac6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ unshell/ ├── src/tree/ # Hierarchical message routing │ ├── component.rs # Component trait (implement for any module) │ ├── endpoint.rs # Endpoint manager -│ ├── protocols/ # Pluggable protocol stack +│ ├── protocols/ # Pluggable protocol stack │ └── tcp/ # Example transport implementations ├── ush-obfuscate/ # Compile-time string obfuscation └── ush-payload/ # Test harness @@ -36,7 +36,24 @@ unshell/ Everything plugs into these abstractions: -### Component - Any Module +### TreeElement - The Foundation + +Every node in the tree implements this trait: + +```rust +use serde_json::Value; +use unshell::tree::TreeElement; + +pub trait TreeElement: Send + Sync { + fn get_type(&self) -> Value; + fn send_message(&mut self, target: Value, message: Value) -> Value; +} +``` + +- `get_type()` returns the element's type identifier +- `send_message()` handles incoming messages and returns responses + +### Component - Extensible Modules ```rust use unshell::tree::Component; @@ -50,10 +67,13 @@ pub trait Component: Send + Sync { } ``` +**Important**: The `init()` method should only configure the component, not establish connections. +For TcpClient, use `auto_connect: true` in config if you need auto-connection after initialization. + ### Protocol - Any Encoding Layer ```rust -use unshell::tree::protocols::Protocol; +use unshell::protocols::Protocol; pub trait Protocol: Send + Sync { fn name(&self) -> &'static str; @@ -76,6 +96,82 @@ pub trait Protocol: Send + Sync { // They expose the same interface as native components ``` +## Tree Message Protocol + +Messages follow a JSON-based format defined in `src/tree/message.rs`: + +```rust +// Create a request +let msg = TreeMessage::new("rpc.call") + .to_target(["components", "tcp-client"]) + .with_payload(json!({ + "method": "connect", + "params": {"address": "127.0.0.1", "port": 443} + })); + +// Send via protocol stack +let encoded = protocol_stack.encode_message(&msg)?; +``` + +### Message Types + +| Type | Description | +|------|-------------| +| `req` | Request - expecting a response | +| `resp` | Response - reply to a request | +| `event` | Unsolicited notification | +| `stream` | Stream data message | + +## ComponentRegistry Usage + +```rust +use unshell::tree::{ComponentRegistry, Component}; + +// Create registry +let mut registry = ComponentRegistry::new(); + +// Register components +let client = Box::new(TcpClient::new("my-client")); +registry.register(client).unwrap(); + +// List components +let names = registry.list(); + +// Send to specific component +let result = registry.send_to_component("my-client", json!({"method": "status"})); + +// Broadcast to all +let results = registry.broadcast(json!({"method": "status"})); + +// Shutdown all gracefully +let shutdown_results = registry.shutdown_all(); + +// Remove component +registry.remove("my-client"); +``` + +## Logging + +The framework includes a feature-gated logging system. Use the logging macros: + +```rust +use unshell::{info, warn, error}; + +// Info messages +info!("Component '{}' registered", name); + +// Warnings +warn!("Component '{}' not found", name); + +// Errors +error!("Connection failed: {}", err); +``` + +Enable logging with the `log` feature: +```toml +unshell = { path = ".", features = ["log"] } +``` + ## Module System ```rust @@ -106,10 +202,19 @@ Load compiled `.so`/`.dll` modules at runtime using `libloading` or in-memory vi Layer protocols arbitrarily: ```rust +use ush_payload::protocols::{ProtocolStack, ProtocolConfig}; + +// Create stack: base64 -> http -> tcp let mut stack = ProtocolStack::new(); stack.push(&ProtocolConfig::Base64(Default::default())).unwrap(); stack.push(&ProtocolConfig::Http(Default::default())).unwrap(); stack.push(&ProtocolConfig::Tcp(Default::default())).unwrap(); + +// Encode: app -> base64 -> http -> tcp -> network +let encoded = stack.encode(data)?; + +// Decode: network -> tcp -> http -> base64 -> app +let decoded = stack.decode(&encoded)?; ``` Order determines encoding: app → base64 → http → tcp → network @@ -151,6 +256,40 @@ impl Protocol for DnsTransport { stack.push(&ProtocolConfig::Custom { name: "dns", config: ... }); ``` +### Create a Custom Component + +```rust +use unshell::tree::{Component, TreeElement, Branch}; +use serde_json::{json, Value}; + +pub struct MyComponent { + name: String, + config: MyConfig, +} + +impl Component for MyComponent { + fn name(&self) -> &str { &self.name } + + fn status(&self) -> Value { + json!({"active": true, "name": self.name}) + } + + fn init(&mut self, config: Value) -> Result<(), String> { + // Configure only - don't connect here + self.config = serde_json::from_value(config)?; + Ok(()) + } + + fn shutdown(&mut self) -> Result<(), String> { + Ok(()) + } +} + +// Register in component registry +let mut registry = ComponentRegistry::new(); +registry.register(Box::new(MyComponent::new("my-component"))).unwrap(); +``` + ## Cross-Compilation ```bash @@ -183,8 +322,12 @@ cargo build --features obfuscate ## Testing ```bash +# Run the test harness cd ush-payload cargo run + +# Run library tests +cargo test -p ush-payload --lib ``` ## Obfuscation @@ -235,7 +378,7 @@ 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"); +pub const MY_CONSTANT: &str = sym!("MyString"); ``` Then use the constant in your code: @@ -260,3 +403,10 @@ Protocol-specific and transport-specific code belongs in `ush-payload`, while th ``` This produces a ~200KB binary (may vary with content). + +### Component Design Guidelines + +1. **init() should configure, not connect**: Only establish connections if explicitly requested via config +2. **Use symbols for string constants**: All user-facing strings should use `sym!()` for obfuscation +3. **Log important operations**: Use the logging macros for registration, connection, and errors +4. **Return structured responses**: Use JSON with `success`, `result`, and `error` fields diff --git a/src/announcement.rs b/src/announcement.rs index dea6210..6427743 100644 --- a/src/announcement.rs +++ b/src/announcement.rs @@ -1,12 +1,26 @@ -// use bincode::{Decode, Encode}; +//! Announcement message types for server communication. +//! +//! This module defines message types for inter-component communication. +//! Currently minimal - most functionality is handled by the tree system. +//! +//! # Usage +//! +//! ```rust +//! use unshell::Announcement; +//! +//! let msg = Announcement::TestAnnouncement("Hello".to_string()); +//! ``` -/// Mostly temporary server message type +/// Server message types for runtime communication. +/// +/// These were previously used for binary encoding with bincode. +/// Currently unused - tree-based messaging handles all communication. // #[derive(Clone, Debug, Encode, Decode)] pub enum Announcement { + /// Test announcement with string payload TestAnnouncement(String), // GetRuntimes, // GetRuntimesAck(usize), - // StartRuntime(RuntimeConfig), // StartRuntimeAck(bool), } diff --git a/src/config/config_struct.rs b/src/config/config_struct.rs index 4e8524e..4ac3f90 100644 --- a/src/config/config_struct.rs +++ b/src/config/config_struct.rs @@ -1,3 +1,21 @@ +//! Configuration structure handling. +//! +//! Provides a struct-based configuration system that maintains +//! both the schema (keys) and values for component configuration. +//! +//! # Usage +//! +//! ```rust +//! use unshell::config::{ConfigStructField, Config}; +//! +//! let keys = vec![ +//! ConfigStructField::String { default: "value".to_string(), max_length: Some(100), protected: false }, +//! ConfigStructField::Integer { default: 42, min: Some(0), max: Some(100) }, +//! ]; +//! +//! let mut config = Config::new(keys); +//! ``` + use serde_json::{json, Value}; use crate::{ @@ -5,15 +23,25 @@ use crate::{ warn, ModuleError, Result, }; +/// Type alias for configuration field definitions pub type ConfigStructKeys = Vec; + +/// Type alias for configuration values pub type ConfigStructValues = Vec; +/// Configuration container holding both schema and values. +/// +/// Manages a structured configuration with typed fields and their values. +/// Supports serialization and tree-based message handling. pub struct Config { keys: ConfigStructKeys, values: ConfigStructValues, } impl Config { + /// Create a new Config with given field definitions. + /// + /// Values are initialized to defaults from the field definitions. pub fn new(keys: ConfigStructKeys) -> Self { let values = keys .iter() @@ -28,6 +56,11 @@ impl Config { Self { keys, values } } + /// Handle tree messages for configuration access. + /// + /// # Errors + /// + /// Returns an error if the message is not a valid config message pub fn get(&mut self, message: TreeMessage) -> Result { match message { TreeMessage::State(InterfaceData::ConfigStruct(values)) => { diff --git a/src/config/config_struct_list.rs b/src/config/config_struct_list.rs index af30830..18a2661 100644 --- a/src/config/config_struct_list.rs +++ b/src/config/config_struct_list.rs @@ -1,16 +1,34 @@ +//! List-based configuration structure. +//! +//! Provides configuration for lists/arrays of structured data. +//! Currently unused - maintained for potential future use. +//! +//! # Overview +//! +//! Similar to `Config` but supports multiple rows of configuration. +//! Useful for configuring lists of items like connection profiles. + use serde_json::Value; use crate::config::ConfigStructField; +/// Type alias for list configuration keys pub type ConfigStructListKeys = Vec; + +/// Type alias for list configuration values (multiple rows) pub type ConfigStructListValues = Vec>; +/// Configuration container for list-based settings. +/// +/// Currently unimplemented - placeholder for future +/// multi-row configuration support. pub struct ConfigStructList { keys: ConfigStructListKeys, values: ConfigStructListValues, } impl ConfigStructList { + /// Create a new ConfigStructList with given keys. pub fn new(keys: ConfigStructListKeys) -> Self { // let values = keys // .iter() diff --git a/src/config/mod.rs b/src/config/mod.rs index 9b49419..048900b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,28 @@ +//! Configuration system for unshell components. +//! +//! This module provides types for runtime configuration of components +//! and tree structures. +//! +//! # Overview +//! +//! - `RuntimeConfig`: Configuration for runtime-loaded modules +//! - `ConfigStructField`: Field types for UI/config structures +//! - `Tree`, `TreeMessage`: Tree-based configuration access +//! - `InterfaceData`, `InterfaceStruct`: Data interchange formats +//! +//! # Usage +//! +//! ```rust +//! use unshell::config::{RuntimeConfig, ConfigStructField}; +//! use std::collections::HashMap; +//! +//! let config = RuntimeConfig { +//! parent_component: "root".to_string(), +//! name: "my_module".to_string(), +//! config: HashMap::new(), +//! }; +//! ``` + pub mod config_struct; // pub mod config_struct_list; mod tree; @@ -6,31 +31,50 @@ pub use tree::{InterfaceData, InterfaceStruct, Tree, TreeMessage}; use std::collections::HashMap; +/// Configuration for a runtime-loaded module or component. +/// +/// This struct holds the information needed to configure +/// and initialize a dynamically loaded component. #[derive(Debug, Clone)] pub struct RuntimeConfig { + /// The parent component that loaded this config pub parent_component: String, + /// Unique name for this configuration/module pub name: String, + /// Key-value configuration pairs pub config: HashMap, } +/// Field types for structured configuration UI. +/// +/// Used to describe the structure of configurable settings +/// in a serializable format suitable for UI rendering or +/// external configuration files. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub enum ConfigStructField { + /// Section header (non-editable label) Header(String), + /// Multi-line text field Text(String), + /// Single-line string input String { - // Default value of string edit in struct + /// Default value #[serde(default)] default: String, + /// Maximum length constraint max_length: Option, - // Display string edit as password + /// Whether to display as password (masked input) #[serde(default)] protected: bool, }, + /// Integer input with bounds Integer { - // Default value of integer in struct + /// Default value #[serde(default)] default: i32, + /// Minimum allowed value min: Option, + /// Maximum allowed value max: Option, }, // Checkbox diff --git a/src/config/tree.rs b/src/config/tree.rs index dc024b5..e576b9c 100644 --- a/src/config/tree.rs +++ b/src/config/tree.rs @@ -1,22 +1,58 @@ +//! Tree-based configuration access. +//! +//! Provides a hierarchical tree structure for configuration access, +//! similar to a file system with folders and values. +//! +//! # Architecture +//! +//! - `Tree` trait: Implement for any config container +//! - `TreeMessage`: Request/response messages +//! - `InterfaceStruct`: Schema definitions +//! - `InterfaceData`: Data values +//! +//! # Usage +//! +//! ```rust +//! use unshell::config::{Tree, TreeMessage}; +//! +//! struct MyConfig; +//! +//! impl Tree for MyConfig { +//! fn select_child(&mut self, child: &str, message: TreeMessage) -> Result { +//! // Handle child selection +//! Ok(TreeMessage::Success) +//! } +//! } +//! ``` + use serde::{Deserialize, Serialize}; use crate::{config::config_struct, ModuleError, Result}; +/// Trait for tree-structured configuration. +/// +/// Implement this trait to provide hierarchical configuration access, +/// similar to a file system with folders (containers) and files (values). pub trait Tree { + /// Check if this node is a folder (container) vs leaf (value) fn is_folder() -> bool { false } + /// Get list of child names (for folders) fn get_children_string(&self) -> Vec { unimplemented!(); } + /// Select and process a child node fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result; + /// Get value at this node fn get_value(&self, _message: TreeMessage) -> TreeMessage { unimplemented!() } + /// Navigate through path elements fn get_path(&mut self, elements: &mut Vec<&str>, message: TreeMessage) -> Result { if elements.is_empty() { return if Self::is_folder() { @@ -37,39 +73,57 @@ pub trait Tree { } } + /// Get value at specified path + /// + /// # Errors + /// + /// Returns error if path is invalid or node not found fn get(&mut self, path: &str, message: TreeMessage) -> Result { let mut path = if path.is_empty() { Vec::new() } else { - path.split("/").collect::>() + path.split('/').collect::>() }; self.get_path(&mut path, message) } } +/// Messages for tree-based configuration access. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum TreeMessage { + /// Request current state/value RequestState, + /// Request structure/schema RequestStruct, + /// Request both structure and current value RequestStructAndValue, + /// Response containing data State(InterfaceData), // Interface(InterfaceStruct), + /// Response with both schema and data InterfaceAndValue(InterfaceStruct, InterfaceData), + /// Operation succeeded Success, + /// Operation failed Failure, + /// Folder response (list of children) Folder(Vec), } +/// Schema/structure definitions. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum InterfaceStruct { + /// Configuration structure with field definitions ConfigStruct(config_struct::ConfigStructKeys), } +/// Data values for interfaces. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum InterfaceData { + /// Configuration values ConfigStruct(config_struct::ConfigStructValues), } diff --git a/src/logger/macros.rs b/src/logger/macros.rs index 68cb1df..63c474e 100644 --- a/src/logger/macros.rs +++ b/src/logger/macros.rs @@ -1,3 +1,24 @@ +//! Logging macros for unshell. +//! +//! Provides convenient logging macros that integrate with the +//! feature-gated logging system. +//! +//! # Usage +//! +//! ```rust +//! use unshell::{info, warn, error, debug}; +//! +//! info!("Application started"); +//! warn!("Configuration file not found, using defaults"); +//! error!("Failed to connect: {}", err); +//! debug!("Processing item {}", idx); +//! ``` +//! +//! # Feature Flags +//! +//! - `log`: Enable logging (required for any output) +//! - `log_debug`: Include file location in log records + #[macro_export] macro_rules! log { ($level:expr, $fmt:tt) => {{ @@ -34,6 +55,9 @@ macro_rules! log { }}; } +/// Log a debug-level message. +/// +/// Only produces output when the `log` feature is enabled. #[macro_export] macro_rules! debug { ($($arg:tt)*) => { @@ -41,6 +65,9 @@ macro_rules! debug { }; } +/// Log an info-level message. +/// +/// Only produces output when the `log` feature is enabled. #[macro_export] macro_rules! info { ($($arg:tt)*) => { @@ -48,6 +75,9 @@ macro_rules! info { }; } +/// Log a warning-level message. +/// +/// Only produces output when the `log` feature is enabled. #[macro_export] macro_rules! warn { ($($arg:tt)*) => { @@ -55,6 +85,9 @@ macro_rules! warn { }; } +/// Log an error-level message. +/// +/// Only produces output when the `log` feature is enabled. #[macro_export] macro_rules! error { ($($arg:tt)*) => { diff --git a/src/logger/macros_disabled.rs b/src/logger/macros_disabled.rs index ab203ab..91c73f0 100644 --- a/src/logger/macros_disabled.rs +++ b/src/logger/macros_disabled.rs @@ -1,5 +1,18 @@ +//! No-op logging macros when logging is disabled. +//! +//! When the `log` feature is not enabled, these macros are used instead. +//! They compile to no code, ensuring zero overhead when logging is disabled. +//! +//! # Usage +//! +//! These macros are automatically used when the `log` feature is disabled. +//! No code changes required - just don't enable the feature. + // Macros that are used that just drop the inside variables +/// No-op debug logging macro. +/// +/// Expands to nothing when `log` feature is disabled. #[macro_export] macro_rules! debug { ($fmt:tt) => {{ @@ -11,6 +24,9 @@ macro_rules! debug { }}; } +/// No-op info logging macro. +/// +/// Expands to nothing when `log` feature is disabled. #[macro_export] macro_rules! info { ($fmt:tt) => {{ @@ -22,6 +38,9 @@ macro_rules! info { }}; } +/// No-op warn logging macro. +/// +/// Expands to nothing when `log` feature is disabled. #[macro_export] macro_rules! warn { ($fmt:tt) => {{ @@ -33,6 +52,9 @@ macro_rules! warn { }}; } +/// No-op error logging macro. +/// +/// Expands to nothing when `log` feature is disabled. #[macro_export] macro_rules! error { ($fmt:tt) => {{ diff --git a/src/logger/pretty_logger.rs b/src/logger/pretty_logger.rs index c41b1c1..e5373fe 100644 --- a/src/logger/pretty_logger.rs +++ b/src/logger/pretty_logger.rs @@ -1,7 +1,30 @@ +//! Pretty console logger implementation. +//! +//! Provides a colored, formatted logger for console output. +//! Supports custom output handlers and ANSI color codes. +//! +//! # Usage +//! +//! ```rust +//! use unshell::logger::PrettyLogger; +//! +//! // Initialize with console output +//! PrettyLogger::init(); +//! +//! // Or with custom output handler +//! PrettyLogger::init_output(|record| { +//! // Custom handling +//! }); +//! ``` + use chrono::{DateTime, Utc}; use crate::logger::{LogLevel, Logger, Record}; +/// A logger that outputs formatted, colored messages to console. +/// +/// Supports ANSI color codes and optional custom output handlers. +/// Output format: `[timestamp] LEVEL message [location]` pub struct PrettyLogger { output: Option>, } @@ -27,6 +50,13 @@ impl Logger for PrettyLogger { } } +/// Format and print a log record to console. +/// +/// Uses ANSI color codes for level differentiation: +/// - Debug: Cyan +/// - Info: Green +/// - Warn: Yellow +/// - Error: Red pub fn log(message: &Record) { let log_level = match message.log_level { LogLevel::Debug => format!("{DEBUG_COLOR}DBUG"), @@ -51,10 +81,14 @@ pub fn log(message: &Record) { } impl PrettyLogger { + /// Initialize with default console output. pub fn init() { crate::logger::set_logger_box(Box::new(PrettyLogger { output: None })); } + /// Initialize with custom output handler. + /// + /// The handler receives each `Record` for custom processing. pub fn init_output(output: T) where T: Fn(&Record) + 'static, diff --git a/src/tree/log.rs b/src/tree/log.rs index d076ca9..fceae96 100644 --- a/src/tree/log.rs +++ b/src/tree/log.rs @@ -1,3 +1,20 @@ +//! Tree-integrated logging system. +//! +//! Provides logging that pipes through the tree structure, +//! allowing remote access to logs via tree messages. +//! +//! # Usage +//! +//! ```rust +//! use unshell::tree::{Branch, TreeElement}; +//! +//! let mut branch = Branch::new("root"); +//! branch.init_logger(); +//! +//! // Now logs can be retrieved via tree messages +//! // branch.send_message(json!("logger"), json!("Get")); +//! ``` + /// Implement logging for the manager use crossbeam_channel::{Receiver, Sender}; use serde_json::{json, Value}; @@ -7,12 +24,17 @@ use crate::{ tree::{symbols, Branch, TreeElement}, }; +/// Logger transmitter - sends logs to channel struct LoggerTX(Sender); + +/// Logger receiver - exposes logs as TreeElement struct LoggerRX(Receiver); impl Branch { - /// Initiate the unshell logger for the local binary, piped through the manager - /// This will allow access to the logs through the tree + /// Initiate the unshell logger for the local binary, piped through the manager. + /// + /// This allows access to logs through the tree structure. + /// Logs can be retrieved using `Get` command or queue length with `GetLength`. pub fn init_logger(&mut self) { let (tx, rx) = crossbeam_channel::unbounded(); let (tx, rx) = (LoggerTX(tx), LoggerRX(rx)); diff --git a/src/tree/queue.rs b/src/tree/queue.rs index 831f365..9c72cfd 100644 --- a/src/tree/queue.rs +++ b/src/tree/queue.rs @@ -1,4 +1,34 @@ //! Queue - A TreeElement wrapper around crossbeam channels for message queuing. +//! +//! Provides a thread-safe queue that can be accessed via tree messages. +//! Useful for inter-thread communication and log buffering. +//! +//! # Tree Interface +//! +//! - `Get`: Receive one message (blocking) +//! - `Poll`: Try to receive without blocking +//! - `GetLength`: Get queue length +//! +//! # Usage +//! +//! ```rust +//! use unshell::tree::queue::Queue; +//! use unshell::tree::{Branch, TreeElement}; +//! use serde_json::json; +//! +//! // Create queue with channel factory +//! let (sender, mut queue) = Queue::::channel(); +//! +//! // Add to branch +//! let mut branch = Branch::new("test"); +//! branch.add_child("my_queue", Box::new(queue)); +//! +//! // Send via sender (different thread) +//! sender.send("message".to_string()).unwrap(); +//! +//! // Receive via tree message +//! let result = branch.send_message(json!("my_queue"), json!("Get")); +//! ``` use crossbeam_channel::{Receiver, Sender}; use serde_json::{json, Value}; @@ -7,39 +37,51 @@ use crate::tree::symbols::{self, TYPE_QUEUE}; use crate::tree::TreeElement; /// Generic queue wrapping crossbeam channels. +/// /// Provides Get, Poll, and GetLength commands via the tree interface. +/// Thread-safe for multi-producer multi-consumer scenarios. pub struct Queue { sender: Sender, receiver: Receiver, } impl Queue { + /// Create a new queue with given sender and receiver. pub fn new(sender: Sender, receiver: Receiver) -> Self { Self { sender, receiver } } + /// Create a channel pair, returning sender and queue. + /// + /// Useful for setting up the queue where one end sends + /// and the other end is exposed via tree. pub fn channel() -> (Sender, Self) { let (tx, rx) = crossbeam_channel::unbounded(); let queue = Self::new(tx.clone(), rx); (tx, queue) } + /// Get reference to the sender for producing messages. pub fn sender(&self) -> &Sender { &self.sender } + /// Get current queue length. pub fn len(&self) -> usize { self.receiver.len() } + /// Check if queue is empty. pub fn is_empty(&self) -> bool { self.receiver.is_empty() } + /// Try to receive without blocking. pub fn try_recv(&self) -> Option { self.receiver.try_recv().ok() } + /// Receive a message (blocking). pub fn recv(&self) -> Option { self.receiver.recv().ok() } diff --git a/src/tree/readonly.rs b/src/tree/readonly.rs index 6357eaa..a539ed2 100644 --- a/src/tree/readonly.rs +++ b/src/tree/readonly.rs @@ -1,6 +1,41 @@ //! TreeVariable - A TreeElement with getters and setters. //! //! ReadOnly - A wrapper around TreeVariable that ignores setters. +//! +//! # Usage +//! +//! ## TreeVariable +//! +//! ```rust +//! use unshell::tree::{TreeVariable, TreeElement}; +//! use serde_json::json; +//! +//! let mut var = TreeVariable::new("default_value", "String"); +//! +//! // Get value via tree message +//! let result = var.send_message(json!(null), json!("Get")); +//! assert_eq!(result, json!("default_value")); +//! +//! // Set value via tree message +//! let result = var.send_message(json!("Set"), json!("new_value")); +//! assert_eq!(result, json!(true)); +//! ``` +//! +//! ## ReadOnly +//! +//! ```rust +//! use unshell::tree::ReadOnly; +//! use serde_json::json; +//! +//! let mut var = ReadOnly::new("immutable", "String"); +//! +//! // Get works +//! let result = var.send_message(json!(null), json!("Get")); +//! assert_eq!(result, json!("immutable")); +//! +//! // Set returns ReadOnly error +//! let result = var.send_message(json!("Set"), json!("new_value")); +//! ``` use serde_json::{json, Value}; @@ -8,12 +43,17 @@ use crate::tree::symbols; use crate::tree::TreeElement; /// A variable with getters and setters exposed through the tree. +/// +/// Supports: +/// - `Get`: Retrieve current value +/// - `Set`: Set new value (requires string message) pub struct TreeVariable { value: String, value_type: &'static str, } impl TreeVariable { + /// Create a new tree variable. pub fn new(value: impl Into, value_type: &'static str) -> Self { Self { value: value.into(), @@ -21,10 +61,12 @@ impl TreeVariable { } } + /// Get the current value. pub fn get(&self) -> &str { &self.value } + /// Set a new value. pub fn set(&mut self, value: impl Into) { self.value = value.into(); } @@ -62,25 +104,32 @@ impl TreeElement for TreeVariable { } /// A read-only wrapper around TreeVariable that ignores setters. +/// +/// Any attempt to set the value returns an error. +/// Useful for exposing immutable endpoint identifiers. pub struct ReadOnly { inner: TreeVariable, } impl ReadOnly { + /// Create a new read-only variable. pub fn new(value: impl Into, value_type: &'static str) -> Self { Self { inner: TreeVariable::new(value, value_type), } } + /// Get the current value. pub fn get(&self) -> &str { self.inner.get() } + /// Get immutable reference to inner TreeVariable. pub fn inner(&self) -> &TreeVariable { &self.inner } + /// Get mutable reference to inner TreeVariable. pub fn inner_mut(&mut self) -> &mut TreeVariable { &mut self.inner } diff --git a/ush-obfuscate/src/lib.rs b/ush-obfuscate/src/lib.rs index 2e0fe7e..b3d7993 100644 --- a/ush-obfuscate/src/lib.rs +++ b/ush-obfuscate/src/lib.rs @@ -1,3 +1,33 @@ +//! Compile-time string obfuscation for stealthy payloads. +//! +//! This crate provides procedural macros for encrypting strings at compile time, +//! making them harder to detect via static analysis. +//! +//! # Features +//! +//! - `obfuscate`: Enable AES encryption (enabled via cargo feature) +//! - When disabled, strings pass through as plain text (for debugging) +//! +//! # Macros +//! +//! - `sym!("string")` - Encrypt a string literal +//! - `xor!("string")` - XOR obfuscate a string +//! - `sym_fn` - Obfuscate function names +//! - `junk_asm` - Insert junk assembly instructions +//! - `file_symbol` - Get obfuscated file location for logging +//! - `format_sym` - Format strings with obfuscation +//! +//! # Usage +//! +//! ```rust +//! use ush_obfuscate::sym; +//! +//! const API_KEY: &str = sym!("SuperSecretKey123"); +//! const C2_URL: &str = sym!("https://C2Server/endpoint"); +//! ``` +//! +//! When `obfuscate` feature is enabled, strings are encrypted at compile time. + #![feature(proc_macro_quote)] #![feature(proc_macro_span)] @@ -23,30 +53,43 @@ use obfuscate as obs; // String obfuscation +/// XOR obfuscate a string at compile time. +/// +/// Simple XOR-based encoding for basic obfuscation. #[proc_macro] pub fn xor(input: TokenStream) -> TokenStream { obs::xor(input) } -/// Represents strings as a symbol. +/// Encrypt a string using AES at compile time. +/// +/// This is the primary macro for string obfuscation. +/// The string is encrypted with a hardcoded key and decrypted at runtime. #[proc_macro] pub fn sym(input: TokenStream) -> TokenStream { obs::aes_str(input) } -/// Represents function names as a symbol. +/// Obfuscate a function name. +/// +/// Can be used to hide function names from static analysis. #[proc_macro_attribute] pub fn sym_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { obs::aes_fn_name(_attr, item) } +/// Insert junk assembly instructions. +/// +/// Adds random assembly instructions to confuse disassembly. #[proc_macro] pub fn junk_asm(input: TokenStream) -> TokenStream { obs::junk_asm(input) } -// - +/// Get obfuscated file location for logging. +/// +/// Encodes the file path and line number for debug logging +/// without exposing readable strings in the binary. #[proc_macro] pub fn file_symbol(_input: TokenStream) -> TokenStream { // Get the call site span to extract file information @@ -66,6 +109,9 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream { output.into() } +/// Format a string with obfuscated parts. +/// +/// Combines format string parsing with string obfuscation. #[proc_macro] pub fn format_sym(input: TokenStream) -> TokenStream { let PrintlnArgs { format_str, args } = parse_macro_input!(input as PrintlnArgs);