Improve Rust code clarity across the workspace

Document public APIs and non-obvious control flow so the protocol, simulator, and macro crates are easier to follow. Tighten a few helper paths and feature gates while preserving behavior and keeping the workspace warning-free.
This commit is contained in:
Michael Mikovsky
2026-04-25 11:11:19 -06:00
parent f49af7fa22
commit ba3f28a78c
26 changed files with 571 additions and 402 deletions
+66 -46
View File
@@ -13,40 +13,56 @@ use crate::model::EndpointProcedureSpec;
/// Root inspector mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InspectorMode {
/// Render the full scenario definition, including information the root has
/// not yet learned through traffic or introspection.
GroundTruth,
/// Render only the subset of state the root host could plausibly know.
Realistic,
}
/// Learned procedure metadata stored by the root host.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LearnedProcedure {
/// Stable protocol identifier for the learned procedure.
pub procedure_id: String,
/// Optional human-readable description learned from config or introspection.
pub description: Option<String>,
}
/// Learned leaf metadata stored by the root host.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LearnedLeaf {
/// Leaf name relative to the endpoint path.
pub leaf_name: String,
/// Optional human-readable description for the leaf.
pub description: Option<String>,
/// Procedures currently known on the leaf.
pub procedures: Vec<LearnedProcedure>,
}
/// Learned endpoint metadata stored by the root host.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LearnedNode {
/// Absolute node path from the root.
pub path: Vec<String>,
/// Optional display title shown in the inspector.
pub title: Option<String>,
/// Optional endpoint description shown in the inspector.
pub description: Option<String>,
/// Whether the node is a direct child of the root.
pub direct_child: bool,
/// Endpoint-level procedures known on the node itself.
pub endpoint_procedures: Vec<LearnedProcedure>,
/// Leaf metadata currently known for the node.
pub leaves: Vec<LearnedLeaf>,
/// Whether endpoint introspection definitely ran against this node.
pub endpoint_introspected: bool,
}
/// Root-host knowledge accumulated from local configuration and observed traffic.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootKnowledge {
/// Learned nodes keyed by their absolute path.
pub nodes: BTreeMap<Vec<String>, LearnedNode>,
}
@@ -58,49 +74,9 @@ impl RootKnowledge {
};
for node in &tree.nodes {
if node.path.is_empty() || node.path.len() == 1 {
// Realistic mode intentionally starts with root plus direct children,
// not the full transitive tree.
let direct_child = node.path.len() == 1;
let mut learned = LearnedNode {
path: node.path.clone(),
title: Some(node.title.clone()),
description: Some(node.description.clone()),
direct_child,
endpoint_procedures: Vec::new(),
leaves: Vec::new(),
endpoint_introspected: node.path.is_empty(),
};
if node.path.is_empty() {
// The root always knows its own procedures and leaves because
// those are locally configured, not discovered remotely.
learned.endpoint_procedures = node
.endpoint_procedures
.iter()
.map(|procedure| LearnedProcedure {
procedure_id: procedure.procedure_id.clone(),
description: Some(procedure.description.clone()),
})
.collect();
learned.leaves = node
.leaves
.iter()
.map(|leaf| LearnedLeaf {
leaf_name: leaf.name.clone(),
description: Some(leaf.description.clone()),
procedures: leaf
.procedures
.iter()
.map(|procedure_id| LearnedProcedure {
procedure_id: procedure_id.clone(),
description: Some(leaf.description.clone()),
})
.collect(),
})
.collect();
}
knowledge.nodes.insert(node.path.clone(), learned);
knowledge
.nodes
.insert(node.path.clone(), initial_learned_node(node));
}
}
knowledge
@@ -223,12 +199,56 @@ impl RootKnowledge {
}
}
/// Builds the root's initial record for one statically known node.
fn initial_learned_node(node: &crate::model::DemoNode) -> LearnedNode {
let mut learned = LearnedNode {
path: node.path.clone(),
title: Some(node.title.clone()),
description: Some(node.description.clone()),
direct_child: node.path.len() == 1,
endpoint_procedures: Vec::new(),
leaves: Vec::new(),
endpoint_introspected: node.path.is_empty(),
};
if node.path.is_empty() {
// The root always knows its own procedures and leaves because those are
// locally configured, not discovered remotely.
learned.endpoint_procedures = node
.endpoint_procedures
.iter()
.map(|procedure| LearnedProcedure {
procedure_id: procedure.procedure_id.clone(),
description: Some(procedure.description.clone()),
})
.collect();
learned.leaves = node
.leaves
.iter()
.map(|leaf| LearnedLeaf {
leaf_name: leaf.name.clone(),
description: Some(leaf.description.clone()),
procedures: leaf
.procedures
.iter()
.map(|procedure_id| LearnedProcedure {
procedure_id: procedure_id.clone(),
description: Some(leaf.description.clone()),
})
.collect(),
})
.collect();
}
learned
}
/// Returns one learned leaf entry, creating it if necessary.
fn ensure_leaf<'a>(
leaves: &'a mut Vec<LearnedLeaf>,
fn ensure_leaf(
leaves: &mut Vec<LearnedLeaf>,
leaf_name: String,
description: Option<String>,
) -> &'a mut LearnedLeaf {
) -> &mut LearnedLeaf {
if let Some(index) = leaves.iter().position(|leaf| leaf.leaf_name == leaf_name) {
if leaves[index].description.is_none() {
leaves[index].description = description;
+41 -30
View File
@@ -32,16 +32,14 @@ impl Simulation {
match procedure.kind {
EndpointProcedureKind::Ping => {
let reply = format!("pong from {}", self.node(node_id).display_path());
let frame = self.nodes[node_id.0]
.endpoint
.make_data(
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
reply.clone().into_bytes(),
true,
)
.map_err(|error| SimError::Protocol(error.to_string()))?;
let frame = self.make_endpoint_data_frame(
node_id,
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
reply.clone().into_bytes(),
true,
)?;
self.record_trace(node_id, format!("endpoint sent ping reply: {reply}"));
self.process_local_frame(node_id, frame)?;
}
@@ -54,16 +52,14 @@ impl Simulation {
.iter()
.enumerate()
{
let frame = self.nodes[node_id.0]
.endpoint
.make_data(
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
text.as_bytes().to_vec(),
index == 2,
)
.map_err(|error| SimError::Protocol(error.to_string()))?;
let frame = self.make_endpoint_data_frame(
node_id,
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
text.as_bytes().to_vec(),
index == 2,
)?;
self.record_trace(node_id, format!("endpoint sent chunk {}", index + 1));
self.process_local_frame(node_id, frame)?;
}
@@ -80,16 +76,14 @@ impl Simulation {
procedure_id: procedure.procedure_id.clone(),
},
);
let frame = self.nodes[node_id.0]
.endpoint
.make_data(
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
b"chat ready".to_vec(),
false,
)
.map_err(|error| SimError::Protocol(error.to_string()))?;
let frame = self.make_endpoint_data_frame(
node_id,
hook.return_path.clone(),
hook.hook_id,
procedure.procedure_id.clone(),
b"chat ready".to_vec(),
false,
)?;
self.record_trace(node_id, "chat handler opened session".to_owned());
self.process_local_frame(node_id, frame)?;
}
@@ -97,6 +91,23 @@ impl Simulation {
Ok(())
}
/// Builds one endpoint-originated data frame after application logic decides
/// what to send back on an already-validated hook.
fn make_endpoint_data_frame(
&mut self,
node_id: NodeId,
return_path: Vec<String>,
hook_id: u64,
procedure_id: String,
data: Vec<u8>,
end_hook: bool,
) -> Result<unshell::protocol::FrameBytes, SimError> {
self.nodes[node_id.0]
.endpoint
.make_data(return_path, hook_id, procedure_id, data, end_hook)
.map_err(|error| SimError::Protocol(error.to_string()))
}
/// Resolves one endpoint procedure from the ground-truth node metadata.
pub(super) fn lookup_endpoint_procedure(
&self,
+53 -42
View File
@@ -20,51 +20,24 @@ impl Simulation {
match event {
LocalEvent::Data { header, message } => {
let text = String::from_utf8_lossy(&message.data).to_string();
self.record_trace(
node_id,
format!(
"local Data on {}: {text}",
format_hook_ref(
self.node(node_id).path.as_slice(),
header.hook_id.unwrap_or(0)
)
),
let hook_ref = format_hook_ref(
self.node(node_id).path.as_slice(),
header.hook_id.unwrap_or(0),
);
self.record_trace(node_id, format!("local Data on {hook_ref}: {text}"));
if let Some(hook_id) = header.hook_id {
if let Some(snapshot) = self.hooks.get_mut(&hook_id) {
// Keep the most recent human-readable payload in the UI.
snapshot.last_message = if text.is_empty() {
format!("binary payload ({} bytes)", message.data.len())
} else {
text.clone()
};
if message.end_hook {
snapshot.closed = true;
}
}
self.update_hook_snapshot(hook_id, &text, message.data.len(), message.end_hook);
if node_id == self.root_id {
self.learn_from_root_data(hook_id, &message);
}
}
if let Some(session) = self
.chat_sessions
.get(&header.hook_id.unwrap_or(0))
.cloned()
.filter(|session| session.node_id == node_id)
{
if let Some(session) = self.chat_session_for_event(node_id, header.hook_id) {
// Rationale: chat responses are implemented here instead of in the
// core endpoint so the protocol crate stays generic. The simulator
// acts as the application layer sitting above validated hook traffic.
let reply = if text.eq_ignore_ascii_case("bye") {
Some(("chat session closed".to_owned(), true))
} else if !text.is_empty() {
Some((format!("chat ack: {}", text.to_uppercase()), false))
} else {
None
};
let reply = chat_reply_for_text(&text);
if let Some((reply, end_hook)) = reply {
let frame = self.nodes[session.node_id.0]
@@ -92,16 +65,13 @@ impl Simulation {
});
}
LocalEvent::Fault { header, message } => {
let hook_ref = format_hook_ref(
self.node(node_id).path.as_slice(),
header.hook_id.unwrap_or(0),
);
self.record_trace(
node_id,
format!(
"local Fault on {}: 0x{:02X}",
format_hook_ref(
self.node(node_id).path.as_slice(),
header.hook_id.unwrap_or(0)
),
message.fault.0
),
format!("local Fault on {hook_ref}: 0x{:02X}", message.fault.0),
);
if let Some(hook_id) = header.hook_id {
if let Some(snapshot) = self.hooks.get_mut(&hook_id) {
@@ -140,4 +110,45 @@ impl Simulation {
}
Ok(())
}
fn update_hook_snapshot(
&mut self,
hook_id: u64,
text: &str,
payload_len: usize,
end_hook: bool,
) {
if let Some(snapshot) = self.hooks.get_mut(&hook_id) {
// Keep the most recent human-readable payload in the UI.
snapshot.last_message = if text.is_empty() {
format!("binary payload ({payload_len} bytes)")
} else {
text.to_owned()
};
if end_hook {
snapshot.closed = true;
}
}
}
fn chat_session_for_event(
&self,
node_id: NodeId,
hook_id: Option<u64>,
) -> Option<super::super::super::types::ChatSession> {
self.chat_sessions
.get(&hook_id.unwrap_or(0))
.cloned()
.filter(|session| session.node_id == node_id)
}
}
fn chat_reply_for_text(text: &str) -> Option<(String, bool)> {
if text.eq_ignore_ascii_case("bye") {
return Some(("chat session closed".to_owned(), true));
}
if text.is_empty() {
return None;
}
Some((format!("chat ack: {}", text.to_uppercase()), false))
}
+35 -17
View File
@@ -1,4 +1,8 @@
//! Root-side knowledge learning from returned data.
//!
//! The simulator learns only from data that arrives back at the root on a known
//! hook. This keeps the realistic inspector aligned with what the UI-triggered
//! action actually observed.
use unshell::protocol::{
DataMessage, EndpointIntrospection, LeafIntrospection, deserialize_archived_bytes,
@@ -17,23 +21,7 @@ impl Simulation {
let demo_node = self.node(node_id).clone();
if snapshot.procedure_id.is_empty() {
if snapshot.target_leaf.is_some() {
if let Ok(introspection) = deserialize_archived_bytes::<
unshell::protocol::introspection::ArchivedLeafIntrospection,
LeafIntrospection,
>(&message.data)
{
self.root_knowledge
.remember_leaf_introspection(&demo_node, &introspection);
}
} else if let Ok(introspection) = deserialize_archived_bytes::<
unshell::protocol::introspection::ArchivedEndpointIntrospection,
EndpointIntrospection,
>(&message.data)
{
self.root_knowledge
.remember_endpoint_introspection(&demo_node, &introspection);
}
self.learn_from_root_introspection(&snapshot, &demo_node, message);
return;
}
@@ -54,3 +42,33 @@ impl Simulation {
}
}
}
impl Simulation {
fn learn_from_root_introspection(
&mut self,
snapshot: &super::super::types::HookSnapshot,
demo_node: &crate::model::DemoNode,
message: &DataMessage,
) {
if snapshot.target_leaf.is_some() {
if let Ok(introspection) = deserialize_archived_bytes::<
unshell::protocol::introspection::ArchivedLeafIntrospection,
LeafIntrospection,
>(&message.data)
{
self.root_knowledge
.remember_leaf_introspection(demo_node, &introspection);
}
return;
}
if let Ok(introspection) = deserialize_archived_bytes::<
unshell::protocol::introspection::ArchivedEndpointIntrospection,
EndpointIntrospection,
>(&message.data)
{
self.root_knowledge
.remember_endpoint_introspection(demo_node, &introspection);
}
}
}