#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TraceparentContext {
pub version: u8,
pub trace_id: [u8; 16],
pub parent_id: [u8; 8],
pub trace_flags: u8,
}
impl TraceparentContext {
pub fn parse(value: &[u8]) -> Option<Self> {
let s = std::str::from_utf8(value).ok()?;
let mut parts = s.splitn(4, '-');
let version_str = parts.next()?;
let trace_id_str = parts.next()?;
let parent_id_str = parts.next()?;
let flags_str = parts.next()?;
if version_str.len() != 2
|| trace_id_str.len() != 32
|| parent_id_str.len() != 16
|| flags_str.len() != 2
{
return None;
}
let version = u8::from_str_radix(version_str, 16).ok()?;
if version != 0 {
return None;
}
let trace_flags = u8::from_str_radix(flags_str, 16).ok()?;
let mut trace_id = [0u8; 16];
hex::decode_to_slice(trace_id_str, &mut trace_id).ok()?;
let mut parent_id = [0u8; 8];
hex::decode_to_slice(parent_id_str, &mut parent_id).ok()?;
if trace_id == [0u8; 16] || parent_id == [0u8; 8] {
return None;
}
Some(Self {
version,
trace_id,
parent_id,
trace_flags,
})
}
pub fn is_sampled(&self) -> bool {
self.trace_flags & 0x01 != 0
}
pub fn to_traceparent_string(self) -> String {
let mut buf = vec![0u8; 55];
hex::encode_to_slice([self.version], &mut buf[0..2]).expect("size=2");
buf[2] = b'-';
hex::encode_to_slice(self.trace_id, &mut buf[3..35]).expect("size=32");
buf[35] = b'-';
hex::encode_to_slice(self.parent_id, &mut buf[36..52]).expect("size=16");
buf[52] = b'-';
hex::encode_to_slice([self.trace_flags], &mut buf[53..55]).expect("size=2");
String::from_utf8(buf).expect("valid UTF-8")
}
}
#[cfg(test)]
mod tests {
use super::*;
const VALID: &str = "00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01";
#[test]
fn parse_valid() {
let tp = TraceparentContext::parse(VALID.as_bytes()).expect("valid");
assert_eq!(tp.version, 0);
assert_eq!(
tp.trace_id,
[
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
]
);
assert_eq!(
tp.parent_id,
[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x07, 0x18]
);
assert_eq!(tp.trace_flags, 0x01);
assert!(tp.is_sampled());
}
#[test]
fn roundtrip() {
let tp = TraceparentContext::parse(VALID.as_bytes()).expect("valid");
assert_eq!(tp.to_traceparent_string(), VALID);
}
#[test]
fn uppercase_hex_accepted() {
let uppercase = "00-11223344556677889900AABBCCDDEEFF-A1B2C3D4E5F60718-01";
let tp = TraceparentContext::parse(uppercase.as_bytes()).expect("uppercase accepted");
assert_eq!(tp.to_traceparent_string(), VALID);
}
#[test]
fn sampled_with_extra_flags() {
let s = "00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-03";
let tp = TraceparentContext::parse(s.as_bytes()).expect("valid");
assert_eq!(tp.trace_flags, 0x03);
assert!(tp.is_sampled());
}
#[test]
fn unsampled_is_valid() {
let s = "00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-00";
let tp = TraceparentContext::parse(s.as_bytes()).expect("valid");
assert_eq!(tp.trace_flags, 0x00);
assert!(!tp.is_sampled());
}
#[test]
fn empty() {
assert!(TraceparentContext::parse(b"").is_none());
}
#[test]
fn degenerate() {
assert!(TraceparentContext::parse(b"---").is_none());
assert!(TraceparentContext::parse(b"00-1-1-00").is_none());
}
#[test]
fn wrong_total_length() {
assert!(
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-0")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-012")
.is_none()
);
}
#[test]
fn wrong_field_sizes() {
assert!(
TraceparentContext::parse(b"0-11223344556677889900aabbccddeeff0-a1b2c3d4e5f60718-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff0-1b2c3d4e5f60718-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-11223344556677889900aabbccddee-a1b2c3d4e5f6071800-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-1223344556677889900aabbccddeeff-a1b2c3d4e5f607180-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-112233445566778899900aabbccddeeff-1b2c3d4e5f6071-01")
.is_none()
);
}
#[test]
fn empty_fields() {
assert!(TraceparentContext::parse(b"00--a1b2c3d4e5f60718-01").is_none());
assert!(TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff--01").is_none());
}
#[test]
fn bad_hex() {
assert!(
TraceparentContext::parse(b"0g-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-x1223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"00-11223344556677889900aabbccddeeff-a1b2c3d4e5f6071x-01")
.is_none()
);
}
#[test]
fn unsupported_version() {
assert!(
TraceparentContext::parse(b"01-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.is_none()
);
assert!(
TraceparentContext::parse(b"ff-11223344556677889900aabbccddeeff-a1b2c3d4e5f60718-01")
.is_none()
);
}
#[test]
fn all_zero_trace_id_rejected() {
let s = "00-00000000000000000000000000000000-a1b2c3d4e5f60718-01";
assert!(TraceparentContext::parse(s.as_bytes()).is_none());
}
#[test]
fn all_zero_parent_id_rejected() {
let s = "00-11223344556677889900aabbccddeeff-0000000000000000-01";
assert!(TraceparentContext::parse(s.as_bytes()).is_none());
}
#[test]
fn non_utf8() {
assert!(TraceparentContext::parse(&[0xff, 0xfe, 0xfd]).is_none());
}
}