Skip to main content

supercov_engine/
rust_probe_transport.rs

1//! Bounded, lock-free, file-backed transport for owned Rust probe events.
2//!
3//! The supervisor creates and authenticates the fixed-layout file before a
4//! test starts. Target code maps that file and publishes variable-length
5//! records through fixed descriptors. A release-store commit byte makes every
6//! complete descriptor independently recoverable even if another writer or
7//! the whole process dies midway through a later record.
8
9use std::{
10    collections::{BTreeMap, BTreeSet},
11    fs::{self, File, OpenOptions},
12    path::Path,
13    sync::atomic::{AtomicU8, AtomicU64, Ordering},
14};
15
16use memmap2::{Mmap, MmapMut, MmapOptions};
17use serde::{Deserialize, Serialize};
18
19use crate::rust_runtime::{RustProbeObservation, valid_probe_id};
20
21pub const RUST_TRANSPORT_ENV: &str = "SUPERCOV_RUST_TRANSPORT_FILE";
22pub const RUST_TRANSPORT_TOKEN_ENV: &str = "SUPERCOV_RUST_TRANSPORT_TOKEN";
23pub const RUST_CONTEXT_ENV: &str = "SUPERCOV_RUST_CONTEXT_ID";
24
25const MAGIC: &[u8; 8] = b"SCVRUST3";
26const VERSION: u32 = 3;
27const HEADER_SIZE: usize = 128;
28const DESCRIPTOR_SIZE: usize = 40;
29const ENDIAN_MARKER: u32 = 0x0102_0304;
30const NEXT_DESCRIPTOR_OFFSET: usize = 32;
31const NEXT_PAYLOAD_OFFSET: usize = 40;
32const DROPPED_OFFSET: usize = 48;
33const TOKEN_OFFSET: usize = 56;
34const TOKEN_SIZE: usize = 16;
35const ATTACHMENTS_OFFSET: usize = 72;
36const NEXT_PHASE_OFFSET: usize = 80;
37
38const COMMIT_OFFSET: usize = 0;
39const KIND_OFFSET: usize = 1;
40const OUTCOME_OFFSET: usize = 2;
41const PID_OFFSET: usize = 4;
42const CONTEXT_OFFSET: usize = 8;
43const PAYLOAD_OFFSET_OFFSET: usize = 16;
44const PAYLOAD_LENGTH_OFFSET: usize = 20;
45const ID_LENGTH_OFFSET: usize = 24;
46const VALUE_LENGTH_OFFSET: usize = 28;
47const CHECKSUM_OFFSET: usize = 32;
48
49const KIND_HIT: u8 = 1;
50const KIND_DECISION: u8 = 2;
51const KIND_ORDINAL_HIT: u8 = 3;
52const KIND_PHASE: u8 = 4;
53const KIND_THREAD_PHASE: u8 = 5;
54const KIND_THREAD_END: u8 = 6;
55const KIND_TEST_BOUNDARY: u8 = 7;
56
57const RUNTIME_TEMPLATE: &str = include_str!("../runtime-assets/rust-mmap-runtime.rs");
58
59#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
60#[serde(rename_all = "camelCase", deny_unknown_fields)]
61pub struct RustTransportRead {
62    pub observations: Vec<RustTransportObservation>,
63    pub ordinal_hits: Vec<RustOrdinalHit>,
64    pub phases: Vec<RustPhaseContext>,
65    pub thread_phases: Vec<RustThreadPhase>,
66    pub thread_ends: Vec<RustThreadEnd>,
67    pub test_boundaries: Vec<RustTestBoundary>,
68    pub committed: u64,
69    pub incomplete: u64,
70    pub dropped: u64,
71    pub attachments: u64,
72}
73
74impl RustTransportRead {
75    pub fn empty() -> Self {
76        Self {
77            observations: Vec::new(),
78            ordinal_hits: Vec::new(),
79            phases: Vec::new(),
80            thread_phases: Vec::new(),
81            thread_ends: Vec::new(),
82            test_boundaries: Vec::new(),
83            committed: 0,
84            incomplete: 0,
85            dropped: 0,
86            attachments: 0,
87        }
88    }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
92#[serde(rename_all = "camelCase", deny_unknown_fields)]
93pub struct RustTransportObservation {
94    pub process_id: u32,
95    pub context_id: u64,
96    pub observation: RustProbeObservation,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
100#[serde(rename_all = "camelCase", deny_unknown_fields)]
101pub struct RustOrdinalHit {
102    pub process_id: u32,
103    pub context_id: u64,
104    pub ordinal: u64,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
108#[serde(rename_all = "camelCase", deny_unknown_fields)]
109pub struct RustPhaseContext {
110    pub process_id: u32,
111    pub child_context_id: u64,
112    pub parent_context_id: u64,
113    pub invocation_nonce: u64,
114    pub decision_id: String,
115}
116
117/// An inherited native thread's derived phase context definition. The
118/// `commit_index` is the record's global transport descriptor index, which is
119/// the total order used by the join-bounded acceptance rule.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
121#[serde(rename_all = "camelCase", deny_unknown_fields)]
122pub struct RustThreadPhase {
123    pub process_id: u32,
124    pub child_context_id: u64,
125    pub parent_context_id: u64,
126    pub invocation_nonce: u64,
127    pub commit_index: u64,
128}
129
130/// The end-of-thread record bounding one thread phase in commit order.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
132#[serde(rename_all = "camelCase", deny_unknown_fields)]
133pub struct RustThreadEnd {
134    pub process_id: u32,
135    pub context_id: u64,
136    pub commit_index: u64,
137}
138
139/// The exact-test boundary record committed when a test context is exited.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
141#[serde(rename_all = "camelCase", deny_unknown_fields)]
142pub struct RustTestBoundary {
143    pub process_id: u32,
144    pub context_id: u64,
145    pub commit_index: u64,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct RustTransportPartition {
150    pub attributed: BTreeMap<u64, RustTransportRead>,
151    pub background: RustTransportRead,
152    /// One entry per thread phase whose lifetime escaped its root test; every
153    /// record under such a chain is deterministic background evidence.
154    pub thread_scope_limitations: BTreeSet<String>,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum RustTransportError {
159    Io(String),
160    UnsafeFile(String),
161    InvalidHeader,
162    InvalidLength,
163    InvalidDescriptor(u64),
164    InvalidRecord(u64),
165    InvalidAssertionContext(String),
166    InvalidAttribution(String),
167}
168
169impl std::fmt::Display for RustTransportError {
170    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        match self {
172            Self::Io(error) => write!(formatter, "Rust transport I/O failed: {error}"),
173            Self::UnsafeFile(path) => write!(formatter, "unsafe Rust transport file: {path}"),
174            Self::InvalidHeader => write!(formatter, "invalid Rust transport header"),
175            Self::InvalidLength => write!(formatter, "invalid Rust transport length"),
176            Self::InvalidDescriptor(index) => {
177                write!(formatter, "invalid Rust transport descriptor {index}")
178            }
179            Self::InvalidRecord(index) => {
180                write!(formatter, "invalid Rust transport record {index}")
181            }
182            Self::InvalidAssertionContext(reason) => {
183                write!(formatter, "invalid Rust assertion context: {reason}")
184            }
185            Self::InvalidAttribution(reason) => {
186                write!(formatter, "invalid Rust transport attribution: {reason}")
187            }
188        }
189    }
190}
191
192impl std::error::Error for RustTransportError {}
193
194pub fn rust_assertion_context_id(
195    parent: u64,
196    decision_id: &str,
197    invocation_nonce: u64,
198) -> Result<u64, RustTransportError> {
199    if parent == 0 {
200        return Ok(0);
201    }
202    if parent == u64::MAX {
203        return Err(RustTransportError::InvalidAssertionContext(
204            "the reserved nesting sentinel cannot be a parent".into(),
205        ));
206    }
207    let digest = decision_id
208        .strip_prefix("rs:decision:")
209        .filter(|digest| digest.len() == 24 && digest.bytes().all(|byte| byte.is_ascii_hexdigit()))
210        .ok_or_else(|| {
211            RustTransportError::InvalidAssertionContext(format!(
212                "invalid decision ID {decision_id}"
213            ))
214        })?;
215    let id_high = u64::from_str_radix(&digest[..16], 16).map_err(|error| {
216        RustTransportError::InvalidAssertionContext(format!(
217            "invalid decision ID {decision_id}: {error}"
218        ))
219    })?;
220    let id_low = u32::from_str_radix(&digest[16..], 16).map_err(|error| {
221        RustTransportError::InvalidAssertionContext(format!(
222            "invalid decision ID {decision_id}: {error}"
223        ))
224    })?;
225    let mut value = 0xcbf2_9ce4_8422_2325_u64;
226    for byte in b"supercov-rust-assertion-phase-v2"
227        .iter()
228        .copied()
229        .chain(parent.to_le_bytes())
230        .chain(id_high.to_le_bytes())
231        .chain(id_low.to_le_bytes())
232        .chain(invocation_nonce.to_le_bytes())
233    {
234        value ^= u64::from(byte);
235        value = value.wrapping_mul(0x0000_0100_0000_01b3);
236    }
237    Ok(if matches!(value, 0 | u64::MAX) {
238        value ^ 0xa5a5_5a5a_d3c3_b4b4
239    } else {
240        value
241    })
242}
243
244/// Derive the exact thread-phase context for an inherited native thread. This
245/// mirrors the in-runtime derivation byte for byte so tampered thread-phase
246/// records fail authentication offline.
247pub fn rust_thread_context_id(parent: u64, invocation_nonce: u64) -> u64 {
248    let mut value = 0xcbf2_9ce4_8422_2325_u64;
249    for byte in b"supercov-rust-thread-phase-v1\0"
250        .iter()
251        .copied()
252        .chain(parent.to_le_bytes())
253        .chain(invocation_nonce.to_le_bytes())
254    {
255        value ^= u64::from(byte);
256        value = value.wrapping_mul(0x0000_0100_0000_01b3);
257    }
258    if matches!(value, 0 | u64::MAX) {
259        value ^ 0xa5a5_5a5a_d3c3_b4b4
260    } else {
261        value
262    }
263}
264
265fn put_u32(target: &mut [u8], offset: usize, value: u32) {
266    target[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
267}
268
269fn get_u32(source: &[u8], offset: usize) -> Option<u32> {
270    Some(u32::from_le_bytes(
271        source.get(offset..offset + 4)?.try_into().ok()?,
272    ))
273}
274
275fn get_u64(source: &[u8], offset: usize) -> Option<u64> {
276    Some(u64::from_le_bytes(
277        source.get(offset..offset + 8)?.try_into().ok()?,
278    ))
279}
280
281/// A field the reader already bounded: the header was checked whole, and
282/// the file was checked to be exactly as long as its descriptors and payload
283/// need, so a field inside either cannot run past the file.
284/// An eight-byte field of a record whose length was checked.
285fn le_u64(field: &[u8]) -> u64 {
286    u64::from_le_bytes(
287        field
288            .try_into()
289            .expect("the record was checked to hold this field"),
290    )
291}
292
293fn bounded_u32(source: &[u8], offset: usize) -> u32 {
294    get_u32(source, offset).expect("the file was checked to hold this field")
295}
296
297fn bounded_u64(source: &[u8], offset: usize) -> u64 {
298    get_u64(source, offset).expect("the file was checked to hold this field")
299}
300
301fn total_size(descriptors: u32, payload: u32) -> Option<usize> {
302    HEADER_SIZE
303        .checked_add(
304            usize::try_from(descriptors)
305                .ok()?
306                .checked_mul(DESCRIPTOR_SIZE)?,
307        )?
308        .checked_add(usize::try_from(payload).ok()?)
309}
310
311fn map_mut(file: &File) -> Result<MmapMut, RustTransportError> {
312    // SAFETY: this function owns the newly created file mapping and no slice
313    // alias is produced outside the returned MmapMut.
314    unsafe { MmapOptions::new().map_mut(file) }
315        .map_err(|error| RustTransportError::Io(error.to_string()))
316}
317
318fn map(file: &File) -> Result<Mmap, RustTransportError> {
319    // SAFETY: the immutable mapping is retained for the duration of all reads.
320    unsafe { MmapOptions::new().map(file) }
321        .map_err(|error| RustTransportError::Io(error.to_string()))
322}
323
324pub fn create_rust_transport(
325    path: &Path,
326    token: [u8; TOKEN_SIZE],
327    descriptor_capacity: u32,
328    payload_capacity: u32,
329) -> Result<(), RustTransportError> {
330    if descriptor_capacity == 0 || payload_capacity == 0 {
331        return Err(RustTransportError::InvalidLength);
332    }
333    let total = total_size(descriptor_capacity, payload_capacity)
334        .ok_or(RustTransportError::InvalidLength)?;
335    let mut options = OpenOptions::new();
336    options.read(true).write(true).create_new(true);
337    #[cfg(unix)]
338    {
339        use std::os::unix::fs::OpenOptionsExt as _;
340        options.mode(0o600);
341    }
342    let file = options
343        .open(path)
344        .map_err(|error| RustTransportError::Io(error.to_string()))?;
345    file.set_len(u64::try_from(total).map_err(|_| RustTransportError::InvalidLength)?)
346        .map_err(|error| RustTransportError::Io(error.to_string()))?;
347    let mut mapping = map_mut(&file)?;
348    mapping[..MAGIC.len()].copy_from_slice(MAGIC);
349    put_u32(&mut mapping, 8, VERSION);
350    put_u32(&mut mapping, 12, HEADER_SIZE as u32);
351    put_u32(&mut mapping, 16, DESCRIPTOR_SIZE as u32);
352    put_u32(&mut mapping, 20, descriptor_capacity);
353    put_u32(&mut mapping, 24, payload_capacity);
354    put_u32(&mut mapping, 28, ENDIAN_MARKER);
355    mapping[TOKEN_OFFSET..TOKEN_OFFSET + TOKEN_SIZE].copy_from_slice(&token);
356    mapping
357        .flush_range(0, HEADER_SIZE)
358        .map_err(|error| RustTransportError::Io(error.to_string()))
359}
360
361fn regular_file(path: &Path) -> Result<File, RustTransportError> {
362    let metadata =
363        fs::symlink_metadata(path).map_err(|error| RustTransportError::Io(error.to_string()))?;
364    if !metadata.file_type().is_file() {
365        return Err(RustTransportError::UnsafeFile(path.display().to_string()));
366    }
367    let mut options = OpenOptions::new();
368    options.read(true);
369    #[cfg(any(target_os = "linux", target_os = "macos"))]
370    {
371        use std::os::unix::fs::OpenOptionsExt as _;
372        #[cfg(target_os = "linux")]
373        const O_NOFOLLOW: i32 = 0x2_0000;
374        #[cfg(target_os = "macos")]
375        const O_NOFOLLOW: i32 = 0x100;
376        options.custom_flags(O_NOFOLLOW);
377    }
378    let file = options
379        .open(path)
380        .map_err(|error| RustTransportError::Io(error.to_string()))?;
381    if !file
382        .metadata()
383        .map_err(|error| RustTransportError::Io(error.to_string()))?
384        .file_type()
385        .is_file()
386    {
387        return Err(RustTransportError::UnsafeFile(path.display().to_string()));
388    }
389    Ok(file)
390}
391
392fn atomic_u64(mapping: &[u8], offset: usize) -> Result<&AtomicU64, RustTransportError> {
393    let pointer = mapping
394        .get(offset..offset + 8)
395        .ok_or(RustTransportError::InvalidHeader)?
396        .as_ptr();
397    if !(pointer as usize).is_multiple_of(std::mem::align_of::<AtomicU64>()) {
398        return Err(RustTransportError::InvalidHeader);
399    }
400    // SAFETY: the fixed layout guarantees alignment and the mapped bytes live
401    // at least as long as the returned reference.
402    Ok(unsafe { &*pointer.cast::<AtomicU64>() })
403}
404
405fn atomic_u8(mapping: &[u8], offset: usize) -> Result<&AtomicU8, RustTransportError> {
406    let pointer = mapping
407        .get(offset)
408        .ok_or(RustTransportError::InvalidLength)? as *const u8;
409    // SAFETY: AtomicU8 has byte alignment and the mapping outlives the reference.
410    Ok(unsafe { &*pointer.cast::<AtomicU8>() })
411}
412
413#[allow(clippy::too_many_arguments)]
414fn checksum(
415    kind: u8,
416    outcome: u8,
417    pid: u32,
418    context: u64,
419    payload_offset: u32,
420    payload_length: u32,
421    id_length: u32,
422    value_length: u32,
423    id: &[u8],
424    values: &[u8],
425) -> u64 {
426    let mut value = 0xcbf2_9ce4_8422_2325_u64;
427    for byte in [kind, outcome]
428        .into_iter()
429        .chain(pid.to_le_bytes())
430        .chain(context.to_le_bytes())
431        .chain(payload_offset.to_le_bytes())
432        .chain(payload_length.to_le_bytes())
433        .chain(id_length.to_le_bytes())
434        .chain(value_length.to_le_bytes())
435        .chain(id.iter().copied())
436        .chain(values.iter().copied())
437    {
438        value ^= u64::from(byte);
439        value = value.wrapping_mul(0x0000_0100_0000_01b3);
440    }
441    value
442}
443
444pub fn read_rust_transport(
445    path: &Path,
446    expected_token: &[u8; TOKEN_SIZE],
447) -> Result<RustTransportRead, RustTransportError> {
448    let file = regular_file(path)?;
449    let mapping = map(&file)?;
450    if mapping.get(..8) != Some(MAGIC.as_slice())
451        || get_u32(&mapping, 8) != Some(VERSION)
452        || get_u32(&mapping, 12) != Some(HEADER_SIZE as u32)
453        || get_u32(&mapping, 16) != Some(DESCRIPTOR_SIZE as u32)
454        || get_u32(&mapping, 28) != Some(ENDIAN_MARKER)
455        || mapping.get(TOKEN_OFFSET..TOKEN_OFFSET + TOKEN_SIZE) != Some(expected_token.as_slice())
456        || mapping.get(52..56).is_none_or(|bytes| bytes != [0; 4])
457        || mapping
458            .get(NEXT_PHASE_OFFSET + 8..HEADER_SIZE)
459            .is_none_or(|bytes| bytes != [0; 40])
460    {
461        return Err(RustTransportError::InvalidHeader);
462    }
463    let descriptors = bounded_u32(&mapping, 20);
464    let payload_capacity = bounded_u32(&mapping, 24);
465    if descriptors == 0 || payload_capacity == 0 {
466        return Err(RustTransportError::InvalidHeader);
467    }
468    if mapping.len()
469        != total_size(descriptors, payload_capacity).ok_or(RustTransportError::InvalidLength)?
470    {
471        return Err(RustTransportError::InvalidLength);
472    }
473    let next = atomic_u64(&mapping, NEXT_DESCRIPTOR_OFFSET)?.load(Ordering::Acquire);
474    let next_payload = atomic_u64(&mapping, NEXT_PAYLOAD_OFFSET)?.load(Ordering::Acquire);
475    let recorded_dropped = atomic_u64(&mapping, DROPPED_OFFSET)?.load(Ordering::Acquire);
476    let attachments = atomic_u64(&mapping, ATTACHMENTS_OFFSET)?.load(Ordering::Acquire);
477    let inspect = next.min(u64::from(descriptors));
478    let payload_base = HEADER_SIZE + descriptors as usize * DESCRIPTOR_SIZE;
479    let mut observations = Vec::new();
480    let mut ordinal_hits = Vec::new();
481    let mut phases = Vec::new();
482    let mut thread_phases = Vec::new();
483    let mut thread_ends = Vec::new();
484    let mut test_boundaries = Vec::new();
485    let mut phase_definitions = BTreeMap::<u64, (u8, u64, u64, String)>::new();
486    let mut thread_end_contexts = BTreeSet::<u64>::new();
487    let mut boundary_contexts = BTreeSet::<u64>::new();
488    let mut committed = 0_u64;
489    for index in 0..inspect {
490        let descriptor = HEADER_SIZE + index as usize * DESCRIPTOR_SIZE;
491        match atomic_u8(&mapping, descriptor + COMMIT_OFFSET)?.load(Ordering::Acquire) {
492            0 => continue,
493            1 => {}
494            _ => return Err(RustTransportError::InvalidDescriptor(index)),
495        }
496        committed += 1;
497        let kind = mapping[descriptor + KIND_OFFSET];
498        let outcome = mapping[descriptor + OUTCOME_OFFSET];
499        if mapping[descriptor + 3] != 0 {
500            return Err(RustTransportError::InvalidDescriptor(index));
501        }
502        let pid = bounded_u32(&mapping, descriptor + PID_OFFSET);
503        let context_id = bounded_u64(&mapping, descriptor + CONTEXT_OFFSET);
504        let payload_offset = bounded_u32(&mapping, descriptor + PAYLOAD_OFFSET_OFFSET) as usize;
505        let payload_length = bounded_u32(&mapping, descriptor + PAYLOAD_LENGTH_OFFSET) as usize;
506        let id_length = bounded_u32(&mapping, descriptor + ID_LENGTH_OFFSET) as usize;
507        let value_length = bounded_u32(&mapping, descriptor + VALUE_LENGTH_OFFSET) as usize;
508        let expected_checksum = bounded_u64(&mapping, descriptor + CHECKSUM_OFFSET);
509        let end = payload_offset
510            .checked_add(payload_length)
511            .filter(|end| *end <= payload_capacity as usize)
512            .ok_or(RustTransportError::InvalidDescriptor(index))?;
513        if payload_length != id_length.saturating_add(value_length)
514            || u64::from(end as u32) > next_payload
515        {
516            return Err(RustTransportError::InvalidDescriptor(index));
517        }
518        let payload = &mapping[payload_base + payload_offset..payload_base + end];
519        let (id, values) = payload.split_at(id_length);
520        if checksum(
521            kind,
522            outcome,
523            pid,
524            context_id,
525            payload_offset as u32,
526            payload_length as u32,
527            id_length as u32,
528            value_length as u32,
529            id,
530            values,
531        ) != expected_checksum
532        {
533            return Err(RustTransportError::InvalidRecord(index));
534        }
535        let id = std::str::from_utf8(id).map_err(|_| RustTransportError::InvalidRecord(index))?;
536        if matches!(kind, KIND_HIT | KIND_DECISION | KIND_PHASE) && !valid_probe_id(id) {
537            return Err(RustTransportError::InvalidRecord(index));
538        }
539        match kind {
540            KIND_HIT if outcome == 0 && values.is_empty() => {
541                observations.push(RustTransportObservation {
542                    process_id: pid,
543                    context_id,
544                    observation: RustProbeObservation::Hit { id: id.into() },
545                });
546            }
547            KIND_DECISION
548                if matches!(outcome, 0 | 1)
549                    && id.starts_with("rs:decision:")
550                    && !values.is_empty()
551                    && values.iter().all(|value| matches!(*value, 0..=2)) =>
552            {
553                observations.push(RustTransportObservation {
554                    process_id: pid,
555                    context_id,
556                    observation: RustProbeObservation::Decision {
557                        id: id.into(),
558                        values: values
559                            .iter()
560                            .map(|value| match value {
561                                0 => None,
562                                1 => Some(false),
563                                2 => Some(true),
564                                _ => unreachable!(),
565                            })
566                            .collect(),
567                        outcome: outcome == 1,
568                    },
569                });
570            }
571            KIND_ORDINAL_HIT if outcome == 0 && id.is_empty() && values.len() == 8 => {
572                ordinal_hits.push(RustOrdinalHit {
573                    process_id: pid,
574                    context_id,
575                    ordinal: le_u64(values),
576                });
577            }
578            KIND_PHASE
579                if outcome == 0
580                    && id.starts_with("rs:decision:")
581                    && values.len() == 16
582                    && !matches!(context_id, 0 | u64::MAX) =>
583            {
584                let parent_context_id = le_u64(&values[..8]);
585                let invocation_nonce = le_u64(&values[8..]);
586                if matches!(parent_context_id, 0 | u64::MAX)
587                    || rust_assertion_context_id(parent_context_id, id, invocation_nonce)?
588                        != context_id
589                {
590                    return Err(RustTransportError::InvalidAssertionContext(format!(
591                        "phase record {index} does not derive child {context_id:016x} from parent {parent_context_id:016x} and {id}"
592                    )));
593                }
594                let definition = (
595                    KIND_PHASE,
596                    parent_context_id,
597                    invocation_nonce,
598                    id.to_owned(),
599                );
600                if phase_definitions
601                    .insert(context_id, definition.clone())
602                    .is_some_and(|existing| existing != definition)
603                {
604                    return Err(RustTransportError::InvalidAssertionContext(format!(
605                        "child {context_id:016x} has conflicting phase definitions"
606                    )));
607                }
608                phases.push(RustPhaseContext {
609                    process_id: pid,
610                    child_context_id: context_id,
611                    parent_context_id,
612                    invocation_nonce,
613                    decision_id: id.into(),
614                });
615            }
616            KIND_THREAD_PHASE
617                if outcome == 0
618                    && id.is_empty()
619                    && values.len() == 16
620                    && !matches!(context_id, 0 | u64::MAX) =>
621            {
622                let parent_context_id = le_u64(&values[..8]);
623                let invocation_nonce = le_u64(&values[8..]);
624                if matches!(parent_context_id, 0 | u64::MAX)
625                    || rust_thread_context_id(parent_context_id, invocation_nonce) != context_id
626                {
627                    return Err(RustTransportError::InvalidAssertionContext(format!(
628                        "thread phase record {index} does not derive child {context_id:016x} from parent {parent_context_id:016x} and nonce {invocation_nonce}"
629                    )));
630                }
631                let definition = (
632                    KIND_THREAD_PHASE,
633                    parent_context_id,
634                    invocation_nonce,
635                    String::new(),
636                );
637                if phase_definitions
638                    .insert(context_id, definition.clone())
639                    .is_some_and(|existing| existing != definition)
640                {
641                    return Err(RustTransportError::InvalidAssertionContext(format!(
642                        "child {context_id:016x} has conflicting phase definitions"
643                    )));
644                }
645                thread_phases.push(RustThreadPhase {
646                    process_id: pid,
647                    child_context_id: context_id,
648                    parent_context_id,
649                    invocation_nonce,
650                    commit_index: index,
651                });
652            }
653            KIND_THREAD_END
654                if outcome == 0
655                    && id.is_empty()
656                    && values.is_empty()
657                    && !matches!(context_id, 0 | u64::MAX) =>
658            {
659                if !thread_end_contexts.insert(context_id) {
660                    return Err(RustTransportError::InvalidRecord(index));
661                }
662                thread_ends.push(RustThreadEnd {
663                    process_id: pid,
664                    context_id,
665                    commit_index: index,
666                });
667            }
668            KIND_TEST_BOUNDARY
669                if outcome == 0
670                    && id.is_empty()
671                    && values.is_empty()
672                    && !matches!(context_id, 0 | u64::MAX) =>
673            {
674                if !boundary_contexts.insert(context_id) {
675                    return Err(RustTransportError::InvalidRecord(index));
676                }
677                test_boundaries.push(RustTestBoundary {
678                    process_id: pid,
679                    context_id,
680                    commit_index: index,
681                });
682            }
683            _ => return Err(RustTransportError::InvalidRecord(index)),
684        }
685    }
686    let overflow = next.saturating_sub(u64::from(descriptors));
687    Ok(RustTransportRead {
688        observations,
689        ordinal_hits,
690        phases,
691        thread_phases,
692        thread_ends,
693        test_boundaries,
694        committed,
695        incomplete: inspect.saturating_sub(committed),
696        dropped: recorded_dropped.max(overflow),
697        attachments,
698    })
699}
700
701pub fn validate_rust_phase_contexts(
702    base_context_id: u64,
703    read: &RustTransportRead,
704) -> Result<(), RustTransportError> {
705    if matches!(base_context_id, 0 | u64::MAX) {
706        return Err(RustTransportError::InvalidAssertionContext(
707            "the supervisor base context must be nonzero and not reserved".into(),
708        ));
709    }
710    let mut definitions = BTreeMap::<u64, (u8, u64, u64, &str)>::new();
711    for phase in &read.phases {
712        let definition = (
713            KIND_PHASE,
714            phase.parent_context_id,
715            phase.invocation_nonce,
716            phase.decision_id.as_str(),
717        );
718        if definitions
719            .insert(phase.child_context_id, definition)
720            .is_some_and(|existing| existing != definition)
721        {
722            return Err(RustTransportError::InvalidAssertionContext(format!(
723                "child {:016x} has conflicting phase definitions",
724                phase.child_context_id
725            )));
726        }
727    }
728    for phase in &read.thread_phases {
729        if rust_thread_context_id(phase.parent_context_id, phase.invocation_nonce)
730            != phase.child_context_id
731        {
732            return Err(RustTransportError::InvalidAssertionContext(format!(
733                "thread phase child {:016x} does not derive from parent {:016x} and nonce {}",
734                phase.child_context_id, phase.parent_context_id, phase.invocation_nonce
735            )));
736        }
737        let definition = (
738            KIND_THREAD_PHASE,
739            phase.parent_context_id,
740            phase.invocation_nonce,
741            "",
742        );
743        if definitions
744            .insert(phase.child_context_id, definition)
745            .is_some_and(|existing| existing != definition)
746        {
747            return Err(RustTransportError::InvalidAssertionContext(format!(
748                "child {:016x} has conflicting phase definitions",
749                phase.child_context_id
750            )));
751        }
752    }
753
754    let used_contexts = read
755        .observations
756        .iter()
757        .map(|observation| observation.context_id)
758        .chain(read.ordinal_hits.iter().map(|hit| hit.context_id))
759        .chain(read.thread_ends.iter().map(|end| end.context_id))
760        .chain(
761            read.test_boundaries
762                .iter()
763                .map(|boundary| boundary.context_id),
764        )
765        .filter(|context| *context != 0 && *context != base_context_id)
766        .collect::<BTreeSet<_>>();
767    for start in used_contexts {
768        let mut context = start;
769        let mut path = BTreeSet::new();
770        while context != base_context_id {
771            if !path.insert(context) {
772                return Err(RustTransportError::InvalidAssertionContext(format!(
773                    "phase context cycle at {context:016x}"
774                )));
775            }
776            let (_, parent, _, _) = definitions.get(&context).ok_or_else(|| {
777                RustTransportError::InvalidAssertionContext(format!(
778                    "context {context:016x} does not resolve to base {base_context_id:016x}"
779                ))
780            })?;
781            context = *parent;
782            if matches!(context, 0 | u64::MAX) {
783                return Err(RustTransportError::InvalidAssertionContext(format!(
784                    "context {start:016x} crosses the supervisor attempt boundary"
785                )));
786            }
787        }
788    }
789    Ok(())
790}
791
792/// Partition one concurrently written artifact transport without copying any
793/// record between tests. Every non-background context must resolve through the
794/// authenticated assertion-phase graph to exactly one known libtest root.
795pub fn partition_rust_transport_by_test_contexts(
796    read: &RustTransportRead,
797    base_contexts: &BTreeSet<u64>,
798) -> Result<RustTransportPartition, RustTransportError> {
799    if base_contexts
800        .iter()
801        .any(|context| matches!(*context, 0 | u64::MAX))
802    {
803        return Err(RustTransportError::InvalidAttribution(
804            "known test roots must be nonzero and non-reserved".into(),
805        ));
806    }
807    if read.incomplete != 0 || read.dropped != 0 {
808        return Err(RustTransportError::InvalidAttribution(format!(
809            "shared transport is incomplete (incomplete={}, dropped={})",
810            read.incomplete, read.dropped
811        )));
812    }
813
814    let mut parents = BTreeMap::<u64, u64>::new();
815    let mut thread_children = BTreeSet::<u64>::new();
816    for (child, parent) in read
817        .phases
818        .iter()
819        .map(|phase| (phase.child_context_id, phase.parent_context_id))
820        .chain(
821            read.thread_phases
822                .iter()
823                .map(|phase| (phase.child_context_id, phase.parent_context_id)),
824        )
825    {
826        if matches!(child, 0 | u64::MAX)
827            || matches!(parent, 0 | u64::MAX)
828            || base_contexts.contains(&child)
829            || parents.insert(child, parent).is_some()
830        {
831            return Err(RustTransportError::InvalidAttribution(format!(
832                "phase {child:016x} has an invalid or repeated ownership definition"
833            )));
834        }
835    }
836    for phase in &read.thread_phases {
837        thread_children.insert(phase.child_context_id);
838    }
839    let mut thread_end_index = BTreeMap::<u64, u64>::new();
840    for end in &read.thread_ends {
841        if !thread_children.contains(&end.context_id)
842            || thread_end_index
843                .insert(end.context_id, end.commit_index)
844                .is_some()
845        {
846            return Err(RustTransportError::InvalidAttribution(format!(
847                "thread end {:016x} has no unique thread-phase definition",
848                end.context_id
849            )));
850        }
851    }
852    let mut boundary_index = BTreeMap::<u64, u64>::new();
853    for boundary in &read.test_boundaries {
854        if !base_contexts.contains(&boundary.context_id)
855            || boundary_index
856                .insert(boundary.context_id, boundary.commit_index)
857                .is_some()
858        {
859            return Err(RustTransportError::InvalidAttribution(format!(
860                "test boundary {:016x} does not identify one known test root",
861                boundary.context_id
862            )));
863        }
864    }
865
866    // One record's exact destination: its root test, safe background, or the
867    // root plus the thread phases whose lifetimes escaped it.
868    enum Resolution {
869        Attributed(u64),
870        Background,
871        Quarantined { root: u64, escaped: Vec<u64> },
872    }
873    let resolve = |start: u64| -> Result<Resolution, RustTransportError> {
874        if start == 0 {
875            return Ok(Resolution::Background);
876        }
877        if start == u64::MAX {
878            return Err(RustTransportError::InvalidAttribution(
879                "evidence used the reserved context sentinel".into(),
880            ));
881        }
882        let mut context = start;
883        let mut seen = BTreeSet::new();
884        let mut chain_threads = Vec::new();
885        loop {
886            if thread_children.contains(&context) {
887                chain_threads.push(context);
888            }
889            let Some(parent) = parents.get(&context) else {
890                break;
891            };
892            if !seen.insert(context) {
893                return Err(RustTransportError::InvalidAttribution(format!(
894                    "phase context cycle at {context:016x}"
895                )));
896            }
897            context = *parent;
898        }
899        if !base_contexts.contains(&context) {
900            return Err(RustTransportError::InvalidAttribution(format!(
901                "context {start:016x} resolves to unknown test root {context:016x}"
902            )));
903        }
904        let boundary = boundary_index.get(&context).copied();
905        let escaped = chain_threads
906            .into_iter()
907            .filter(|thread| {
908                !matches!(
909                    (thread_end_index.get(thread), boundary),
910                    (Some(end), Some(boundary)) if *end < boundary
911                )
912            })
913            .collect::<Vec<_>>();
914        if escaped.is_empty() {
915            Ok(Resolution::Attributed(context))
916        } else {
917            Ok(Resolution::Quarantined {
918                root: context,
919                escaped,
920            })
921        }
922    };
923
924    let mut attributed = base_contexts
925        .iter()
926        .map(|context| (*context, RustTransportRead::empty()))
927        .collect::<BTreeMap<_, _>>();
928    let mut background = RustTransportRead::empty();
929    background.attachments = read.attachments;
930    let mut thread_scope_limitations = BTreeSet::new();
931    let mut note_escape = |root: u64, escaped: Vec<u64>| {
932        for thread in escaped {
933            thread_scope_limitations.insert(format!(
934                "RUST_THREAD_OUTLIVED_TEST: thread phase {thread:016x} escaped test {root:016x}"
935            ));
936        }
937    };
938
939    for observation in &read.observations {
940        match resolve(observation.context_id)? {
941            Resolution::Attributed(root) => attributed
942                .get_mut(&root)
943                .expect("resolved root was preallocated")
944                .observations
945                .push(observation.clone()),
946            Resolution::Background => background.observations.push(observation.clone()),
947            Resolution::Quarantined { root, escaped } => {
948                note_escape(root, escaped);
949                background.observations.push(observation.clone());
950            }
951        }
952    }
953    for hit in &read.ordinal_hits {
954        match resolve(hit.context_id)? {
955            Resolution::Attributed(root) => attributed
956                .get_mut(&root)
957                .expect("resolved root was preallocated")
958                .ordinal_hits
959                .push(*hit),
960            Resolution::Background => background.ordinal_hits.push(*hit),
961            Resolution::Quarantined { root, escaped } => {
962                note_escape(root, escaped);
963                background.ordinal_hits.push(*hit);
964            }
965        }
966    }
967    for phase in &read.phases {
968        match resolve(phase.child_context_id)? {
969            Resolution::Attributed(root) => attributed
970                .get_mut(&root)
971                .expect("resolved root was preallocated")
972                .phases
973                .push(phase.clone()),
974            Resolution::Background => {
975                return Err(RustTransportError::InvalidAttribution(format!(
976                    "phase {:016x} resolved to background",
977                    phase.child_context_id
978                )));
979            }
980            Resolution::Quarantined { root, escaped } => {
981                note_escape(root, escaped);
982                background.phases.push(phase.clone());
983            }
984        }
985    }
986    for phase in &read.thread_phases {
987        match resolve(phase.child_context_id)? {
988            Resolution::Attributed(root) => attributed
989                .get_mut(&root)
990                .expect("resolved root was preallocated")
991                .thread_phases
992                .push(*phase),
993            Resolution::Background => {
994                return Err(RustTransportError::InvalidAttribution(format!(
995                    "thread phase {:016x} resolved to background",
996                    phase.child_context_id
997                )));
998            }
999            Resolution::Quarantined { root, escaped } => {
1000                note_escape(root, escaped);
1001                background.thread_phases.push(*phase);
1002            }
1003        }
1004    }
1005    for end in &read.thread_ends {
1006        match resolve(end.context_id)? {
1007            Resolution::Attributed(root) => attributed
1008                .get_mut(&root)
1009                .expect("resolved root was preallocated")
1010                .thread_ends
1011                .push(*end),
1012            Resolution::Background => {
1013                return Err(RustTransportError::InvalidAttribution(format!(
1014                    "thread end {:016x} resolved to background",
1015                    end.context_id
1016                )));
1017            }
1018            Resolution::Quarantined { root, escaped } => {
1019                note_escape(root, escaped);
1020                background.thread_ends.push(*end);
1021            }
1022        }
1023    }
1024    for boundary in &read.test_boundaries {
1025        attributed
1026            .get_mut(&boundary.context_id)
1027            .expect("boundary contexts were checked against the known roots")
1028            .test_boundaries
1029            .push(*boundary);
1030    }
1031
1032    let set_committed = |transport: &mut RustTransportRead| -> Result<u64, RustTransportError> {
1033        transport.committed = u64::try_from(
1034            transport.observations.len()
1035                + transport.ordinal_hits.len()
1036                + transport.phases.len()
1037                + transport.thread_phases.len()
1038                + transport.thread_ends.len()
1039                + transport.test_boundaries.len(),
1040        )
1041        .map_err(|_| {
1042            RustTransportError::InvalidAttribution("partition record count exceeds u64".into())
1043        })?;
1044        Ok(transport.committed)
1045    };
1046    let mut assigned = set_committed(&mut background)?;
1047    for transport in attributed.values_mut() {
1048        assigned = assigned
1049            .checked_add(set_committed(transport)?)
1050            .ok_or_else(|| {
1051                RustTransportError::InvalidAttribution(
1052                    "partition record count overflowed u64".into(),
1053                )
1054            })?;
1055    }
1056    if assigned != read.committed {
1057        return Err(RustTransportError::InvalidAttribution(format!(
1058            "partition assigned {assigned} of {} committed records",
1059            read.committed
1060        )));
1061    }
1062    for (base, transport) in &attributed {
1063        validate_rust_phase_contexts(*base, transport)?;
1064    }
1065    Ok(RustTransportPartition {
1066        attributed,
1067        background,
1068        thread_scope_limitations,
1069    })
1070}
1071
1072pub fn render_rust_mmap_runtime(module_name: &str) -> Result<String, String> {
1073    let valid_identifier = !module_name.is_empty()
1074        && module_name.bytes().enumerate().all(|(index, byte)| {
1075            byte == b'_' || byte.is_ascii_alphabetic() || (index > 0 && byte.is_ascii_digit())
1076        });
1077    if !valid_identifier {
1078        return Err("invalid Rust runtime module name".into());
1079    }
1080    Ok(RUNTIME_TEMPLATE.replace("__SUPERCOV_MODULE__", module_name))
1081}
1082
1083#[cfg(test)]
1084mod tests {
1085    use std::{
1086        fs,
1087        io::{BufRead as _, BufReader},
1088        process::{Command, Stdio},
1089        sync::atomic::{AtomicUsize, Ordering},
1090        time::{SystemTime, UNIX_EPOCH},
1091    };
1092
1093    use super::*;
1094
1095    const TOKEN: [u8; TOKEN_SIZE] = [0x42; TOKEN_SIZE];
1096    const CONTEXT: u64 = 42;
1097
1098    fn token_hex() -> String {
1099        TOKEN.iter().map(|byte| format!("{byte:02x}")).collect()
1100    }
1101
1102    fn temporary_directory(name: &str) -> std::path::PathBuf {
1103        let nonce = SystemTime::now()
1104            .duration_since(UNIX_EPOCH)
1105            .unwrap()
1106            .as_nanos();
1107        let path = std::env::temp_dir().join(format!(
1108            "supercov-rust-transport-{}-{nonce}-{name}",
1109            std::process::id()
1110        ));
1111        fs::create_dir(&path).unwrap();
1112        path
1113    }
1114
1115    fn compile_fixture(directory: &Path) -> std::path::PathBuf {
1116        let source = directory.join("main.rs");
1117        let binary = directory.join("program");
1118        let runtime = render_rust_mmap_runtime("__supercov_runtime_v1").unwrap();
1119        fs::write(
1120            &source,
1121            format!(
1122                r#"{runtime}
1123fn main() {{
1124    let mode = std::env::args().nth(1).unwrap_or_default();
1125    if mode == "threads" {{
1126        let mut threads = Vec::new();
1127        for _ in 0..8 {{
1128            threads.push(std::thread::spawn(|| {{
1129                for _ in 0..100 {{ __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567"); }}
1130            }}));
1131        }}
1132        for thread in threads {{ thread.join().unwrap(); }}
1133        let mut frame = __supercov_runtime_v1::DecisionFrame::new("rs:decision:0123456789abcdef01234567", 2);
1134        let first = __supercov_runtime_v1::condition(true, &mut frame, 0);
1135        let second = __supercov_runtime_v1::condition(false, &mut frame, 1);
1136        __supercov_runtime_v1::decision(first && second, &mut frame);
1137        __supercov_runtime_v1::ordinal_hit(7);
1138    }} else if mode == "contexts" {{
1139        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1140        let outer = __supercov_runtime_v1::enter_context(100);
1141        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1142        let inner = __supercov_runtime_v1::enter_context(200);
1143        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1144        __supercov_runtime_v1::exit_context(inner);
1145        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1146        __supercov_runtime_v1::exit_context(outer);
1147        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1148        let assertion = __supercov_runtime_v1::enter_assertion_context(
1149            0x0123_4567_89ab_cdef,
1150            0x0123_4567,
1151        );
1152        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1153        __supercov_runtime_v1::exit_context(assertion);
1154        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1155        let repeated_assertion = __supercov_runtime_v1::enter_assertion_context(
1156            0x0123_4567_89ab_cdef,
1157            0x0123_4567,
1158        );
1159        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1160        __supercov_runtime_v1::exit_context(repeated_assertion);
1161        __supercov_runtime_v1::hit("rs:statement:0123456789abcdef01234567");
1162    }} else if mode == "mir-decisions" {{
1163        let before_outer = __supercov_runtime_v1::enter_context(901);
1164        let outer = __supercov_runtime_v1::mir_decision_start(
1165            0x0123_4567_89ab_cdef,
1166            0x0123_4567,
1167            2,
1168        );
1169        __supercov_runtime_v1::mir_decision_condition(outer, 0, true);
1170        let before_inner = __supercov_runtime_v1::enter_context(902);
1171        let inner = __supercov_runtime_v1::mir_decision_start(
1172            0xfedc_ba98_7654_3210,
1173            0xfedc_ba98,
1174            1,
1175        );
1176        let migrated = std::thread::spawn(move || {{
1177            __supercov_runtime_v1::mir_decision_condition(inner, 0, false);
1178            __supercov_runtime_v1::mir_decision_finish(inner, false);
1179        }});
1180        __supercov_runtime_v1::exit_context(before_inner);
1181        __supercov_runtime_v1::mir_decision_condition(outer, 1, true);
1182        __supercov_runtime_v1::exit_context(before_outer);
1183        __supercov_runtime_v1::mir_decision_finish(outer, true);
1184        migrated.join().unwrap();
1185        let before_branch = __supercov_runtime_v1::enter_context(903);
1186        let branch = __supercov_runtime_v1::mir_branch_start();
1187        __supercov_runtime_v1::exit_context(before_branch);
1188        let migrated_branch = std::thread::spawn(move || {{
1189            __supercov_runtime_v1::mir_branch_hit(branch, 777);
1190            __supercov_runtime_v1::mir_branch_hit(branch, 888);
1191        }});
1192        migrated_branch.join().unwrap();
1193    }} else if mode == "kill" {{
1194        __supercov_runtime_v1::hit("rs:function:fedcba9876543210fedcba98");
1195        let interrupted = __supercov_runtime_v1::mir_decision_start(
1196            0x1111_1111_1111_1111,
1197            0x2222_2222,
1198            2,
1199        );
1200        __supercov_runtime_v1::mir_decision_condition(interrupted, 0, true);
1201        let _interrupted_branch = __supercov_runtime_v1::mir_branch_start();
1202        println!("ready");
1203        use std::io::Write as _;
1204        std::io::stdout().flush().unwrap();
1205        std::thread::sleep(std::time::Duration::from_secs(30));
1206    }} else {{
1207        for _ in 0..3 {{ __supercov_runtime_v1::hit("rs:branch:aaaaaaaaaaaaaaaaaaaaaaaa"); }}
1208    }}
1209}}
1210"#
1211            ),
1212        )
1213        .unwrap();
1214        let output = Command::new("rustc")
1215            .args(["--edition=2024"])
1216            .arg(&source)
1217            .arg("-o")
1218            .arg(&binary)
1219            .output()
1220            .unwrap();
1221        assert!(
1222            output.status.success(),
1223            "{}",
1224            String::from_utf8_lossy(&output.stderr)
1225        );
1226        binary
1227    }
1228
1229    #[test]
1230    fn implementation_matches_frozen_transport_contract() {
1231        let contract = supercov_contracts::rust_probe_transport_v3_contract().unwrap();
1232        assert_eq!(MAGIC.as_slice(), contract.magic.as_bytes());
1233        assert_eq!(VERSION, contract.protocol_version);
1234        assert_eq!(HEADER_SIZE, contract.header_size);
1235        assert_eq!(DESCRIPTOR_SIZE, contract.descriptor_size);
1236        assert_eq!(TOKEN_SIZE, contract.token_size);
1237        assert_eq!(ENDIAN_MARKER, contract.endian_marker);
1238        assert_eq!(
1239            NEXT_DESCRIPTOR_OFFSET,
1240            contract.header_offsets.next_descriptor
1241        );
1242        assert_eq!(NEXT_PAYLOAD_OFFSET, contract.header_offsets.next_payload);
1243        assert_eq!(DROPPED_OFFSET, contract.header_offsets.dropped);
1244        assert_eq!(TOKEN_OFFSET, contract.header_offsets.token);
1245        assert_eq!(ATTACHMENTS_OFFSET, contract.header_offsets.attachments);
1246        assert_eq!(Some(NEXT_PHASE_OFFSET), contract.header_offsets.next_phase);
1247        assert_eq!(COMMIT_OFFSET, contract.descriptor_offsets.commit);
1248        assert_eq!(PID_OFFSET, contract.descriptor_offsets.process_id);
1249        assert_eq!(CONTEXT_OFFSET, contract.descriptor_offsets.context_id);
1250        assert_eq!(
1251            PAYLOAD_OFFSET_OFFSET,
1252            contract.descriptor_offsets.payload_offset
1253        );
1254        assert_eq!(CHECKSUM_OFFSET, contract.descriptor_offsets.checksum);
1255        assert_eq!(KIND_HIT, contract.record_kinds.hit);
1256        assert_eq!(KIND_DECISION, contract.record_kinds.decision);
1257        assert_eq!(KIND_ORDINAL_HIT, contract.record_kinds.ordinal_hit);
1258        assert_eq!(Some(KIND_PHASE), contract.record_kinds.phase);
1259        assert_eq!(Some(KIND_THREAD_PHASE), contract.record_kinds.thread_phase);
1260        assert_eq!(Some(KIND_THREAD_END), contract.record_kinds.thread_end);
1261        assert_eq!(
1262            Some(KIND_TEST_BOUNDARY),
1263            contract.record_kinds.test_boundary
1264        );
1265        assert!(RUNTIME_TEMPLATE.contains("b\"SCVRUST3\""));
1266        assert!(RUNTIME_TEMPLATE.contains("const DESCRIPTOR_SIZE: usize = 40;"));
1267    }
1268
1269    #[test]
1270    fn assertion_context_derivation_is_exact_nested_and_never_promotes_background() {
1271        let first =
1272            rust_assertion_context_id(CONTEXT, "rs:decision:0123456789abcdef01234567", 0).unwrap();
1273        let nested =
1274            rust_assertion_context_id(first, "rs:decision:fedcba9876543210fedcba98", 1).unwrap();
1275        assert_ne!(first, CONTEXT);
1276        assert_ne!(nested, first);
1277        assert_eq!(
1278            rust_assertion_context_id(0, "rs:decision:0123456789abcdef01234567", 2).unwrap(),
1279            0
1280        );
1281        assert!(matches!(
1282            rust_assertion_context_id(CONTEXT, "not-a-decision", 2),
1283            Err(RustTransportError::InvalidAssertionContext(_))
1284        ));
1285        assert!(matches!(
1286            rust_assertion_context_id(u64::MAX, "rs:decision:0123456789abcdef01234567", 2),
1287            Err(RustTransportError::InvalidAssertionContext(_))
1288        ));
1289    }
1290
1291    #[test]
1292    fn phase_context_chains_resolve_exactly_to_the_supervisor_attempt() {
1293        let outer_id = "rs:decision:0123456789abcdef01234567";
1294        let inner_id = "rs:decision:fedcba9876543210fedcba98";
1295        let outer = rust_assertion_context_id(CONTEXT, outer_id, 10).unwrap();
1296        let inner = rust_assertion_context_id(outer, inner_id, 11).unwrap();
1297        let read = RustTransportRead {
1298            observations: vec![RustTransportObservation {
1299                process_id: 7,
1300                context_id: inner,
1301                observation: RustProbeObservation::Hit {
1302                    id: "rs:statement:0123456789abcdef01234567".into(),
1303                },
1304            }],
1305            ordinal_hits: Vec::new(),
1306            phases: vec![
1307                RustPhaseContext {
1308                    process_id: 7,
1309                    child_context_id: outer,
1310                    parent_context_id: CONTEXT,
1311                    invocation_nonce: 10,
1312                    decision_id: outer_id.into(),
1313                },
1314                RustPhaseContext {
1315                    process_id: 7,
1316                    child_context_id: inner,
1317                    parent_context_id: outer,
1318                    invocation_nonce: 11,
1319                    decision_id: inner_id.into(),
1320                },
1321            ],
1322            thread_phases: Vec::new(),
1323            thread_ends: Vec::new(),
1324            test_boundaries: Vec::new(),
1325            committed: 3,
1326            incomplete: 0,
1327            dropped: 0,
1328            attachments: 1,
1329        };
1330        validate_rust_phase_contexts(CONTEXT, &read).unwrap();
1331
1332        let mut threaded = read.clone();
1333        let thread_child = rust_thread_context_id(inner, 7);
1334        threaded.thread_phases.push(RustThreadPhase {
1335            process_id: 7,
1336            child_context_id: thread_child,
1337            parent_context_id: inner,
1338            invocation_nonce: 7,
1339            commit_index: 3,
1340        });
1341        threaded.observations[0].context_id = thread_child;
1342        threaded.committed = 4;
1343        validate_rust_phase_contexts(CONTEXT, &threaded).unwrap();
1344
1345        let mut tampered_thread = threaded.clone();
1346        tampered_thread.thread_phases[0].invocation_nonce = 8;
1347        assert!(matches!(
1348            validate_rust_phase_contexts(CONTEXT, &tampered_thread),
1349            Err(RustTransportError::InvalidAssertionContext(_))
1350        ));
1351
1352        let mut invalid = read.clone();
1353        invalid.phases[0].parent_context_id = 99;
1354        assert!(matches!(
1355            validate_rust_phase_contexts(CONTEXT, &invalid),
1356            Err(RustTransportError::InvalidAssertionContext(_))
1357        ));
1358
1359        let mut unused = read.clone();
1360        let unused_child = rust_assertion_context_id(CONTEXT, inner_id, 12).unwrap();
1361        unused.phases.push(RustPhaseContext {
1362            process_id: 7,
1363            child_context_id: unused_child,
1364            parent_context_id: CONTEXT,
1365            invocation_nonce: 12,
1366            decision_id: inner_id.into(),
1367        });
1368        validate_rust_phase_contexts(CONTEXT, &unused).unwrap();
1369
1370        let mut cycle = read;
1371        cycle.phases[0].parent_context_id = inner;
1372        assert!(matches!(
1373            validate_rust_phase_contexts(CONTEXT, &cycle),
1374            Err(RustTransportError::InvalidAssertionContext(_))
1375        ));
1376    }
1377
1378    #[test]
1379    fn shared_transport_partitions_each_record_once_by_exact_test_root() {
1380        const SECOND: u64 = 84;
1381        let outer_id = "rs:decision:0123456789abcdef01234567";
1382        let inner_id = "rs:decision:fedcba9876543210fedcba98";
1383        let outer = rust_assertion_context_id(CONTEXT, outer_id, 10).unwrap();
1384        let inner = rust_assertion_context_id(outer, inner_id, 11).unwrap();
1385        let read = RustTransportRead {
1386            observations: vec![
1387                RustTransportObservation {
1388                    process_id: 7,
1389                    context_id: inner,
1390                    observation: RustProbeObservation::Hit {
1391                        id: "rs:statement:0123456789abcdef01234567".into(),
1392                    },
1393                },
1394                RustTransportObservation {
1395                    process_id: 7,
1396                    context_id: SECOND,
1397                    observation: RustProbeObservation::Hit {
1398                        id: "rs:function:fedcba9876543210fedcba98".into(),
1399                    },
1400                },
1401                RustTransportObservation {
1402                    process_id: 7,
1403                    context_id: 0,
1404                    observation: RustProbeObservation::Hit {
1405                        id: "rs:statement:aaaaaaaaaaaaaaaaaaaaaaaa".into(),
1406                    },
1407                },
1408            ],
1409            ordinal_hits: vec![RustOrdinalHit {
1410                process_id: 7,
1411                context_id: CONTEXT,
1412                ordinal: 9,
1413            }],
1414            phases: vec![
1415                RustPhaseContext {
1416                    process_id: 7,
1417                    child_context_id: outer,
1418                    parent_context_id: CONTEXT,
1419                    invocation_nonce: 10,
1420                    decision_id: outer_id.into(),
1421                },
1422                RustPhaseContext {
1423                    process_id: 7,
1424                    child_context_id: inner,
1425                    parent_context_id: outer,
1426                    invocation_nonce: 11,
1427                    decision_id: inner_id.into(),
1428                },
1429            ],
1430            thread_phases: Vec::new(),
1431            thread_ends: Vec::new(),
1432            test_boundaries: Vec::new(),
1433            committed: 6,
1434            incomplete: 0,
1435            dropped: 0,
1436            attachments: 3,
1437        };
1438        let bases = BTreeSet::from([CONTEXT, SECOND]);
1439        let partition = partition_rust_transport_by_test_contexts(&read, &bases).unwrap();
1440        assert_eq!(partition.attributed[&CONTEXT].committed, 4);
1441        assert_eq!(partition.attributed[&SECOND].committed, 1);
1442        assert_eq!(partition.background.committed, 1);
1443        assert_eq!(partition.background.attachments, 3);
1444        assert!(partition.thread_scope_limitations.is_empty());
1445        assert_eq!(
1446            partition
1447                .attributed
1448                .values()
1449                .map(|transport| transport.committed)
1450                .sum::<u64>()
1451                + partition.background.committed,
1452            read.committed
1453        );
1454
1455        let mut foreign = read.clone();
1456        foreign.observations[1].context_id = 99;
1457        assert!(matches!(
1458            partition_rust_transport_by_test_contexts(&foreign, &bases),
1459            Err(RustTransportError::InvalidAttribution(_))
1460        ));
1461
1462        let mut incomplete = read.clone();
1463        incomplete.dropped = 1;
1464        assert!(matches!(
1465            partition_rust_transport_by_test_contexts(&incomplete, &bases),
1466            Err(RustTransportError::InvalidAttribution(_))
1467        ));
1468
1469        let mut cycle = read;
1470        cycle.phases[0].parent_context_id = inner;
1471        assert!(matches!(
1472            partition_rust_transport_by_test_contexts(&cycle, &bases),
1473            Err(RustTransportError::InvalidAttribution(_))
1474        ));
1475    }
1476
1477    #[test]
1478    fn thread_phases_are_join_bounded_by_the_test_boundary() {
1479        const SECOND: u64 = 84;
1480        let bases = BTreeSet::from([CONTEXT, SECOND]);
1481        let joined = rust_thread_context_id(CONTEXT, 0);
1482        let escaped = rust_thread_context_id(CONTEXT, 1);
1483        let hit = |context_id: u64| RustTransportObservation {
1484            process_id: 7,
1485            context_id,
1486            observation: RustProbeObservation::Hit {
1487                id: "rs:statement:0123456789abcdef01234567".into(),
1488            },
1489        };
1490        let read = RustTransportRead {
1491            observations: vec![hit(joined), hit(escaped), hit(CONTEXT)],
1492            thread_phases: vec![
1493                RustThreadPhase {
1494                    process_id: 7,
1495                    child_context_id: joined,
1496                    parent_context_id: CONTEXT,
1497                    invocation_nonce: 0,
1498                    commit_index: 0,
1499                },
1500                RustThreadPhase {
1501                    process_id: 7,
1502                    child_context_id: escaped,
1503                    parent_context_id: CONTEXT,
1504                    invocation_nonce: 1,
1505                    commit_index: 1,
1506                },
1507            ],
1508            thread_ends: vec![RustThreadEnd {
1509                process_id: 7,
1510                context_id: joined,
1511                commit_index: 5,
1512            }],
1513            test_boundaries: vec![
1514                RustTestBoundary {
1515                    process_id: 7,
1516                    context_id: CONTEXT,
1517                    commit_index: 6,
1518                },
1519                RustTestBoundary {
1520                    process_id: 7,
1521                    context_id: SECOND,
1522                    commit_index: 7,
1523                },
1524            ],
1525            committed: 8,
1526            attachments: 1,
1527            ..RustTransportRead::empty()
1528        };
1529        let partition = partition_rust_transport_by_test_contexts(&read, &bases).unwrap();
1530        // The joined thread's phase, end and hit are exactly attributed; the
1531        // escaped thread's definition and hit fail closed to background.
1532        assert_eq!(partition.attributed[&CONTEXT].committed, 5);
1533        assert_eq!(partition.attributed[&CONTEXT].thread_phases.len(), 1);
1534        assert_eq!(partition.attributed[&CONTEXT].thread_ends.len(), 1);
1535        assert_eq!(partition.attributed[&CONTEXT].test_boundaries.len(), 1);
1536        assert_eq!(partition.attributed[&SECOND].committed, 1);
1537        assert_eq!(partition.background.committed, 2);
1538        assert_eq!(partition.background.thread_phases.len(), 1);
1539        assert_eq!(
1540            partition.background.observations,
1541            vec![hit(escaped)],
1542            "the escaped thread's record must be background"
1543        );
1544        assert_eq!(
1545            partition.thread_scope_limitations,
1546            BTreeSet::from([format!(
1547                "RUST_THREAD_OUTLIVED_TEST: thread phase {escaped:016x} escaped test {CONTEXT:016x}"
1548            )])
1549        );
1550
1551        // An end that commits after the boundary is escaped, not joined.
1552        let mut late_end = read.clone();
1553        late_end.thread_ends.push(RustThreadEnd {
1554            process_id: 7,
1555            context_id: escaped,
1556            commit_index: 8,
1557        });
1558        late_end.committed = 9;
1559        let partition = partition_rust_transport_by_test_contexts(&late_end, &bases).unwrap();
1560        assert_eq!(partition.thread_scope_limitations.len(), 1);
1561        assert_eq!(partition.background.committed, 3);
1562
1563        // A missing boundary for the root quarantines even ended threads.
1564        let mut unbounded = read.clone();
1565        unbounded.test_boundaries.remove(0);
1566        unbounded.committed = 7;
1567        let partition = partition_rust_transport_by_test_contexts(&unbounded, &bases).unwrap();
1568        assert_eq!(partition.attributed[&CONTEXT].committed, 1);
1569        assert_eq!(partition.thread_scope_limitations.len(), 2);
1570
1571        let mut duplicate_end = read.clone();
1572        duplicate_end.thread_ends.push(RustThreadEnd {
1573            process_id: 7,
1574            context_id: joined,
1575            commit_index: 9,
1576        });
1577        duplicate_end.committed = 9;
1578        assert!(matches!(
1579            partition_rust_transport_by_test_contexts(&duplicate_end, &bases),
1580            Err(RustTransportError::InvalidAttribution(_))
1581        ));
1582
1583        let mut orphan_end = read.clone();
1584        orphan_end.thread_ends.push(RustThreadEnd {
1585            process_id: 7,
1586            context_id: 99,
1587            commit_index: 9,
1588        });
1589        orphan_end.committed = 9;
1590        assert!(matches!(
1591            partition_rust_transport_by_test_contexts(&orphan_end, &bases),
1592            Err(RustTransportError::InvalidAttribution(_))
1593        ));
1594
1595        let mut duplicate_boundary = read.clone();
1596        duplicate_boundary.test_boundaries.push(RustTestBoundary {
1597            process_id: 7,
1598            context_id: CONTEXT,
1599            commit_index: 9,
1600        });
1601        duplicate_boundary.committed = 9;
1602        assert!(matches!(
1603            partition_rust_transport_by_test_contexts(&duplicate_boundary, &bases),
1604            Err(RustTransportError::InvalidAttribution(_))
1605        ));
1606
1607        let mut unknown_boundary = read.clone();
1608        unknown_boundary.test_boundaries.push(RustTestBoundary {
1609            process_id: 7,
1610            context_id: 99,
1611            commit_index: 9,
1612        });
1613        unknown_boundary.committed = 9;
1614        assert!(matches!(
1615            partition_rust_transport_by_test_contexts(&unknown_boundary, &bases),
1616            Err(RustTransportError::InvalidAttribution(_))
1617        ));
1618
1619        let mut duplicate_definition = read;
1620        duplicate_definition.phases.push(RustPhaseContext {
1621            process_id: 7,
1622            child_context_id: joined,
1623            parent_context_id: CONTEXT,
1624            invocation_nonce: 0,
1625            decision_id: "rs:decision:0123456789abcdef01234567".into(),
1626        });
1627        duplicate_definition.committed = 9;
1628        assert!(matches!(
1629            partition_rust_transport_by_test_contexts(&duplicate_definition, &bases),
1630            Err(RustTransportError::InvalidAttribution(_))
1631        ));
1632    }
1633
1634    #[test]
1635    fn a_damaged_transport_is_refused_with_what_is_wrong() {
1636        // A killed test process, a full disk or a stray writer each leave a
1637        // transport like one of these; each is refused, never read.
1638        let directory = temporary_directory("damaged");
1639        let id: &[u8] = b"rs:statement:0123456789abcdef01234567";
1640        let case = AtomicUsize::new(0);
1641        let write = |bytes: &[u8]| {
1642            let path = directory.join(format!(
1643                "case-{}.transport",
1644                case.fetch_add(1, Ordering::Relaxed)
1645            ));
1646            fs::write(&path, bytes).unwrap();
1647            path
1648        };
1649        let base = directory.join("base.transport");
1650        create_rust_transport(&base, TOKEN, 4, 512).unwrap();
1651        let empty = fs::read(&base).unwrap();
1652        let with = |records: &[(u8, u8, &[u8], &[u8])]| {
1653            let mut bytes = empty.clone();
1654            for (kind, outcome, id, values) in records {
1655                append_record(&mut bytes, *kind, *outcome, 1, id, values);
1656            }
1657            bytes
1658        };
1659        let valid = with(&[(KIND_HIT, 0, id, b"")]);
1660        let read = |bytes: &[u8], token: &[u8; TOKEN_SIZE]| match read_rust_transport(
1661            &write(bytes),
1662            token,
1663        ) {
1664            Ok(_) => "accepted".to_owned(),
1665            Err(error) => error.to_string(),
1666        };
1667        assert_eq!(read(&valid, &TOKEN), "accepted");
1668        let set = |at: usize, value: &[u8]| {
1669            let mut bytes = valid.clone();
1670            bytes[at..at + value.len()].copy_from_slice(value);
1671            bytes
1672        };
1673        let descriptor = HEADER_SIZE;
1674        let mut longer = valid.clone();
1675        longer.push(0);
1676        for (bytes, token, reason) in [
1677            (set(0, b"X"), TOKEN, "invalid Rust transport header"),
1678            (
1679                valid.clone(),
1680                [0x41; TOKEN_SIZE],
1681                "invalid Rust transport header",
1682            ),
1683            (set(52, &[1]), TOKEN, "invalid Rust transport header"),
1684            (
1685                set(20, &0_u32.to_le_bytes()),
1686                TOKEN,
1687                "invalid Rust transport header",
1688            ),
1689            (longer, TOKEN, "invalid Rust transport length"),
1690            (
1691                set(descriptor + COMMIT_OFFSET, &[2]),
1692                TOKEN,
1693                "invalid Rust transport descriptor 0",
1694            ),
1695            (
1696                set(descriptor + 3, &[1]),
1697                TOKEN,
1698                "invalid Rust transport descriptor 0",
1699            ),
1700            (
1701                set(descriptor + PAYLOAD_LENGTH_OFFSET, &99_u32.to_le_bytes()),
1702                TOKEN,
1703                "invalid Rust transport descriptor 0",
1704            ),
1705            (
1706                set(descriptor + PAYLOAD_OFFSET_OFFSET, &4_000_u32.to_le_bytes()),
1707                TOKEN,
1708                "invalid Rust transport descriptor 0",
1709            ),
1710            (
1711                set(descriptor + CHECKSUM_OFFSET, &0_u64.to_le_bytes()),
1712                TOKEN,
1713                "invalid Rust transport record 0",
1714            ),
1715            (
1716                with(&[(KIND_HIT, 0, b"not-a-probe", b"")]),
1717                TOKEN,
1718                "invalid Rust transport record 0",
1719            ),
1720            (
1721                with(&[(KIND_HIT, 0, &[0xff, 0xfe], b"")]),
1722                TOKEN,
1723                "invalid Rust transport record 0",
1724            ),
1725            (
1726                with(&[(KIND_HIT, 0, id, b"x")]),
1727                TOKEN,
1728                "invalid Rust transport record 0",
1729            ),
1730            (
1731                with(&[(99, 0, id, b"")]),
1732                TOKEN,
1733                "invalid Rust transport record 0",
1734            ),
1735        ] {
1736            let said = read(&bytes, &token);
1737            assert!(said.contains(reason), "{reason}: {said}");
1738        }
1739        // A descriptor whose commit byte never landed is skipped, as a
1740        // killed writer leaves it.
1741        let uncommitted = {
1742            let mut bytes = valid.clone();
1743            bytes[descriptor + COMMIT_OFFSET] = 0;
1744            bytes
1745        };
1746        assert_eq!(read(&uncommitted, &TOKEN), "accepted");
1747        fs::remove_dir_all(&directory).ok();
1748    }
1749
1750    fn append_record(
1751        bytes: &mut [u8],
1752        kind: u8,
1753        outcome: u8,
1754        context_id: u64,
1755        id: &[u8],
1756        values: &[u8],
1757    ) {
1758        let descriptor_capacity = get_u32(bytes, 20).unwrap() as usize;
1759        let payload_base = HEADER_SIZE + descriptor_capacity * DESCRIPTOR_SIZE;
1760        let next = get_u64(bytes, NEXT_DESCRIPTOR_OFFSET).unwrap();
1761        let next_payload = get_u64(bytes, NEXT_PAYLOAD_OFFSET).unwrap();
1762        let descriptor = HEADER_SIZE + usize::try_from(next).unwrap() * DESCRIPTOR_SIZE;
1763        let payload_length = (id.len() + values.len()) as u32;
1764        bytes[descriptor + KIND_OFFSET] = kind;
1765        bytes[descriptor + OUTCOME_OFFSET] = outcome;
1766        bytes[descriptor + 3] = 0;
1767        put_u32(bytes, descriptor + PID_OFFSET, 7);
1768        bytes[descriptor + CONTEXT_OFFSET..descriptor + CONTEXT_OFFSET + 8]
1769            .copy_from_slice(&context_id.to_le_bytes());
1770        put_u32(
1771            bytes,
1772            descriptor + PAYLOAD_OFFSET_OFFSET,
1773            next_payload as u32,
1774        );
1775        put_u32(bytes, descriptor + PAYLOAD_LENGTH_OFFSET, payload_length);
1776        put_u32(bytes, descriptor + ID_LENGTH_OFFSET, id.len() as u32);
1777        put_u32(bytes, descriptor + VALUE_LENGTH_OFFSET, values.len() as u32);
1778        let record_checksum = checksum(
1779            kind,
1780            outcome,
1781            7,
1782            context_id,
1783            next_payload as u32,
1784            payload_length,
1785            id.len() as u32,
1786            values.len() as u32,
1787            id,
1788            values,
1789        );
1790        bytes[descriptor + CHECKSUM_OFFSET..descriptor + CHECKSUM_OFFSET + 8]
1791            .copy_from_slice(&record_checksum.to_le_bytes());
1792        let payload = payload_base + usize::try_from(next_payload).unwrap();
1793        bytes[payload..payload + id.len()].copy_from_slice(id);
1794        bytes[payload + id.len()..payload + id.len() + values.len()].copy_from_slice(values);
1795        bytes[descriptor + COMMIT_OFFSET] = 1;
1796        bytes[NEXT_DESCRIPTOR_OFFSET..NEXT_DESCRIPTOR_OFFSET + 8]
1797            .copy_from_slice(&(next + 1).to_le_bytes());
1798        bytes[NEXT_PAYLOAD_OFFSET..NEXT_PAYLOAD_OFFSET + 8]
1799            .copy_from_slice(&(next_payload + u64::from(payload_length)).to_le_bytes());
1800    }
1801
1802    #[test]
1803    fn thread_kind_records_are_authenticated_unique_and_strict() {
1804        let directory = temporary_directory("thread-kinds");
1805        let child = rust_thread_context_id(CONTEXT, 3);
1806        let mut definition = [0_u8; 16];
1807        definition[..8].copy_from_slice(&CONTEXT.to_le_bytes());
1808        definition[8..].copy_from_slice(&3_u64.to_le_bytes());
1809
1810        type SyntheticRecord<'bytes> = (u8, u8, u64, &'bytes [u8], &'bytes [u8]);
1811        let case = AtomicUsize::new(0);
1812        let build = |records: &[SyntheticRecord<'_>]| {
1813            let path = directory.join(format!(
1814                "case-{}.transport",
1815                case.fetch_add(1, Ordering::Relaxed)
1816            ));
1817            create_rust_transport(&path, TOKEN, 16, 4_096).unwrap();
1818            let mut bytes = fs::read(&path).unwrap();
1819            for (kind, outcome, context, id, values) in records {
1820                append_record(&mut bytes, *kind, *outcome, *context, id, values);
1821            }
1822            fs::write(&path, bytes).unwrap();
1823            path
1824        };
1825
1826        let valid = build(&[
1827            (KIND_THREAD_PHASE, 0, child, b"", &definition),
1828            (KIND_THREAD_END, 0, child, b"", b""),
1829            (KIND_TEST_BOUNDARY, 0, CONTEXT, b"", b""),
1830        ]);
1831        let read = read_rust_transport(&valid, &TOKEN).unwrap();
1832        assert_eq!(read.committed, 3);
1833        assert_eq!(
1834            read.thread_phases,
1835            vec![RustThreadPhase {
1836                process_id: 7,
1837                child_context_id: child,
1838                parent_context_id: CONTEXT,
1839                invocation_nonce: 3,
1840                commit_index: 0,
1841            }]
1842        );
1843        assert_eq!(
1844            read.thread_ends,
1845            vec![RustThreadEnd {
1846                process_id: 7,
1847                context_id: child,
1848                commit_index: 1,
1849            }]
1850        );
1851        assert_eq!(
1852            read.test_boundaries,
1853            vec![RustTestBoundary {
1854                process_id: 7,
1855                context_id: CONTEXT,
1856                commit_index: 2,
1857            }]
1858        );
1859
1860        // A thread phase whose context does not derive from its payload fails
1861        // authentication like a tampered assertion phase.
1862        let tampered = build(&[(KIND_THREAD_PHASE, 0, child ^ 1, b"", &definition)]);
1863        assert!(matches!(
1864            read_rust_transport(&tampered, &TOKEN),
1865            Err(RustTransportError::InvalidAssertionContext(_))
1866        ));
1867
1868        let conflicting_definition = {
1869            let mut other = [0_u8; 16];
1870            other[..8].copy_from_slice(&CONTEXT.to_le_bytes());
1871            other[8..].copy_from_slice(&4_u64.to_le_bytes());
1872            build(&[
1873                (KIND_THREAD_PHASE, 0, child, b"", &definition),
1874                (KIND_THREAD_PHASE, 0, child, b"", &other),
1875            ])
1876        };
1877        assert!(matches!(
1878            read_rust_transport(&conflicting_definition, &TOKEN),
1879            Err(RustTransportError::InvalidAssertionContext(_))
1880        ));
1881
1882        let duplicate_end = build(&[
1883            (KIND_THREAD_PHASE, 0, child, b"", &definition),
1884            (KIND_THREAD_END, 0, child, b"", b""),
1885            (KIND_THREAD_END, 0, child, b"", b""),
1886        ]);
1887        assert_eq!(
1888            read_rust_transport(&duplicate_end, &TOKEN),
1889            Err(RustTransportError::InvalidRecord(2))
1890        );
1891
1892        let duplicate_boundary = build(&[
1893            (KIND_TEST_BOUNDARY, 0, CONTEXT, b"", b""),
1894            (KIND_TEST_BOUNDARY, 0, CONTEXT, b"", b""),
1895        ]);
1896        assert_eq!(
1897            read_rust_transport(&duplicate_boundary, &TOKEN),
1898            Err(RustTransportError::InvalidRecord(1))
1899        );
1900
1901        for invalid in [
1902            // Zero and reserved contexts are never valid thread-kind contexts.
1903            build(&[(KIND_THREAD_END, 0, 0, b"", b"")]),
1904            build(&[(KIND_TEST_BOUNDARY, 0, u64::MAX, b"", b"")]),
1905            // Thread-kind records carry no probe identity or extra payload.
1906            build(&[(KIND_THREAD_END, 0, CONTEXT, b"x", b"")]),
1907            build(&[(KIND_TEST_BOUNDARY, 0, CONTEXT, b"", b"y")]),
1908            build(&[(KIND_THREAD_PHASE, 0, child, b"", &definition[..8])]),
1909            // Outcomes are meaningless for thread-kind records.
1910            build(&[(KIND_THREAD_END, 1, CONTEXT, b"", b"")]),
1911        ] {
1912            assert_eq!(
1913                read_rust_transport(&invalid, &TOKEN),
1914                Err(RustTransportError::InvalidRecord(0))
1915            );
1916        }
1917
1918        fs::remove_dir_all(directory).unwrap();
1919    }
1920
1921    /// Off macOS, Linux and Windows the runtime template compiles to its
1922    /// stub, whose probes record nothing and attach to nothing. That is the
1923    /// premise of `run_direct_rust` refusing there: the stub must build, and
1924    /// it must not pretend to measure.
1925    #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1926    #[test]
1927    fn runtime_stub_compiles_and_records_nothing_elsewhere() {
1928        let directory = temporary_directory("stub");
1929        let binary = compile_fixture(&directory);
1930        let transport = directory.join("stub.transport");
1931        create_rust_transport(&transport, TOKEN, 1_024, 128 * 1024).unwrap();
1932        let output = Command::new(&binary)
1933            .arg("threads")
1934            .env(RUST_TRANSPORT_ENV, &transport)
1935            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
1936            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
1937            .output()
1938            .unwrap();
1939        assert!(output.status.success());
1940        let read = read_rust_transport(&transport, &TOKEN).unwrap();
1941        assert_eq!(read.committed, 0);
1942        assert_eq!(read.attachments, 0);
1943        fs::remove_dir_all(directory).unwrap();
1944    }
1945
1946    #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
1947    #[test]
1948    fn mmap_transport_is_concurrent_bounded_strict_and_kill_resilient() {
1949        let directory = temporary_directory("all");
1950        let binary = compile_fixture(&directory);
1951
1952        let concurrent = directory.join("concurrent.transport");
1953        create_rust_transport(&concurrent, TOKEN, 1_024, 128 * 1024).unwrap();
1954        let output = Command::new(&binary)
1955            .arg("threads")
1956            .env(RUST_TRANSPORT_ENV, &concurrent)
1957            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
1958            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
1959            .output()
1960            .unwrap();
1961        assert!(output.status.success());
1962        let read = read_rust_transport(&concurrent, &TOKEN).unwrap();
1963        assert_eq!(read.committed, 818);
1964        assert_eq!(read.incomplete, 0);
1965        assert_eq!(read.dropped, 0);
1966        assert_eq!(read.attachments, 1);
1967        assert_eq!(read.observations.len(), 801);
1968        assert!(
1969            matches!(read.observations.last(), Some(RustTransportObservation { context_id: CONTEXT, observation: RustProbeObservation::Decision { values, outcome: false, .. }, .. }) if values == &[Some(true), Some(false)])
1970        );
1971        // Every spawned thread runs under its own derived thread-phase context
1972        // parented to the creating context; the main thread stays exact.
1973        assert_eq!(read.thread_phases.len(), 8);
1974        assert!(
1975            read.thread_phases
1976                .iter()
1977                .all(|phase| phase.parent_context_id == CONTEXT)
1978        );
1979        assert_eq!(read.thread_ends.len(), 8);
1980        let thread_contexts = read
1981            .thread_phases
1982            .iter()
1983            .map(|phase| phase.child_context_id)
1984            .collect::<BTreeSet<_>>();
1985        assert_eq!(thread_contexts.len(), 8);
1986        assert_eq!(
1987            read.thread_ends
1988                .iter()
1989                .map(|end| end.context_id)
1990                .collect::<BTreeSet<_>>(),
1991            thread_contexts
1992        );
1993        assert!(read.observations.iter().all(|item| {
1994            item.context_id == CONTEXT || thread_contexts.contains(&item.context_id)
1995        }));
1996        assert_eq!(
1997            read.observations
1998                .iter()
1999                .filter(|item| thread_contexts.contains(&item.context_id))
2000                .count(),
2001            800
2002        );
2003        validate_rust_phase_contexts(CONTEXT, &read).unwrap();
2004        assert_eq!(read.ordinal_hits.len(), 1);
2005        assert_eq!(read.ordinal_hits[0].ordinal, 7);
2006        assert_eq!(read.ordinal_hits[0].context_id, CONTEXT);
2007        assert_eq!(
2008            read_rust_transport(&concurrent, &[0x43; TOKEN_SIZE]),
2009            Err(RustTransportError::InvalidHeader),
2010            "a supervisor must never accept evidence from another task token"
2011        );
2012
2013        let mir_decisions = directory.join("mir-decisions.transport");
2014        create_rust_transport(&mir_decisions, TOKEN, 16, 4_096).unwrap();
2015        let output = Command::new(&binary)
2016            .arg("mir-decisions")
2017            .env(RUST_TRANSPORT_ENV, &mir_decisions)
2018            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2019            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2020            .output()
2021            .unwrap();
2022        assert!(output.status.success());
2023        let read = read_rust_transport(&mir_decisions, &TOKEN).unwrap();
2024        assert_eq!(
2025            read.committed, 7,
2026            "two migrated threads add one thread phase and one thread end each"
2027        );
2028        assert_eq!(read.thread_phases.len(), 2);
2029        assert_eq!(read.thread_ends.len(), 2);
2030        assert_eq!(read.dropped, 0);
2031        assert_eq!(read.incomplete, 0);
2032        assert!(read.observations.iter().any(|observation| matches!(
2033            observation,
2034            RustTransportObservation {
2035                context_id: 901,
2036                observation: RustProbeObservation::Decision { id, values, outcome: true },
2037                ..
2038            }
2039                if id == "rs:decision:0123456789abcdef01234567"
2040                    && values == &[Some(true), Some(true)]
2041        )));
2042        assert!(
2043            read.ordinal_hits
2044                .iter()
2045                .any(|hit| hit.context_id == 903 && hit.ordinal == 777)
2046        );
2047        assert!(!read.ordinal_hits.iter().any(|hit| hit.ordinal == 888));
2048        assert!(read.observations.iter().any(|observation| matches!(
2049            observation,
2050            RustTransportObservation {
2051                context_id: 902,
2052                observation: RustProbeObservation::Decision { id, values, outcome: false },
2053                ..
2054            }
2055                if id == "rs:decision:fedcba9876543210fedcba98"
2056                    && values == &[Some(false)]
2057        )));
2058
2059        let processes = directory.join("processes.transport");
2060        create_rust_transport(&processes, TOKEN, 64, 8 * 1024).unwrap();
2061        let mut children = (0..8)
2062            .map(|_| {
2063                Command::new(&binary)
2064                    .env(RUST_TRANSPORT_ENV, &processes)
2065                    .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2066                    .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2067                    .spawn()
2068                    .unwrap()
2069            })
2070            .collect::<Vec<_>>();
2071        for child in &mut children {
2072            assert!(child.wait().unwrap().success());
2073        }
2074        let read = read_rust_transport(&processes, &TOKEN).unwrap();
2075        assert_eq!(read.attachments, 8);
2076        assert_eq!(read.committed, 24);
2077        assert_eq!(read.incomplete, 0);
2078        assert_eq!(read.dropped, 0);
2079        assert_eq!(
2080            read.observations
2081                .iter()
2082                .map(|item| item.process_id)
2083                .collect::<std::collections::BTreeSet<_>>()
2084                .len(),
2085            8
2086        );
2087
2088        let nested = directory.join("nested-context.transport");
2089        create_rust_transport(&nested, TOKEN, 16, 4_096).unwrap();
2090        let output = Command::new(&binary)
2091            .arg("contexts")
2092            .env(RUST_TRANSPORT_ENV, &nested)
2093            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2094            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2095            .output()
2096            .unwrap();
2097        assert!(output.status.success());
2098        let read = read_rust_transport(&nested, &TOKEN).unwrap();
2099        assert_eq!(
2100            read.observations
2101                .iter()
2102                .map(|item| item.context_id)
2103                .collect::<Vec<_>>(),
2104            [
2105                CONTEXT,
2106                100,
2107                200,
2108                100,
2109                CONTEXT,
2110                rust_assertion_context_id(CONTEXT, "rs:decision:0123456789abcdef01234567", 0)
2111                    .unwrap(),
2112                CONTEXT,
2113                rust_assertion_context_id(CONTEXT, "rs:decision:0123456789abcdef01234567", 1)
2114                    .unwrap(),
2115                CONTEXT,
2116            ]
2117        );
2118        let assertion =
2119            rust_assertion_context_id(CONTEXT, "rs:decision:0123456789abcdef01234567", 0).unwrap();
2120        assert_eq!(read.phases.len(), 2);
2121        assert_eq!(read.phases[0].child_context_id, assertion);
2122        assert_eq!(read.phases[0].parent_context_id, CONTEXT);
2123        assert_eq!(read.phases[0].invocation_nonce, 0);
2124        assert_eq!(
2125            read.phases[0].decision_id,
2126            "rs:decision:0123456789abcdef01234567"
2127        );
2128        assert_ne!(
2129            read.phases[0].child_context_id,
2130            read.phases[1].child_context_id
2131        );
2132        assert_eq!(read.phases[1].parent_context_id, CONTEXT);
2133        assert_eq!(read.phases[1].invocation_nonce, 1);
2134        assert_eq!(read.phases[1].decision_id, read.phases[0].decision_id);
2135        assert_eq!(read.committed, 11);
2136
2137        let invalid_phase = directory.join("invalid-phase.transport");
2138        fs::copy(&nested, &invalid_phase).unwrap();
2139        let mut bytes = fs::read(&invalid_phase).unwrap();
2140        let descriptor_capacity = get_u32(&bytes, 20).unwrap();
2141        let payload_base = HEADER_SIZE + descriptor_capacity as usize * DESCRIPTOR_SIZE;
2142        let descriptor = (0..read.committed as usize)
2143            .map(|index| HEADER_SIZE + index * DESCRIPTOR_SIZE)
2144            .find(|offset| bytes[*offset + KIND_OFFSET] == KIND_PHASE)
2145            .unwrap();
2146        let payload_offset = get_u32(&bytes, descriptor + PAYLOAD_OFFSET_OFFSET).unwrap();
2147        let payload_length = get_u32(&bytes, descriptor + PAYLOAD_LENGTH_OFFSET).unwrap();
2148        let id_length = get_u32(&bytes, descriptor + ID_LENGTH_OFFSET).unwrap();
2149        let value_length = get_u32(&bytes, descriptor + VALUE_LENGTH_OFFSET).unwrap();
2150        let payload = payload_base + payload_offset as usize;
2151        let id = bytes[payload..payload + id_length as usize].to_vec();
2152        let values =
2153            bytes[payload + id_length as usize..payload + payload_length as usize].to_vec();
2154        let invalid_child = 7_u64;
2155        bytes[descriptor + CONTEXT_OFFSET..descriptor + CONTEXT_OFFSET + 8]
2156            .copy_from_slice(&invalid_child.to_le_bytes());
2157        let replacement_checksum = checksum(
2158            KIND_PHASE,
2159            0,
2160            get_u32(&bytes, descriptor + PID_OFFSET).unwrap(),
2161            invalid_child,
2162            payload_offset,
2163            payload_length,
2164            id_length,
2165            value_length,
2166            &id,
2167            &values,
2168        );
2169        bytes[descriptor + CHECKSUM_OFFSET..descriptor + CHECKSUM_OFFSET + 8]
2170            .copy_from_slice(&replacement_checksum.to_le_bytes());
2171        fs::write(&invalid_phase, bytes).unwrap();
2172        assert!(matches!(
2173            read_rust_transport(&invalid_phase, &TOKEN),
2174            Err(RustTransportError::InvalidAssertionContext(_))
2175        ));
2176
2177        let rejected = directory.join("rejected-token.transport");
2178        create_rust_transport(&rejected, TOKEN, 16, 4_096).unwrap();
2179        let output = Command::new(&binary)
2180            .env(RUST_TRANSPORT_ENV, &rejected)
2181            .env(RUST_TRANSPORT_TOKEN_ENV, "00".repeat(TOKEN_SIZE))
2182            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2183            .output()
2184            .unwrap();
2185        assert!(output.status.success());
2186        let read = read_rust_transport(&rejected, &TOKEN).unwrap();
2187        assert_eq!(read.attachments, 0);
2188        assert_eq!(read.committed, 0);
2189        assert_eq!(read.dropped, 0);
2190
2191        let malformed_context = directory.join("malformed-context.transport");
2192        create_rust_transport(&malformed_context, TOKEN, 16, 4_096).unwrap();
2193        let output = Command::new(&binary)
2194            .env(RUST_TRANSPORT_ENV, &malformed_context)
2195            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2196            .env(RUST_CONTEXT_ENV, "not-a-context")
2197            .output()
2198            .unwrap();
2199        assert!(output.status.success());
2200        let read = read_rust_transport(&malformed_context, &TOKEN).unwrap();
2201        assert_eq!(read.attachments, 0);
2202        assert_eq!(read.committed, 0);
2203
2204        let bounded = directory.join("bounded.transport");
2205        create_rust_transport(&bounded, TOKEN, 2, 256).unwrap();
2206        let output = Command::new(&binary)
2207            .env(RUST_TRANSPORT_ENV, &bounded)
2208            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2209            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2210            .output()
2211            .unwrap();
2212        assert!(output.status.success());
2213        let read = read_rust_transport(&bounded, &TOKEN).unwrap();
2214        assert_eq!(read.committed, 2);
2215        assert_eq!(read.dropped, 1);
2216
2217        let payload_bounded = directory.join("payload-bounded.transport");
2218        create_rust_transport(&payload_bounded, TOKEN, 8, 16).unwrap();
2219        let output = Command::new(&binary)
2220            .env(RUST_TRANSPORT_ENV, &payload_bounded)
2221            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2222            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2223            .output()
2224            .unwrap();
2225        assert!(output.status.success());
2226        let read = read_rust_transport(&payload_bounded, &TOKEN).unwrap();
2227        assert_eq!(read.committed, 0);
2228        assert_eq!(read.incomplete, 3);
2229        assert_eq!(read.dropped, 3);
2230
2231        let killed = directory.join("killed.transport");
2232        create_rust_transport(&killed, TOKEN, 16, 4_096).unwrap();
2233        let mut child = Command::new(&binary)
2234            .arg("kill")
2235            .env(RUST_TRANSPORT_ENV, &killed)
2236            .env(RUST_TRANSPORT_TOKEN_ENV, token_hex())
2237            .env(RUST_CONTEXT_ENV, format!("{CONTEXT:016x}"))
2238            .stdout(Stdio::piped())
2239            .spawn()
2240            .unwrap();
2241        let mut ready = String::new();
2242        BufReader::new(child.stdout.take().unwrap())
2243            .read_line(&mut ready)
2244            .unwrap();
2245        assert_eq!(ready, "ready\n");
2246        child.kill().unwrap();
2247        child.wait().unwrap();
2248        let read = read_rust_transport(&killed, &TOKEN).unwrap();
2249        assert_eq!(read.committed, 1);
2250        assert_eq!(read.incomplete, 2);
2251        assert_eq!(read.dropped, 0);
2252        assert_eq!(
2253            read.observations,
2254            [RustTransportObservation {
2255                process_id: read.observations[0].process_id,
2256                context_id: CONTEXT,
2257                observation: RustProbeObservation::Hit {
2258                    id: "rs:function:fedcba9876543210fedcba98".into()
2259                }
2260            }]
2261        );
2262
2263        let corrupt = directory.join("corrupt.transport");
2264        create_rust_transport(&corrupt, TOKEN, 2, 256).unwrap();
2265        let mut bytes = fs::read(&corrupt).unwrap();
2266        bytes[0] ^= 1;
2267        fs::write(&corrupt, bytes).unwrap();
2268        assert_eq!(
2269            read_rust_transport(&corrupt, &TOKEN),
2270            Err(RustTransportError::InvalidHeader)
2271        );
2272
2273        let invalid_commit = directory.join("invalid-commit.transport");
2274        fs::copy(&bounded, &invalid_commit).unwrap();
2275        let mut bytes = fs::read(&invalid_commit).unwrap();
2276        bytes[HEADER_SIZE + COMMIT_OFFSET] = 2;
2277        fs::write(&invalid_commit, bytes).unwrap();
2278        assert_eq!(
2279            read_rust_transport(&invalid_commit, &TOKEN),
2280            Err(RustTransportError::InvalidDescriptor(0))
2281        );
2282
2283        let invalid_flags = directory.join("invalid-flags.transport");
2284        fs::copy(&bounded, &invalid_flags).unwrap();
2285        let mut bytes = fs::read(&invalid_flags).unwrap();
2286        bytes[HEADER_SIZE + 3] = 1;
2287        fs::write(&invalid_flags, bytes).unwrap();
2288        assert_eq!(
2289            read_rust_transport(&invalid_flags, &TOKEN),
2290            Err(RustTransportError::InvalidDescriptor(0))
2291        );
2292
2293        let invalid_checksum = directory.join("invalid-checksum.transport");
2294        fs::copy(&bounded, &invalid_checksum).unwrap();
2295        let mut bytes = fs::read(&invalid_checksum).unwrap();
2296        bytes[HEADER_SIZE + CHECKSUM_OFFSET] ^= 1;
2297        fs::write(&invalid_checksum, bytes).unwrap();
2298        assert_eq!(
2299            read_rust_transport(&invalid_checksum, &TOKEN),
2300            Err(RustTransportError::InvalidRecord(0))
2301        );
2302
2303        #[cfg(unix)]
2304        {
2305            use std::os::unix::fs::symlink;
2306
2307            let linked = directory.join("linked.transport");
2308            symlink(&bounded, &linked).unwrap();
2309            assert_eq!(
2310                read_rust_transport(&linked, &TOKEN),
2311                Err(RustTransportError::UnsafeFile(linked.display().to_string()))
2312            );
2313        }
2314
2315        let truncated = directory.join("truncated.transport");
2316        create_rust_transport(&truncated, TOKEN, 2, 256).unwrap();
2317        OpenOptions::new()
2318            .write(true)
2319            .open(&truncated)
2320            .unwrap()
2321            .set_len(63)
2322            .unwrap();
2323        assert_eq!(
2324            read_rust_transport(&truncated, &TOKEN),
2325            Err(RustTransportError::InvalidHeader)
2326        );
2327
2328        let incomplete = directory.join("incomplete.transport");
2329        create_rust_transport(&incomplete, TOKEN, 2, 256).unwrap();
2330        let file = OpenOptions::new()
2331            .read(true)
2332            .write(true)
2333            .open(&incomplete)
2334            .unwrap();
2335        let mapping = map_mut(&file).unwrap();
2336        atomic_u64(&mapping, NEXT_DESCRIPTOR_OFFSET)
2337            .unwrap()
2338            .store(1, Ordering::Release);
2339        mapping.flush().unwrap();
2340        drop(mapping);
2341        let read = read_rust_transport(&incomplete, &TOKEN).unwrap();
2342        assert_eq!(read.committed, 0);
2343        assert_eq!(read.incomplete, 1);
2344        assert!(read.observations.is_empty());
2345        assert!(read.ordinal_hits.is_empty());
2346
2347        fs::remove_dir_all(directory).unwrap();
2348    }
2349}