Skip to main content

renacer_core/
trace_context.rs

1// Sprint 33: W3C Trace Context Propagation
2//
3// Implements W3C Trace Context standard for distributed tracing.
4// Reference: https://www.w3.org/TR/trace-context/
5//
6// Format: version-trace_id-parent_id-trace_flags
7// Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
8
9use std::fmt;
10
11/// W3C Trace Context (traceparent header)
12///
13/// Represents distributed trace context passed across service boundaries.
14/// Enables Renacer to create syscall spans as children of application spans.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct TraceContext {
17    pub version: u8,
18    pub trace_id: [u8; 16], // 128-bit trace ID
19    pub parent_id: [u8; 8], // 64-bit parent span ID
20    pub trace_flags: u8,    // 8-bit flags (01 = sampled)
21}
22
23impl TraceContext {
24    /// Parse W3C traceparent string
25    ///
26    /// Format: "00-{trace_id}-{parent_id}-{flags}"
27    /// Example: "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
28    pub fn parse(traceparent: &str) -> Result<Self, TraceContextError> {
29        // Split into 4 parts: version-trace_id-parent_id-flags
30        let parts: Vec<&str> = traceparent.split('-').collect();
31        if parts.len() != 4 {
32            return Err(TraceContextError::InvalidFormat);
33        }
34
35        // Parse version (must be "00")
36        let version =
37            u8::from_str_radix(parts[0], 16).map_err(|_| TraceContextError::InvalidVersion)?;
38        if version != 0 {
39            return Err(TraceContextError::InvalidVersion);
40        }
41
42        // Parse trace_id (32 hex chars = 16 bytes)
43        if parts[1].len() != 32 {
44            return Err(TraceContextError::InvalidTraceId);
45        }
46        let trace_id = hex_to_bytes_16(parts[1]).ok_or(TraceContextError::InvalidTraceId)?;
47
48        // Validate trace_id is not all zeros
49        if trace_id.iter().all(|&b| b == 0) {
50            return Err(TraceContextError::AllZeroTraceId);
51        }
52
53        // Parse parent_id (16 hex chars = 8 bytes)
54        if parts[2].len() != 16 {
55            return Err(TraceContextError::InvalidParentId);
56        }
57        let parent_id = hex_to_bytes_8(parts[2]).ok_or(TraceContextError::InvalidParentId)?;
58
59        // Validate parent_id is not all zeros
60        if parent_id.iter().all(|&b| b == 0) {
61            return Err(TraceContextError::AllZeroParentId);
62        }
63
64        // Parse trace_flags (2 hex chars = 1 byte)
65        if parts[3].len() != 2 {
66            return Err(TraceContextError::InvalidTraceFlags);
67        }
68        let trace_flags =
69            u8::from_str_radix(parts[3], 16).map_err(|_| TraceContextError::InvalidTraceFlags)?;
70
71        Ok(TraceContext {
72            version,
73            trace_id,
74            parent_id,
75            trace_flags,
76        })
77    }
78
79    /// Extract trace context from environment variables
80    ///
81    /// Checks TRACEPARENT and `OTEL_TRACEPARENT` (in that order)
82    pub fn from_env() -> Option<Self> {
83        std::env::var("TRACEPARENT")
84            .or_else(|_| std::env::var("OTEL_TRACEPARENT"))
85            .ok()
86            .and_then(|s| Self::parse(&s).ok())
87    }
88
89    /// Set trace context as environment variable (Sprint 42: Batuta Integration)
90    ///
91    /// Sets TRACEPARENT for child processes to inherit trace context.
92    /// This enables distributed tracing across `exec()` boundaries.
93    pub fn set_env(&self) {
94        std::env::set_var("TRACEPARENT", self.to_string());
95    }
96
97    /// Extract logical clock from environment variable (Sprint 42: Batuta Integration)
98    ///
99    /// Checks `RENACER_LOGICAL_CLOCK` environment variable.
100    /// Returns None if not set or invalid format.
101    pub fn logical_clock_from_env() -> Option<u64> {
102        std::env::var("RENACER_LOGICAL_CLOCK")
103            .ok()
104            .and_then(|s| s.parse::<u64>().ok())
105    }
106
107    /// Set logical clock as environment variable (Sprint 42: Batuta Integration)
108    ///
109    /// Sets `RENACER_LOGICAL_CLOCK` for child processes to inherit.
110    /// This propagates Lamport logical clocks across `exec()` boundaries.
111    pub fn set_logical_clock_env(timestamp: u64) {
112        std::env::set_var("RENACER_LOGICAL_CLOCK", timestamp.to_string());
113    }
114
115    /// Check if trace is sampled (`trace_flags` & 0x01)
116    pub fn is_sampled(&self) -> bool {
117        self.trace_flags & 0x01 != 0
118    }
119
120    /// Convert `trace_id` to OpenTelemetry `TraceId`
121    #[cfg(feature = "otlp")]
122    pub fn otel_trace_id(&self) -> opentelemetry::trace::TraceId {
123        opentelemetry::trace::TraceId::from_bytes(self.trace_id)
124    }
125
126    /// Convert `parent_id` to OpenTelemetry `SpanId`
127    #[cfg(feature = "otlp")]
128    pub fn otel_parent_id(&self) -> opentelemetry::trace::SpanId {
129        opentelemetry::trace::SpanId::from_bytes(self.parent_id)
130    }
131}
132
133impl fmt::Display for TraceContext {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(
136            f,
137            "{:02x}-{}-{}-{:02x}",
138            self.version,
139            bytes_to_hex_16(&self.trace_id),
140            bytes_to_hex_8(&self.parent_id),
141            self.trace_flags
142        )
143    }
144}
145
146/// Trace Context parsing errors
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum TraceContextError {
149    /// Invalid format (must be version-trace_id-parent_id-flags)
150    InvalidFormat,
151    /// Invalid version (must be 00)
152    InvalidVersion,
153    /// Invalid `trace_id` (must be 32 hex chars)
154    InvalidTraceId,
155    /// Invalid `parent_id` (must be 16 hex chars)
156    InvalidParentId,
157    /// Invalid `trace_flags` (must be 2 hex chars)
158    InvalidTraceFlags,
159    /// Trace ID is all zeros (forbidden by W3C spec)
160    AllZeroTraceId,
161    /// Parent ID is all zeros (forbidden by W3C spec)
162    AllZeroParentId,
163}
164
165impl fmt::Display for TraceContextError {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            Self::InvalidFormat => {
169                write!(
170                    f,
171                    "Invalid traceparent format (expected: version-trace_id-parent_id-flags)"
172                )
173            }
174            Self::InvalidVersion => write!(f, "Invalid version (must be 00)"),
175            Self::InvalidTraceId => write!(f, "Invalid trace_id (must be 32 hex characters)"),
176            Self::InvalidParentId => write!(f, "Invalid parent_id (must be 16 hex characters)"),
177            Self::InvalidTraceFlags => write!(f, "Invalid trace_flags (must be 2 hex characters)"),
178            Self::AllZeroTraceId => write!(f, "Trace ID cannot be all zeros"),
179            Self::AllZeroParentId => write!(f, "Parent ID cannot be all zeros"),
180        }
181    }
182}
183
184impl std::error::Error for TraceContextError {}
185
186// Helper functions for hex conversion
187
188fn hex_to_bytes_16(hex: &str) -> Option<[u8; 16]> {
189    let mut bytes = [0u8; 16];
190    for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
191        if i >= 16 {
192            return None;
193        }
194        let hex_str = std::str::from_utf8(chunk).ok()?;
195        bytes[i] = u8::from_str_radix(hex_str, 16).ok()?;
196    }
197    Some(bytes)
198}
199
200fn hex_to_bytes_8(hex: &str) -> Option<[u8; 8]> {
201    let mut bytes = [0u8; 8];
202    for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
203        if i >= 8 {
204            return None;
205        }
206        let hex_str = std::str::from_utf8(chunk).ok()?;
207        bytes[i] = u8::from_str_radix(hex_str, 16).ok()?;
208    }
209    Some(bytes)
210}
211
212fn bytes_to_hex_16(bytes: &[u8; 16]) -> String {
213    bytes.iter().map(|b| format!("{b:02x}")).collect()
214}
215
216fn bytes_to_hex_8(bytes: &[u8; 8]) -> String {
217    bytes.iter().map(|b| format!("{b:02x}")).collect()
218}
219
220// Compile-time thread-safety verification (Sprint 59)
221static_assertions::assert_impl_all!(TraceContext: Send, Sync);
222static_assertions::assert_impl_all!(TraceContextError: Send, Sync);
223
224// ============================================================================
225// UNIT TESTS (EXTREME TDD - RED Phase)
226// ============================================================================
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    // Test 1: Parse valid traceparent
233    #[test]
234    fn test_parse_valid_traceparent() {
235        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
236        let ctx = TraceContext::parse(traceparent).expect("test");
237
238        assert_eq!(ctx.version, 0);
239        assert_eq!(ctx.trace_flags, 1);
240        assert!(ctx.is_sampled());
241
242        // Verify trace_id
243        let expected_trace_id = [
244            0x0a, 0xf7, 0x65, 0x19, 0x16, 0xcd, 0x43, 0xdd, 0x84, 0x48, 0xeb, 0x21, 0x1c, 0x80,
245            0x31, 0x9c,
246        ];
247        assert_eq!(ctx.trace_id, expected_trace_id);
248
249        // Verify parent_id
250        let expected_parent_id = [0xb7, 0xad, 0x6b, 0x71, 0x69, 0x20, 0x33, 0x31];
251        assert_eq!(ctx.parent_id, expected_parent_id);
252    }
253
254    // Test 2: Parse another valid traceparent (not sampled)
255    #[test]
256    fn test_parse_valid_traceparent_not_sampled() {
257        let traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00";
258        let ctx = TraceContext::parse(traceparent).expect("test");
259
260        assert_eq!(ctx.version, 0);
261        assert_eq!(ctx.trace_flags, 0);
262        assert!(!ctx.is_sampled());
263    }
264
265    // Test 3: Invalid format (missing parts)
266    #[test]
267    fn test_parse_invalid_format_missing_parts() {
268        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331";
269        let result = TraceContext::parse(traceparent);
270
271        assert_eq!(result, Err(TraceContextError::InvalidFormat));
272    }
273
274    // Test 4: Invalid format (too many parts)
275    #[test]
276    fn test_parse_invalid_format_too_many_parts() {
277        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01-extra";
278        let result = TraceContext::parse(traceparent);
279
280        assert_eq!(result, Err(TraceContextError::InvalidFormat));
281    }
282
283    // Test 5: Invalid format (empty string)
284    #[test]
285    fn test_parse_invalid_format_empty() {
286        let result = TraceContext::parse("");
287        assert_eq!(result, Err(TraceContextError::InvalidFormat));
288    }
289
290    // Test 6: All-zero trace_id
291    #[test]
292    fn test_parse_all_zero_trace_id() {
293        let traceparent = "00-00000000000000000000000000000000-b7ad6b7169203331-01";
294        let result = TraceContext::parse(traceparent);
295
296        assert_eq!(result, Err(TraceContextError::AllZeroTraceId));
297    }
298
299    // Test 7: All-zero parent_id
300    #[test]
301    fn test_parse_all_zero_parent_id() {
302        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-0000000000000000-01";
303        let result = TraceContext::parse(traceparent);
304
305        assert_eq!(result, Err(TraceContextError::AllZeroParentId));
306    }
307
308    // Test 8: Invalid version
309    #[test]
310    fn test_parse_invalid_version() {
311        let traceparent = "99-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
312        let result = TraceContext::parse(traceparent);
313
314        assert_eq!(result, Err(TraceContextError::InvalidVersion));
315    }
316
317    // Test 9: Invalid version (non-hex)
318    #[test]
319    fn test_parse_invalid_version_non_hex() {
320        let traceparent = "XX-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
321        let result = TraceContext::parse(traceparent);
322
323        assert_eq!(result, Err(TraceContextError::InvalidVersion));
324    }
325
326    // Test 10: Invalid trace_id (wrong length)
327    #[test]
328    fn test_parse_invalid_trace_id_wrong_length() {
329        let traceparent = "00-0af7651916cd43dd-b7ad6b7169203331-01";
330        let result = TraceContext::parse(traceparent);
331
332        assert_eq!(result, Err(TraceContextError::InvalidTraceId));
333    }
334
335    // Test 11: Invalid trace_id (non-hex)
336    #[test]
337    fn test_parse_invalid_trace_id_non_hex() {
338        let traceparent = "00-ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ-b7ad6b7169203331-01";
339        let result = TraceContext::parse(traceparent);
340
341        assert_eq!(result, Err(TraceContextError::InvalidTraceId));
342    }
343
344    // Test 12: Invalid parent_id (wrong length)
345    #[test]
346    fn test_parse_invalid_parent_id_wrong_length() {
347        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad-01";
348        let result = TraceContext::parse(traceparent);
349
350        assert_eq!(result, Err(TraceContextError::InvalidParentId));
351    }
352
353    // Test 13: Invalid parent_id (non-hex)
354    #[test]
355    fn test_parse_invalid_parent_id_non_hex() {
356        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-XXXXXXXXXXXXXXXX-01";
357        let result = TraceContext::parse(traceparent);
358
359        assert_eq!(result, Err(TraceContextError::InvalidParentId));
360    }
361
362    // Test 14: Invalid trace_flags (wrong length)
363    #[test]
364    fn test_parse_invalid_trace_flags_wrong_length() {
365        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-1";
366        let result = TraceContext::parse(traceparent);
367
368        assert_eq!(result, Err(TraceContextError::InvalidTraceFlags));
369    }
370
371    // Test 15: Invalid trace_flags (non-hex)
372    #[test]
373    fn test_parse_invalid_trace_flags_non_hex() {
374        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-XX";
375        let result = TraceContext::parse(traceparent);
376
377        assert_eq!(result, Err(TraceContextError::InvalidTraceFlags));
378    }
379
380    // Test 16: is_sampled() with flag = 01
381    #[test]
382    fn test_is_sampled_flag_set() {
383        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
384        let ctx = TraceContext::parse(traceparent).expect("test");
385
386        assert!(ctx.is_sampled());
387    }
388
389    // Test 17: is_sampled() with flag = 00
390    #[test]
391    fn test_is_sampled_flag_unset() {
392        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00";
393        let ctx = TraceContext::parse(traceparent).expect("test");
394
395        assert!(!ctx.is_sampled());
396    }
397
398    // Test 18: Display formatting
399    #[test]
400    fn test_display_formatting() {
401        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
402        let ctx = TraceContext::parse(traceparent).expect("test");
403
404        assert_eq!(ctx.to_string(), traceparent);
405    }
406
407    // Test 19: from_env() with TRACEPARENT set
408    #[test]
409    #[ignore = "flaky: env var race with parallel tests (set_var/remove_var not thread-safe)"]
410    fn test_from_env_traceparent() {
411        std::env::set_var(
412            "TRACEPARENT",
413            "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
414        );
415
416        let ctx = TraceContext::from_env();
417        assert!(ctx.is_some());
418
419        let ctx = ctx.expect("test");
420        assert_eq!(ctx.version, 0);
421        assert!(ctx.is_sampled());
422
423        std::env::remove_var("TRACEPARENT");
424    }
425
426    // Test 20: from_env() with OTEL_TRACEPARENT set
427    #[test]
428    #[ignore = "flaky: env var race with parallel tests (set_var/remove_var not thread-safe)"]
429    fn test_from_env_otel_traceparent() {
430        std::env::remove_var("TRACEPARENT");
431        std::env::set_var(
432            "OTEL_TRACEPARENT",
433            "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00",
434        );
435
436        let ctx = TraceContext::from_env();
437        assert!(ctx.is_some());
438
439        let ctx = ctx.expect("test");
440        assert_eq!(ctx.version, 0);
441        assert!(!ctx.is_sampled());
442
443        std::env::remove_var("OTEL_TRACEPARENT");
444    }
445
446    // Test 21: from_env() with no env var set
447    #[test]
448    #[ignore = "flaky: env var race with parallel tests (TRACEPARENT set by another test)"]
449    fn test_from_env_missing() {
450        std::env::remove_var("TRACEPARENT");
451        std::env::remove_var("OTEL_TRACEPARENT");
452
453        let ctx = TraceContext::from_env();
454        assert!(ctx.is_none());
455    }
456
457    // Test 22: from_env() with invalid format
458    #[test]
459    fn test_from_env_invalid_format() {
460        std::env::set_var("TRACEPARENT", "INVALID");
461
462        let ctx = TraceContext::from_env();
463        assert!(ctx.is_none());
464
465        std::env::remove_var("TRACEPARENT");
466    }
467
468    // Test 23: Clone trait
469    #[test]
470    fn test_trace_context_clone() {
471        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
472        let ctx1 = TraceContext::parse(traceparent).expect("test");
473        let ctx2 = ctx1.clone();
474
475        assert_eq!(ctx1, ctx2);
476    }
477
478    // Test 24: Debug trait
479    #[test]
480    fn test_trace_context_debug() {
481        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
482        let ctx = TraceContext::parse(traceparent).expect("test");
483
484        let debug_str = format!("{:?}", ctx);
485        assert!(debug_str.contains("TraceContext"));
486    }
487
488    // Test 25: Error Display trait
489    #[test]
490    fn test_error_display() {
491        let err = TraceContextError::InvalidFormat;
492        assert!(err.to_string().contains("Invalid traceparent format"));
493
494        let err = TraceContextError::AllZeroTraceId;
495        assert!(err.to_string().contains("all zeros"));
496    }
497}
498
499// ============================================================================
500// LAMPORT CLOCK IMPLEMENTATION (Specification Section 6.2)
501// ============================================================================
502
503use std::sync::atomic::{AtomicU64, Ordering};
504
505/// Lamport Clock for happens-before ordering
506///
507/// Implements Lamport's logical clocks for establishing causal relationships
508/// between events in a distributed tracing system.
509///
510/// Reference: "Time, Clocks, and the Ordering of Events in a Distributed System"
511/// Lamport, L. Communications of the ACM, 21(7), 558-565 (1978)
512///
513/// # Properties
514///
515/// 1. **Transitivity**: a → b ∧ b → c ⇒ a → c
516/// 2. **Irreflexivity**: ¬(a → a)
517/// 3. **Timestamp consistency**: a → b ⇒ timestamp(a) < timestamp(b)
518#[derive(Debug)]
519pub struct LamportClock {
520    counter: AtomicU64,
521}
522
523impl LamportClock {
524    /// Create a new Lamport clock starting at 0
525    pub fn new() -> Self {
526        LamportClock {
527            counter: AtomicU64::new(0),
528        }
529    }
530
531    /// Create a Lamport clock with a specific starting value
532    pub fn with_initial_value(initial: u64) -> Self {
533        LamportClock {
534            counter: AtomicU64::new(initial),
535        }
536    }
537
538    /// Increment clock on local event
539    ///
540    /// Returns the new timestamp after increment.
541    /// Uses `SeqCst` ordering to ensure happens-before consistency.
542    pub fn tick(&self) -> u64 {
543        self.counter.fetch_add(1, Ordering::SeqCst) + 1
544    }
545
546    /// Synchronize clock on message receive
547    ///
548    /// Updates local clock to max(local, remote) + 1.
549    /// Ensures received messages have higher timestamps than their causally preceding events.
550    pub fn sync(&self, remote_timestamp: u64) -> u64 {
551        loop {
552            let current = self.counter.load(Ordering::SeqCst);
553            let new_value = current.max(remote_timestamp) + 1;
554
555            match self.counter.compare_exchange(
556                current,
557                new_value,
558                Ordering::SeqCst,
559                Ordering::SeqCst,
560            ) {
561                Ok(_) => return new_value,
562                Err(_) => continue, // Retry on concurrent modification
563            }
564        }
565    }
566
567    /// Get current clock value without modifying it
568    pub fn now(&self) -> u64 {
569        self.counter.load(Ordering::SeqCst)
570    }
571
572    /// Check if timestamp a happens-before timestamp b
573    ///
574    /// Returns true if a < b (strict temporal ordering).
575    /// Note: This is a simple timestamp comparison. Full happens-before
576    /// semantics require checking causal chains (see `UnifiedTrace`).
577    pub fn happens_before(a: u64, b: u64) -> bool {
578        a < b
579    }
580}
581
582impl Default for LamportClock {
583    fn default() -> Self {
584        Self::new()
585    }
586}
587
588impl Clone for LamportClock {
589    fn clone(&self) -> Self {
590        LamportClock {
591            counter: AtomicU64::new(self.counter.load(Ordering::SeqCst)),
592        }
593    }
594}
595
596// Compile-time thread-safety verification for LamportClock
597static_assertions::assert_impl_all!(LamportClock: Send, Sync);
598
599// ============================================================================
600// LAMPORT CLOCK TESTS (EXTREME TDD)
601// ============================================================================
602
603#[cfg(test)]
604mod lamport_tests {
605    use super::*;
606
607    // Test 1: Clock starts at 0
608    #[test]
609    fn test_clock_starts_at_zero() {
610        let clock = LamportClock::new();
611        assert_eq!(clock.now(), 0);
612    }
613
614    // Test 2: Clock starts at custom value
615    #[test]
616    fn test_clock_with_initial_value() {
617        let clock = LamportClock::with_initial_value(100);
618        assert_eq!(clock.now(), 100);
619    }
620
621    // Test 3: Tick increments by 1
622    #[test]
623    fn test_tick_increments() {
624        let clock = LamportClock::new();
625        assert_eq!(clock.tick(), 1);
626        assert_eq!(clock.tick(), 2);
627        assert_eq!(clock.tick(), 3);
628    }
629
630    // Test 4: Tick returns incremented value
631    #[test]
632    fn test_tick_return_value() {
633        let clock = LamportClock::new();
634        let ts1 = clock.tick();
635        let ts2 = clock.tick();
636        assert!(ts2 > ts1);
637        assert_eq!(ts2, ts1 + 1);
638    }
639
640    // Test 5: Sync with lower remote timestamp
641    #[test]
642    fn test_sync_lower_remote() {
643        let clock = LamportClock::new();
644        clock.tick(); // local = 1
645        clock.tick(); // local = 2
646        clock.tick(); // local = 3
647
648        let new_ts = clock.sync(1); // remote = 1 < local = 3
649        assert_eq!(new_ts, 4); // max(3, 1) + 1 = 4
650    }
651
652    // Test 6: Sync with higher remote timestamp
653    #[test]
654    fn test_sync_higher_remote() {
655        let clock = LamportClock::new();
656        clock.tick(); // local = 1
657
658        let new_ts = clock.sync(10); // remote = 10 > local = 1
659        assert_eq!(new_ts, 11); // max(1, 10) + 1 = 11
660    }
661
662    // Test 7: Sync with equal remote timestamp
663    #[test]
664    fn test_sync_equal_remote() {
665        let clock = LamportClock::new();
666        clock.tick(); // local = 1
667        clock.tick(); // local = 2
668        clock.tick(); // local = 3
669
670        let new_ts = clock.sync(3); // remote = 3 == local = 3
671        assert_eq!(new_ts, 4); // max(3, 3) + 1 = 4
672    }
673
674    // Test 8: Multiple syncs in sequence
675    #[test]
676    fn test_multiple_syncs() {
677        let clock = LamportClock::new();
678
679        let ts1 = clock.sync(5);
680        assert_eq!(ts1, 6); // max(0, 5) + 1
681
682        let ts2 = clock.sync(10);
683        assert_eq!(ts2, 11); // max(6, 10) + 1
684
685        let ts3 = clock.sync(8);
686        assert_eq!(ts3, 12); // max(11, 8) + 1
687    }
688
689    // Test 9: Interleaved ticks and syncs
690    #[test]
691    fn test_interleaved_operations() {
692        let clock = LamportClock::new();
693
694        assert_eq!(clock.tick(), 1);
695        assert_eq!(clock.sync(5), 6);
696        assert_eq!(clock.tick(), 7);
697        assert_eq!(clock.tick(), 8);
698        assert_eq!(clock.sync(10), 11);
699    }
700
701    // Test 10: now() doesn't modify clock
702    #[test]
703    fn test_now_readonly() {
704        let clock = LamportClock::new();
705        clock.tick(); // local = 1
706
707        assert_eq!(clock.now(), 1);
708        assert_eq!(clock.now(), 1); // Still 1, not incremented
709        assert_eq!(clock.now(), 1);
710    }
711
712    // Test 11: happens_before with a < b
713    #[test]
714    fn test_happens_before_true() {
715        assert!(LamportClock::happens_before(1, 2));
716        assert!(LamportClock::happens_before(10, 20));
717        assert!(LamportClock::happens_before(0, 1));
718    }
719
720    // Test 12: happens_before with a >= b
721    #[test]
722    fn test_happens_before_false() {
723        assert!(!LamportClock::happens_before(2, 1));
724        assert!(!LamportClock::happens_before(5, 5));
725        assert!(!LamportClock::happens_before(10, 5));
726    }
727
728    // Test 13: Clone preserves value
729    #[test]
730    fn test_clone_preserves_value() {
731        let clock1 = LamportClock::new();
732        clock1.tick();
733        clock1.tick();
734        clock1.tick(); // clock1 = 3
735
736        let clock2 = clock1.clone();
737        assert_eq!(clock2.now(), 3);
738    }
739
740    // Test 14: Cloned clocks are independent
741    #[test]
742    fn test_cloned_clocks_independent() {
743        let clock1 = LamportClock::new();
744        clock1.tick(); // clock1 = 1
745
746        let clock2 = clock1.clone();
747
748        clock1.tick(); // clock1 = 2
749        clock2.tick(); // clock2 = 2
750
751        assert_eq!(clock1.now(), 2);
752        assert_eq!(clock2.now(), 2);
753
754        clock1.tick(); // clock1 = 3
755        assert_eq!(clock1.now(), 3);
756        assert_eq!(clock2.now(), 2); // clock2 unchanged
757    }
758
759    // Test 15: Default trait
760    #[test]
761    fn test_default_trait() {
762        let clock: LamportClock = Default::default();
763        assert_eq!(clock.now(), 0);
764    }
765
766    // Test 16: Large timestamp values
767    #[test]
768    fn test_large_timestamps() {
769        let clock = LamportClock::with_initial_value(u64::MAX - 10);
770        assert_eq!(clock.now(), u64::MAX - 10);
771
772        // Note: This will overflow, which is acceptable for Lamport clocks
773        // In production, we'd handle overflow gracefully
774        let _ts = clock.tick();
775    }
776
777    // Test 17: Sync updates clock correctly
778    #[test]
779    fn test_sync_updates_clock() {
780        let clock = LamportClock::new();
781        clock.sync(100);
782
783        // After sync, local clock should be 101
784        assert_eq!(clock.now(), 101);
785    }
786
787    // Test 18: Transitivity property
788    #[test]
789    fn test_transitivity_property() {
790        let a = 1u64;
791        let b = 5u64;
792        let c = 10u64;
793
794        // If a → b and b → c, then a → c
795        assert!(LamportClock::happens_before(a, b));
796        assert!(LamportClock::happens_before(b, c));
797        assert!(LamportClock::happens_before(a, c));
798    }
799
800    // Test 19: Irreflexivity property
801    #[test]
802    fn test_irreflexivity_property() {
803        let a = 5u64;
804
805        // ¬(a → a): An event cannot happen before itself
806        assert!(!LamportClock::happens_before(a, a));
807    }
808
809    // Test 20: Timestamp consistency
810    #[test]
811    fn test_timestamp_consistency() {
812        let clock = LamportClock::new();
813
814        let ts1 = clock.tick();
815        let ts2 = clock.tick();
816
817        // If event1 happens before event2, then ts1 < ts2
818        assert!(LamportClock::happens_before(ts1, ts2));
819        assert!(ts1 < ts2);
820    }
821
822    // Test 21: Concurrent operations (simulated)
823    #[test]
824    fn test_concurrent_ticks() {
825        use std::sync::Arc;
826        use std::thread;
827
828        let clock = Arc::new(LamportClock::new());
829        let mut handles = vec![];
830
831        // Spawn 10 threads, each doing 10 ticks
832        for _ in 0..10 {
833            let clock_clone = Arc::clone(&clock);
834            let handle = thread::spawn(move || {
835                for _ in 0..10 {
836                    clock_clone.tick();
837                }
838            });
839            handles.push(handle);
840        }
841
842        for handle in handles {
843            handle.join().expect("test");
844        }
845
846        // After 10 threads × 10 ticks = 100 total ticks
847        assert_eq!(clock.now(), 100);
848    }
849
850    // Test 22: Concurrent syncs (simulated)
851    #[test]
852    fn test_concurrent_syncs() {
853        use std::sync::Arc;
854        use std::thread;
855
856        let clock = Arc::new(LamportClock::new());
857        let mut handles = vec![];
858
859        // Spawn 5 threads, each syncing with increasing remote timestamps
860        for i in 0..5 {
861            let clock_clone = Arc::clone(&clock);
862            let remote_ts = (i as u64 + 1) * 10; // 10, 20, 30, 40, 50
863            let handle = thread::spawn(move || {
864                clock_clone.sync(remote_ts);
865            });
866            handles.push(handle);
867        }
868
869        for handle in handles {
870            handle.join().expect("test");
871        }
872
873        // Final value should be at least 51 (max remote + 1)
874        assert!(clock.now() >= 51);
875    }
876
877    // Test 23: Debug trait
878    #[test]
879    fn test_debug_trait() {
880        let clock = LamportClock::new();
881        let debug_str = format!("{:?}", clock);
882        assert!(debug_str.contains("LamportClock"));
883    }
884
885    // Test 24: Sync with zero timestamp
886    #[test]
887    fn test_sync_with_zero() {
888        let clock = LamportClock::new();
889        let ts = clock.sync(0);
890        assert_eq!(ts, 1); // max(0, 0) + 1 = 1
891    }
892
893    // Test 25: Multiple ticks preserve order
894    #[test]
895    fn test_multiple_ticks_ordering() {
896        let clock = LamportClock::new();
897
898        let timestamps: Vec<u64> = (0..100).map(|_| clock.tick()).collect();
899
900        // Verify all timestamps are strictly increasing
901        for i in 1..timestamps.len() {
902            assert!(timestamps[i] > timestamps[i - 1]);
903        }
904    }
905}
906
907// Kani formal verification proofs
908#[cfg(kani)]
909mod kani_proofs {
910    use super::*;
911
912    /// Prove tick() always returns a strictly increasing value
913    #[kani::proof]
914    fn proof_tick_monotonicity() {
915        let clock = LamportClock::new();
916        let ts1 = clock.tick();
917        let ts2 = clock.tick();
918        kani::assert(ts2 > ts1, "tick must be strictly monotonic");
919    }
920
921    /// Prove sync() result is always greater than both local and remote timestamps
922    #[kani::proof]
923    fn proof_sync_dominates() {
924        let remote: u64 = kani::any();
925        kani::assume(remote < u64::MAX - 1);
926        let clock = LamportClock::new();
927        let result = clock.sync(remote);
928        kani::assert(result > remote, "sync result must exceed remote timestamp");
929    }
930
931    /// Prove happens_before is irreflexive: not (a -> a)
932    #[kani::proof]
933    fn proof_happens_before_irreflexive() {
934        let a: u64 = kani::any();
935        kani::assert(
936            !LamportClock::happens_before(a, a),
937            "happens-before must be irreflexive",
938        );
939    }
940
941    /// Prove happens_before is transitive: (a -> b) && (b -> c) => (a -> c)
942    #[kani::proof]
943    fn proof_happens_before_transitive() {
944        let a: u64 = kani::any();
945        let b: u64 = kani::any();
946        let c: u64 = kani::any();
947        kani::assume(LamportClock::happens_before(a, b));
948        kani::assume(LamportClock::happens_before(b, c));
949        kani::assert(
950            LamportClock::happens_before(a, c),
951            "happens-before must be transitive",
952        );
953    }
954
955    /// Prove is_sampled only depends on bit 0 of trace_flags
956    #[kani::proof]
957    fn proof_is_sampled_bit0() {
958        let flags: u8 = kani::any();
959        let ctx = TraceContext {
960            version: 0,
961            trace_id: [1; 16],
962            parent_id: [1; 8],
963            trace_flags: flags,
964        };
965        kani::assert(
966            ctx.is_sampled() == (flags & 0x01 != 0),
967            "is_sampled must check bit 0",
968        );
969    }
970}