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
+154 -4
View File
@@ -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