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    fn test_from_env_traceparent() {
410        std::env::set_var(
411            "TRACEPARENT",
412            "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
413        );
414
415        let ctx = TraceContext::from_env();
416        assert!(ctx.is_some());
417
418        let ctx = ctx.expect("test");
419        assert_eq!(ctx.version, 0);
420        assert!(ctx.is_sampled());
421
422        std::env::remove_var("TRACEPARENT");
423    }
424
425    // Test 20: from_env() with OTEL_TRACEPARENT set
426    #[test]
427    fn test_from_env_otel_traceparent() {
428        std::env::remove_var("TRACEPARENT");
429        std::env::set_var(
430            "OTEL_TRACEPARENT",
431            "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00",
432        );
433
434        let ctx = TraceContext::from_env();
435        assert!(ctx.is_some());
436
437        let ctx = ctx.expect("test");
438        assert_eq!(ctx.version, 0);
439        assert!(!ctx.is_sampled());
440
441        std::env::remove_var("OTEL_TRACEPARENT");
442    }
443
444    // Test 21: from_env() with no env var set
445    #[test]
446    fn test_from_env_missing() {
447        std::env::remove_var("TRACEPARENT");
448        std::env::remove_var("OTEL_TRACEPARENT");
449
450        let ctx = TraceContext::from_env();
451        assert!(ctx.is_none());
452    }
453
454    // Test 22: from_env() with invalid format
455    #[test]
456    fn test_from_env_invalid_format() {
457        std::env::set_var("TRACEPARENT", "INVALID");
458
459        let ctx = TraceContext::from_env();
460        assert!(ctx.is_none());
461
462        std::env::remove_var("TRACEPARENT");
463    }
464
465    // Test 23: Clone trait
466    #[test]
467    fn test_trace_context_clone() {
468        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
469        let ctx1 = TraceContext::parse(traceparent).expect("test");
470        let ctx2 = ctx1.clone();
471
472        assert_eq!(ctx1, ctx2);
473    }
474
475    // Test 24: Debug trait
476    #[test]
477    fn test_trace_context_debug() {
478        let traceparent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";
479        let ctx = TraceContext::parse(traceparent).expect("test");
480
481        let debug_str = format!("{:?}", ctx);
482        assert!(debug_str.contains("TraceContext"));
483    }
484
485    // Test 25: Error Display trait
486    #[test]
487    fn test_error_display() {
488        let err = TraceContextError::InvalidFormat;
489        assert!(err.to_string().contains("Invalid traceparent format"));
490
491        let err = TraceContextError::AllZeroTraceId;
492        assert!(err.to_string().contains("all zeros"));
493    }
494}
495
496// ============================================================================
497// LAMPORT CLOCK IMPLEMENTATION (Specification Section 6.2)
498// ============================================================================
499
500use std::sync::atomic::{AtomicU64, Ordering};
501
502/// Lamport Clock for happens-before ordering
503///
504/// Implements Lamport's logical clocks for establishing causal relationships
505/// between events in a distributed tracing system.
506///
507/// Reference: "Time, Clocks, and the Ordering of Events in a Distributed System"
508/// Lamport, L. Communications of the ACM, 21(7), 558-565 (1978)
509///
510/// # Properties
511///
512/// 1. **Transitivity**: a → b ∧ b → c ⇒ a → c
513/// 2. **Irreflexivity**: ¬(a → a)
514/// 3. **Timestamp consistency**: a → b ⇒ timestamp(a) < timestamp(b)
515#[derive(Debug)]
516pub struct LamportClock {
517    counter: AtomicU64,
518}
519
520impl LamportClock {
521    /// Create a new Lamport clock starting at 0
522    pub fn new() -> Self {
523        LamportClock {
524            counter: AtomicU64::new(0),
525        }
526    }
527
528    /// Create a Lamport clock with a specific starting value
529    pub fn with_initial_value(initial: u64) -> Self {
530        LamportClock {
531            counter: AtomicU64::new(initial),
532        }
533    }
534
535    /// Increment clock on local event
536    ///
537    /// Returns the new timestamp after increment.
538    /// Uses `SeqCst` ordering to ensure happens-before consistency.
539    pub fn tick(&self) -> u64 {
540        self.counter.fetch_add(1, Ordering::SeqCst) + 1
541    }
542
543    /// Synchronize clock on message receive
544    ///
545    /// Updates local clock to max(local, remote) + 1.
546    /// Ensures received messages have higher timestamps than their causally preceding events.
547    pub fn sync(&self, remote_timestamp: u64) -> u64 {
548        loop {
549            let current = self.counter.load(Ordering::SeqCst);
550            let new_value = current.max(remote_timestamp) + 1;
551
552            match self.counter.compare_exchange(
553                current,
554                new_value,
555                Ordering::SeqCst,
556                Ordering::SeqCst,
557            ) {
558                Ok(_) => return new_value,
559                Err(_) => continue, // Retry on concurrent modification
560            }
561        }
562    }
563
564    /// Get current clock value without modifying it
565    pub fn now(&self) -> u64 {
566        self.counter.load(Ordering::SeqCst)
567    }
568
569    /// Check if timestamp a happens-before timestamp b
570    ///
571    /// Returns true if a < b (strict temporal ordering).
572    /// Note: This is a simple timestamp comparison. Full happens-before
573    /// semantics require checking causal chains (see `UnifiedTrace`).
574    pub fn happens_before(a: u64, b: u64) -> bool {
575        a < b
576    }
577}
578
579impl Default for LamportClock {
580    fn default() -> Self {
581        Self::new()
582    }
583}
584
585impl Clone for LamportClock {
586    fn clone(&self) -> Self {
587        LamportClock {
588            counter: AtomicU64::new(self.counter.load(Ordering::SeqCst)),
589        }
590    }
591}
592
593// Compile-time thread-safety verification for LamportClock
594static_assertions::assert_impl_all!(LamportClock: Send, Sync);
595
596// ============================================================================
597// LAMPORT CLOCK TESTS (EXTREME TDD)
598// ============================================================================
599
600#[cfg(test)]
601mod lamport_tests {
602    use super::*;
603
604    // Test 1: Clock starts at 0
605    #[test]
606    fn test_clock_starts_at_zero() {
607        let clock = LamportClock::new();
608        assert_eq!(clock.now(), 0);
609    }
610
611    // Test 2: Clock starts at custom value
612    #[test]
613    fn test_clock_with_initial_value() {
614        let clock = LamportClock::with_initial_value(100);
615        assert_eq!(clock.now(), 100);
616    }
617
618    // Test 3: Tick increments by 1
619    #[test]
620    fn test_tick_increments() {
621        let clock = LamportClock::new();
622        assert_eq!(clock.tick(), 1);
623        assert_eq!(clock.tick(), 2);
624        assert_eq!(clock.tick(), 3);
625    }
626
627    // Test 4: Tick returns incremented value
628    #[test]
629    fn test_tick_return_value() {
630        let clock = LamportClock::new();
631        let ts1 = clock.tick();
632        let ts2 = clock.tick();
633        assert!(ts2 > ts1);
634        assert_eq!(ts2, ts1 + 1);
635    }
636
637    // Test 5: Sync with lower remote timestamp
638    #[test]
639    fn test_sync_lower_remote() {
640        let clock = LamportClock::new();
641        clock.tick(); // local = 1
642        clock.tick(); // local = 2
643        clock.tick(); // local = 3
644
645        let new_ts = clock.sync(1); // remote = 1 < local = 3
646        assert_eq!(new_ts, 4); // max(3, 1) + 1 = 4
647    }
648
649    // Test 6: Sync with higher remote timestamp
650    #[test]
651    fn test_sync_higher_remote() {
652        let clock = LamportClock::new();
653        clock.tick(); // local = 1
654
655        let new_ts = clock.sync(10); // remote = 10 > local = 1
656        assert_eq!(new_ts, 11); // max(1, 10) + 1 = 11
657    }
658
659    // Test 7: Sync with equal remote timestamp
660    #[test]
661    fn test_sync_equal_remote() {
662        let clock = LamportClock::new();
663        clock.tick(); // local = 1
664        clock.tick(); // local = 2
665        clock.tick(); // local = 3
666
667        let new_ts = clock.sync(3); // remote = 3 == local = 3
668        assert_eq!(new_ts, 4); // max(3, 3) + 1 = 4
669    }
670
671    // Test 8: Multiple syncs in sequence
672    #[test]
673    fn test_multiple_syncs() {
674        let clock = LamportClock::new();
675
676        let ts1 = clock.sync(5);
677        assert_eq!(ts1, 6); // max(0, 5) + 1
678
679        let ts2 = clock.sync(10);
680        assert_eq!(ts2, 11); // max(6, 10) + 1
681
682        let ts3 = clock.sync(8);
683        assert_eq!(ts3, 12); // max(11, 8) + 1
684    }
685
686    // Test 9: Interleaved ticks and syncs
687    #[test]
688    fn test_interleaved_operations() {
689        let clock = LamportClock::new();
690
691        assert_eq!(clock.tick(), 1);
692        assert_eq!(clock.sync(5), 6);
693        assert_eq!(clock.tick(), 7);
694        assert_eq!(clock.tick(), 8);
695        assert_eq!(clock.sync(10), 11);
696    }
697
698    // Test 10: now() doesn't modify clock
699    #[test]
700    fn test_now_readonly() {
701        let clock = LamportClock::new();
702        clock.tick(); // local = 1
703
704        assert_eq!(clock.now(), 1);
705        assert_eq!(clock.now(), 1); // Still 1, not incremented
706        assert_eq!(clock.now(), 1);
707    }
708
709    // Test 11: happens_before with a < b
710    #[test]
711    fn test_happens_before_true() {
712        assert!(LamportClock::happens_before(1, 2));
713        assert!(LamportClock::happens_before(10, 20));
714        assert!(LamportClock::happens_before(0, 1));
715    }
716
717    // Test 12: happens_before with a >= b
718    #[test]
719    fn test_happens_before_false() {
720        assert!(!LamportClock::happens_before(2, 1));
721        assert!(!LamportClock::happens_before(5, 5));
722        assert!(!LamportClock::happens_before(10, 5));
723    }
724
725    // Test 13: Clone preserves value
726    #[test]
727    fn test_clone_preserves_value() {
728        let clock1 = LamportClock::new();
729        clock1.tick();
730        clock1.tick();
731        clock1.tick(); // clock1 = 3
732
733        let clock2 = clock1.clone();
734        assert_eq!(clock2.now(), 3);
735    }
736
737    // Test 14: Cloned clocks are independent
738    #[test]
739    fn test_cloned_clocks_independent() {
740        let clock1 = LamportClock::new();
741        clock1.tick(); // clock1 = 1
742
743        let clock2 = clock1.clone();
744
745        clock1.tick(); // clock1 = 2
746        clock2.tick(); // clock2 = 2
747
748        assert_eq!(clock1.now(), 2);
749        assert_eq!(clock2.now(), 2);
750
751        clock1.tick(); // clock1 = 3
752        assert_eq!(clock1.now(), 3);
753        assert_eq!(clock2.now(), 2); // clock2 unchanged
754    }
755
756    // Test 15: Default trait
757    #[test]
758    fn test_default_trait() {
759        let clock: LamportClock = Default::default();
760        assert_eq!(clock.now(), 0);
761    }
762
763    // Test 16: Large timestamp values
764    #[test]
765    fn test_large_timestamps() {
766        let clock = LamportClock::with_initial_value(u64::MAX - 10);
767        assert_eq!(clock.now(), u64::MAX - 10);
768
769        // Note: This will overflow, which is acceptable for Lamport clocks
770        // In production, we'd handle overflow gracefully
771        let _ts = clock.tick();
772    }
773
774    // Test 17: Sync updates clock correctly
775    #[test]
776    fn test_sync_updates_clock() {
777        let clock = LamportClock::new();
778        clock.sync(100);
779
780        // After sync, local clock should be 101
781        assert_eq!(clock.now(), 101);
782    }
783
784    // Test 18: Transitivity property
785    #[test]
786    fn test_transitivity_property() {
787        let a = 1u64;
788        let b = 5u64;
789        let c = 10u64;
790
791        // If a → b and b → c, then a → c
792        assert!(LamportClock::happens_before(a, b));
793        assert!(LamportClock::happens_before(b, c));
794        assert!(LamportClock::happens_before(a, c));
795    }
796
797    // Test 19: Irreflexivity property
798    #[test]
799    fn test_irreflexivity_property() {
800        let a = 5u64;
801
802        // ¬(a → a): An event cannot happen before itself
803        assert!(!LamportClock::happens_before(a, a));
804    }
805
806    // Test 20: Timestamp consistency
807    #[test]
808    fn test_timestamp_consistency() {
809        let clock = LamportClock::new();
810
811        let ts1 = clock.tick();
812        let ts2 = clock.tick();
813
814        // If event1 happens before event2, then ts1 < ts2
815        assert!(LamportClock::happens_before(ts1, ts2));
816        assert!(ts1 < ts2);
817    }
818
819    // Test 21: Concurrent operations (simulated)
820    #[test]
821    fn test_concurrent_ticks() {
822        use std::sync::Arc;
823        use std::thread;
824
825        let clock = Arc::new(LamportClock::new());
826        let mut handles = vec![];
827
828        // Spawn 10 threads, each doing 10 ticks
829        for _ in 0..10 {
830            let clock_clone = Arc::clone(&clock);
831            let handle = thread::spawn(move || {
832                for _ in 0..10 {
833                    clock_clone.tick();
834                }
835            });
836            handles.push(handle);
837        }
838
839        for handle in handles {
840            handle.join().expect("test");
841        }
842
843        // After 10 threads × 10 ticks = 100 total ticks
844        assert_eq!(clock.now(), 100);
845    }
846
847    // Test 22: Concurrent syncs (simulated)
848    #[test]
849    fn test_concurrent_syncs() {
850        use std::sync::Arc;
851        use std::thread;
852
853        let clock = Arc::new(LamportClock::new());
854        let mut handles = vec![];
855
856        // Spawn 5 threads, each syncing with increasing remote timestamps
857        for i in 0..5 {
858            let clock_clone = Arc::clone(&clock);
859            let remote_ts = (i as u64 + 1) * 10; // 10, 20, 30, 40, 50
860            let handle = thread::spawn(move || {
861                clock_clone.sync(remote_ts);
862            });
863            handles.push(handle);
864        }
865
866        for handle in handles {
867            handle.join().expect("test");
868        }
869
870        // Final value should be at least 51 (max remote + 1)
871        assert!(clock.now() >= 51);
872    }
873
874    // Test 23: Debug trait
875    #[test]
876    fn test_debug_trait() {
877        let clock = LamportClock::new();
878        let debug_str = format!("{:?}", clock);
879        assert!(debug_str.contains("LamportClock"));
880    }
881
882    // Test 24: Sync with zero timestamp
883    #[test]
884    fn test_sync_with_zero() {
885        let clock = LamportClock::new();
886        let ts = clock.sync(0);
887        assert_eq!(ts, 1); // max(0, 0) + 1 = 1
888    }
889
890    // Test 25: Multiple ticks preserve order
891    #[test]
892    fn test_multiple_ticks_ordering() {
893        let clock = LamportClock::new();
894
895        let timestamps: Vec<u64> = (0..100).map(|_| clock.tick()).collect();
896
897        // Verify all timestamps are strictly increasing
898        for i in 1..timestamps.len() {
899            assert!(timestamps[i] > timestamps[i - 1]);
900        }
901    }
902}
903
904// Kani formal verification proofs
905#[cfg(kani)]
906mod kani_proofs {
907    use super::*;
908
909    /// Prove tick() always returns a strictly increasing value
910    #[kani::proof]
911    fn proof_tick_monotonicity() {
912        let clock = LamportClock::new();
913        let ts1 = clock.tick();
914        let ts2 = clock.tick();
915        kani::assert(ts2 > ts1, "tick must be strictly monotonic");
916    }
917
918    /// Prove sync() result is always greater than both local and remote timestamps
919    #[kani::proof]
920    fn proof_sync_dominates() {
921        let remote: u64 = kani::any();
922        kani::assume(remote < u64::MAX - 1);
923        let clock = LamportClock::new();
924        let result = clock.sync(remote);
925        kani::assert(result > remote, "sync result must exceed remote timestamp");
926    }
927
928    /// Prove happens_before is irreflexive: not (a -> a)
929    #[kani::proof]
930    fn proof_happens_before_irreflexive() {
931        let a: u64 = kani::any();
932        kani::assert(
933            !LamportClock::happens_before(a, a),
934            "happens-before must be irreflexive",
935        );
936    }
937
938    /// Prove happens_before is transitive: (a -> b) && (b -> c) => (a -> c)
939    #[kani::proof]
940    fn proof_happens_before_transitive() {
941        let a: u64 = kani::any();
942        let b: u64 = kani::any();
943        let c: u64 = kani::any();
944        kani::assume(LamportClock::happens_before(a, b));
945        kani::assume(LamportClock::happens_before(b, c));
946        kani::assert(
947            LamportClock::happens_before(a, c),
948            "happens-before must be transitive",
949        );
950    }
951
952    /// Prove is_sampled only depends on bit 0 of trace_flags
953    #[kani::proof]
954    fn proof_is_sampled_bit0() {
955        let flags: u8 = kani::any();
956        let ctx = TraceContext {
957            version: 0,
958            trace_id: [1; 16],
959            parent_id: [1; 8],
960            trace_flags: flags,
961        };
962        kani::assert(
963            ctx.is_sampled() == (flags & 0x01 != 0),
964            "is_sampled must check bit 0",
965        );
966    }
967}