mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Add Docs
This commit is contained in:
@@ -26,7 +26,7 @@ unshell/
|
|||||||
├── src/tree/ # Hierarchical message routing
|
├── src/tree/ # Hierarchical message routing
|
||||||
│ ├── component.rs # Component trait (implement for any module)
|
│ ├── component.rs # Component trait (implement for any module)
|
||||||
│ ├── endpoint.rs # Endpoint manager
|
│ ├── endpoint.rs # Endpoint manager
|
||||||
│ ├── protocols/ # Pluggable protocol stack
|
│ ├── protocols/ # Pluggable protocol stack
|
||||||
│ └── tcp/ # Example transport implementations
|
│ └── tcp/ # Example transport implementations
|
||||||
├── ush-obfuscate/ # Compile-time string obfuscation
|
├── ush-obfuscate/ # Compile-time string obfuscation
|
||||||
└── ush-payload/ # Test harness
|
└── ush-payload/ # Test harness
|
||||||
@@ -36,7 +36,24 @@ unshell/
|
|||||||
|
|
||||||
Everything plugs into these abstractions:
|
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
|
```rust
|
||||||
use unshell::tree::Component;
|
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
|
### Protocol - Any Encoding Layer
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use unshell::tree::protocols::Protocol;
|
use unshell::protocols::Protocol;
|
||||||
|
|
||||||
pub trait Protocol: Send + Sync {
|
pub trait Protocol: Send + Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
@@ -76,6 +96,82 @@ pub trait Protocol: Send + Sync {
|
|||||||
// They expose the same interface as native components
|
// 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
|
## Module System
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -106,10 +202,19 @@ Load compiled `.so`/`.dll` modules at runtime using `libloading` or in-memory vi
|
|||||||
Layer protocols arbitrarily:
|
Layer protocols arbitrarily:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use ush_payload::protocols::{ProtocolStack, ProtocolConfig};
|
||||||
|
|
||||||
|
// Create stack: base64 -> http -> tcp
|
||||||
let mut stack = ProtocolStack::new();
|
let mut stack = ProtocolStack::new();
|
||||||
stack.push(&ProtocolConfig::Base64(Default::default())).unwrap();
|
stack.push(&ProtocolConfig::Base64(Default::default())).unwrap();
|
||||||
stack.push(&ProtocolConfig::Http(Default::default())).unwrap();
|
stack.push(&ProtocolConfig::Http(Default::default())).unwrap();
|
||||||
stack.push(&ProtocolConfig::Tcp(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
|
Order determines encoding: app → base64 → http → tcp → network
|
||||||
@@ -151,6 +256,40 @@ impl Protocol for DnsTransport {
|
|||||||
stack.push(&ProtocolConfig::Custom { name: "dns", config: ... });
|
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
|
## Cross-Compilation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -183,8 +322,12 @@ cargo build --features obfuscate
|
|||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Run the test harness
|
||||||
cd ush-payload
|
cd ush-payload
|
||||||
cargo run
|
cargo run
|
||||||
|
|
||||||
|
# Run library tests
|
||||||
|
cargo test -p ush-payload --lib
|
||||||
```
|
```
|
||||||
|
|
||||||
## Obfuscation
|
## Obfuscation
|
||||||
@@ -235,7 +378,7 @@ To avoid redundant `sym!()` calls, define constants in `src/tree/symbols.rs`:
|
|||||||
```rust
|
```rust
|
||||||
use crate::obfuscate::sym;
|
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:
|
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).
|
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)]
|
// #[derive(Clone, Debug, Encode, Decode)]
|
||||||
pub enum Announcement {
|
pub enum Announcement {
|
||||||
|
/// Test announcement with string payload
|
||||||
TestAnnouncement(String),
|
TestAnnouncement(String),
|
||||||
// GetRuntimes,
|
// GetRuntimes,
|
||||||
// GetRuntimesAck(usize),
|
// GetRuntimesAck(usize),
|
||||||
|
|
||||||
// StartRuntime(RuntimeConfig),
|
// StartRuntime(RuntimeConfig),
|
||||||
// StartRuntimeAck(bool),
|
// 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 serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -5,15 +23,25 @@ use crate::{
|
|||||||
warn, ModuleError, Result,
|
warn, ModuleError, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Type alias for configuration field definitions
|
||||||
pub type ConfigStructKeys = Vec<ConfigStructField>;
|
pub type ConfigStructKeys = Vec<ConfigStructField>;
|
||||||
|
|
||||||
|
/// Type alias for configuration values
|
||||||
pub type ConfigStructValues = Vec<Value>;
|
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 {
|
pub struct Config {
|
||||||
keys: ConfigStructKeys,
|
keys: ConfigStructKeys,
|
||||||
values: ConfigStructValues,
|
values: ConfigStructValues,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
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 {
|
pub fn new(keys: ConfigStructKeys) -> Self {
|
||||||
let values = keys
|
let values = keys
|
||||||
.iter()
|
.iter()
|
||||||
@@ -28,6 +56,11 @@ impl Config {
|
|||||||
Self { keys, values }
|
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> {
|
pub fn get(&mut self, message: TreeMessage) -> Result<TreeMessage> {
|
||||||
match message {
|
match message {
|
||||||
TreeMessage::State(InterfaceData::ConfigStruct(values)) => {
|
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 serde_json::Value;
|
||||||
|
|
||||||
use crate::config::ConfigStructField;
|
use crate::config::ConfigStructField;
|
||||||
|
|
||||||
|
/// Type alias for list configuration keys
|
||||||
pub type ConfigStructListKeys = Vec<ConfigStructField>;
|
pub type ConfigStructListKeys = Vec<ConfigStructField>;
|
||||||
|
|
||||||
|
/// Type alias for list configuration values (multiple rows)
|
||||||
pub type ConfigStructListValues = Vec<Vec<Value>>;
|
pub type ConfigStructListValues = Vec<Vec<Value>>;
|
||||||
|
|
||||||
|
/// Configuration container for list-based settings.
|
||||||
|
///
|
||||||
|
/// Currently unimplemented - placeholder for future
|
||||||
|
/// multi-row configuration support.
|
||||||
pub struct ConfigStructList {
|
pub struct ConfigStructList {
|
||||||
keys: ConfigStructListKeys,
|
keys: ConfigStructListKeys,
|
||||||
values: ConfigStructListValues,
|
values: ConfigStructListValues,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigStructList {
|
impl ConfigStructList {
|
||||||
|
/// Create a new ConfigStructList with given keys.
|
||||||
pub fn new(keys: ConfigStructListKeys) -> Self {
|
pub fn new(keys: ConfigStructListKeys) -> Self {
|
||||||
// let values = keys
|
// let values = keys
|
||||||
// .iter()
|
// .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;
|
||||||
// pub mod config_struct_list;
|
// pub mod config_struct_list;
|
||||||
mod tree;
|
mod tree;
|
||||||
@@ -6,31 +31,50 @@ pub use tree::{InterfaceData, InterfaceStruct, Tree, TreeMessage};
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RuntimeConfig {
|
pub struct RuntimeConfig {
|
||||||
|
/// The parent component that loaded this config
|
||||||
pub parent_component: String,
|
pub parent_component: String,
|
||||||
|
/// Unique name for this configuration/module
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// Key-value configuration pairs
|
||||||
pub config: HashMap<String, String>,
|
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)]
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
pub enum ConfigStructField {
|
pub enum ConfigStructField {
|
||||||
|
/// Section header (non-editable label)
|
||||||
Header(String),
|
Header(String),
|
||||||
|
/// Multi-line text field
|
||||||
Text(String),
|
Text(String),
|
||||||
|
/// Single-line string input
|
||||||
String {
|
String {
|
||||||
// Default value of string edit in struct
|
/// Default value
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
default: String,
|
default: String,
|
||||||
|
/// Maximum length constraint
|
||||||
max_length: Option<usize>,
|
max_length: Option<usize>,
|
||||||
// Display string edit as password
|
/// Whether to display as password (masked input)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
protected: bool,
|
protected: bool,
|
||||||
},
|
},
|
||||||
|
/// Integer input with bounds
|
||||||
Integer {
|
Integer {
|
||||||
// Default value of integer in struct
|
/// Default value
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
default: i32,
|
default: i32,
|
||||||
|
/// Minimum allowed value
|
||||||
min: Option<i32>,
|
min: Option<i32>,
|
||||||
|
/// Maximum allowed value
|
||||||
max: Option<i32>,
|
max: Option<i32>,
|
||||||
},
|
},
|
||||||
// Checkbox
|
// 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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{config::config_struct, ModuleError, Result};
|
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 {
|
pub trait Tree {
|
||||||
|
/// Check if this node is a folder (container) vs leaf (value)
|
||||||
fn is_folder() -> bool {
|
fn is_folder() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get list of child names (for folders)
|
||||||
fn get_children_string(&self) -> Vec<String> {
|
fn get_children_string(&self) -> Vec<String> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Select and process a child node
|
||||||
fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result<TreeMessage>;
|
fn select_child(&mut self, child: &str, _message: TreeMessage) -> Result<TreeMessage>;
|
||||||
|
|
||||||
|
/// Get value at this node
|
||||||
fn get_value(&self, _message: TreeMessage) -> TreeMessage {
|
fn get_value(&self, _message: TreeMessage) -> TreeMessage {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Navigate through path elements
|
||||||
fn get_path(&mut self, elements: &mut Vec<&str>, message: TreeMessage) -> Result<TreeMessage> {
|
fn get_path(&mut self, elements: &mut Vec<&str>, message: TreeMessage) -> Result<TreeMessage> {
|
||||||
if elements.is_empty() {
|
if elements.is_empty() {
|
||||||
return if Self::is_folder() {
|
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> {
|
fn get(&mut self, path: &str, message: TreeMessage) -> Result<TreeMessage> {
|
||||||
let mut path = if path.is_empty() {
|
let mut path = if path.is_empty() {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
path.split("/").collect::<Vec<&str>>()
|
path.split('/').collect::<Vec<&str>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.get_path(&mut path, message)
|
self.get_path(&mut path, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Messages for tree-based configuration access.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum TreeMessage {
|
pub enum TreeMessage {
|
||||||
|
/// Request current state/value
|
||||||
RequestState,
|
RequestState,
|
||||||
|
/// Request structure/schema
|
||||||
RequestStruct,
|
RequestStruct,
|
||||||
|
/// Request both structure and current value
|
||||||
RequestStructAndValue,
|
RequestStructAndValue,
|
||||||
|
|
||||||
|
/// Response containing data
|
||||||
State(InterfaceData),
|
State(InterfaceData),
|
||||||
// Interface(InterfaceStruct),
|
// Interface(InterfaceStruct),
|
||||||
|
/// Response with both schema and data
|
||||||
InterfaceAndValue(InterfaceStruct, InterfaceData),
|
InterfaceAndValue(InterfaceStruct, InterfaceData),
|
||||||
|
|
||||||
|
/// Operation succeeded
|
||||||
Success,
|
Success,
|
||||||
|
/// Operation failed
|
||||||
Failure,
|
Failure,
|
||||||
|
|
||||||
|
/// Folder response (list of children)
|
||||||
Folder(Vec<String>),
|
Folder(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Schema/structure definitions.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum InterfaceStruct {
|
pub enum InterfaceStruct {
|
||||||
|
/// Configuration structure with field definitions
|
||||||
ConfigStruct(config_struct::ConfigStructKeys),
|
ConfigStruct(config_struct::ConfigStructKeys),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data values for interfaces.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum InterfaceData {
|
pub enum InterfaceData {
|
||||||
|
/// Configuration values
|
||||||
ConfigStruct(config_struct::ConfigStructValues),
|
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_export]
|
||||||
macro_rules! log {
|
macro_rules! log {
|
||||||
($level:expr, $fmt:tt) => {{
|
($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_export]
|
||||||
macro_rules! debug {
|
macro_rules! debug {
|
||||||
($($arg:tt)*) => {
|
($($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_export]
|
||||||
macro_rules! info {
|
macro_rules! info {
|
||||||
($($arg:tt)*) => {
|
($($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_export]
|
||||||
macro_rules! warn {
|
macro_rules! warn {
|
||||||
($($arg:tt)*) => {
|
($($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_export]
|
||||||
macro_rules! error {
|
macro_rules! error {
|
||||||
($($arg:tt)*) => {
|
($($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
|
// 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_export]
|
||||||
macro_rules! debug {
|
macro_rules! debug {
|
||||||
($fmt:tt) => {{
|
($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_export]
|
||||||
macro_rules! info {
|
macro_rules! info {
|
||||||
($fmt:tt) => {{
|
($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_export]
|
||||||
macro_rules! warn {
|
macro_rules! warn {
|
||||||
($fmt:tt) => {{
|
($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_export]
|
||||||
macro_rules! error {
|
macro_rules! error {
|
||||||
($fmt:tt) => {{
|
($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 chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::logger::{LogLevel, Logger, Record};
|
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 {
|
pub struct PrettyLogger {
|
||||||
output: Option<Box<dyn Fn(&Record)>>,
|
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) {
|
pub fn log(message: &Record) {
|
||||||
let log_level = match message.log_level {
|
let log_level = match message.log_level {
|
||||||
LogLevel::Debug => format!("{DEBUG_COLOR}DBUG"),
|
LogLevel::Debug => format!("{DEBUG_COLOR}DBUG"),
|
||||||
@@ -51,10 +81,14 @@ pub fn log(message: &Record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PrettyLogger {
|
impl PrettyLogger {
|
||||||
|
/// Initialize with default console output.
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
crate::logger::set_logger_box(Box::new(PrettyLogger { output: None }));
|
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)
|
pub fn init_output<T>(output: T)
|
||||||
where
|
where
|
||||||
T: Fn(&Record) + 'static,
|
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
|
/// Implement logging for the manager
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@@ -7,12 +24,17 @@ use crate::{
|
|||||||
tree::{symbols, Branch, TreeElement},
|
tree::{symbols, Branch, TreeElement},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Logger transmitter - sends logs to channel
|
||||||
struct LoggerTX(Sender<Record>);
|
struct LoggerTX(Sender<Record>);
|
||||||
|
|
||||||
|
/// Logger receiver - exposes logs as TreeElement
|
||||||
struct LoggerRX(Receiver<Record>);
|
struct LoggerRX(Receiver<Record>);
|
||||||
|
|
||||||
impl Branch {
|
impl Branch {
|
||||||
/// Initiate the unshell logger for the local binary, piped through the manager
|
/// Initiate the unshell logger for the local binary, piped through the manager.
|
||||||
/// This will allow access to the logs through the tree
|
///
|
||||||
|
/// 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) {
|
pub fn init_logger(&mut self) {
|
||||||
let (tx, rx) = crossbeam_channel::unbounded();
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
let (tx, rx) = (LoggerTX(tx), LoggerRX(rx));
|
let (tx, rx) = (LoggerTX(tx), LoggerRX(rx));
|
||||||
|
|||||||
@@ -1,4 +1,34 @@
|
|||||||
//! Queue - A TreeElement wrapper around crossbeam channels for message queuing.
|
//! 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 crossbeam_channel::{Receiver, Sender};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@@ -7,39 +37,51 @@ use crate::tree::symbols::{self, TYPE_QUEUE};
|
|||||||
use crate::tree::TreeElement;
|
use crate::tree::TreeElement;
|
||||||
|
|
||||||
/// Generic queue wrapping crossbeam channels.
|
/// Generic queue wrapping crossbeam channels.
|
||||||
|
///
|
||||||
/// Provides Get, Poll, and GetLength commands via the tree interface.
|
/// Provides Get, Poll, and GetLength commands via the tree interface.
|
||||||
|
/// Thread-safe for multi-producer multi-consumer scenarios.
|
||||||
pub struct Queue<T> {
|
pub struct Queue<T> {
|
||||||
sender: Sender<T>,
|
sender: Sender<T>,
|
||||||
receiver: Receiver<T>,
|
receiver: Receiver<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Queue<T> {
|
impl<T> Queue<T> {
|
||||||
|
/// Create a new queue with given sender and receiver.
|
||||||
pub fn new(sender: Sender<T>, receiver: Receiver<T>) -> Self {
|
pub fn new(sender: Sender<T>, receiver: Receiver<T>) -> Self {
|
||||||
Self { sender, receiver }
|
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) {
|
pub fn channel() -> (Sender<T>, Self) {
|
||||||
let (tx, rx) = crossbeam_channel::unbounded();
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
let queue = Self::new(tx.clone(), rx);
|
let queue = Self::new(tx.clone(), rx);
|
||||||
(tx, queue)
|
(tx, queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get reference to the sender for producing messages.
|
||||||
pub fn sender(&self) -> &Sender<T> {
|
pub fn sender(&self) -> &Sender<T> {
|
||||||
&self.sender
|
&self.sender
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get current queue length.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.receiver.len()
|
self.receiver.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if queue is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.receiver.is_empty()
|
self.receiver.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to receive without blocking.
|
||||||
pub fn try_recv(&self) -> Option<T> {
|
pub fn try_recv(&self) -> Option<T> {
|
||||||
self.receiver.try_recv().ok()
|
self.receiver.try_recv().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive a message (blocking).
|
||||||
pub fn recv(&self) -> Option<T> {
|
pub fn recv(&self) -> Option<T> {
|
||||||
self.receiver.recv().ok()
|
self.receiver.recv().ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,41 @@
|
|||||||
//! TreeVariable - A TreeElement with getters and setters.
|
//! TreeVariable - A TreeElement with getters and setters.
|
||||||
//!
|
//!
|
||||||
//! ReadOnly - A wrapper around TreeVariable that ignores 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};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
@@ -8,12 +43,17 @@ use crate::tree::symbols;
|
|||||||
use crate::tree::TreeElement;
|
use crate::tree::TreeElement;
|
||||||
|
|
||||||
/// A variable with getters and setters exposed through the tree.
|
/// 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 {
|
pub struct TreeVariable {
|
||||||
value: String,
|
value: String,
|
||||||
value_type: &'static str,
|
value_type: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeVariable {
|
impl TreeVariable {
|
||||||
|
/// Create a new tree variable.
|
||||||
pub fn new(value: impl Into<String>, value_type: &'static str) -> Self {
|
pub fn new(value: impl Into<String>, value_type: &'static str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
@@ -21,10 +61,12 @@ impl TreeVariable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current value.
|
||||||
pub fn get(&self) -> &str {
|
pub fn get(&self) -> &str {
|
||||||
&self.value
|
&self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a new value.
|
||||||
pub fn set(&mut self, value: impl Into<String>) {
|
pub fn set(&mut self, value: impl Into<String>) {
|
||||||
self.value = value.into();
|
self.value = value.into();
|
||||||
}
|
}
|
||||||
@@ -62,25 +104,32 @@ impl TreeElement for TreeVariable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A read-only wrapper around TreeVariable that ignores setters.
|
/// 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 {
|
pub struct ReadOnly {
|
||||||
inner: TreeVariable,
|
inner: TreeVariable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadOnly {
|
impl ReadOnly {
|
||||||
|
/// Create a new read-only variable.
|
||||||
pub fn new(value: impl Into<String>, value_type: &'static str) -> Self {
|
pub fn new(value: impl Into<String>, value_type: &'static str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: TreeVariable::new(value, value_type),
|
inner: TreeVariable::new(value, value_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current value.
|
||||||
pub fn get(&self) -> &str {
|
pub fn get(&self) -> &str {
|
||||||
self.inner.get()
|
self.inner.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get immutable reference to inner TreeVariable.
|
||||||
pub fn inner(&self) -> &TreeVariable {
|
pub fn inner(&self) -> &TreeVariable {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get mutable reference to inner TreeVariable.
|
||||||
pub fn inner_mut(&mut self) -> &mut TreeVariable {
|
pub fn inner_mut(&mut self) -> &mut TreeVariable {
|
||||||
&mut self.inner
|
&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_quote)]
|
||||||
#![feature(proc_macro_span)]
|
#![feature(proc_macro_span)]
|
||||||
|
|
||||||
@@ -23,30 +53,43 @@ use obfuscate as obs;
|
|||||||
|
|
||||||
// String obfuscation
|
// String obfuscation
|
||||||
|
|
||||||
|
/// XOR obfuscate a string at compile time.
|
||||||
|
///
|
||||||
|
/// Simple XOR-based encoding for basic obfuscation.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn xor(input: TokenStream) -> TokenStream {
|
pub fn xor(input: TokenStream) -> TokenStream {
|
||||||
obs::xor(input)
|
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]
|
#[proc_macro]
|
||||||
pub fn sym(input: TokenStream) -> TokenStream {
|
pub fn sym(input: TokenStream) -> TokenStream {
|
||||||
obs::aes_str(input)
|
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]
|
#[proc_macro_attribute]
|
||||||
pub fn sym_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn sym_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
obs::aes_fn_name(_attr, item)
|
obs::aes_fn_name(_attr, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert junk assembly instructions.
|
||||||
|
///
|
||||||
|
/// Adds random assembly instructions to confuse disassembly.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn junk_asm(input: TokenStream) -> TokenStream {
|
pub fn junk_asm(input: TokenStream) -> TokenStream {
|
||||||
obs::junk_asm(input)
|
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]
|
#[proc_macro]
|
||||||
pub fn file_symbol(_input: TokenStream) -> TokenStream {
|
pub fn file_symbol(_input: TokenStream) -> TokenStream {
|
||||||
// Get the call site span to extract file information
|
// Get the call site span to extract file information
|
||||||
@@ -66,6 +109,9 @@ pub fn file_symbol(_input: TokenStream) -> TokenStream {
|
|||||||
output.into()
|
output.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a string with obfuscated parts.
|
||||||
|
///
|
||||||
|
/// Combines format string parsing with string obfuscation.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn format_sym(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);
|
||||||
|
|||||||
Reference in New Issue
Block a user