1use std::fmt;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct TraceContext {
17 pub version: u8,
18 pub trace_id: [u8; 16], pub parent_id: [u8; 8], pub trace_flags: u8, }
22
23impl TraceContext {
24 pub fn parse(traceparent: &str) -> Result<Self, TraceContextError> {
29 let parts: Vec<&str> = traceparent.split('-').collect();
31 if parts.len() != 4 {
32 return Err(TraceContextError::InvalidFormat);
33 }
34
35 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 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 if trace_id.iter().all(|&b| b == 0) {
50 return Err(TraceContextError::AllZeroTraceId);
51 }
52
53 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 if parent_id.iter().all(|&b| b == 0) {
61 return Err(TraceContextError::AllZeroParentId);
62 }
63
64 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 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 pub fn set_env(&self) {
94 std::env::set_var("TRACEPARENT", self.to_string());
95 }
96
97 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 pub fn set_logical_clock_env(timestamp: u64) {
112 std::env::set_var("RENACER_LOGICAL_CLOCK", timestamp.to_string());
113 }
114
115 pub fn is_sampled(&self) -> bool {
117 self.trace_flags & 0x01 != 0
118 }
119
120 #[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 #[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#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum TraceContextError {
149 InvalidFormat,
151 InvalidVersion,
153 InvalidTraceId,
155 InvalidParentId,
157 InvalidTraceFlags,
159 AllZeroTraceId,
161 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
186fn 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
220static_assertions::assert_impl_all!(TraceContext: Send, Sync);
222static_assertions::assert_impl_all!(TraceContextError: Send, Sync);
223
224#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[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 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 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]
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]
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]
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]
285 fn test_parse_invalid_format_empty() {
286 let result = TraceContext::parse("");
287 assert_eq!(result, Err(TraceContextError::InvalidFormat));
288 }
289
290 #[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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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
499use std::sync::atomic::{AtomicU64, Ordering};
504
505#[derive(Debug)]
519pub struct LamportClock {
520 counter: AtomicU64,
521}
522
523impl LamportClock {
524 pub fn new() -> Self {
526 LamportClock {
527 counter: AtomicU64::new(0),
528 }
529 }
530
531 pub fn with_initial_value(initial: u64) -> Self {
533 LamportClock {
534 counter: AtomicU64::new(initial),
535 }
536 }
537
538 pub fn tick(&self) -> u64 {
543 self.counter.fetch_add(1, Ordering::SeqCst) + 1
544 }
545
546 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, }
564 }
565 }
566
567 pub fn now(&self) -> u64 {
569 self.counter.load(Ordering::SeqCst)
570 }
571
572 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
596static_assertions::assert_impl_all!(LamportClock: Send, Sync);
598
599#[cfg(test)]
604mod lamport_tests {
605 use super::*;
606
607 #[test]
609 fn test_clock_starts_at_zero() {
610 let clock = LamportClock::new();
611 assert_eq!(clock.now(), 0);
612 }
613
614 #[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]
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]
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]
642 fn test_sync_lower_remote() {
643 let clock = LamportClock::new();
644 clock.tick(); clock.tick(); clock.tick(); let new_ts = clock.sync(1); assert_eq!(new_ts, 4); }
651
652 #[test]
654 fn test_sync_higher_remote() {
655 let clock = LamportClock::new();
656 clock.tick(); let new_ts = clock.sync(10); assert_eq!(new_ts, 11); }
661
662 #[test]
664 fn test_sync_equal_remote() {
665 let clock = LamportClock::new();
666 clock.tick(); clock.tick(); clock.tick(); let new_ts = clock.sync(3); assert_eq!(new_ts, 4); }
673
674 #[test]
676 fn test_multiple_syncs() {
677 let clock = LamportClock::new();
678
679 let ts1 = clock.sync(5);
680 assert_eq!(ts1, 6); let ts2 = clock.sync(10);
683 assert_eq!(ts2, 11); let ts3 = clock.sync(8);
686 assert_eq!(ts3, 12); }
688
689 #[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]
703 fn test_now_readonly() {
704 let clock = LamportClock::new();
705 clock.tick(); assert_eq!(clock.now(), 1);
708 assert_eq!(clock.now(), 1); assert_eq!(clock.now(), 1);
710 }
711
712 #[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]
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]
730 fn test_clone_preserves_value() {
731 let clock1 = LamportClock::new();
732 clock1.tick();
733 clock1.tick();
734 clock1.tick(); let clock2 = clock1.clone();
737 assert_eq!(clock2.now(), 3);
738 }
739
740 #[test]
742 fn test_cloned_clocks_independent() {
743 let clock1 = LamportClock::new();
744 clock1.tick(); let clock2 = clock1.clone();
747
748 clock1.tick(); clock2.tick(); assert_eq!(clock1.now(), 2);
752 assert_eq!(clock2.now(), 2);
753
754 clock1.tick(); assert_eq!(clock1.now(), 3);
756 assert_eq!(clock2.now(), 2); }
758
759 #[test]
761 fn test_default_trait() {
762 let clock: LamportClock = Default::default();
763 assert_eq!(clock.now(), 0);
764 }
765
766 #[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 let _ts = clock.tick();
775 }
776
777 #[test]
779 fn test_sync_updates_clock() {
780 let clock = LamportClock::new();
781 clock.sync(100);
782
783 assert_eq!(clock.now(), 101);
785 }
786
787 #[test]
789 fn test_transitivity_property() {
790 let a = 1u64;
791 let b = 5u64;
792 let c = 10u64;
793
794 assert!(LamportClock::happens_before(a, b));
796 assert!(LamportClock::happens_before(b, c));
797 assert!(LamportClock::happens_before(a, c));
798 }
799
800 #[test]
802 fn test_irreflexivity_property() {
803 let a = 5u64;
804
805 assert!(!LamportClock::happens_before(a, a));
807 }
808
809 #[test]
811 fn test_timestamp_consistency() {
812 let clock = LamportClock::new();
813
814 let ts1 = clock.tick();
815 let ts2 = clock.tick();
816
817 assert!(LamportClock::happens_before(ts1, ts2));
819 assert!(ts1 < ts2);
820 }
821
822 #[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 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 assert_eq!(clock.now(), 100);
848 }
849
850 #[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 for i in 0..5 {
861 let clock_clone = Arc::clone(&clock);
862 let remote_ts = (i as u64 + 1) * 10; 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 assert!(clock.now() >= 51);
875 }
876
877 #[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]
887 fn test_sync_with_zero() {
888 let clock = LamportClock::new();
889 let ts = clock.sync(0);
890 assert_eq!(ts, 1); }
892
893 #[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 for i in 1..timestamps.len() {
902 assert!(timestamps[i] > timestamps[i - 1]);
903 }
904 }
905}
906
907#[cfg(kani)]
909mod kani_proofs {
910 use super::*;
911
912 #[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 #[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 #[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 #[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 #[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}