Skip to main content

provide_telemetry/
propagation.rs

1// SPDX-FileCopyrightText: Copyright (C) 2026 provide.io llc
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-Comment: Part of provide-telemetry.
4//
5
6use serde_json::Value;
7
8use crate::context::{bind_context, ContextGuard};
9use crate::tracer::set_trace_context;
10
11const MAX_HEADER_LENGTH: usize = 512;
12const MAX_TRACESTATE_PAIRS: usize = 32;
13const MAX_BAGGAGE_LENGTH: usize = 8192;
14
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct PropagationContext {
17    pub traceparent: Option<String>,
18    pub tracestate: Option<String>,
19    pub baggage: Option<String>,
20    pub trace_id: Option<String>,
21    pub span_id: Option<String>,
22}
23
24pub struct PropagationGuard {
25    trace_guard: Option<ContextGuard>,
26    context_guard: Option<ContextGuard>,
27}
28
29impl Drop for PropagationGuard {
30    #[cfg_attr(test, mutants::skip)] // Equivalent mutant: fields still drop after an empty body.
31    fn drop(&mut self) {
32        drop(self.trace_guard.take());
33        drop(self.context_guard.take());
34    }
35}
36
37fn parse_traceparent(value: Option<&str>) -> (Option<String>, Option<String>, Option<String>) {
38    let Some(raw) = value else {
39        return (None, None, None);
40    };
41    let parts = raw.split('-').collect::<Vec<_>>();
42    if parts.len() != 4 {
43        return (None, None, None);
44    }
45    let version = parts[0];
46    let trace_id = parts[1];
47    let span_id = parts[2];
48    let flags = parts[3];
49    let valid = version.len() == 2
50        && trace_id.len() == 32
51        && span_id.len() == 16
52        && flags.len() == 2
53        && !version.eq_ignore_ascii_case("ff")
54        && trace_id != "00000000000000000000000000000000"
55        && span_id != "0000000000000000"
56        && [version, trace_id, span_id, flags]
57            .iter()
58            .all(|part| part.chars().all(|ch| ch.is_ascii_hexdigit()));
59
60    if !valid {
61        return (None, None, None);
62    }
63
64    (
65        Some(raw.to_string()),
66        Some(trace_id.to_ascii_lowercase()),
67        Some(span_id.to_ascii_lowercase()),
68    )
69}
70
71pub fn extract_w3c_context(
72    traceparent: Option<&str>,
73    tracestate: Option<&str>,
74    baggage: Option<&str>,
75) -> PropagationContext {
76    let traceparent = traceparent.and_then(|value| {
77        if value.len() > MAX_HEADER_LENGTH {
78            None
79        } else {
80            Some(value)
81        }
82    });
83    let tracestate = tracestate.and_then(|value| {
84        if value.len() > MAX_HEADER_LENGTH || value.split(',').count() > MAX_TRACESTATE_PAIRS {
85            None
86        } else {
87            Some(value.to_string())
88        }
89    });
90    let baggage = baggage.and_then(|value| {
91        if value.len() > MAX_BAGGAGE_LENGTH {
92            None
93        } else {
94            Some(value.to_string())
95        }
96    });
97    let (traceparent, trace_id, span_id) = parse_traceparent(traceparent);
98
99    PropagationContext {
100        traceparent,
101        tracestate,
102        baggage,
103        trace_id,
104        span_id,
105    }
106}
107
108pub fn bind_propagation_context(context: PropagationContext) -> PropagationGuard {
109    let mut fields = Vec::new();
110    if let Some(traceparent) = context.traceparent.clone() {
111        fields.push(("traceparent".to_string(), Value::String(traceparent)));
112    }
113    if let Some(tracestate) = context.tracestate.clone() {
114        fields.push(("tracestate".to_string(), Value::String(tracestate)));
115    }
116    if let Some(baggage) = context.baggage.clone() {
117        fields.push(("baggage".to_string(), Value::String(baggage)));
118    }
119
120    let context_guard = if fields.is_empty() {
121        None
122    } else {
123        Some(bind_context(fields))
124    };
125    let trace_guard = if context.trace_id.is_some() || context.span_id.is_some() {
126        Some(set_trace_context(context.trace_id, context.span_id))
127    } else {
128        None
129    };
130
131    PropagationGuard {
132        trace_guard,
133        context_guard,
134    }
135}