mesofact_core/proxy/
trace.rs1use std::sync::atomic::{AtomicU64, Ordering};
16use std::time::{SystemTime, UNIX_EPOCH};
17
18static COUNTER: AtomicU64 = AtomicU64::new(0);
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct TraceParent {
22 pub trace_id: String,
24 pub span_id: String,
26}
27
28impl TraceParent {
29 pub fn incoming_or_new(header: Option<&str>) -> Self {
32 if let Some(trace_id) = header.and_then(parse_trace_id) {
33 return Self {
34 trace_id,
35 span_id: hex16(next_u64()),
36 };
37 }
38 Self {
39 trace_id: format!("{}{}", hex16(next_u64()), hex16(next_u64())),
40 span_id: hex16(next_u64()),
41 }
42 }
43
44 pub fn header_value(&self) -> String {
46 format!("00-{}-{}-01", self.trace_id, self.span_id)
47 }
48}
49
50fn parse_trace_id(header: &str) -> Option<String> {
55 let mut parts = header.trim().split('-');
56 let version = parts.next()?;
57 let trace_id = parts.next()?;
58 let parent_id = parts.next()?;
59 let flags = parts.next()?;
60 if parts.next().is_some() {
61 return None;
62 }
63 if version != "00"
64 || trace_id.len() != 32
65 || parent_id.len() != 16
66 || flags.len() != 2
67 || !is_hex(trace_id)
68 || !is_hex(parent_id)
69 || !is_hex(flags)
70 || trace_id.bytes().all(|b| b == b'0')
71 || parent_id.bytes().all(|b| b == b'0')
72 {
73 return None;
74 }
75 Some(trace_id.to_ascii_lowercase())
76}
77
78fn is_hex(s: &str) -> bool {
79 s.bytes().all(|b| b.is_ascii_hexdigit())
80}
81
82fn hex16(v: u64) -> String {
83 format!("{v:016x}")
84}
85
86fn next_u64() -> u64 {
88 let nanos = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .map(|d| d.as_nanos() as u64)
91 .unwrap_or(0);
92 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
93 splitmix64(nanos ^ n.wrapping_mul(0x9E37_79B9_7F4A_7C15))
94}
95
96fn splitmix64(seed: u64) -> u64 {
97 let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
98 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
99 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
100 z ^ (z >> 31)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn fresh_traceparent_is_well_formed() {
109 let tp = TraceParent::incoming_or_new(None);
110 assert_eq!(tp.trace_id.len(), 32);
111 assert_eq!(tp.span_id.len(), 16);
112 assert!(is_hex(&tp.trace_id) && is_hex(&tp.span_id));
113 let h = tp.header_value();
114 assert!(h.starts_with("00-") && h.ends_with("-01"));
115 assert_eq!(parse_trace_id(&h), Some(tp.trace_id));
117 }
118
119 #[test]
120 fn continues_inbound_trace_with_new_span() {
121 let inbound = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
122 let tp = TraceParent::incoming_or_new(Some(inbound));
123 assert_eq!(tp.trace_id, "4bf92f3577b34da6a3ce929d0e0e4736");
124 assert_ne!(tp.span_id, "00f067aa0ba902b7");
126 }
127
128 #[test]
129 fn malformed_inbound_falls_back_to_fresh() {
130 for bad in [
131 "garbage",
132 "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "00-tooshort-00f067aa0ba902b7-01",
134 "00-00000000000000000000000000000000-00f067aa0ba902b7-01", "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000000-01", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", ] {
138 let tp = TraceParent::incoming_or_new(Some(bad));
139 assert_ne!(tp.trace_id, "4bf92f3577b34da6a3ce929d0e0e4736", "bad={bad}");
140 assert_eq!(tp.trace_id.len(), 32, "bad={bad}");
141 }
142 }
143
144 #[test]
145 fn ids_are_unique_across_calls() {
146 let a = TraceParent::incoming_or_new(None);
147 let b = TraceParent::incoming_or_new(None);
148 assert_ne!(a.trace_id, b.trace_id);
149 assert_ne!(a.span_id, b.span_id);
150 }
151}