Make new error structs, improve tests, remake file structure.

This commit is contained in:
Michael Mikovsky
2026-05-28 12:41:32 -06:00
parent 3973589a35
commit 65a7f675a9
14 changed files with 958 additions and 385 deletions
+93 -71
View File
@@ -1,105 +1,127 @@
use crate::{
endpoint::{Endpoint, error::EndpointError},
packet::Packet,
};
use crate::{Endpoint, EndpointError, Packet, RouteDirection};
impl Endpoint {
/// Register an inbound packet and route it
/// Register an inbound packet and route it through the local endpoint state.
///
/// Inbound transport data still uses the same local routing rules as packets
/// generated by leaves: local destinations are delivered to `inbound`, and
/// transit destinations are queued by their immediate next hop.
pub fn add_inbound(&mut self, packet: Packet) -> Result<(), EndpointError> {
// In case some leaf hasn't assigned the endpoint a path yet.
if self.path.is_empty() {
return Err(EndpointError::NoAbsoultePathYet);
}
// If the packet is routed towards this endpoint
if packet.path == *self.path {
// Get the last segment of the path
let local_id = self
.path
.last()
.cloned()
.ok_or(EndpointError::IncorrectAbsolutePath)?;
self.inbound.entry(local_id).or_default().push_back(packet);
Ok(())
} else {
let (next_hop, is_upward) = self.next_hop_for(&packet)?;
if !self.connections.contains(&(next_hop, is_upward)) {
return Err(EndpointError::RouteNotExist);
}
self.queue_outbound(packet, next_hop)
}
self.route_packet(packet)
}
/// Register an outbound packet produced locally and route it to the next queue.
///
/// This intentionally shares the same implementation as [`Self::add_inbound`]
/// so local leaf output and received transport packets cannot drift into subtly
/// different route semantics.
pub fn add_outbound(&mut self, packet: Packet) -> Result<(), EndpointError> {
// In case some leaf hasn't assigned the endpoint a path yet.
if self.path.is_empty() {
return Err(EndpointError::NoAbsoultePathYet);
}
self.route_packet(packet)
}
// If this packet is routed towards this node
if packet.path == *self.path {
// Grab the last endpoint ID
/// Route a packet by classifying its destination and mutating exactly one queue.
///
/// Hook cleanup is deliberately last. A packet with `end_hook = true` should not
/// tear down local hook state unless the packet has a valid route and is actually
/// queued for forwarding. The route branches are kept inline rather than using
/// an intermediate decision enum so size-focused builds have less structure to
/// optimize away.
fn route_packet(&mut self, packet: Packet) -> Result<(), EndpointError> {
self.ensure_path_is_set()?;
if packet.path == self.path {
let local_id = self
.path
.last()
.cloned()
.ok_or(EndpointError::IncorrectAbsolutePath)?;
.copied()
.ok_or(EndpointError::EndpointPathUnset)?;
// Add it to the inbound queue
self.inbound.entry(local_id).or_default().push_back(packet);
return Ok(());
}
let (next_hop, is_upward) = self.next_hop_for(&packet)?;
if !self.connections.contains(&(next_hop, is_upward)) {
return Err(EndpointError::RouteNotExist);
}
self.queue_outbound(packet, next_hop)
}
fn queue_outbound(&mut self, packet: Packet, next_hop: u32) -> Result<(), EndpointError> {
if packet.end_hook {
self.hooks.remove(&packet.hook_id);
}
self.outbound.entry(next_hop).or_default().push_back(packet);
Ok(())
}
fn next_hop_for(&self, packet: &Packet) -> Result<(u32, bool), EndpointError> {
// Direction is derived from the local path. The packet never gets to declare
// whether it is moving upward, because that would make the trust boundary spoofable.
if packet.path.starts_with(&self.path) {
let next_hop = packet
.path
.get(self.path.len())
.cloned()
.ok_or(EndpointError::IncorrectAbsolutePath)?;
.copied()
.ok_or(EndpointError::DestinationOutsideLocalTree)?;
Ok((next_hop, false))
} else if self.path.starts_with(&packet.path) {
// SECURITY: All upward-routed packets must be checked against local hook state.
self.ensure_registered_connection(next_hop, RouteDirection::Downward)?;
self.queue_outbound(packet, next_hop, RouteDirection::Downward);
return Ok(());
}
if self.path.starts_with(&packet.path) {
// Upward-routed packets must be tied to local hook state. Otherwise a
// peer could forge a packet to an ancestor by choosing an older path.
if !self.hooks.contains_key(&packet.hook_id) {
return Err(EndpointError::HookNotExist);
return Err(EndpointError::UnknownHook {
hook_id: packet.hook_id,
});
}
let parent_index = self
.path
.len()
.checked_sub(2)
.ok_or(EndpointError::RouteNotExist)?;
.ok_or(EndpointError::MissingParentRoute)?;
Ok((self.path[parent_index], true))
let next_hop = self.path[parent_index];
self.ensure_registered_connection(next_hop, RouteDirection::Upward)?;
self.queue_outbound(packet, next_hop, RouteDirection::Upward);
return Ok(());
}
Err(EndpointError::DestinationOutsideLocalTree)
}
/// Reject routing before path-relative decisions when no absolute path is known.
///
/// This preserves the current runtime sentinel where an empty path means the
/// endpoint has not been attached to the tree yet.
fn ensure_path_is_set(&self) -> Result<(), EndpointError> {
if self.path.is_empty() {
Err(EndpointError::EndpointPathUnset)
} else {
Err(EndpointError::IncorrectAbsolutePath)
Ok(())
}
}
/// Verify that the derived adjacent endpoint is registered in this direction.
///
/// The current connection table stores direction as a boolean. Keeping the bool
/// conversion here confines that legacy representation to one place in routing.
fn ensure_registered_connection(
&self,
next_hop: u32,
direction: RouteDirection,
) -> Result<(), EndpointError> {
let is_upward = matches!(direction, RouteDirection::Upward);
if self.connections.contains(&(next_hop, is_upward)) {
Ok(())
} else {
Err(EndpointError::MissingConnection {
next_hop,
direction,
})
}
}
/// Queue `packet` after all route validation has already succeeded.
///
/// `end_hook` closes local hook state only when hook traffic is moving upward
/// toward the hook host. Downward calls may carry a response hook id, but that
/// id is only a promise for future upward traffic and must not delete local
/// state if it happens to collide with an existing hook id.
fn queue_outbound(&mut self, packet: Packet, next_hop: u32, direction: RouteDirection) {
if matches!(direction, RouteDirection::Upward) && packet.end_hook {
self.hooks.remove(&packet.hook_id);
}
self.outbound.entry(next_hop).or_default().push_back(packet);
}
}