Skip to main content

rustev_otel/
ids.rs

1//! Trace and span ids (spec 028, 3.5 and 3.7): W3C sizes, never all zero.
2//!
3//! [`SeededIds`] is deterministic, for goldens (3.5.8); [`RandomIds`] is
4//! what a sink uses unless the host supplies its own source.
5
6use std::collections::hash_map::RandomState;
7use std::hash::{BuildHasher, Hasher};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use sha2::{Digest, Sha256};
11
12/// A source of trace and span ids.
13pub trait IdSource: Send {
14    /// A 16-byte trace id, never all zero.
15    fn trace_id(&mut self) -> [u8; 16];
16    /// An 8-byte span id, never all zero.
17    fn span_id(&mut self) -> [u8; 8];
18}
19
20/// Ids from SHA-256 over a seed and a counter: the same seed gives the same
21/// sequence.
22#[derive(Debug, Clone)]
23pub struct SeededIds {
24    seed: [u8; 32],
25    counter: u64,
26}
27
28impl SeededIds {
29    pub fn new(seed: &[u8]) -> Self {
30        SeededIds {
31            seed: Sha256::digest(seed).into(),
32            counter: 0,
33        }
34    }
35
36    fn next(&mut self) -> [u8; 32] {
37        loop {
38            self.counter += 1;
39            let mut h = Sha256::new();
40            h.update(b"rustev-otel.ids/1");
41            h.update(self.seed);
42            h.update(self.counter.to_be_bytes());
43            let out: [u8; 32] = h.finalize().into();
44            if out[..16].iter().any(|b| *b != 0) && out[..8].iter().any(|b| *b != 0) {
45                return out;
46            }
47        }
48    }
49}
50
51impl IdSource for SeededIds {
52    fn trace_id(&mut self) -> [u8; 16] {
53        let mut id = [0u8; 16];
54        id.copy_from_slice(&self.next()[..16]);
55        id
56    }
57
58    fn span_id(&mut self) -> [u8; 8] {
59        let mut id = [0u8; 8];
60        id.copy_from_slice(&self.next()[..8]);
61        id
62    }
63}
64
65/// Ids seeded once per source from the process's randomized hasher keys,
66/// the wall clock and the process id. Unpredictable enough for correlation
67/// ids; they carry no secret.
68#[derive(Debug, Clone)]
69pub struct RandomIds(SeededIds);
70
71impl Default for RandomIds {
72    fn default() -> Self {
73        let mut h = RandomState::new().build_hasher();
74        h.write_u128(
75            SystemTime::now()
76                .duration_since(UNIX_EPOCH)
77                .map(|d| d.as_nanos())
78                .unwrap_or(0),
79        );
80        h.write_u32(std::process::id());
81        let a = h.finish();
82        let mut h2 = RandomState::new().build_hasher();
83        h2.write_u64(a);
84        let b = h2.finish();
85        let mut seed = [0u8; 16];
86        seed[..8].copy_from_slice(&a.to_be_bytes());
87        seed[8..].copy_from_slice(&b.to_be_bytes());
88        RandomIds(SeededIds::new(&seed))
89    }
90}
91
92impl IdSource for RandomIds {
93    fn trace_id(&mut self) -> [u8; 16] {
94        self.0.trace_id()
95    }
96
97    fn span_id(&mut self) -> [u8; 8] {
98        self.0.span_id()
99    }
100}
101
102/// Lowercase hex.
103pub fn hex(bytes: &[u8]) -> String {
104    let mut s = String::with_capacity(bytes.len() * 2);
105    for b in bytes {
106        s.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
107        s.push(char::from_digit(u32::from(b & 0xf), 16).unwrap_or('0'));
108    }
109    s
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn seeded_ids_repeat_and_random_ids_differ() {
118        let mut a = SeededIds::new(b"x");
119        let mut b = SeededIds::new(b"x");
120        assert_eq!(a.trace_id(), b.trace_id());
121        assert_eq!(a.span_id(), b.span_id());
122        let mut r1 = RandomIds::default();
123        let mut r2 = RandomIds::default();
124        assert_ne!(r1.trace_id(), r2.trace_id());
125        assert_eq!(hex(&[0x0a, 0xff]), "0aff");
126    }
127}