e2e_protection/
e2e.rs

1//! Core E2E traits and the session wrapper.
2
3/// Errors that may happen during protect/check/unwrap.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum E2eError {
6    /// Payload or frame length is out of the profile’s constraints.
7    Length,
8    /// CRC (integrity) verification failed.
9    Crc,
10    /// Data-ID (or Data-ID-List based mapping) check failed.
11    DataId,
12    /// Counter policy violation (gap, wrong step, illegal value).
13    Counter,
14    /// Feature or operation is not supported by the active profile.
15    Unsupported,
16}
17
18/// Minimal report returned by `E2eProfile::check`.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct CheckInfo {
21    /// Extracted counter (zero-extended).
22    pub counter: u32,
23    /// True if profile-level checks (CRC, DataId, basic length) passed.
24    pub ok: bool,
25}
26
27/// How counters should evolve across a session.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum CounterPolicy {
30    /// Monotonic with rollover of `bits` width and fixed `step` (usually 1).
31    Rollover { bits: u8, step: u32 },
32    /// No policy (accept any counter).
33    None,
34}
35impl CounterPolicy {
36    /// Compute the next counter to use for transmission given the previous one.
37    pub fn next(self, prev: Option<u32>) -> u32 {
38        match self {
39            CounterPolicy::None => prev.unwrap_or(0),
40            CounterPolicy::Rollover { bits, step } => {
41                if bits == 32 {
42                    // full 32-bit rollover
43                    prev.unwrap_or(u32::MAX).wrapping_add(step)
44                } else {
45                    let m = (1u32 << bits) - 1;
46                    let p = prev.unwrap_or(m);
47                    (p + step) & m
48                }
49            }
50        }
51    }
52    /// Validate whether `next` is the correct successor of `prev`.
53    pub fn ok(self, prev: Option<u32>, next: u32) -> bool {
54        match self {
55            CounterPolicy::None => true,
56            CounterPolicy::Rollover { bits, step } => {
57                if bits == 32 {
58                    prev.map(|p| p.wrapping_add(step) == next).unwrap_or(true)
59                } else {
60                    let m = (1u32 << bits) - 1;
61                    prev.map(|p| ((p + step) & m) == (next & m)).unwrap_or(true)
62                }
63            }
64        }
65    }
66}
67
68/// A single E2E profile.
69/// Implementations encapsulate packing, CRC calculation, and validation.
70pub trait E2eProfile {
71    /// Create a protected frame for the given `payload` and `counter`.
72    fn protect(&self, payload: &[u8], counter: u32) -> Result<Vec<u8>, E2eError>;
73
74    /// Validate a received `frame`, returning parsed `CheckInfo` if valid.
75    fn check(&self, frame: &[u8]) -> Result<CheckInfo, E2eError>;
76
77    /// Return the original payload length (for zero-copy `unwrap`) if derivable.
78    fn payload_len(&self, frame: &[u8]) -> Option<usize>;
79}
80
81/// High-level session object that holds a profile and a counter policy.
82pub struct E2eSession<P: E2eProfile> {
83    p: P,
84    pol: CounterPolicy,
85    last: Option<u32>,
86}
87impl<P: E2eProfile> E2eSession<P> {
88    /// Create a new session with a profile and a counter policy.
89    pub fn new(p: P, pol: CounterPolicy) -> Self {
90        Self { p, pol, last: None }
91    }
92
93    /// Wrap `payload` with the next session counter according to the policy.
94    pub fn wrap(&mut self, payload: &[u8]) -> Result<Vec<u8>, E2eError> {
95        let c = self.pol.next(self.last);
96        let f = self.p.protect(payload, c)?;
97        self.last = Some(c);
98        Ok(f)
99    }
100
101    /// Unwrap a `frame`: profile validation + policy check → return payload slice.
102    pub fn unwrap<'a>(&mut self, frame: &'a [u8]) -> Result<&'a [u8], E2eError> {
103        let info = self.p.check(frame)?;
104        if !self.pol.ok(self.last, info.counter) {
105            return Err(E2eError::Counter);
106        }
107        self.last = Some(info.counter);
108        let n = self.p.payload_len(frame).ok_or(E2eError::Unsupported)?;
109        Ok(&frame[..n])
110    }
111
112    /// The last successfully transmitted/accepted counter (if any).
113    pub fn last_counter(&self) -> Option<u32> { self.last }
114}