This commit is contained in:
Michael Mikovsky
2026-02-20 14:05:43 -07:00
parent 3954e4519e
commit 989b5057d0
13 changed files with 578 additions and 17 deletions
+153 -3
View File
@@ -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
View File
@@ -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),
}
+33
View File
@@ -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)) => {
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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),
}
+33
View File
@@ -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)*) => {
+22
View File
@@ -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) => {{
+34
View File
@@ -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
View File
@@ -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));
+42
View File
@@ -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()
}
+49
View File
@@ -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
}
+50 -4
View File
@@ -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);