nixfleet_control_plane/db/
allowed_nonces.rs1use std::collections::HashMap;
10
11use chrono::{DateTime, Utc};
12use nixfleet_proto::{BootstrapNonceEntry, BootstrapNonces};
13
14#[derive(Debug, Default, Clone)]
16pub struct AllowedNoncesView {
17 by_nonce: HashMap<String, BootstrapNonceEntry>,
19}
20
21impl AllowedNoncesView {
22 pub fn from_artifact(artifact: BootstrapNonces) -> Self {
23 let by_nonce = artifact
24 .bootstrap_nonces
25 .into_iter()
26 .map(|e| (e.nonce.clone(), e))
27 .collect();
28 Self { by_nonce }
29 }
30
31 pub fn lookup(&self, nonce: &str) -> Option<&BootstrapNonceEntry> {
32 self.by_nonce.get(nonce)
33 }
34
35 pub fn len(&self) -> usize {
36 self.by_nonce.len()
37 }
38
39 pub fn is_empty(&self) -> bool {
40 self.by_nonce.is_empty()
41 }
42
43 pub fn entry_is_live(entry: &BootstrapNonceEntry, now: DateTime<Utc>) -> bool {
47 entry.expires_at >= now
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use nixfleet_proto::Meta;
55
56 fn meta_v1() -> Meta {
57 Meta {
58 schema_version: 1,
59 signed_at: Some("2026-05-13T10:00:00Z".parse().unwrap()),
60 ci_commit: None,
61 signature_algorithm: Some("ecdsa-p256".into()),
62 }
63 }
64
65 #[test]
66 fn lookup_finds_declared_nonce() {
67 let view = AllowedNoncesView::from_artifact(BootstrapNonces {
68 schema_version: 1,
69 bootstrap_nonces: vec![BootstrapNonceEntry {
70 nonce: "abc".into(),
71 hostname: "agent-01".into(),
72 expires_at: "2026-05-14T00:00:00Z".parse().unwrap(),
73 minted_at: None,
74 minted_by: None,
75 }],
76 meta: meta_v1(),
77 });
78 let entry = view.lookup("abc").expect("declared nonce found");
79 assert_eq!(entry.hostname, "agent-01");
80 }
81
82 #[test]
83 fn lookup_returns_none_for_unknown_nonce() {
84 let view = AllowedNoncesView::default();
85 assert!(view.lookup("unknown").is_none());
86 }
87
88 #[test]
89 fn entry_is_live_strict_inequality() {
90 let entry = BootstrapNonceEntry {
91 nonce: "abc".into(),
92 hostname: "agent-01".into(),
93 expires_at: "2026-05-13T10:00:00Z".parse().unwrap(),
94 minted_at: None,
95 minted_by: None,
96 };
97 assert!(AllowedNoncesView::entry_is_live(
98 &entry,
99 "2026-05-13T10:00:00Z".parse().unwrap()
100 ));
101 assert!(AllowedNoncesView::entry_is_live(
102 &entry,
103 "2026-05-13T09:59:59Z".parse().unwrap()
104 ));
105 assert!(!AllowedNoncesView::entry_is_live(
106 &entry,
107 "2026-05-13T10:00:01Z".parse().unwrap()
108 ));
109 }
110}