SessionDatagram redesign: add src_addr, reclassify error signals

Add src_addr to SessionDatagram envelope (34-byte header: msg_type +
src_addr + dest_addr + hop_limit) so transit routers can route error
signals back to the packet's originator.

Reclassify CoordsRequired/PathBroken as link-layer error signals
(plaintext inside SessionDatagram) rather than e2e encrypted session
messages. Transit routers generate these when forwarding fails and
route them to src_addr; if source is also unreachable, drop silently.

Remove redundant src_addr/dest_addr/hop_limit from SessionSetup,
SessionAck, and DataPacket (now in envelope). DataPacket header
reduced from 36 to 4 bytes. Remove PathBroken.original_src.

Fix routing loop vulnerability: gate bloom filter path on having
cached dest_coords to prevent blind forwarding between peers.
Simplify select_best_candidate() to require coordinates.

Fix gossip protocol type codes (0x11->0x20, 0x12->0x30, 0x13->0x31)
for consistency across all design docs.

All 5 design docs updated and cross-checked for consistency.
335 tests pass, zero warnings.
This commit is contained in:
Johnathan Corgan
2026-02-12 11:32:45 +00:00
parent 2f8e97c0ab
commit d41009b778
9 changed files with 535 additions and 349 deletions
+28 -39
View File
@@ -713,16 +713,19 @@ impl Node {
/// Routing priority:
/// 1. Destination is self → `None` (local delivery)
/// 2. Destination is a direct peer → that peer
/// 3. Bloom filter candidates + greedy tree routing → among peers whose
/// 3. Bloom filter candidates with cached dest coords → among peers whose
/// bloom filter contains the destination, pick the one that minimizes
/// tree distance to the destination (if dest coords are cached), with
/// tree distance to the destination, with
/// `(link_cost, tree_distance_to_dest, node_addr)` tie-breaking.
/// Falls back to greedy tree routing if no bloom filter hits.
/// 4. No route → `None`
/// The self-distance check ensures only peers strictly closer to the
/// destination than us are considered (prevents routing loops).
/// 4. Greedy tree routing fallback (requires cached dest coords)
/// 5. No route → `None`
///
/// The self-distance check from greedy routing also applies to bloom
/// filter candidates: a peer is only selected if it is strictly closer
/// to the destination than we are (prevents routing loops).
/// Both the bloom filter and tree routing paths require cached destination
/// coordinates. Without coordinates, the node cannot make loop-free
/// forwarding decisions. The caller should signal `CoordsRequired` back
/// to the source when `None` is returned for a non-local destination.
pub fn find_next_hop(&self, dest_node_addr: &NodeAddr) -> Option<&ActivePeer> {
// 1. Local delivery
if dest_node_addr == self.node_addr() {
@@ -736,21 +739,20 @@ impl Node {
}
}
// Look up destination coords (used by both bloom and tree paths)
// Look up destination coords (required by both bloom and tree paths)
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
let dest_coords = self.coord_cache.get(dest_node_addr, now_ms);
let dest_coords = self.coord_cache.get(dest_node_addr, now_ms)?;
// 3. Bloom filter candidates, scored by tree distance to dest
// 3. Bloom filter candidates — requires dest_coords for loop-free selection
let candidates: Vec<&ActivePeer> = self.destination_in_filters(dest_node_addr);
if !candidates.is_empty() {
return self.select_best_candidate(&candidates, dest_coords);
}
// 4. Greedy tree routing fallback
let dest_coords = dest_coords?;
let next_hop_id = self.tree_state.find_next_hop(dest_coords)?;
self.peers.get(&next_hop_id).filter(|p| p.can_send())
@@ -758,22 +760,18 @@ impl Node {
/// Select the best peer from a set of bloom filter candidates.
///
/// When dest_coords are available, uses distance from each candidate's
/// coordinates to the destination as the primary metric (after link_cost).
/// Only selects peers that are strictly closer to the destination than
/// we are (self-distance check prevents loops).
///
/// When dest_coords are not available, falls back to distance from us
/// to the candidate peer (a weaker heuristic — prefers closer peers on
/// the theory that shorter paths are better).
/// Uses distance from each candidate's tree coordinates to the destination
/// as the primary metric (after link_cost). Only selects peers that are
/// strictly closer to the destination than we are (self-distance check
/// prevents routing loops).
///
/// Ordering: `(link_cost, distance_to_dest, node_addr)`.
fn select_best_candidate<'a>(
&'a self,
candidates: &[&'a ActivePeer],
dest_coords: Option<&crate::tree::TreeCoordinate>,
dest_coords: &crate::tree::TreeCoordinate,
) -> Option<&'a ActivePeer> {
let my_distance = dest_coords.map(|dc| self.tree_state.my_coords().distance_to(dc));
let my_distance = self.tree_state.my_coords().distance_to(dest_coords);
let mut best: Option<(&ActivePeer, f64, usize)> = None;
@@ -784,25 +782,16 @@ impl Node {
let cost = candidate.link_cost();
// Compute distance: peer→dest if coords available, else us→peer
let dist = match dest_coords {
Some(dc) => self
.tree_state
.peer_coords(candidate.node_addr())
.map(|pc| pc.distance_to(dc))
.unwrap_or(usize::MAX),
None => self
.tree_state
.distance_to_peer(candidate.node_addr())
.unwrap_or(usize::MAX),
};
let dist = self
.tree_state
.peer_coords(candidate.node_addr())
.map(|pc| pc.distance_to(dest_coords))
.unwrap_or(usize::MAX);
// Self-distance check: when dest coords are available,
// only consider peers that are strictly closer than us
if let Some(my_dist) = my_distance {
if dist >= my_dist {
continue;
}
// Self-distance check: only consider peers strictly closer
// to the destination than we are (prevents routing loops)
if dist >= my_distance {
continue;
}
let dominated = match &best {
+101 -12
View File
@@ -54,6 +54,7 @@ fn test_routing_unknown_destination() {
fn test_routing_bloom_filter_hit() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
// Create two peers
let link_id1 = LinkId::new(1);
@@ -68,8 +69,27 @@ fn test_routing_bloom_filter_hit() {
node.add_connection(conn2).unwrap();
node.promote_connection(link_id2, id2, 2000).unwrap();
// Destination not directly connected
// Set up tree: we are root, both peers are our children
let peer1_coords = TreeCoordinate::from_addrs(vec![peer1_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(peer1_addr, my_addr, 1, 1000),
peer1_coords,
);
let peer2_coords = TreeCoordinate::from_addrs(vec![peer2_addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(peer2_addr, my_addr, 1, 1000),
peer2_coords,
);
// Destination not directly connected — placed under peer1 in the tree
let dest = make_node_addr(99);
let dest_coords =
TreeCoordinate::from_addrs(vec![dest, peer1_addr, my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
// Add dest to peer1's bloom filter only
let peer1 = node.get_peer_mut(&peer1_addr).unwrap();
@@ -77,7 +97,7 @@ fn test_routing_bloom_filter_hit() {
filter.insert(&dest);
peer1.update_filter(filter, 1, 3000);
// Should route through peer1 (bloom filter hit)
// Should route through peer1 (bloom filter hit, closer to dest)
let result = node.find_next_hop(&dest);
assert!(result.is_some());
assert_eq!(result.unwrap().node_addr(), &peer1_addr);
@@ -90,6 +110,7 @@ fn test_routing_bloom_filter_hit() {
fn test_routing_bloom_filter_multiple_hits_tiebreak() {
let mut node = make_node();
let transport_id = TransportId::new(1);
let my_addr = *node.node_addr();
// Create three peers
let mut peer_addrs = Vec::new();
@@ -102,7 +123,25 @@ fn test_routing_bloom_filter_multiple_hits_tiebreak() {
node.promote_connection(link_id, id, 2000).unwrap();
}
// Set up tree: we are root, all peers are our children (equidistant)
for &addr in &peer_addrs {
let coords = TreeCoordinate::from_addrs(vec![addr, my_addr]).unwrap();
node.tree_state_mut().update_peer(
ParentDeclaration::new(addr, my_addr, 1, 1000),
coords,
);
}
// Destination placed under the first peer (arbitrary — all peers are
// equidistant from dest since dest is 2 hops from root via any child)
let dest = make_node_addr(99);
let dest_coords =
TreeCoordinate::from_addrs(vec![dest, peer_addrs[0], my_addr]).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
node.coord_cache_mut().insert(dest, dest_coords, now_ms);
// Add dest to ALL peers' bloom filters
for &addr in &peer_addrs {
@@ -112,13 +151,13 @@ fn test_routing_bloom_filter_multiple_hits_tiebreak() {
peer.update_filter(filter, 1, 3000);
}
// All peers have equal link_cost (1.0) and no tree coords set,
// so tree distance is usize::MAX for all. Tie-break by smallest node_addr.
// All peers have equal link_cost (1.0). peer_addrs[0] is closest to dest
// (distance 1 vs distance 3 for the others). Self-distance check filters
// peers that aren't strictly closer than us (our distance = 2).
// peer_addrs[0] has distance 1 (passes), others have distance 3 (filtered).
let result = node.find_next_hop(&dest);
assert!(result.is_some());
let smallest_addr = peer_addrs.iter().min().unwrap();
assert_eq!(result.unwrap().node_addr(), smallest_addr);
assert_eq!(result.unwrap().node_addr(), &peer_addrs[0]);
}
// === Greedy tree routing ===
@@ -183,6 +222,42 @@ fn test_routing_tree_no_coords_in_cache() {
assert!(node.find_next_hop(&dest).is_none());
}
// === Bloom filter without coords → no route (loop prevention) ===
#[test]
fn test_routing_bloom_hit_without_coords_returns_none() {
let mut node = make_node();
let transport_id = TransportId::new(1);
// Create two peers
let link_id1 = LinkId::new(1);
let (conn1, id1) = make_completed_connection(&mut node, link_id1, transport_id, 1000);
let peer1_addr = *id1.node_addr();
node.add_connection(conn1).unwrap();
node.promote_connection(link_id1, id1, 2000).unwrap();
let link_id2 = LinkId::new(2);
let (conn2, id2) = make_completed_connection(&mut node, link_id2, transport_id, 1000);
let peer2_addr = *id2.node_addr();
node.add_connection(conn2).unwrap();
node.promote_connection(link_id2, id2, 2000).unwrap();
let dest = make_node_addr(99);
// Add dest to BOTH peers' bloom filters
for &addr in &[peer1_addr, peer2_addr] {
let peer = node.get_peer_mut(&addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
peer.update_filter(filter, 1, 3000);
}
// Bloom filter candidates exist, but dest coords are NOT cached.
// find_next_hop must return None to prevent routing loops.
// The caller should signal CoordsRequired back to the source.
assert!(node.find_next_hop(&dest).is_none());
}
// === Integration: converged network ===
#[tokio::test]
@@ -270,18 +345,31 @@ async fn test_routing_bloom_preferred_over_tree() {
drain_all_packets(&mut nodes, false).await;
// Create a destination beyond the network
// Create a destination beyond the network and cache its coords.
// Place dest as a child of peer2 in the converged tree so bloom
// filter routing selects peer2 (strictly closer to dest than us).
let dest = make_node_addr(99);
let peer2_addr = *nodes[2].node.node_addr();
let mut dest_path: Vec<NodeAddr> =
nodes[2].node.tree_state().my_coords().node_addrs().copied().collect();
dest_path.insert(0, dest);
let dest_coords = TreeCoordinate::from_addrs(dest_path).unwrap();
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0);
nodes[0]
.node
.coord_cache_mut()
.insert(dest, dest_coords, now_ms);
// Add dest to peer 2's bloom filter (from node 0's perspective)
let peer2_addr = *nodes[2].node.node_addr();
let peer2 = nodes[0].node.get_peer_mut(&peer2_addr).unwrap();
let mut filter = BloomFilter::new();
filter.insert(&dest);
peer2.update_filter(filter, 100, 50000);
// Even though we could use tree routing (if coords were cached),
// the bloom filter hit should be preferred.
// Bloom filter hit with cached coords should route via peer 2.
let hop = nodes[0].node.find_next_hop(&dest);
assert!(hop.is_some(), "Should route via bloom filter");
assert_eq!(
@@ -401,7 +489,8 @@ async fn test_routing_reachability_100_nodes() {
// Populate coord caches: every node learns every other node's coordinates.
// In production this happens via SessionSetup/LookupResponse; here we
// inject them directly so routing can make progress-based decisions.
// inject them directly. Bloom filter routing requires cached dest_coords
// for loop-free forwarding — without coords, find_next_hop returns None.
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
+27 -7
View File
@@ -244,25 +244,45 @@ impl Disconnect {
// Session Datagram (Link-Layer Encapsulation)
// ============================================================================
/// Encapsulated session-layer datagram for forwarding.
/// Encapsulated session-layer datagram for multi-hop forwarding.
///
/// This is a link-layer message that carries an opaque, end-to-end encrypted
/// session-layer payload. Intermediate nodes route based on the destination
/// address but cannot decrypt the payload.
/// This is a link-layer message (type 0x40) that carries session-layer
/// payloads through the mesh. The envelope provides source and destination
/// addressing that transit routers use for forwarding decisions and error
/// routing.
///
/// ## Wire Format (34-byte fixed header)
///
/// | Offset | Field | Size | Description |
/// |--------|-----------|----------|--------------------------------|
/// | 0 | msg_type | 1 byte | 0x40 |
/// | 1 | src_addr | 16 bytes | Source node_addr |
/// | 17 | dest_addr | 16 bytes | Destination node_addr |
/// | 33 | hop_limit | 1 byte | Decremented each hop |
/// | 34 | payload | variable | Session-layer message |
///
/// The payload is either end-to-end encrypted (SessionSetup, SessionAck,
/// DataPacket) or plaintext link-layer error signals (CoordsRequired,
/// PathBroken) generated by transit routers.
#[derive(Clone, Debug)]
pub struct SessionDatagram {
/// Source node address (originator of this datagram).
/// For data traffic: the source endpoint.
/// For error signals: the transit router that generated the error.
pub src_addr: NodeAddr,
/// Destination node address (for routing decisions).
pub dest_addr: NodeAddr,
/// Hop limit (decremented at each hop).
/// Hop limit (decremented at each hop, dropped at zero).
pub hop_limit: u8,
/// Encrypted session-layer payload (opaque to intermediate nodes).
/// Session-layer payload (e2e encrypted or plaintext error signal).
pub payload: Vec<u8>,
}
impl SessionDatagram {
/// Create a new session datagram.
pub fn new(dest_addr: NodeAddr, payload: Vec<u8>) -> Self {
pub fn new(src_addr: NodeAddr, dest_addr: NodeAddr, payload: Vec<u8>) -> Self {
Self {
src_addr,
dest_addr,
hop_limit: 64,
payload,
+54 -115
View File
@@ -8,11 +8,13 @@ use std::fmt;
// Session Layer Message Types
// ============================================================================
/// Session-layer message type identifiers.
/// SessionDatagram payload message type identifiers.
///
/// These messages are exchanged end-to-end between FIPS nodes, encrypted
/// with session keys that intermediate nodes cannot read. They are carried
/// as payloads inside `LinkMessageType::SessionDatagram`.
/// These messages are carried as payloads inside `SessionDatagram` (link
/// message type 0x40). Session-layer messages (SessionSetup, SessionAck,
/// DataPacket) are end-to-end encrypted with session keys. Error signals
/// (CoordsRequired, PathBroken) are plaintext link-layer messages generated
/// by transit routers that cannot establish e2e sessions with the source.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum SessionMessageType {
@@ -26,10 +28,10 @@ pub enum SessionMessageType {
/// Encrypted IPv6 datagram payload.
DataPacket = 0x10,
// Errors (0x20-0x2F)
/// Router cache miss - needs coordinates.
// Link-layer error signals (0x20-0x2F) — plaintext, from transit routers
/// Router cache miss needs coordinates (link-layer error signal).
CoordsRequired = 0x20,
/// Routing failure (local minimum or unreachable).
/// Routing failure local minimum or unreachable (link-layer error signal).
PathBroken = 0x21,
}
@@ -123,14 +125,12 @@ impl SessionFlags {
/// Session setup to establish cached coordinate state.
///
/// Sent before data packets to warm router caches with coordinate
/// information. Routers along the path cache the mappings.
/// Carried inside a SessionDatagram envelope which provides src_addr and
/// dest_addr. The SessionSetup payload contains only coordinates and the
/// Noise handshake data needed for route cache warming and session
/// establishment.
#[derive(Clone, Debug)]
pub struct SessionSetup {
/// Source node address.
pub src_addr: NodeAddr,
/// Destination node address.
pub dest_addr: NodeAddr,
/// Source coordinates (for return path caching).
pub src_coords: TreeCoordinate,
/// Destination coordinates (for forward routing).
@@ -141,15 +141,8 @@ pub struct SessionSetup {
impl SessionSetup {
/// Create a new session setup message.
pub fn new(
src_addr: NodeAddr,
dest_addr: NodeAddr,
src_coords: TreeCoordinate,
dest_coords: TreeCoordinate,
) -> Self {
pub fn new(src_coords: TreeCoordinate, dest_coords: TreeCoordinate) -> Self {
Self {
src_addr,
dest_addr,
src_coords,
dest_coords,
flags: SessionFlags::new(),
@@ -169,25 +162,19 @@ impl SessionSetup {
/// Session acknowledgement.
///
/// Sent in response to SessionSetup when request_ack is set.
/// Carried inside a SessionDatagram envelope which provides src_addr and
/// dest_addr. The SessionAck payload contains the acknowledger's coordinates
/// for route cache warming.
#[derive(Clone, Debug)]
pub struct SessionAck {
/// Source node address (the acknowledger).
pub src_addr: NodeAddr,
/// Destination node address (original session initiator).
pub dest_addr: NodeAddr,
/// Acknowledger's coordinates.
pub src_coords: TreeCoordinate,
}
impl SessionAck {
/// Create a new session acknowledgement.
pub fn new(src_addr: NodeAddr, dest_addr: NodeAddr, src_coords: TreeCoordinate) -> Self {
Self {
src_addr,
dest_addr,
src_coords,
}
pub fn new(src_coords: TreeCoordinate) -> Self {
Self { src_coords }
}
}
@@ -252,73 +239,44 @@ impl DataFlags {
}
}
/// Data packet header size in bytes (excluding payload).
/// flags(1) + hop_limit(1) + payload_length(2) + src_addr(16) + dest_addr(16) = 36
pub const DATA_HEADER_SIZE: usize = 36;
/// DataPacket header size in bytes (excluding payload).
/// msg_type(1) + flags(1) + payload_length(2) = 4
/// (Addressing and hop_limit are in the SessionDatagram envelope.)
pub const DATA_HEADER_SIZE: usize = 4;
/// Minimal data packet with addresses only (no coordinates).
/// Encrypted application data carried inside a SessionDatagram.
///
/// The 36-byte header contains:
/// - flags (1 byte)
/// - hop_limit (1 byte)
/// The 4-byte header contains:
/// - msg_type (1 byte): 0x10
/// - flags (1 byte): COORDS_PRESENT, etc.
/// - payload_length (2 bytes)
/// - src_addr (16 bytes)
/// - dest_addr (16 bytes)
///
/// Routers use cached coordinates for routing decisions.
/// Addressing (src_addr, dest_addr) and hop_limit are provided by the
/// enclosing SessionDatagram envelope. The total on-wire overhead for a
/// minimal data packet is 34 (SessionDatagram) + 4 (DataPacket) = 38 bytes.
#[derive(Clone, Debug)]
pub struct DataPacket {
/// Packet flags.
pub flags: DataFlags,
/// Hop limit (TTL).
pub hop_limit: u8,
/// Source node address.
pub src_addr: NodeAddr,
/// Destination node address.
pub dest_addr: NodeAddr,
/// Payload data.
/// Payload data (end-to-end encrypted application data).
pub payload: Vec<u8>,
}
impl DataPacket {
/// Create a new data packet.
pub fn new(src_addr: NodeAddr, dest_addr: NodeAddr, payload: Vec<u8>) -> Self {
pub fn new(payload: Vec<u8>) -> Self {
Self {
flags: DataFlags::new(),
hop_limit: 64,
src_addr,
dest_addr,
payload,
}
}
/// Set the hop limit.
pub fn with_hop_limit(mut self, hop_limit: u8) -> Self {
self.hop_limit = hop_limit;
self
}
/// Set the flags.
pub fn with_flags(mut self, flags: DataFlags) -> Self {
self.flags = flags;
self
}
/// Decrement hop limit, returning false if exhausted.
pub fn decrement_hop_limit(&mut self) -> bool {
if self.hop_limit > 0 {
self.hop_limit -= 1;
true
} else {
false
}
}
/// Check if the packet can be forwarded.
pub fn can_forward(&self) -> bool {
self.hop_limit > 0
}
/// Get the payload length.
pub fn payload_len(&self) -> usize {
self.payload.len()
@@ -339,10 +297,14 @@ impl DataPacket {
// Error Messages
// ============================================================================
/// Error indicating router cache miss - needs coordinates.
/// Link-layer error signal indicating router cache miss.
///
/// Sent back to the source when a router doesn't have cached
/// coordinates for the destination.
/// Generated by a transit router when it cannot forward a SessionDatagram
/// due to missing cached coordinates for the destination. Carried inside
/// a new SessionDatagram addressed back to the original source
/// (src_addr=reporter, dest_addr=original_source). Plaintext — not
/// end-to-end encrypted, since the transit router has no session with
/// the source.
#[derive(Clone, Debug)]
pub struct CoordsRequired {
/// Destination that couldn't be routed.
@@ -360,11 +322,12 @@ impl CoordsRequired {
/// Error indicating routing failure (local minimum or unreachable).
///
/// Sent back to the source when greedy routing fails.
/// Carried inside a SessionDatagram addressed back to the original source.
/// The reporting router creates a new SessionDatagram with src_addr=reporter
/// and dest_addr=original_source, so the `original_src` field from the old
/// design is no longer needed — it's the SessionDatagram's dest_addr.
#[derive(Clone, Debug)]
pub struct PathBroken {
/// Original source of the failed packet.
pub original_src: NodeAddr,
/// Destination that couldn't be reached.
pub dest_addr: NodeAddr,
/// Node that detected the failure.
@@ -375,9 +338,8 @@ pub struct PathBroken {
impl PathBroken {
/// Create a new PathBroken error.
pub fn new(original_src: NodeAddr, dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
Self {
original_src,
dest_addr,
reporter,
last_known_coords: None,
@@ -457,39 +419,19 @@ mod tests {
#[test]
fn test_data_packet_size() {
let packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![0u8; 100]);
let packet = DataPacket::new(vec![0u8; 100]);
// 36 byte header + 100 byte payload
assert_eq!(packet.total_size(), 136);
assert_eq!(packet.header_size(), 36);
// 4 byte header + 100 byte payload
assert_eq!(packet.total_size(), 104);
assert_eq!(packet.header_size(), 4);
assert_eq!(packet.payload_len(), 100);
}
#[test]
fn test_data_packet_hop_limit() {
let mut packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![]);
packet.hop_limit = 2;
assert!(packet.can_forward());
assert!(packet.decrement_hop_limit());
assert_eq!(packet.hop_limit, 1);
assert!(packet.decrement_hop_limit());
assert_eq!(packet.hop_limit, 0);
assert!(!packet.can_forward());
assert!(!packet.decrement_hop_limit());
assert_eq!(packet.hop_limit, 0);
}
#[test]
fn test_data_packet_builder() {
let packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![1, 2, 3])
.with_hop_limit(32)
let packet = DataPacket::new(vec![1, 2, 3])
.with_flags(DataFlags::from_byte(0x80));
assert_eq!(packet.hop_limit, 32);
assert_eq!(packet.flags.to_byte(), 0x80);
}
@@ -525,13 +467,8 @@ mod tests {
#[test]
fn test_session_setup() {
let setup = SessionSetup::new(
make_node_addr(1),
make_node_addr(2),
make_coords(&[1, 0]),
make_coords(&[2, 0]),
)
.with_flags(SessionFlags::new().with_ack());
let setup = SessionSetup::new(make_coords(&[1, 0]), make_coords(&[2, 0]))
.with_flags(SessionFlags::new().with_ack());
assert!(setup.flags.request_ack);
assert!(!setup.flags.bidirectional);
@@ -551,9 +488,11 @@ mod tests {
#[test]
fn test_path_broken() {
let err = PathBroken::new(make_node_addr(1), make_node_addr(2), make_node_addr(3))
let err = PathBroken::new(make_node_addr(2), make_node_addr(3))
.with_last_coords(make_coords(&[2, 0]));
assert_eq!(err.dest_addr, make_node_addr(2));
assert_eq!(err.reporter, make_node_addr(3));
assert!(err.last_known_coords.is_some());
}
}