devices/usb/backend/fido_backend/
fido_transaction.rs1use std::collections::VecDeque;
6use std::time::Instant;
7
8use base::error;
9use base::warn;
10
11cfg_if::cfg_if! {
12 if #[cfg(test)] {
13 use base::FakeClock as Clock;
14 } else {
15 use base::Clock;
16 }
17}
18
19use crate::usb::backend::fido_backend::constants;
20use crate::usb::backend::fido_backend::error::Result;
21use crate::usb::backend::fido_backend::poll_thread::PollTimer;
22
23#[derive(Clone, Copy, Debug)]
25pub struct FidoTransaction {
26 pub cid: [u8; constants::CID_SIZE],
28 pub resp_bcnt: u16,
30 pub resp_size: u16,
32 pub nonce: [u8; constants::NONCE_SIZE],
35 submission_time: Instant,
37}
38
39pub struct TransactionManager {
42 transactions: VecDeque<FidoTransaction>,
44 last_transaction_time: Instant,
46 pub transaction_timer: PollTimer,
48 clock: Clock,
50}
51
52impl TransactionManager {
53 pub fn new() -> Result<TransactionManager> {
54 let timer = PollTimer::new(
55 "transaction timer".to_string(),
56 std::time::Duration::from_millis(constants::TRANSACTION_TIMEOUT_MILLIS / 10),
59 )?;
60 let clock = Clock::new();
61 Ok(TransactionManager {
62 transactions: VecDeque::new(),
63 last_transaction_time: clock.now(),
64 clock,
65 transaction_timer: timer,
66 })
67 }
68
69 pub fn pop_transaction(&mut self) -> Option<FidoTransaction> {
70 self.transactions.pop_front()
71 }
72
73 pub fn close_transaction(&mut self, cid: [u8; constants::CID_SIZE]) -> bool {
77 match self.transactions.iter().position(|t| t.cid == cid) {
78 Some(index) => {
79 self.transactions.remove(index);
80 }
81 None => {
82 warn!(
83 "Tried to close a transaction that does not exist. Silently dropping request."
84 );
85 }
86 };
87
88 if self.transactions.is_empty() {
89 return true;
90 }
91 false
92 }
93
94 pub fn start_transaction(
97 &mut self,
98 cid: [u8; constants::CID_SIZE],
99 nonce: [u8; constants::NONCE_SIZE],
100 ) -> bool {
101 let transaction = FidoTransaction {
102 cid,
103 resp_bcnt: 0,
104 resp_size: 0,
105 nonce,
106 submission_time: self.clock.now(),
107 };
108
109 if self.transactions.len() >= constants::MAX_TRANSACTIONS {
111 let _ = self.pop_transaction();
112 }
113 self.last_transaction_time = transaction.submission_time;
114 self.transactions.push_back(transaction);
115 if self.transactions.len() == 1 {
116 return true;
117 }
118 false
119 }
120
121 pub fn expire_transactions(&mut self) -> bool {
125 if self.transactions.is_empty() {
127 return true;
128 }
129
130 if self
133 .clock
134 .now()
135 .duration_since(self.last_transaction_time)
136 .as_millis()
137 >= constants::TRANSACTION_TIMEOUT_MILLIS.into()
138 {
139 self.reset();
140 return true;
141 }
142 false
143 }
144
145 pub fn reset(&mut self) {
147 self.transactions = VecDeque::new();
148 self.last_transaction_time = self.clock.now();
149 if let Err(e) = self.transaction_timer.clear() {
150 error!(
151 "Unable to clear transaction manager timer, silently failing. {}",
152 e
153 );
154 }
155 }
156
157 pub fn update_transaction(
159 &mut self,
160 cid: [u8; constants::CID_SIZE],
161 resp_bcnt: u16,
162 resp_size: u16,
163 ) {
164 let index = match self
165 .transactions
166 .iter()
167 .position(|t: &FidoTransaction| t.cid == cid)
168 {
169 Some(index) => index,
170 None => {
171 warn!(
172 "No u2f transaction found with (cid {:?}) in the list. Skipping.",
173 cid
174 );
175 return;
176 }
177 };
178 match self.transactions.get_mut(index) {
179 Some(t_ref) => {
180 t_ref.resp_bcnt = resp_bcnt;
181 t_ref.resp_size = resp_size;
182 }
183 None => {
184 error!(
185 "A u2f transaction was found at index {} but now is gone. This is a bug.",
186 index
187 );
188 }
189 };
190 }
191
192 pub fn get_transaction(&mut self, cid: [u8; constants::CID_SIZE]) -> Option<FidoTransaction> {
194 let index = match self
195 .transactions
196 .iter()
197 .position(|t: &FidoTransaction| t.cid == cid)
198 {
199 Some(index) => index,
200 None => {
201 return None;
202 }
203 };
204 match self.transactions.get(index) {
205 Some(t_ref) => Some(*t_ref),
206 None => {
207 error!(
208 "A u2f transaction was found at index {} but now is gone. This is a bug.",
209 index
210 );
211 None
212 }
213 }
214 }
215
216 pub fn get_transaction_from_nonce(
218 &mut self,
219 nonce: [u8; constants::NONCE_SIZE],
220 ) -> Option<FidoTransaction> {
221 let index =
222 match self.transactions.iter().position(|t: &FidoTransaction| {
223 t.cid == constants::BROADCAST_CID && t.nonce == nonce
224 }) {
225 Some(index) => index,
226 None => {
227 return None;
228 }
229 };
230 match self.transactions.get(index) {
231 Some(t_ref) => Some(*t_ref),
232 None => {
233 error!(
234 "A u2f transaction was found at index {} but now is gone. This is a bug.",
235 index
236 );
237 None
238 }
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245
246 use crate::usb::backend::fido_backend::constants::EMPTY_NONCE;
247 use crate::usb::backend::fido_backend::constants::MAX_TRANSACTIONS;
248 use crate::usb::backend::fido_backend::constants::TRANSACTION_TIMEOUT_MILLIS;
249 use crate::usb::backend::fido_backend::fido_transaction::TransactionManager;
250
251 #[test]
252 fn test_start_transaction() {
253 let mut manager = TransactionManager::new().unwrap();
254 let cid = [0x01, 0x02, 0x03, 0x04];
255
256 assert!(manager.start_transaction(cid, EMPTY_NONCE));
257 assert_eq!(manager.transactions.len(), 1);
258 assert_eq!(manager.last_transaction_time, manager.clock.now());
259
260 manager.clock.add_ns(100);
261
262 assert!(!manager.start_transaction(cid, EMPTY_NONCE));
263 assert_eq!(manager.transactions.len(), 2);
264 assert_eq!(manager.last_transaction_time, manager.clock.now());
265
266 manager.reset();
267
268 for _ in 0..MAX_TRANSACTIONS + 1 {
271 manager.start_transaction(cid, EMPTY_NONCE);
272 }
273
274 assert_eq!(manager.transactions.len(), MAX_TRANSACTIONS);
275 }
276
277 #[test]
278 fn test_pop_transaction() {
279 let mut manager = TransactionManager::new().unwrap();
280 let cid1 = [0x01, 0x02, 0x03, 0x04];
281 let cid2 = [0x05, 0x06, 0x07, 0x08];
282
283 manager.start_transaction(cid1, EMPTY_NONCE);
284 manager.start_transaction(cid2, EMPTY_NONCE);
285
286 let popped_transaction = manager.pop_transaction().unwrap();
287
288 assert_eq!(popped_transaction.cid, cid1);
289 }
290
291 #[test]
292 fn test_close_transaction() {
293 let mut manager = TransactionManager::new().unwrap();
294 let cid1 = [0x01, 0x02, 0x03, 0x04];
295 let cid2 = [0x05, 0x06, 0x07, 0x08];
296
297 manager.start_transaction(cid1, EMPTY_NONCE);
298 manager.start_transaction(cid2, EMPTY_NONCE);
299
300 assert!(!manager.close_transaction(cid2));
301 assert!(!manager.close_transaction(cid2));
304 assert_eq!(manager.transactions.len(), 1);
305 assert!(manager.close_transaction(cid1));
306 }
307
308 #[test]
309 fn test_update_transaction() {
310 let mut manager = TransactionManager::new().unwrap();
311 let cid = [0x01, 0x02, 0x03, 0x04];
312 let bcnt = 17;
313 let size = 56;
314
315 manager.start_transaction(cid, EMPTY_NONCE);
316 manager.update_transaction(cid, bcnt, size);
317
318 let transaction = manager.get_transaction(cid).unwrap();
319
320 assert_eq!(transaction.resp_bcnt, bcnt);
321 assert_eq!(transaction.resp_size, size);
322 }
323
324 #[test]
325 fn test_expire_transactions() {
326 let mut manager = TransactionManager::new().unwrap();
327 let cid = [0x01, 0x02, 0x03, 0x04];
328
329 assert!(manager.expire_transactions());
331
332 manager.start_transaction(cid, EMPTY_NONCE);
333 assert!(!manager.expire_transactions());
334
335 manager
337 .clock
338 .add_ns(TRANSACTION_TIMEOUT_MILLIS * 1000000 + 1);
339 assert!(manager.expire_transactions());
340 assert_eq!(manager.transactions.len(), 0);
341 }
342}