umsh_mac/
forward_id.rs

1//! Routing identity of a packet as seen by a forwarder.
2//!
3//! This is the identity that duplicate suppression and forwarding
4//! confirmation both key on, and it is intentionally not the
5//! destination's logical delivery identity:
6//!
7//! - delivery identity is governed by replay windows / frame counters at
8//!   the destination
9//! - routing identity must remain stable across repeater rewrites of
10//!   dynamic routing metadata
11//! - forwarding-confirmation identity matches routing identity so a node
12//!   can recognize "the same packet, forwarded onward"
13//!
14//! It lives outside the coordinator because it is an interop surface,
15//! not private state: any forwarder that participates in the same mesh —
16//! including an [internet bridge], whose two radios must agree on which
17//! frames are the same packet — has to compute it bit-for-bit
18//! identically.
19//!
20//! [internet bridge]: https://darconeous.github.io/umsh/docs/protocol/internet-bridging.html
21
22use umsh_core::{OptionNumber, PacketHeader, PacketType, ParsedOptions};
23
24use crate::cache::DupCacheKey;
25
26/// Routing identity of an already-parsed frame.
27///
28/// `None` when the frame carries a MIC this cache cannot key on (absent,
29/// or wider than the 16 bytes the key holds), or when its options do not
30/// parse.
31pub fn forwarding_dup_key_parsed(header: &PacketHeader, frame: &[u8]) -> Option<DupCacheKey> {
32    if !header.packet_type().is_secure() {
33        return Some(DupCacheKey::Hash32(normalized_routable_hash32(
34            header, frame,
35        )));
36    }
37    let options = ParsedOptions::extract(frame, header.options_range.clone()).ok()?;
38    let mic = frame.get(header.mic_range.clone())?;
39    if mic.is_empty() || mic.len() > 16 {
40        return None;
41    }
42    let mut bytes = [0u8; 16];
43    bytes[..mic.len()].copy_from_slice(mic);
44    Some(DupCacheKey::Mic {
45        bytes,
46        len: mic.len() as u8,
47        route_retry: options.route_retry,
48    })
49}
50
51/// Routing identity of a frame, parsing its header first.
52///
53/// `None` when the frame does not parse, or for the reasons
54/// [`forwarding_dup_key_parsed`] gives.
55pub fn forwarding_dup_key(frame: &[u8]) -> Option<DupCacheKey> {
56    let header = PacketHeader::parse(frame).ok()?;
57    forwarding_dup_key_parsed(&header, frame)
58}
59
60/// FNV-1a over the fields a repeater may not rewrite, for packets that
61/// carry no MIC to key on.
62fn normalized_routable_hash32(header: &PacketHeader, frame: &[u8]) -> u32 {
63    let mut hash = 0x811C_9DC5u32;
64
65    hash_u8(&mut hash, header.packet_type() as u8);
66    hash_u8(&mut hash, header.fcf.full_source() as u8);
67
68    if !header.options_range.is_empty() {
69        for entry in umsh_core::iter_options(frame, header.options_range.clone()) {
70            let Ok((number, value)) = entry else {
71                continue;
72            };
73            let option = OptionNumber::from(number);
74            if option.is_dynamic() {
75                continue;
76            }
77            hash_u16(&mut hash, number);
78            hash_u16(&mut hash, value.len() as u16);
79            hash_bytes(&mut hash, value);
80        }
81    }
82
83    match header.packet_type() {
84        PacketType::Broadcast => {
85            match header.source {
86                umsh_core::SourceAddrRef::Hint(hint) => hash_bytes(&mut hash, &hint.0),
87                umsh_core::SourceAddrRef::FullKeyAt { offset } => {
88                    if let Some(key) = frame.get(offset..offset + 32) {
89                        hash_bytes(&mut hash, key);
90                    }
91                }
92                umsh_core::SourceAddrRef::Encrypted { offset, len } => {
93                    if let Some(src) = frame.get(offset..offset + len) {
94                        hash_bytes(&mut hash, src);
95                    }
96                }
97                umsh_core::SourceAddrRef::None => {}
98            }
99            if let Some(payload) = frame.get(header.body_range.clone()) {
100                hash_bytes(&mut hash, payload);
101            }
102        }
103        PacketType::MacAck => {
104            // The ack trailer (`ack_mic || ack_tag`) uniquely identifies
105            // the acknowledged exchange; the ack carries no other
106            // distinguishing fields.
107            if let Some(trailer) = frame.get(header.mic_range.clone()) {
108                hash_bytes(&mut hash, trailer);
109            }
110        }
111        _ => {
112            if let Some(bytes) = frame.get(header.body_range.clone()) {
113                hash_bytes(&mut hash, bytes);
114            }
115        }
116    }
117    hash
118}
119
120fn hash_u8(hash: &mut u32, value: u8) {
121    *hash ^= u32::from(value);
122    *hash = hash.wrapping_mul(0x0100_0193);
123}
124
125fn hash_u16(hash: &mut u32, value: u16) {
126    hash_bytes(hash, &value.to_be_bytes());
127}
128
129fn hash_bytes(hash: &mut u32, bytes: &[u8]) {
130    for byte in bytes {
131        hash_u8(hash, *byte);
132    }
133}