tf_core_no_std/
nonce_cache.rs1#[cfg(not(feature = "alloc"))]
18use heapless::String as HString;
19
20#[cfg(feature = "alloc")]
21use alloc::collections::VecDeque;
22#[cfg(feature = "alloc")]
23use alloc::string::String;
24
25pub const PACKET_ID_CAP: usize = 64;
28pub const TIMESTAMP_CAP: usize = 32;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum RejectReason {
34 Replay,
35 Expired,
36 FutureDated,
37 IdTooLarge,
39 CacheFull,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum ReceiverDecision {
46 Accept,
47 Reject(RejectReason),
48}
49
50#[cfg(feature = "alloc")]
55#[derive(Debug)]
56pub struct PacketReceiver {
57 seen: VecDeque<(String, String)>,
58 capacity: usize,
59}
60
61#[cfg(feature = "alloc")]
62impl PacketReceiver {
63 pub fn new(capacity: usize) -> Self {
64 let cap = capacity.max(1);
65 PacketReceiver {
66 seen: VecDeque::with_capacity(cap),
67 capacity: cap,
68 }
69 }
70
71 pub fn observe(
72 &mut self,
73 packet_id: &str,
74 expires_at: Option<&str>,
75 now: &str,
76 ) -> ReceiverDecision {
77 if let Some(exp) = expires_at {
78 if exp < now {
79 return ReceiverDecision::Reject(RejectReason::Expired);
80 }
81 }
82 if self.seen.iter().any(|(id, _)| id == packet_id) {
83 return ReceiverDecision::Reject(RejectReason::Replay);
84 }
85 if self.seen.len() >= self.capacity {
86 self.seen.pop_front();
87 }
88 self.seen
89 .push_back((packet_id.into(), expires_at.unwrap_or("").into()));
90 ReceiverDecision::Accept
91 }
92
93 pub fn len(&self) -> usize {
94 self.seen.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
98 self.seen.is_empty()
99 }
100}
101
102#[cfg(not(feature = "alloc"))]
107#[derive(Debug)]
108pub struct PacketReceiver<const N: usize> {
109 seen: heapless::Deque<HString<PACKET_ID_CAP>, N>,
110}
111
112#[cfg(not(feature = "alloc"))]
113impl<const N: usize> PacketReceiver<N> {
114 pub fn new() -> Self {
115 PacketReceiver {
116 seen: heapless::Deque::new(),
117 }
118 }
119
120 pub fn observe(
121 &mut self,
122 packet_id: &str,
123 expires_at: Option<&str>,
124 now: &str,
125 ) -> ReceiverDecision {
126 if let Some(exp) = expires_at {
127 if exp < now {
128 return ReceiverDecision::Reject(RejectReason::Expired);
129 }
130 }
131 let mut hid: HString<PACKET_ID_CAP> = HString::new();
132 if hid.push_str(packet_id).is_err() {
133 return ReceiverDecision::Reject(RejectReason::IdTooLarge);
134 }
135 if self.seen.iter().any(|s| s == &hid) {
136 return ReceiverDecision::Reject(RejectReason::Replay);
137 }
138 if self.seen.is_full() {
139 self.seen.pop_front();
140 }
141 if self.seen.push_back(hid).is_err() {
142 return ReceiverDecision::Reject(RejectReason::CacheFull);
143 }
144 ReceiverDecision::Accept
145 }
146
147 pub fn len(&self) -> usize {
148 self.seen.len()
149 }
150
151 pub fn is_empty(&self) -> bool {
152 self.seen.is_empty()
153 }
154}
155
156#[cfg(not(feature = "alloc"))]
157impl<const N: usize> Default for PacketReceiver<N> {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[cfg(feature = "alloc")]
168 #[test]
169 fn replay_rejected_alloc() {
170 let mut rx = PacketReceiver::new(8);
171 let now = "2026-04-25T00:00:00Z";
172 assert_eq!(
173 rx.observe("pkt-001", Some("2099-01-01T00:00:00Z"), now),
174 ReceiverDecision::Accept
175 );
176 assert_eq!(
177 rx.observe("pkt-001", Some("2099-01-01T00:00:00Z"), now),
178 ReceiverDecision::Reject(RejectReason::Replay)
179 );
180 assert_eq!(rx.len(), 1);
181 }
182
183 #[cfg(feature = "alloc")]
184 #[test]
185 fn expired_rejected_alloc() {
186 let mut rx = PacketReceiver::new(8);
187 assert_eq!(
188 rx.observe(
189 "pkt-1",
190 Some("2026-04-01T00:00:00Z"),
191 "2026-04-25T00:00:00Z"
192 ),
193 ReceiverDecision::Reject(RejectReason::Expired)
194 );
195 }
196
197 #[cfg(feature = "alloc")]
198 #[test]
199 fn evicts_oldest_when_full_alloc() {
200 let mut rx = PacketReceiver::new(2);
201 rx.observe("a", None, "2026-04-25T00:00:00Z");
202 rx.observe("b", None, "2026-04-25T00:00:00Z");
203 rx.observe("c", None, "2026-04-25T00:00:00Z");
204 assert_eq!(
206 rx.observe("a", None, "2026-04-25T00:00:00Z"),
207 ReceiverDecision::Accept
208 );
209 }
210
211 #[cfg(not(feature = "alloc"))]
212 #[test]
213 fn replay_rejected_no_alloc() {
214 let mut rx: PacketReceiver<8> = PacketReceiver::new();
215 let now = "2026-04-25T00:00:00Z";
216 assert_eq!(
217 rx.observe("pkt-001", Some("2099-01-01T00:00:00Z"), now),
218 ReceiverDecision::Accept
219 );
220 assert_eq!(
221 rx.observe("pkt-001", Some("2099-01-01T00:00:00Z"), now),
222 ReceiverDecision::Reject(RejectReason::Replay)
223 );
224 }
225}