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 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]
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]
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]
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]
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]
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]
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
496use std::sync::atomic::{AtomicU64, Ordering};
501
502#[derive(Debug)]
516pub struct LamportClock {
517 counter: AtomicU64,
518}
519
520impl LamportClock {
521 pub fn new() -> Self {
523 LamportClock {
524 counter: AtomicU64::new(0),
525 }
526 }
527
528 pub fn with_initial_value(initial: u64) -> Self {
530 LamportClock {
531 counter: AtomicU64::new(initial),
532 }
533 }
534
535 pub fn tick(&self) -> u64 {
540 self.counter.fetch_add(1, Ordering::SeqCst) + 1
541 }
542
543 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, }
561 }
562 }
563
564 pub fn now(&self) -> u64 {
566 self.counter.load(Ordering::SeqCst)
567 }
568
569 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
593static_assertions::assert_impl_all!(LamportClock: Send, Sync);
595
596#[cfg(test)]
601mod lamport_tests {
602 use super::*;
603
604 #[test]
606 fn test_clock_starts_at_zero() {
607 let clock = LamportClock::new();
608 assert_eq!(clock.now(), 0);
609 }
610
611 #[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]
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]
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]
639 fn test_sync_lower_remote() {
640 let clock = LamportClock::new();
641 clock.tick(); clock.tick(); clock.tick(); let new_ts = clock.sync(1); assert_eq!(new_ts, 4); }
648
649 #[test]
651 fn test_sync_higher_remote() {
652 let clock = LamportClock::new();
653 clock.tick(); let new_ts = clock.sync(10); assert_eq!(new_ts, 11); }
658
659 #[test]
661 fn test_sync_equal_remote() {
662 let clock = LamportClock::new();
663 clock.tick(); clock.tick(); clock.tick(); let new_ts = clock.sync(3); assert_eq!(new_ts, 4); }
670
671 #[test]
673 fn test_multiple_syncs() {
674 let clock = LamportClock::new();
675
676 let ts1 = clock.sync(5);
677 assert_eq!(ts1, 6); let ts2 = clock.sync(10);
680 assert_eq!(ts2, 11); let ts3 = clock.sync(8);
683 assert_eq!(ts3, 12); }
685
686 #[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]
700 fn test_now_readonly() {
701 let clock = LamportClock::new();
702 clock.tick(); assert_eq!(clock.now(), 1);
705 assert_eq!(clock.now(), 1); assert_eq!(clock.now(), 1);
707 }
708
709 #[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]
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]
727 fn test_clone_preserves_value() {
728 let clock1 = LamportClock::new();
729 clock1.tick();
730 clock1.tick();
731 clock1.tick(); let clock2 = clock1.clone();
734 assert_eq!(clock2.now(), 3);
735 }
736
737 #[test]
739 fn test_cloned_clocks_independent() {
740 let clock1 = LamportClock::new();
741 clock1.tick(); let clock2 = clock1.clone();
744
745 clock1.tick(); clock2.tick(); assert_eq!(clock1.now(), 2);
749 assert_eq!(clock2.now(), 2);
750
751 clock1.tick(); assert_eq!(clock1.now(), 3);
753 assert_eq!(clock2.now(), 2); }
755
756 #[test]
758 fn test_default_trait() {
759 let clock: LamportClock = Default::default();
760 assert_eq!(clock.now(), 0);
761 }
762
763 #[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 let _ts = clock.tick();
772 }
773
774 #[test]
776 fn test_sync_updates_clock() {
777 let clock = LamportClock::new();
778 clock.sync(100);
779
780 assert_eq!(clock.now(), 101);
782 }
783
784 #[test]
786 fn test_transitivity_property() {
787 let a = 1u64;
788 let b = 5u64;
789 let c = 10u64;
790
791 assert!(LamportClock::happens_before(a, b));
793 assert!(LamportClock::happens_before(b, c));
794 assert!(LamportClock::happens_before(a, c));
795 }
796
797 #[test]
799 fn test_irreflexivity_property() {
800 let a = 5u64;
801
802 assert!(!LamportClock::happens_before(a, a));
804 }
805
806 #[test]
808 fn test_timestamp_consistency() {
809 let clock = LamportClock::new();
810
811 let ts1 = clock.tick();
812 let ts2 = clock.tick();
813
814 assert!(LamportClock::happens_before(ts1, ts2));
816 assert!(ts1 < ts2);
817 }
818
819 #[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 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 assert_eq!(clock.now(), 100);
845 }
846
847 #[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 for i in 0..5 {
858 let clock_clone = Arc::clone(&clock);
859 let remote_ts = (i as u64 + 1) * 10; 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 assert!(clock.now() >= 51);
872 }
873
874 #[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]
884 fn test_sync_with_zero() {
885 let clock = LamportClock::new();
886 let ts = clock.sync(0);
887 assert_eq!(ts, 1); }
889
890 #[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 for i in 1..timestamps.len() {
899 assert!(timestamps[i] > timestamps[i - 1]);
900 }
901 }
902}
903
904#[cfg(kani)]
906mod kani_proofs {
907 use super::*;
908
909 #[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 #[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 #[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 #[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 #[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}