mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
Reduce protocol packet-flow allocations
Replace vector-backed endpoint outcomes with single-action results, skip payload deserialization on forwarded data and faults, and route local call and data emissions without encode/decode roundtrips.
This commit is contained in:
@@ -76,13 +76,12 @@ fn protocol_endpoint_introspection_returns_leaf_summary() {
|
|||||||
.receive(&Ingress::Local, frame)
|
.receive(&Ingress::Local, frame)
|
||||||
.expect("endpoint should handle introspection");
|
.expect("endpoint should handle introspection");
|
||||||
|
|
||||||
assert!(outcome.forwards.is_empty());
|
assert!(outcome.forward.is_none());
|
||||||
assert_eq!(outcome.events.len(), 1);
|
|
||||||
|
|
||||||
let LocalEvent::Data {
|
let LocalEvent::Data {
|
||||||
header,
|
header,
|
||||||
message: response,
|
message: response,
|
||||||
} = &outcome.events[0]
|
} = outcome.event.as_ref().expect("expected local data event")
|
||||||
else {
|
else {
|
||||||
panic!("expected local data event");
|
panic!("expected local data event");
|
||||||
};
|
};
|
||||||
@@ -139,11 +138,10 @@ fn invalid_hook_peer_emits_local_fault_event() {
|
|||||||
.receive(&Ingress::Local, frame)
|
.receive(&Ingress::Local, frame)
|
||||||
.expect("invalid peer should be handled");
|
.expect("invalid peer should be handled");
|
||||||
|
|
||||||
assert!(outcome.forwards.is_empty());
|
assert!(outcome.forward.is_none());
|
||||||
assert_eq!(outcome.events.len(), 1);
|
|
||||||
assert!(!outcome.dropped);
|
assert!(!outcome.dropped);
|
||||||
|
|
||||||
match &outcome.events[0] {
|
match outcome.event.as_ref().expect("expected event") {
|
||||||
LocalEvent::Fault { header, message } => {
|
LocalEvent::Fault { header, message } => {
|
||||||
assert_eq!(header.packet_type, PacketType::Fault);
|
assert_eq!(header.packet_type, PacketType::Fault);
|
||||||
assert_eq!(header.hook_id, Some(hook_id));
|
assert_eq!(header.hook_id, Some(hook_id));
|
||||||
|
|||||||
@@ -11,10 +11,95 @@ use crate::protocol::{
|
|||||||
encode_packet, validate_call, validate_header, validate_procedure_id,
|
encode_packet, validate_call, validate_header, validate_procedure_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::core::{ChildRoute, EndpointError, ProtocolEndpoint};
|
use super::super::RouteDecision;
|
||||||
|
use super::core::{ChildRoute, EndpointError, EndpointOutcome, ProtocolEndpoint};
|
||||||
use crate::protocol::tree::LeafSpec;
|
use crate::protocol::tree::LeafSpec;
|
||||||
|
|
||||||
impl ProtocolEndpoint {
|
impl ProtocolEndpoint {
|
||||||
|
fn prepare_call(
|
||||||
|
&self,
|
||||||
|
dst_path: Vec<String>,
|
||||||
|
dst_leaf: Option<String>,
|
||||||
|
procedure_id: impl Into<String>,
|
||||||
|
response_hook_id: Option<u64>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<(PacketHeader, CallMessage), EndpointError> {
|
||||||
|
let procedure_id = procedure_id.into();
|
||||||
|
validate_procedure_id(&procedure_id)?;
|
||||||
|
|
||||||
|
let response_hook = response_hook_id.map(|hook_id| HookTarget {
|
||||||
|
hook_id,
|
||||||
|
return_path: self.path.clone(),
|
||||||
|
});
|
||||||
|
let header = PacketHeader {
|
||||||
|
packet_type: PacketType::Call,
|
||||||
|
src_path: self.path.clone(),
|
||||||
|
dst_path,
|
||||||
|
dst_leaf,
|
||||||
|
hook_id: None,
|
||||||
|
};
|
||||||
|
let call = CallMessage {
|
||||||
|
procedure_id,
|
||||||
|
data,
|
||||||
|
response_hook,
|
||||||
|
};
|
||||||
|
|
||||||
|
validate_header(&header)?;
|
||||||
|
validate_call(&header, &call)?;
|
||||||
|
Ok((header, call))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_outbound_call_hook(
|
||||||
|
&mut self,
|
||||||
|
header: &PacketHeader,
|
||||||
|
call: &CallMessage,
|
||||||
|
) -> Result<(), EndpointError> {
|
||||||
|
if let Some(hook) = &call.response_hook
|
||||||
|
&& self
|
||||||
|
.hooks
|
||||||
|
.insert_active(ActiveHook {
|
||||||
|
return_path: hook.return_path.clone(),
|
||||||
|
hook_id: hook.hook_id,
|
||||||
|
peer_path: header.dst_path.clone(),
|
||||||
|
procedure_id: call.procedure_id.clone(),
|
||||||
|
dst_leaf: header.dst_leaf.clone(),
|
||||||
|
peer_finished: false,
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err(EndpointError::Validation(ValidationError::InvalidHookId));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_data(
|
||||||
|
&self,
|
||||||
|
dst_path: Vec<String>,
|
||||||
|
hook_id: u64,
|
||||||
|
procedure_id: impl Into<String>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
end_hook: bool,
|
||||||
|
) -> Result<(PacketHeader, DataMessage), EndpointError> {
|
||||||
|
let procedure_id = procedure_id.into();
|
||||||
|
validate_procedure_id(&procedure_id)?;
|
||||||
|
|
||||||
|
let header = PacketHeader {
|
||||||
|
packet_type: PacketType::Data,
|
||||||
|
src_path: self.path.clone(),
|
||||||
|
dst_path,
|
||||||
|
dst_leaf: None,
|
||||||
|
hook_id: Some(hook_id),
|
||||||
|
};
|
||||||
|
let message = DataMessage {
|
||||||
|
procedure_id,
|
||||||
|
data,
|
||||||
|
end_hook,
|
||||||
|
};
|
||||||
|
|
||||||
|
validate_header(&header)?;
|
||||||
|
Ok((header, message))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a runtime endpoint with static tree topology and leaf metadata.
|
/// Creates a runtime endpoint with static tree topology and leaf metadata.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -69,48 +154,31 @@ impl ProtocolEndpoint {
|
|||||||
response_hook_id: Option<u64>,
|
response_hook_id: Option<u64>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
) -> Result<FrameBytes, EndpointError> {
|
) -> Result<FrameBytes, EndpointError> {
|
||||||
let procedure_id = procedure_id.into();
|
let (header, call) =
|
||||||
validate_procedure_id(&procedure_id)?;
|
self.prepare_call(dst_path, dst_leaf, procedure_id, response_hook_id, data)?;
|
||||||
|
self.register_outbound_call_hook(&header, &call)?;
|
||||||
let response_hook = response_hook_id.map(|hook_id| HookTarget {
|
|
||||||
hook_id,
|
|
||||||
return_path: self.path.clone(),
|
|
||||||
});
|
|
||||||
let header = PacketHeader {
|
|
||||||
packet_type: PacketType::Call,
|
|
||||||
src_path: self.path.clone(),
|
|
||||||
dst_path: dst_path.clone(),
|
|
||||||
dst_leaf: dst_leaf.clone(),
|
|
||||||
hook_id: None,
|
|
||||||
};
|
|
||||||
let call = CallMessage {
|
|
||||||
procedure_id: procedure_id.clone(),
|
|
||||||
data,
|
|
||||||
response_hook,
|
|
||||||
};
|
|
||||||
|
|
||||||
validate_header(&header)?;
|
|
||||||
validate_call(&header, &call)?;
|
|
||||||
|
|
||||||
if let Some(hook) = &call.response_hook
|
|
||||||
&& self
|
|
||||||
.hooks
|
|
||||||
.insert_active(ActiveHook {
|
|
||||||
return_path: hook.return_path.clone(),
|
|
||||||
hook_id: hook.hook_id,
|
|
||||||
peer_path: dst_path,
|
|
||||||
procedure_id,
|
|
||||||
dst_leaf,
|
|
||||||
peer_finished: false,
|
|
||||||
})
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return Err(EndpointError::Validation(ValidationError::InvalidHookId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(encode_packet(&header, &call)?)
|
Ok(encode_packet(&header, &call)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Routes one locally originated `Call` without an encode/decode roundtrip.
|
||||||
|
pub fn send_call(
|
||||||
|
&mut self,
|
||||||
|
dst_path: Vec<String>,
|
||||||
|
dst_leaf: Option<String>,
|
||||||
|
procedure_id: impl Into<String>,
|
||||||
|
response_hook_id: Option<u64>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
) -> Result<EndpointOutcome, EndpointError> {
|
||||||
|
let (header, call) =
|
||||||
|
self.prepare_call(dst_path, dst_leaf, procedure_id, response_hook_id, data)?;
|
||||||
|
self.register_outbound_call_hook(&header, &call)?;
|
||||||
|
|
||||||
|
match self.decide_route(&header.dst_path) {
|
||||||
|
RouteDecision::Local => self.handle_local_call(header, call),
|
||||||
|
route => Ok(EndpointOutcome::forward(route, encode_packet(&header, &call)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds an outbound `Data` packet for an existing hook.
|
/// Builds an outbound `Data` packet for an existing hook.
|
||||||
pub fn make_data(
|
pub fn make_data(
|
||||||
&self,
|
&self,
|
||||||
@@ -120,23 +188,24 @@ impl ProtocolEndpoint {
|
|||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
end_hook: bool,
|
end_hook: bool,
|
||||||
) -> Result<FrameBytes, EndpointError> {
|
) -> Result<FrameBytes, EndpointError> {
|
||||||
let procedure_id = procedure_id.into();
|
let (header, message) = self.prepare_data(dst_path, hook_id, procedure_id, data, end_hook)?;
|
||||||
validate_procedure_id(&procedure_id)?;
|
|
||||||
|
|
||||||
let header = PacketHeader {
|
|
||||||
packet_type: PacketType::Data,
|
|
||||||
src_path: self.path.clone(),
|
|
||||||
dst_path,
|
|
||||||
dst_leaf: None,
|
|
||||||
hook_id: Some(hook_id),
|
|
||||||
};
|
|
||||||
let message = DataMessage {
|
|
||||||
procedure_id,
|
|
||||||
data,
|
|
||||||
end_hook,
|
|
||||||
};
|
|
||||||
|
|
||||||
validate_header(&header)?;
|
|
||||||
Ok(encode_packet(&header, &message)?)
|
Ok(encode_packet(&header, &message)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Routes one locally originated `Data` packet without an encode/decode roundtrip.
|
||||||
|
pub fn send_data(
|
||||||
|
&mut self,
|
||||||
|
dst_path: Vec<String>,
|
||||||
|
hook_id: u64,
|
||||||
|
procedure_id: impl Into<String>,
|
||||||
|
data: Vec<u8>,
|
||||||
|
end_hook: bool,
|
||||||
|
) -> Result<EndpointOutcome, EndpointError> {
|
||||||
|
let (header, message) = self.prepare_data(dst_path, hook_id, procedure_id, data, end_hook)?;
|
||||||
|
|
||||||
|
match self.decide_route(&header.dst_path) {
|
||||||
|
RouteDecision::Local => self.handle_local_data(header, message),
|
||||||
|
route => Ok(EndpointOutcome::forward(route, encode_packet(&header, &message)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,14 +93,46 @@ pub enum LocalEvent {
|
|||||||
/// Result of processing one framed packet.
|
/// Result of processing one framed packet.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct EndpointOutcome {
|
pub struct EndpointOutcome {
|
||||||
/// Forwarding actions to perform after local processing.
|
/// Forwarding action to perform after local processing.
|
||||||
pub forwards: Vec<(RouteDecision, FrameBytes)>,
|
pub forward: Option<(RouteDecision, FrameBytes)>,
|
||||||
/// Events delivered to local runtime consumers.
|
/// Event delivered to the local runtime consumer.
|
||||||
pub events: Vec<LocalEvent>,
|
pub event: Option<LocalEvent>,
|
||||||
/// Whether the packet was intentionally dropped with no other side effects.
|
/// Whether the packet was intentionally dropped with no other side effects.
|
||||||
pub dropped: bool,
|
pub dropped: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EndpointOutcome {
|
||||||
|
/// Returns an outcome that only forwards one frame.
|
||||||
|
#[must_use]
|
||||||
|
pub fn forward(route: RouteDecision, frame: FrameBytes) -> Self {
|
||||||
|
Self {
|
||||||
|
forward: Some((route, frame)),
|
||||||
|
event: None,
|
||||||
|
dropped: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an outcome that only delivers one local event.
|
||||||
|
#[must_use]
|
||||||
|
pub fn event(event: LocalEvent) -> Self {
|
||||||
|
Self {
|
||||||
|
forward: None,
|
||||||
|
event: Some(event),
|
||||||
|
dropped: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an outcome that silently drops the packet.
|
||||||
|
#[must_use]
|
||||||
|
pub fn dropped() -> Self {
|
||||||
|
Self {
|
||||||
|
forward: None,
|
||||||
|
event: None,
|
||||||
|
dropped: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Errors returned while decoding or validating a packet.
|
/// Errors returned while decoding or validating a packet.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EndpointError {
|
pub enum EndpointError {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! These methods implement the hook lifecycle described in `PROTOCOL.md`:
|
//! These methods implement the hook lifecycle described in `PROTOCOL.md`:
|
||||||
//! pending contexts, active contexts, peer validation, and fault emission.
|
//! pending contexts, active contexts, peer validation, and fault emission.
|
||||||
|
|
||||||
use alloc::{string::String, vec};
|
use alloc::string::String;
|
||||||
|
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
DataMessage, FaultMessage, PacketHeader, PacketType, ProtocolFault, encode_packet,
|
DataMessage, FaultMessage, PacketHeader, PacketType, ProtocolFault, encode_packet,
|
||||||
@@ -20,10 +20,7 @@ impl ProtocolEndpoint {
|
|||||||
fault: ProtocolFault,
|
fault: ProtocolFault,
|
||||||
) -> Result<EndpointOutcome, EndpointError> {
|
) -> Result<EndpointOutcome, EndpointError> {
|
||||||
let Some(key) = key else {
|
let Some(key) = key else {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.hooks.remove_pending(&key);
|
self.hooks.remove_pending(&key);
|
||||||
@@ -40,16 +37,10 @@ impl ProtocolEndpoint {
|
|||||||
let route = self.decide_route(&key.return_path);
|
let route = self.decide_route(&key.return_path);
|
||||||
|
|
||||||
match route {
|
match route {
|
||||||
RouteDecision::Local => Ok(EndpointOutcome {
|
RouteDecision::Local => Ok(EndpointOutcome::event(LocalEvent::Fault { header, message })),
|
||||||
events: vec![LocalEvent::Fault { header, message }],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
}),
|
|
||||||
_ => {
|
_ => {
|
||||||
let frame = encode_packet(&header, &message)?;
|
let frame = encode_packet(&header, &message)?;
|
||||||
Ok(EndpointOutcome {
|
Ok(EndpointOutcome::forward(route, frame))
|
||||||
forwards: vec![(route, frame)],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,47 +76,35 @@ impl ProtocolEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(active) = self.hooks.active(&key) else {
|
let Some(active) = self.hooks.active(&key) else {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if active.peer_path != header.src_path {
|
if active.peer_path != header.src_path {
|
||||||
self.hooks.remove_active(&key);
|
self.hooks.remove_active(&key);
|
||||||
self.hooks.remove_pending(&key);
|
self.hooks.remove_pending(&key);
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::event(LocalEvent::Fault {
|
||||||
events: vec![LocalEvent::Fault {
|
header: PacketHeader {
|
||||||
header: PacketHeader {
|
packet_type: PacketType::Fault,
|
||||||
packet_type: PacketType::Fault,
|
src_path: header.src_path,
|
||||||
src_path: header.src_path,
|
dst_path: self.path.clone(),
|
||||||
dst_path: self.path.clone(),
|
dst_leaf: None,
|
||||||
dst_leaf: None,
|
hook_id: Some(key.hook_id),
|
||||||
hook_id: Some(key.hook_id),
|
},
|
||||||
},
|
message: FaultMessage {
|
||||||
message: FaultMessage {
|
fault: ProtocolFault::INVALID_HOOK_PEER,
|
||||||
fault: ProtocolFault::INVALID_HOOK_PEER,
|
},
|
||||||
},
|
}));
|
||||||
}],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if active.procedure_id != message.procedure_id {
|
if active.procedure_id != message.procedure_id {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.end_hook {
|
if message.end_hook {
|
||||||
self.hooks.remove_active(&key);
|
self.hooks.remove_active(&key);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(EndpointOutcome {
|
Ok(EndpointOutcome::event(LocalEvent::Data { header, message }))
|
||||||
events: vec![LocalEvent::Data { header, message }],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles locally delivered hook `Fault` packets.
|
/// Handles locally delivered hook `Fault` packets.
|
||||||
@@ -145,19 +124,13 @@ impl ProtocolEndpoint {
|
|||||||
.is_some_and(|pending| pending.caller_src_path == header.src_path);
|
.is_some_and(|pending| pending.caller_src_path == header.src_path);
|
||||||
|
|
||||||
if !matches {
|
if !matches {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hooks.remove_active(&key);
|
self.hooks.remove_active(&key);
|
||||||
self.hooks.remove_pending(&key);
|
self.hooks.remove_pending(&key);
|
||||||
|
|
||||||
Ok(EndpointOutcome {
|
Ok(EndpointOutcome::event(LocalEvent::Fault { header, message }))
|
||||||
events: vec![LocalEvent::Fault { header, message }],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chooses the next hop using the protocol's longest-prefix routing rule.
|
/// Chooses the next hop using the protocol's longest-prefix routing rule.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! This code implements the reserved empty-procedure behavior from the
|
//! This code implements the reserved empty-procedure behavior from the
|
||||||
//! introspection sections of `PROTOCOL.md`.
|
//! introspection sections of `PROTOCOL.md`.
|
||||||
|
|
||||||
use alloc::{string::String, vec};
|
use alloc::string::String;
|
||||||
use rkyv::{rancor::Error as RkyvError, to_bytes};
|
use rkyv::{rancor::Error as RkyvError, to_bytes};
|
||||||
|
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
@@ -22,14 +22,9 @@ impl ProtocolEndpoint {
|
|||||||
key: Option<HookKey>,
|
key: Option<HookKey>,
|
||||||
) -> Result<EndpointOutcome, EndpointError> {
|
) -> Result<EndpointOutcome, EndpointError> {
|
||||||
let Some(key) = key else {
|
let Some(key) = key else {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.hooks.activate_pending(&key, header.src_path.clone());
|
|
||||||
|
|
||||||
let payload = if let Some(leaf_name) = &header.dst_leaf {
|
let payload = if let Some(leaf_name) = &header.dst_leaf {
|
||||||
let Some(leaf) = self.leaves.get(leaf_name) else {
|
let Some(leaf) = self.leaves.get(leaf_name) else {
|
||||||
return self.emit_fault_if_possible(Some(key), ProtocolFault::UNKNOWN_LEAF);
|
return self.emit_fault_if_possible(Some(key), ProtocolFault::UNKNOWN_LEAF);
|
||||||
@@ -77,19 +72,15 @@ impl ProtocolEndpoint {
|
|||||||
let route = self.decide_route(&key.return_path);
|
let route = self.decide_route(&key.return_path);
|
||||||
|
|
||||||
match route {
|
match route {
|
||||||
super::super::RouteDecision::Local => Ok(EndpointOutcome {
|
super::super::RouteDecision::Local => Ok(EndpointOutcome::event(
|
||||||
events: vec![super::core::LocalEvent::Data {
|
super::core::LocalEvent::Data {
|
||||||
header: response_header,
|
header: response_header,
|
||||||
message: response,
|
message: response,
|
||||||
}],
|
},
|
||||||
..EndpointOutcome::default()
|
)),
|
||||||
}),
|
|
||||||
_ => {
|
_ => {
|
||||||
let frame = encode_packet(&response_header, &response)?;
|
let frame = encode_packet(&response_header, &response)?;
|
||||||
Ok(EndpointOutcome {
|
Ok(EndpointOutcome::forward(route, frame))
|
||||||
forwards: vec![(route, frame)],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
//! This file implements the transport-facing packet entry point and maps it to
|
//! This file implements the transport-facing packet entry point and maps it to
|
||||||
//! the `Call`, `Data`, and `Fault` sections of `PROTOCOL.md`.
|
//! the `Call`, `Data`, and `Fault` sections of `PROTOCOL.md`.
|
||||||
|
|
||||||
use alloc::vec;
|
|
||||||
|
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
CallMessage, PacketType, ProtocolFault, decode_frame, introspection::INTROSPECTION_PROCEDURE_ID,
|
CallMessage, PacketType, ProtocolFault, decode_frame, introspection::INTROSPECTION_PROCEDURE_ID,
|
||||||
validate_call, validate_header,
|
validate_call, validate_header,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{HookKey, PendingHook, RouteDecision};
|
use super::super::{ActiveHook, HookKey, RouteDecision};
|
||||||
use super::core::{
|
use super::core::{
|
||||||
Endpoint, EndpointError, EndpointOutcome, Ingress, LocalEvent, ProtocolEndpoint,
|
Endpoint, EndpointError, EndpointOutcome, Ingress, LocalEvent, ProtocolEndpoint,
|
||||||
};
|
};
|
||||||
@@ -27,22 +25,6 @@ impl ProtocolEndpoint {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|hook| HookKey::new(hook.return_path.clone(), hook.hook_id));
|
.map(|hook| HookKey::new(hook.return_path.clone(), hook.hook_id));
|
||||||
|
|
||||||
if let Some(hook) = &message.response_hook
|
|
||||||
&& hook.return_path != self.path
|
|
||||||
&& self
|
|
||||||
.hooks
|
|
||||||
.insert_pending(PendingHook {
|
|
||||||
caller_src_path: header.src_path.clone(),
|
|
||||||
return_path: hook.return_path.clone(),
|
|
||||||
hook_id: hook.hook_id,
|
|
||||||
procedure_id: message.procedure_id.clone(),
|
|
||||||
dst_leaf: header.dst_leaf.clone(),
|
|
||||||
})
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return self.emit_fault_if_possible(key, ProtocolFault::INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if message.procedure_id == INTROSPECTION_PROCEDURE_ID {
|
if message.procedure_id == INTROSPECTION_PROCEDURE_ID {
|
||||||
return self.handle_introspection(&header, key);
|
return self.handle_introspection(&header, key);
|
||||||
}
|
}
|
||||||
@@ -73,14 +55,24 @@ impl ProtocolEndpoint {
|
|||||||
return self.emit_fault_if_possible(key, fault);
|
return self.emit_fault_if_possible(key, fault);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(key) = &key {
|
if let Some(hook) = &message.response_hook
|
||||||
self.hooks.activate_pending(key, header.src_path.clone());
|
&& hook.return_path != self.path
|
||||||
|
&& self
|
||||||
|
.hooks
|
||||||
|
.insert_active(ActiveHook {
|
||||||
|
return_path: hook.return_path.clone(),
|
||||||
|
hook_id: hook.hook_id,
|
||||||
|
peer_path: header.src_path.clone(),
|
||||||
|
procedure_id: message.procedure_id.clone(),
|
||||||
|
dst_leaf: header.dst_leaf.clone(),
|
||||||
|
peer_finished: false,
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return self.emit_fault_if_possible(key, ProtocolFault::INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(EndpointOutcome {
|
Ok(EndpointOutcome::event(LocalEvent::Call { header, message }))
|
||||||
events: vec![LocalEvent::Call { header, message }],
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,73 +91,56 @@ impl Endpoint for ProtocolEndpoint {
|
|||||||
validate_header(header)?;
|
validate_header(header)?;
|
||||||
|
|
||||||
if !self.valid_source_for_ingress(ingress, &header.src_path) {
|
if !self.valid_source_for_ingress(ingress, &header.src_path) {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match header.packet_type {
|
match header.packet_type {
|
||||||
PacketType::Call => {
|
PacketType::Call => {
|
||||||
let message = parsed.deserialize_call()?;
|
let message = parsed.deserialize_call()?;
|
||||||
if !matches!(ingress, Ingress::Parent | Ingress::Local) {
|
if !matches!(ingress, Ingress::Parent | Ingress::Local) {
|
||||||
return Ok(EndpointOutcome {
|
return Ok(EndpointOutcome::dropped());
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_call(header, &message)?;
|
validate_call(header, &message)?;
|
||||||
match self.decide_route(&header.dst_path) {
|
match self.decide_route(&header.dst_path) {
|
||||||
RouteDecision::Child(index) => Ok(EndpointOutcome {
|
RouteDecision::Child(index) => {
|
||||||
forwards: vec![(RouteDecision::Child(index), frame)],
|
Ok(EndpointOutcome::forward(RouteDecision::Child(index), frame))
|
||||||
..EndpointOutcome::default()
|
}
|
||||||
}),
|
RouteDecision::Parent => {
|
||||||
RouteDecision::Parent => Ok(EndpointOutcome {
|
Ok(EndpointOutcome::forward(RouteDecision::Parent, frame))
|
||||||
forwards: vec![(RouteDecision::Parent, frame)],
|
}
|
||||||
..EndpointOutcome::default()
|
RouteDecision::Drop => Ok(EndpointOutcome::dropped()),
|
||||||
}),
|
RouteDecision::Local => self.handle_local_call(parsed.deserialize_header(), message),
|
||||||
RouteDecision::Drop => Ok(EndpointOutcome {
|
|
||||||
dropped: true,
|
|
||||||
..EndpointOutcome::default()
|
|
||||||
}),
|
|
||||||
RouteDecision::Local => self.handle_local_call(header.clone(), message),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PacketType::Data => {
|
PacketType::Data => {
|
||||||
let message = parsed.deserialize_data()?;
|
|
||||||
match self.decide_route(&header.dst_path) {
|
match self.decide_route(&header.dst_path) {
|
||||||
RouteDecision::Local => self.handle_local_data(header.clone(), message),
|
RouteDecision::Local => {
|
||||||
RouteDecision::Child(index) => Ok(EndpointOutcome {
|
let message = parsed.deserialize_data()?;
|
||||||
forwards: vec![(RouteDecision::Child(index), frame)],
|
self.handle_local_data(parsed.deserialize_header(), message)
|
||||||
..EndpointOutcome::default()
|
}
|
||||||
}),
|
RouteDecision::Child(index) => {
|
||||||
RouteDecision::Parent => Ok(EndpointOutcome {
|
Ok(EndpointOutcome::forward(RouteDecision::Child(index), frame))
|
||||||
forwards: vec![(RouteDecision::Parent, frame)],
|
}
|
||||||
..EndpointOutcome::default()
|
RouteDecision::Parent => {
|
||||||
}),
|
Ok(EndpointOutcome::forward(RouteDecision::Parent, frame))
|
||||||
RouteDecision::Drop => Ok(EndpointOutcome {
|
}
|
||||||
dropped: true,
|
RouteDecision::Drop => Ok(EndpointOutcome::dropped()),
|
||||||
..EndpointOutcome::default()
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PacketType::Fault => {
|
PacketType::Fault => {
|
||||||
let message = parsed.deserialize_fault()?;
|
|
||||||
match self.decide_route(&header.dst_path) {
|
match self.decide_route(&header.dst_path) {
|
||||||
RouteDecision::Local => self.handle_local_fault(header.clone(), message),
|
RouteDecision::Local => {
|
||||||
RouteDecision::Child(index) => Ok(EndpointOutcome {
|
let message = parsed.deserialize_fault()?;
|
||||||
forwards: vec![(RouteDecision::Child(index), frame)],
|
self.handle_local_fault(parsed.deserialize_header(), message)
|
||||||
..EndpointOutcome::default()
|
}
|
||||||
}),
|
RouteDecision::Child(index) => {
|
||||||
RouteDecision::Parent => Ok(EndpointOutcome {
|
Ok(EndpointOutcome::forward(RouteDecision::Child(index), frame))
|
||||||
forwards: vec![(RouteDecision::Parent, frame)],
|
}
|
||||||
..EndpointOutcome::default()
|
RouteDecision::Parent => {
|
||||||
}),
|
Ok(EndpointOutcome::forward(RouteDecision::Parent, frame))
|
||||||
RouteDecision::Drop => Ok(EndpointOutcome {
|
}
|
||||||
dropped: true,
|
RouteDecision::Drop => Ok(EndpointOutcome::dropped()),
|
||||||
..EndpointOutcome::default()
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,9 +190,9 @@ impl Simulation {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.ok_or(SimError::UnknownHook(hook_id))?;
|
.ok_or(SimError::UnknownHook(hook_id))?;
|
||||||
|
|
||||||
let frame = self.nodes[self.root_id.0]
|
let outcome = self.nodes[self.root_id.0]
|
||||||
.endpoint
|
.endpoint
|
||||||
.make_data(
|
.send_data(
|
||||||
snapshot.peer_path.clone(),
|
snapshot.peer_path.clone(),
|
||||||
hook_id,
|
hook_id,
|
||||||
snapshot.procedure_id.clone(),
|
snapshot.procedure_id.clone(),
|
||||||
@@ -208,7 +208,7 @@ impl Simulation {
|
|||||||
format_hook_ref(self.node(self.root_id).path.as_slice(), hook_id)
|
format_hook_ref(self.node(self.root_id).path.as_slice(), hook_id)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.process_local_frame(self.root_id, frame)?;
|
self.process_outcome(self.root_id, outcome)?;
|
||||||
Ok(ActionResult {
|
Ok(ActionResult {
|
||||||
label: format!("Send hook data {hook_id}"),
|
label: format!("Send hook data {hook_id}"),
|
||||||
hook_id: Some(hook_id),
|
hook_id: Some(hook_id),
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ impl Simulation {
|
|||||||
// Hook allocation happens on the root host because the root is the hook
|
// Hook allocation happens on the root host because the root is the hook
|
||||||
// owner for every user-driven action in the demo.
|
// owner for every user-driven action in the demo.
|
||||||
let hook_id = self.nodes[self.root_id.0].endpoint.allocate_hook_id();
|
let hook_id = self.nodes[self.root_id.0].endpoint.allocate_hook_id();
|
||||||
let frame = self.nodes[self.root_id.0]
|
let outcome = self.nodes[self.root_id.0]
|
||||||
.endpoint
|
.endpoint
|
||||||
.make_call(
|
.send_call(
|
||||||
dst_path.clone(),
|
dst_path.clone(),
|
||||||
dst_leaf.clone(),
|
dst_leaf.clone(),
|
||||||
procedure_id.to_owned(),
|
procedure_id.to_owned(),
|
||||||
@@ -65,7 +65,7 @@ impl Simulation {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.process_local_frame(self.root_id, frame)
|
self.process_outcome(self.root_id, outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delivers a frame into one endpoint as locally-originated traffic.
|
/// Delivers a frame into one endpoint as locally-originated traffic.
|
||||||
@@ -91,7 +91,7 @@ impl Simulation {
|
|||||||
self.record_trace(node_id, "packet dropped".to_owned());
|
self.record_trace(node_id, "packet dropped".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (route, frame) in outcome.forwards {
|
if let Some((route, frame)) = outcome.forward {
|
||||||
match route {
|
match route {
|
||||||
RouteDecision::Child(index) => {
|
RouteDecision::Child(index) => {
|
||||||
let child_id = self.nodes[node_id.0]
|
let child_id = self.nodes[node_id.0]
|
||||||
@@ -150,7 +150,7 @@ impl Simulation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in outcome.events {
|
if let Some(event) = outcome.event {
|
||||||
self.handle_local_event(node_id, event)?;
|
self.handle_local_event(node_id, event)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl Simulation {
|
|||||||
let leaf = self.require_leaf(node_id, leaf_name)?.clone();
|
let leaf = self.require_leaf(node_id, leaf_name)?.clone();
|
||||||
match leaf.kind {
|
match leaf.kind {
|
||||||
LeafKind::Echo => {
|
LeafKind::Echo => {
|
||||||
let frame = self.make_endpoint_data_frame(
|
let outcome = self.send_endpoint_data(
|
||||||
node_id,
|
node_id,
|
||||||
hook.return_path.clone(),
|
hook.return_path.clone(),
|
||||||
hook.hook_id,
|
hook.hook_id,
|
||||||
@@ -37,7 +37,7 @@ impl Simulation {
|
|||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
self.record_trace(node_id, format!("leaf {leaf_name} echoed {} bytes", message.data.len()));
|
self.record_trace(node_id, format!("leaf {leaf_name} echoed {} bytes", message.data.len()));
|
||||||
self.process_local_frame(node_id, frame)?;
|
self.process_outcome(node_id, outcome)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -51,7 +51,7 @@ impl Simulation {
|
|||||||
match procedure.kind {
|
match procedure.kind {
|
||||||
EndpointProcedureKind::Ping => {
|
EndpointProcedureKind::Ping => {
|
||||||
let reply = format!("pong from {}", self.node(node_id).display_path());
|
let reply = format!("pong from {}", self.node(node_id).display_path());
|
||||||
let frame = self.make_endpoint_data_frame(
|
let outcome = self.send_endpoint_data(
|
||||||
node_id,
|
node_id,
|
||||||
hook.return_path.clone(),
|
hook.return_path.clone(),
|
||||||
hook.hook_id,
|
hook.hook_id,
|
||||||
@@ -60,7 +60,7 @@ impl Simulation {
|
|||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
self.record_trace(node_id, format!("endpoint sent ping reply: {reply}"));
|
self.record_trace(node_id, format!("endpoint sent ping reply: {reply}"));
|
||||||
self.process_local_frame(node_id, frame)?;
|
self.process_outcome(node_id, outcome)?;
|
||||||
}
|
}
|
||||||
EndpointProcedureKind::ChunkedGreeting => {
|
EndpointProcedureKind::ChunkedGreeting => {
|
||||||
for (index, text) in [
|
for (index, text) in [
|
||||||
@@ -71,7 +71,7 @@ impl Simulation {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let frame = self.make_endpoint_data_frame(
|
let outcome = self.send_endpoint_data(
|
||||||
node_id,
|
node_id,
|
||||||
hook.return_path.clone(),
|
hook.return_path.clone(),
|
||||||
hook.hook_id,
|
hook.hook_id,
|
||||||
@@ -80,7 +80,7 @@ impl Simulation {
|
|||||||
index == 2,
|
index == 2,
|
||||||
)?;
|
)?;
|
||||||
self.record_trace(node_id, format!("endpoint sent chunk {}", index + 1));
|
self.record_trace(node_id, format!("endpoint sent chunk {}", index + 1));
|
||||||
self.process_local_frame(node_id, frame)?;
|
self.process_outcome(node_id, outcome)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EndpointProcedureKind::Chat => {
|
EndpointProcedureKind::Chat => {
|
||||||
@@ -95,7 +95,7 @@ impl Simulation {
|
|||||||
procedure_id: procedure.procedure_id.clone(),
|
procedure_id: procedure.procedure_id.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let frame = self.make_endpoint_data_frame(
|
let outcome = self.send_endpoint_data(
|
||||||
node_id,
|
node_id,
|
||||||
hook.return_path.clone(),
|
hook.return_path.clone(),
|
||||||
hook.hook_id,
|
hook.hook_id,
|
||||||
@@ -104,15 +104,15 @@ impl Simulation {
|
|||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
self.record_trace(node_id, "chat handler opened session".to_owned());
|
self.record_trace(node_id, "chat handler opened session".to_owned());
|
||||||
self.process_local_frame(node_id, frame)?;
|
self.process_outcome(node_id, outcome)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds one endpoint-originated data frame after application logic decides
|
/// Routes one endpoint-originated data packet after application logic decides
|
||||||
/// what to send back on an already-validated hook.
|
/// what to send back on an already-validated hook.
|
||||||
fn make_endpoint_data_frame(
|
fn send_endpoint_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
return_path: Vec<String>,
|
return_path: Vec<String>,
|
||||||
@@ -120,10 +120,10 @@ impl Simulation {
|
|||||||
procedure_id: String,
|
procedure_id: String,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
end_hook: bool,
|
end_hook: bool,
|
||||||
) -> Result<unshell::protocol::FrameBytes, SimError> {
|
) -> Result<unshell::protocol::tree::EndpointOutcome, SimError> {
|
||||||
self.nodes[node_id.0]
|
self.nodes[node_id.0]
|
||||||
.endpoint
|
.endpoint
|
||||||
.make_data(return_path, hook_id, procedure_id, data, end_hook)
|
.send_data(return_path, hook_id, procedure_id, data, end_hook)
|
||||||
.map_err(|error| SimError::Protocol(error.to_string()))
|
.map_err(|error| SimError::Protocol(error.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ impl Simulation {
|
|||||||
let reply = chat_reply_for_text(&text);
|
let reply = chat_reply_for_text(&text);
|
||||||
|
|
||||||
if let Some((reply, end_hook)) = reply {
|
if let Some((reply, end_hook)) = reply {
|
||||||
let frame = self.nodes[session.node_id.0]
|
let outcome = self.nodes[session.node_id.0]
|
||||||
.endpoint
|
.endpoint
|
||||||
.make_data(
|
.send_data(
|
||||||
session.host_path.clone(),
|
session.host_path.clone(),
|
||||||
session.hook_id,
|
session.hook_id,
|
||||||
session.procedure_id.clone(),
|
session.procedure_id.clone(),
|
||||||
@@ -51,7 +51,7 @@ impl Simulation {
|
|||||||
)
|
)
|
||||||
.map_err(|error| SimError::Protocol(error.to_string()))?;
|
.map_err(|error| SimError::Protocol(error.to_string()))?;
|
||||||
self.record_trace(session.node_id, format!("chat handler sent: {reply}"));
|
self.record_trace(session.node_id, format!("chat handler sent: {reply}"));
|
||||||
self.process_local_frame(session.node_id, frame)?;
|
self.process_outcome(session.node_id, outcome)?;
|
||||||
if end_hook {
|
if end_hook {
|
||||||
self.chat_sessions.remove(&session.hook_id);
|
self.chat_sessions.remove(&session.hook_id);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user