mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 14:36:01 -06:00
Add Docs
This commit is contained in:
@@ -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
|
||||
|
||||
+17
-3
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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<ConfigStructField>;
|
||||
|
||||
/// Type alias for configuration values
|
||||
pub type ConfigStructValues = Vec<Value>;
|
||||
|
||||
/// 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<TreeMessage> {
|
||||
match message {
|
||||
TreeMessage::State(InterfaceData::ConfigStruct(values)) => {
|
||||
|
||||
@@ -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<ConfigStructField>;
|
||||
|
||||
/// Type alias for list configuration values (multiple rows)
|
||||
pub type ConfigStructListValues = Vec<Vec<Value>>;
|
||||
|
||||
/// 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()
|
||||
|
||||
+47
-3
@@ -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<String, String>,
|
||||
}
|
||||
|
||||
/// 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<usize>,
|
||||
// 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<i32>,
|
||||
/// Maximum allowed value
|
||||
max: Option<i32>,
|
||||
},
|
||||
// Checkbox
|
||||
|
||||
+55
-1
@@ -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<TreeMessage> {
|
||||
//! // 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<String> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
/// Select and process a child node
|
||||
fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result<TreeMessage>;
|
||||
|
||||
/// 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<TreeMessage> {
|
||||
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<TreeMessage> {
|
||||
let mut path = if path.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
path.split("/").collect::<Vec<&str>>()
|
||||
path.split('/').collect::<Vec<&str>>()
|
||||
};
|
||||
|
||||
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<String>),
|
||||
}
|
||||
|
||||
/// 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),
|
||||
}
|
||||
|
||||
@@ -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)*) => {
|
||||
|
||||
@@ -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) => {{
|
||||
|
||||
@@ -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<Box<dyn Fn(&Record)>>,
|
||||
}
|
||||
@@ -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<T>(output: T)
|
||||
where
|
||||
T: Fn(&Record) + 'static,
|
||||
|
||||
+24
-2
@@ -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<Record>);
|
||||
|
||||
/// Logger receiver - exposes logs as TreeElement
|
||||
struct LoggerRX(Receiver<Record>);
|
||||
|
||||
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));
|
||||
|
||||
@@ -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::<String>::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<T> {
|
||||
sender: Sender<T>,
|
||||
receiver: Receiver<T>,
|
||||
}
|
||||
|
||||
impl<T> Queue<T> {
|
||||
/// Create a new queue with given sender and receiver.
|
||||
pub fn new(sender: Sender<T>, receiver: Receiver<T>) -> 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<T>, 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<T> {
|
||||
&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<T> {
|
||||
self.receiver.try_recv().ok()
|
||||
}
|
||||
|
||||
/// Receive a message (blocking).
|
||||
pub fn recv(&self) -> Option<T> {
|
||||
self.receiver.recv().ok()
|
||||
}
|
||||
|
||||
@@ -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<String>, 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<String>) {
|
||||
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<String>, 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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user