gam_runtime/warm_start/
key.rs1use serde::de::{self, Visitor};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use sha2::{Digest, Sha256};
12use std::fmt;
13
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Fingerprint([u8; 32]);
17
18impl Serialize for Fingerprint {
19 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
23 serializer.serialize_str(&self.to_hex())
24 }
25}
26
27impl<'de> Deserialize<'de> for Fingerprint {
28 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
29 struct HexVisitor;
30 impl Visitor<'_> for HexVisitor {
31 type Value = Fingerprint;
32 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.write_str("a 64-character hex-encoded SHA-256 fingerprint")
34 }
35 fn visit_str<E: de::Error>(self, v: &str) -> Result<Fingerprint, E> {
36 Fingerprint::from_hex(v).ok_or_else(|| de::Error::custom("invalid hex fingerprint"))
37 }
38 }
39 deserializer.deserialize_str(HexVisitor)
40 }
41}
42
43impl Fingerprint {
44 pub const fn as_bytes(&self) -> &[u8; 32] {
45 &self.0
46 }
47
48 pub fn to_hex(&self) -> String {
49 let mut s = String::with_capacity(64);
50 for b in &self.0 {
51 use std::fmt::Write;
52 write!(&mut s, "{:02x}", b).expect("writing to String is infallible");
53 }
54 s
55 }
56
57 pub fn from_hex(s: &str) -> Option<Self> {
58 if s.len() != 64 {
59 return None;
60 }
61 let bytes = s.as_bytes();
62 let mut out = [0u8; 32];
63 for i in 0..32 {
64 let hi = from_hex_nibble(bytes[2 * i])?;
65 let lo = from_hex_nibble(bytes[2 * i + 1])?;
66 out[i] = (hi << 4) | lo;
67 }
68 Some(Fingerprint(out))
69 }
70}
71
72const fn from_hex_nibble(c: u8) -> Option<u8> {
73 match c {
74 b'0'..=b'9' => Some(c - b'0'),
75 b'a'..=b'f' => Some(c - b'a' + 10),
76 b'A'..=b'F' => Some(c - b'A' + 10),
77 _ => None,
78 }
79}
80
81impl fmt::Debug for Fingerprint {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "Fingerprint({})", self.to_hex())
84 }
85}
86
87impl fmt::Display for Fingerprint {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str(&self.to_hex())
90 }
91}
92
93pub struct Fingerprinter {
101 h: Sha256,
102}
103
104mod type_code {
108 pub const TAG: u8 = 0;
109 pub const BYTES: u8 = 1;
110 pub const STR: u8 = 2;
111 pub const U64: u8 = 3;
112}
113
114impl Fingerprinter {
115 pub fn new() -> Self {
116 Self { h: Sha256::new() }
117 }
118
119 fn frame(&mut self, code: u8, tag: &[u8]) {
121 self.h.update([code]);
122 let tag_len = u32::try_from(tag.len()).expect("fingerprint tag length must fit in u32");
123 self.h.update(tag_len.to_le_bytes());
124 self.h.update(tag);
125 }
126
127 pub fn absorb_tag(&mut self, tag: &[u8]) {
129 self.frame(type_code::TAG, tag);
130 }
131
132 pub fn absorb_bytes(&mut self, tag: &[u8], data: &[u8]) {
133 self.frame(type_code::BYTES, tag);
134 self.h.update((data.len() as u64).to_le_bytes());
135 self.h.update(data);
136 }
137
138 pub fn absorb_str(&mut self, tag: &[u8], s: &str) {
139 self.frame(type_code::STR, tag);
140 self.h.update((s.len() as u64).to_le_bytes());
141 self.h.update(s.as_bytes());
142 }
143
144 pub fn absorb_u64(&mut self, tag: &[u8], v: u64) {
145 self.frame(type_code::U64, tag);
146 self.h.update(v.to_le_bytes());
147 }
148
149 pub fn finalize(self) -> Fingerprint {
150 let out = self.h.finalize();
151 let mut bytes = [0u8; 32];
152 bytes.copy_from_slice(&out);
153 Fingerprint(bytes)
154 }
155
156 pub fn write_bytes(&mut self, data: &[u8]) {
167 self.h.update(data);
168 }
169
170 pub fn write_u8(&mut self, value: u8) {
171 self.h.update([value]);
172 }
173
174 pub fn write_bool(&mut self, value: bool) {
175 self.h.update([u8::from(value)]);
176 }
177
178 pub fn write_u64(&mut self, value: u64) {
179 self.h.update(value.to_le_bytes());
180 }
181
182 pub fn write_usize(&mut self, value: usize) {
183 self.h.update((value as u64).to_le_bytes());
184 }
185
186 pub fn write_f64(&mut self, value: f64) {
187 let normalized = if value == 0.0 { 0.0 } else { value };
191 self.h.update(normalized.to_bits().to_le_bytes());
192 }
193
194 pub fn write_str(&mut self, value: &str) {
195 self.write_usize(value.len());
196 self.h.update(value.as_bytes());
197 }
198
199 pub fn write_f64_slice(&mut self, values: &[f64]) {
207 self.write_usize(values.len());
208 self.write_f64_slice_payload(values);
209 }
210
211 fn write_f64_slice_payload(&mut self, values: &[f64]) {
212 #[cfg(target_endian = "little")]
213 {
214 let needs_normalization = values
215 .iter()
216 .any(|&value| value.is_nan() || (value == 0.0 && value.is_sign_negative()));
217 if !needs_normalization {
218 let bytes = unsafe {
223 std::slice::from_raw_parts(
224 values.as_ptr() as *const u8,
225 std::mem::size_of_val(values),
226 )
227 };
228 self.h.update(bytes);
229 return;
230 }
231 }
232 self.write_f64_slice_payload_slow(values);
233 }
234
235 fn write_f64_slice_payload_slow(&mut self, values: &[f64]) {
236 for &value in values {
237 self.write_f64(value);
238 }
239 }
240
241 pub fn write_f64_array1(&mut self, values: &ndarray::Array1<f64>) {
245 self.write_usize(values.len());
246 if let Some(slice) = values.as_slice() {
247 self.write_f64_slice_payload(slice);
248 } else {
249 self.write_f64_slice_payload_slow_iter(values.iter().copied());
250 }
251 }
252
253 pub fn write_f64_array2(&mut self, values: &ndarray::Array2<f64>) {
259 self.write_usize(values.nrows());
260 self.write_usize(values.ncols());
261 if let Some(slice) = values.as_slice() {
262 self.write_f64_slice_payload(slice);
263 } else {
264 self.write_f64_slice_payload_slow_iter(values.iter().copied());
265 }
266 }
267
268 fn write_f64_slice_payload_slow_iter<I>(&mut self, values: I)
269 where
270 I: IntoIterator<Item = f64>,
271 {
272 for value in values {
273 self.write_f64(value);
274 }
275 }
276
277 pub fn finish_u64(self) -> u64 {
282 let out = self.h.finalize();
283 let mut bytes = [0u8; 8];
284 bytes.copy_from_slice(&out[..8]);
285 u64::from_le_bytes(bytes)
286 }
287
288 pub fn finish_hex(self) -> String {
292 format!("{:016x}", self.finish_u64())
293 }
294}
295
296impl Default for Fingerprinter {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn write_f64_slice_bulk_matches_element_protocol() {
308 fn pseudo_random_values(n: usize) -> Vec<f64> {
309 let mut state = 0x4d59_5df4_d0f3_3173_u64;
310 let mut values = Vec::with_capacity(n);
311 for idx in 0..n {
312 state = state
313 .wrapping_mul(6364136223846793005)
314 .wrapping_add(1442695040888963407);
315 let mantissa = state >> 12;
316 let unit = f64::from_bits(0x3ff0_0000_0000_0000 | mantissa) - 1.0;
317 values.push((unit - 0.5) * ((idx % 17) as f64 + 1.0));
318 }
319 values
320 }
321
322 fn fast_key(values: &[f64]) -> Fingerprint {
323 let mut fp = Fingerprinter::new();
324 fp.write_str("write_f64_slice_bulk_matches_element_protocol");
325 fp.write_f64_slice(values);
326 fp.finalize()
327 }
328
329 fn slow_key(values: &[f64]) -> Fingerprint {
330 let mut fp = Fingerprinter::new();
331 fp.write_str("write_f64_slice_bulk_matches_element_protocol");
332 fp.write_usize(values.len());
333 fp.write_f64_slice_payload_slow(values);
334 fp.finalize()
335 }
336
337 let clean = pseudo_random_values(257);
338 assert_eq!(fast_key(&clean), slow_key(&clean));
339
340 let mut normalized = clean.clone();
341 normalized[7] = -0.0;
342 normalized[113] = f64::from_bits(0x7ff8_0000_0000_0042);
343 assert_eq!(fast_key(&normalized), slow_key(&normalized));
344 }
345
346 #[test]
347 fn write_f64_arrays_match_element_protocol() {
348 let values = ndarray::Array2::from_shape_vec(
349 (3, 4),
350 vec![
351 1.25,
352 -2.5,
353 3.75,
354 4.0,
355 5.5,
356 -0.0,
357 7.25,
358 8.5,
359 9.75,
360 10.0,
361 f64::from_bits(0x7ff8_0000_0000_0100),
362 12.25,
363 ],
364 )
365 .expect("test array shape is valid");
366
367 let mut fast = Fingerprinter::new();
368 fast.write_str("write_f64_arrays_match_element_protocol");
369 fast.write_f64_array2(&values);
370
371 let mut slow = Fingerprinter::new();
372 slow.write_str("write_f64_arrays_match_element_protocol");
373 slow.write_usize(values.nrows());
374 slow.write_usize(values.ncols());
375 slow.write_f64_slice_payload_slow_iter(values.iter().copied());
376
377 assert_eq!(fast.finalize(), slow.finalize());
378 }
379
380 #[test]
381 fn fingerprint_serde_roundtrips_as_hex() {
382 let mut fp = Fingerprinter::new();
383 fp.absorb_str(b"k", "fingerprint-serde");
384 let key = fp.finalize();
385 let json = serde_json::to_string(&key).expect("serialize");
386 assert_eq!(json, format!("\"{}\"", key.to_hex()));
388 let back: Fingerprint = serde_json::from_str(&json).expect("deserialize");
389 assert_eq!(key, back);
390 assert!(serde_json::from_str::<Fingerprint>("\"not-hex\"").is_err());
392 }
393
394 #[test]
395 fn invalid_hex_rejected() {
396 assert!(Fingerprint::from_hex("not hex").is_none());
397 assert!(Fingerprint::from_hex(&"a".repeat(63)).is_none());
398 assert!(Fingerprint::from_hex(&"z".repeat(64)).is_none());
399 }
400}