isb_apps/template/
generate.rs1use base64::Engine;
5use ring::rand::{SecureRandom, SystemRandom};
6
7fn random_bytes(n: usize) -> Vec<u8> {
8 let mut b = vec![0u8; n];
9 SystemRandom::new()
10 .fill(&mut b)
11 .expect("the OS random number generator failed");
12 b
13}
14
15fn from_alphabet(alphabet: &[u8], n: usize) -> String {
18 let limit = 256 - (256 % alphabet.len());
19 let mut out = String::with_capacity(n);
20 while out.len() < n {
21 for b in random_bytes(n * 2) {
22 if (b as usize) < limit && out.len() < n {
23 out.push(alphabet[b as usize % alphabet.len()] as char);
24 }
25 }
26 }
27 out
28}
29
30pub fn password(len: u32) -> String {
33 from_alphabet(
34 b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
35 len as usize,
36 )
37}
38
39pub fn base64(bytes: u32) -> String {
41 base64::engine::general_purpose::STANDARD.encode(random_bytes(bytes as usize))
42}
43
44pub fn hex(bytes: u32) -> String {
46 random_bytes(bytes as usize)
47 .iter()
48 .map(|b| format!("{b:02x}"))
49 .collect()
50}
51
52pub fn uuid() -> String {
54 let mut b = random_bytes(16);
55 b[6] = (b[6] & 0x0f) | 0x40;
56 b[8] = (b[8] & 0x3f) | 0x80;
57 let h: String = b.iter().map(|x| format!("{x:02x}")).collect();
58 format!(
59 "{}-{}-{}-{}-{}",
60 &h[0..8],
61 &h[8..12],
62 &h[12..16],
63 &h[16..20],
64 &h[20..32]
65 )
66}
67
68pub fn username(len: u32) -> String {
70 from_alphabet(b"abcdefghijklmnopqrstuvwxyz", len.max(1) as usize)
71}
72
73pub fn free_port() -> Option<u16> {
76 for _ in 0..200 {
77 let b = random_bytes(2);
78 let p = 20000 + (u16::from_le_bytes([b[0], b[1]]) % 20000);
79 if std::net::TcpListener::bind(("0.0.0.0", p)).is_ok() {
80 return Some(p);
81 }
82 }
83 None
84}
85
86pub fn now_secs() -> i64 {
87 std::time::SystemTime::now()
88 .duration_since(std::time::UNIX_EPOCH)
89 .map(|d| d.as_secs() as i64)
90 .unwrap_or(0)
91}
92
93pub fn parse_date(s: &str) -> Option<i64> {
95 let s = s.trim().trim_end_matches('Z');
96 let (date, time) = s.split_once(['T', ' ']).unwrap_or((s, "00:00:00"));
97 let d: Vec<i64> = date
98 .split('-')
99 .map(|p| p.parse().ok())
100 .collect::<Option<_>>()?;
101 let t: Vec<i64> = time
102 .split('.')
103 .next()?
104 .split(':')
105 .map(|p| p.parse().ok())
106 .collect::<Option<_>>()?;
107 if d.len() != 3 || t.is_empty() || t.len() > 3 {
108 return None;
109 }
110 let (y, m, day) = (d[0], d[1], d[2]);
111 if !(1..=12).contains(&m) || !(1..=31).contains(&day) {
112 return None;
113 }
114 let y2 = if m <= 2 { y - 1 } else { y };
116 let era = if y2 >= 0 { y2 } else { y2 - 399 } / 400;
117 let yoe = y2 - era * 400;
118 let mp = (m + 9) % 12;
119 let doy = (153 * mp + 2) / 5 + day - 1;
120 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
121 let days = era * 146097 + doe - 719468;
122 let secs = t[0] * 3600 + t.get(1).unwrap_or(&0) * 60 + t.get(2).unwrap_or(&0);
123 Some(days * 86400 + secs)
124}
125
126pub fn jwt(secret: &str, payload: &serde_json::Value) -> String {
128 let enc = |b: &[u8]| base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b);
129 let header = enc(br#"{"alg":"HS256","typ":"JWT"}"#);
130 let body = enc(payload.to_string().as_bytes());
131 let input = format!("{header}.{body}");
132 let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, secret.as_bytes());
133 let sig = ring::hmac::sign(&key, input.as_bytes());
134 format!("{input}.{}", enc(sig.as_ref()))
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn shapes() {
143 let p = password(32);
144 assert_eq!(p.len(), 32);
145 assert!(p.chars().all(|c| c.is_ascii_alphanumeric()));
146 assert_ne!(password(32), p);
147 let b = base64(48);
148 assert_eq!(
149 base64::engine::general_purpose::STANDARD
150 .decode(&b)
151 .unwrap()
152 .len(),
153 48
154 );
155 assert_eq!(hex(16).len(), 32);
156 let u = uuid();
157 assert_eq!(u.len(), 36);
158 assert_eq!(&u[14..15], "4");
159 assert!("89ab".contains(&u[19..20]));
160 assert!(username(8).chars().all(|c| c.is_ascii_lowercase()));
161 let port = free_port().unwrap();
162 assert!((20000..40000).contains(&port));
163 }
164
165 #[test]
166 fn dates() {
167 assert_eq!(parse_date("1970-01-01T00:00:00Z"), Some(0));
168 assert_eq!(parse_date("2030-01-01T00:00:00Z"), Some(1893456000));
169 assert_eq!(parse_date("2000-03-01"), Some(951868800));
170 assert_eq!(parse_date("tomorrow"), None);
171 }
172
173 #[test]
174 fn jwt_is_hs256() {
175 let t = jwt("secret", &serde_json::json!({"sub": "1"}));
178 let parts: Vec<&str> = t.split('.').collect();
179 assert_eq!(parts.len(), 3);
180 assert_eq!(parts[0], "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
181 assert_eq!(parts[1], "eyJzdWIiOiIxIn0");
182 let key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, b"secret");
183 let sig = base64::engine::general_purpose::URL_SAFE_NO_PAD
184 .decode(parts[2])
185 .unwrap();
186 ring::hmac::verify(&key, format!("{}.{}", parts[0], parts[1]).as_bytes(), &sig).unwrap();
187 }
188}