Skip to main content

elfo_core/tracing/
validator.rs

1use std::{convert::TryFrom, time::Duration};
2
3use super::trace_id::{TraceId, TruncatedTime};
4
5/// A [`TraceId`] validator.
6///
7/// By default, it checks the following properties:
8/// * Cannot be zero.
9/// * Must be 63-bit.
10/// * Cannot have the same node no, because it's sent outside the elfo system.
11///
12/// Optionally, it can also check the time difference, see
13/// [`TraceIdValidator::max_time_difference`].
14#[derive(Clone, Default)]
15pub struct TraceIdValidator {
16    max_time_difference: Option<Duration>,
17}
18
19// Errors
20const CANNOT_BE_ZERO: &str = "cannot be zero";
21const HIGHEST_BIT_MUST_BE_ZERO: &str = "highest bit must be zero";
22const INVALID_TIMESTAMP: &str = "invalid timestamp";
23
24impl TraceIdValidator {
25    /// Allowed time difference between now and timestamp in a raw trace id.
26    /// Checks the absolute difference to handle both situations:
27    /// * Too old timestamp, possible incorrectly generated.
28    /// * Too new timestamp, something wrong with time synchronization.
29    pub fn max_time_difference(&mut self, time_lag: impl Into<Option<Duration>>) -> Self {
30        self.max_time_difference = time_lag.into();
31        self.clone()
32    }
33
34    /// Validates a raw trace id transforms it into [`TraceId`] if valid.
35    pub fn validate(&self, raw_trace_id: u64) -> Result<TraceId, &'static str> {
36        let trace_id = TraceId::try_from(raw_trace_id).map_err(|_| CANNOT_BE_ZERO)?;
37        let layout = trace_id.to_layout();
38
39        // The highest bit must be zero.
40        if raw_trace_id & (1 << 63) != 0 {
41            return Err(HIGHEST_BIT_MUST_BE_ZERO);
42        }
43
44        if let Some(time_lag) = self.max_time_difference {
45            let truncated_now = TruncatedTime::now();
46            let delta = truncated_now.abs_delta(layout.timestamp);
47
48            if i64::from(delta) > time_lag.as_secs() as i64 {
49                return Err(INVALID_TIMESTAMP);
50            }
51        }
52
53        Ok(trace_id)
54    }
55}
56
57#[test]
58fn it_works() {
59    elfo_utils::time::with_mock(|mock| {
60        let validator = TraceIdValidator::default().max_time_difference(Duration::from_secs(5));
61
62        assert_eq!(validator.validate(0), Err(CANNOT_BE_ZERO));
63        assert_eq!(validator.validate(1 << 63), Err(HIGHEST_BIT_MUST_BE_ZERO));
64
65        assert!(validator.validate(5 << 38).is_ok());
66        assert!(validator.validate(((1 << 25) - 5) << 38).is_ok());
67        assert_eq!(validator.validate(6 << 38), Err(INVALID_TIMESTAMP));
68        assert_eq!(
69            validator.validate(((1 << 25) - 6) << 38),
70            Err(INVALID_TIMESTAMP)
71        );
72
73        mock.advance(Duration::from_secs(10));
74        assert_eq!(validator.validate(16 << 38), Err(INVALID_TIMESTAMP));
75        assert_eq!(validator.validate(4 << 38), Err(INVALID_TIMESTAMP));
76    });
77}