1use std::fmt;
11use std::str::FromStr;
12
13use rand::RngCore;
14use serde::{Deserialize, Serialize};
15
16#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct TraceId(pub [u8; 16]);
21
22impl TraceId {
23 pub const ZERO: Self = Self([0u8; 16]);
25
26 pub fn generate() -> Self {
28 let mut bytes = [0u8; 16];
29 rand::rng().fill_bytes(&mut bytes);
30 if bytes == [0u8; 16] {
32 bytes[15] = 1;
33 }
34 Self(bytes)
35 }
36
37 pub fn from_traceparent(s: &str) -> Option<(TraceId, SpanId, u8)> {
45 let parts: Vec<&str> = s.splitn(4, '-').collect();
46 if parts.len() != 4 {
47 return None;
48 }
49 if parts[0] != "00" {
51 return None;
52 }
53 let trace_hex = parts[1];
55 if trace_hex.len() != 32 {
56 return None;
57 }
58 let mut trace_bytes = [0u8; 16];
59 for (i, chunk) in trace_hex.as_bytes().chunks(2).enumerate() {
60 let hi = hex_val(chunk[0])?;
61 let lo = hex_val(chunk[1])?;
62 trace_bytes[i] = (hi << 4) | lo;
63 }
64 let span_hex = parts[2];
66 if span_hex.len() != 16 {
67 return None;
68 }
69 let mut span_bytes = [0u8; 8];
70 for (i, chunk) in span_hex.as_bytes().chunks(2).enumerate() {
71 let hi = hex_val(chunk[0])?;
72 let lo = hex_val(chunk[1])?;
73 span_bytes[i] = (hi << 4) | lo;
74 }
75 let flags_hex = parts[3];
77 if flags_hex.len() != 2 {
78 return None;
79 }
80 let fhi = hex_val(flags_hex.as_bytes()[0])?;
81 let flo = hex_val(flags_hex.as_bytes()[1])?;
82 let flags = (fhi << 4) | flo;
83
84 Some((TraceId(trace_bytes), SpanId(span_bytes), flags))
85 }
86
87 pub fn to_traceparent_header(&self, span_id: SpanId, flags: u8) -> String {
91 format!("00-{self}-{span_id}-{flags:02x}")
92 }
93}
94
95#[inline]
97fn hex_val(b: u8) -> Option<u8> {
98 match b {
99 b'0'..=b'9' => Some(b - b'0'),
100 b'a'..=b'f' => Some(b - b'a' + 10),
101 b'A'..=b'F' => Some(b - b'A' + 10),
102 _ => None,
103 }
104}
105
106impl fmt::Display for TraceId {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 for byte in &self.0 {
109 write!(f, "{byte:02x}")?;
110 }
111 Ok(())
112 }
113}
114
115impl fmt::Debug for TraceId {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 write!(f, "TraceId({self})")
118 }
119}
120
121#[derive(Debug, thiserror::Error)]
123#[error("invalid TraceId: expected 32 lowercase hex chars, got {0:?}")]
124pub struct TraceIdParseError(String);
125
126impl FromStr for TraceId {
127 type Err = TraceIdParseError;
128
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 if s.len() != 32 {
131 return Err(TraceIdParseError(s.to_owned()));
132 }
133 let mut bytes = [0u8; 16];
134 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
135 let hi = hex_val(chunk[0]).ok_or_else(|| TraceIdParseError(s.to_owned()))?;
136 let lo = hex_val(chunk[1]).ok_or_else(|| TraceIdParseError(s.to_owned()))?;
137 bytes[i] = (hi << 4) | lo;
138 }
139 Ok(TraceId(bytes))
140 }
141}
142
143#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
147pub struct SpanId(pub [u8; 8]);
148
149impl SpanId {
150 pub const ZERO: Self = Self([0u8; 8]);
152
153 pub fn generate() -> Self {
155 let mut bytes = [0u8; 8];
156 rand::rng().fill_bytes(&mut bytes);
157 if bytes == [0u8; 8] {
158 bytes[7] = 1;
159 }
160 Self(bytes)
161 }
162}
163
164impl fmt::Display for SpanId {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 for byte in &self.0 {
167 write!(f, "{byte:02x}")?;
168 }
169 Ok(())
170 }
171}
172
173impl fmt::Debug for SpanId {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 write!(f, "SpanId({self})")
176 }
177}
178
179#[derive(Debug, thiserror::Error)]
181#[error("invalid SpanId: expected 16 lowercase hex chars, got {0:?}")]
182pub struct SpanIdParseError(String);
183
184impl FromStr for SpanId {
185 type Err = SpanIdParseError;
186
187 fn from_str(s: &str) -> Result<Self, Self::Err> {
188 if s.len() != 16 {
189 return Err(SpanIdParseError(s.to_owned()));
190 }
191 let mut bytes = [0u8; 8];
192 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
193 let hi = hex_val(chunk[0]).ok_or_else(|| SpanIdParseError(s.to_owned()))?;
194 let lo = hex_val(chunk[1]).ok_or_else(|| SpanIdParseError(s.to_owned()))?;
195 bytes[i] = (hi << 4) | lo;
196 }
197 Ok(SpanId(bytes))
198 }
199}
200
201#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn generate_produces_nonzero_ids() {
209 let id = TraceId::generate();
210 assert_ne!(id, TraceId::ZERO);
211 }
212
213 #[test]
214 fn two_successive_generate_calls_differ() {
215 let a = TraceId::generate();
216 let b = TraceId::generate();
217 assert_ne!(a, b);
219 }
220
221 #[test]
222 fn display_produces_32_lowercase_hex_chars() {
223 let id = TraceId::generate();
224 let s = id.to_string();
225 assert_eq!(s.len(), 32);
226 assert!(
227 s.chars()
228 .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase())
229 );
230 }
231
232 #[test]
233 fn from_str_roundtrips_through_display() {
234 let id = TraceId::generate();
235 let s = id.to_string();
236 let parsed: TraceId = s.parse().expect("parse must succeed");
237 assert_eq!(id, parsed);
238 }
239
240 #[test]
241 fn from_traceparent_extracts_correct_values() {
242 let s = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
243 let (tid, sid, flags) = TraceId::from_traceparent(s).expect("valid traceparent");
244 let expected_trace = hex_decode_16("4bf92f3577b34da6a3ce929d0e0e4736");
246 assert_eq!(tid.0, expected_trace);
247 let expected_span = hex_decode_8("00f067aa0ba902b7");
249 assert_eq!(sid.0, expected_span);
250 assert_eq!(flags, 0x01);
251 }
252
253 #[test]
254 fn from_traceparent_rejects_wrong_version() {
255 assert!(
256 TraceId::from_traceparent("01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
257 .is_none()
258 );
259 }
260
261 #[test]
262 fn from_traceparent_rejects_wrong_trace_length() {
263 assert!(
265 TraceId::from_traceparent("00-4bf92f3577b34da6a3ce929d0e0e47-00f067aa0ba902b7-01")
266 .is_none()
267 );
268 }
269
270 #[test]
271 fn from_traceparent_rejects_wrong_field_count() {
272 assert!(
273 TraceId::from_traceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7")
274 .is_none()
275 );
276 }
277
278 #[test]
279 fn from_traceparent_rejects_non_hex_chars() {
280 assert!(
281 TraceId::from_traceparent("00-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-00f067aa0ba902b7-01")
282 .is_none()
283 );
284 }
285
286 #[test]
287 fn to_traceparent_header_roundtrips_with_from_traceparent() {
288 let tid = TraceId::generate();
289 let sid = SpanId::generate();
290 let header = tid.to_traceparent_header(sid, 0x01);
291 let (parsed_tid, parsed_sid, flags) = TraceId::from_traceparent(&header).expect("valid");
292 assert_eq!(parsed_tid, tid);
293 assert_eq!(parsed_sid, sid);
294 assert_eq!(flags, 0x01);
295 }
296
297 #[test]
298 fn span_id_generate_nonzero() {
299 let sid = SpanId::generate();
300 assert_ne!(sid, SpanId::ZERO);
301 }
302
303 #[test]
304 fn span_id_display_16_lowercase_hex() {
305 let sid = SpanId::generate();
306 let s = sid.to_string();
307 assert_eq!(s.len(), 16);
308 assert!(
309 s.chars()
310 .all(|c| c.is_ascii_hexdigit() && !c.is_uppercase())
311 );
312 }
313
314 fn hex_decode_16(s: &str) -> [u8; 16] {
317 let mut out = [0u8; 16];
318 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
319 out[i] = u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap();
320 }
321 out
322 }
323
324 fn hex_decode_8(s: &str) -> [u8; 8] {
325 let mut out = [0u8; 8];
326 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
327 out[i] = u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap();
328 }
329 out
330 }
331}