1use std::collections::BTreeMap;
21use std::error::Error;
22use std::fmt;
23use std::io;
24use std::io::Read;
25use std::io::Write;
26use std::net::Ipv4Addr;
27use std::net::Ipv6Addr;
28use std::path::PathBuf;
29
30use chrono::DateTime;
31use chrono::Utc;
32use serde::Deserialize;
33use serde::Serialize;
34
35use crate::fd::OpenFileId;
36use crate::time::LogicalTime;
37
38pub const NETWORK_TRACE_MAGIC: [u8; 16] = *b"HERMIT-NET-TRACE";
40pub const NETWORK_TRACE_VERSION_V1: u32 = 1;
42
43const FRAME_HEADER_LEN: usize = NETWORK_TRACE_MAGIC.len() + 4 + 8;
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum NetworkTraceMode {
49 #[default]
51 Off,
52 Record,
54 Replay,
56}
57
58#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(deny_unknown_fields)]
65pub struct NetworkTraceConfig {
66 pub mode: NetworkTraceMode,
67 pub path: Option<PathBuf>,
68 pub network_perturb_seed: Option<u64>,
69}
70
71impl NetworkTraceConfig {
72 pub fn validate(&self) -> Result<(), NetworkTraceConfigError> {
74 match self.mode {
75 NetworkTraceMode::Off => {
76 if self.path.is_some() || self.network_perturb_seed.is_some() {
77 return Err(NetworkTraceConfigError::OptionsWhileOff);
78 }
79 }
80 NetworkTraceMode::Record => {
81 validate_trace_path(self.path.as_ref())?;
82 if self.network_perturb_seed.is_some() {
83 return Err(NetworkTraceConfigError::PerturbationDuringRecord);
84 }
85 }
86 NetworkTraceMode::Replay => validate_trace_path(self.path.as_ref())?,
87 }
88 Ok(())
89 }
90}
91
92fn validate_trace_path(path: Option<&PathBuf>) -> Result<(), NetworkTraceConfigError> {
93 let Some(path) = path else {
94 return Err(NetworkTraceConfigError::MissingPath);
95 };
96 if path.as_os_str().is_empty() {
97 return Err(NetworkTraceConfigError::EmptyPath);
98 }
99 Ok(())
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum NetworkTraceConfigError {
105 OptionsWhileOff,
106 MissingPath,
107 EmptyPath,
108 PerturbationDuringRecord,
109}
110
111impl fmt::Display for NetworkTraceConfigError {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 Self::OptionsWhileOff => {
115 write!(f, "network trace options require record or replay mode")
116 }
117 Self::MissingPath => write!(f, "network trace record/replay requires a path"),
118 Self::EmptyPath => write!(f, "network trace path must not be empty"),
119 Self::PerturbationDuringRecord => {
120 write!(f, "network perturbation is replay-only")
121 }
122 }
123 }
124}
125
126impl Error for NetworkTraceConfigError {}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case", deny_unknown_fields)]
131pub enum NetworkAddressV1 {
132 Inet4 {
133 address: [u8; 4],
134 port: u16,
135 },
136 Inet6 {
137 address: [u8; 16],
138 port: u16,
139 flowinfo: u32,
140 scope_id: u32,
141 },
142}
143
144impl NetworkAddressV1 {
145 fn is_supported_external_peer(&self) -> bool {
146 match self {
147 Self::Inet4 { address, port } => {
148 *port != 0 && is_supported_external_ipv4(Ipv4Addr::from(*address))
149 }
150 Self::Inet6 { address, port, .. } => {
151 let address = Ipv6Addr::from(*address);
152 *port != 0
153 && match address.to_ipv4_mapped() {
154 Some(mapped) => is_supported_external_ipv4(mapped),
155 None => {
156 !address.is_loopback()
157 && !address.is_unspecified()
158 && !address.is_multicast()
159 }
160 }
161 }
162 }
163 }
164
165 pub fn port(&self) -> u16 {
167 match self {
168 Self::Inet4 { port, .. } | Self::Inet6 { port, .. } => *port,
169 }
170 }
171
172 pub fn is_traceable_peer(&self) -> bool {
177 self.port() != 0 && !self.is_unroutable()
178 }
179
180 fn is_unroutable(&self) -> bool {
181 match self {
182 Self::Inet4 { address, .. } => {
183 let address = Ipv4Addr::from(*address);
184 address.is_unspecified() || address.is_multicast() || address.is_broadcast()
185 }
186 Self::Inet6 { address, .. } => {
187 let address = Ipv6Addr::from(*address);
188 if let Some(mapped) = address.to_ipv4_mapped() {
190 return mapped.is_unspecified()
191 || mapped.is_multicast()
192 || mapped.is_broadcast();
193 }
194 address.is_unspecified() || address.is_multicast()
195 }
196 }
197 }
198
199 fn same_family(&self, other: &Self) -> bool {
200 matches!(
201 (self, other),
202 (Self::Inet4 { .. }, Self::Inet4 { .. }) | (Self::Inet6 { .. }, Self::Inet6 { .. })
203 )
204 }
205}
206
207fn is_supported_external_ipv4(address: Ipv4Addr) -> bool {
208 !address.is_loopback()
209 && !address.is_unspecified()
210 && !address.is_multicast()
211 && !address.is_broadcast()
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "snake_case")]
217pub enum NetworkTransportV1 {
218 Tcp,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(rename_all = "snake_case")]
225pub enum NetworkEndpointRoleV1 {
226 OutboundClient,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231#[serde(deny_unknown_fields)]
232pub struct NetworkChannelV1 {
233 pub id: OpenFileId,
235 pub transport: NetworkTransportV1,
236 pub role: NetworkEndpointRoleV1,
237 pub local_address: NetworkAddressV1,
238 pub peer_address: NetworkAddressV1,
239 pub created_before_competing_threads: bool,
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246#[serde(deny_unknown_fields)]
247pub struct NetworkReleaseV1 {
248 pub not_before_global_time: LogicalTime,
251 pub after_transmitted_offset: u64,
253}
254
255impl NetworkReleaseV1 {
256 pub fn is_eligible(
258 &self,
259 committed_global_time: LogicalTime,
260 validated_transmitted_offset: u64,
261 ) -> bool {
262 committed_global_time >= self.not_before_global_time
263 && validated_transmitted_offset >= self.after_transmitted_offset
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269#[serde(rename_all = "snake_case", deny_unknown_fields)]
270pub enum NetworkInputKindV1 {
271 InboundBytes {
272 stream_offset: u64,
273 bytes: Vec<u8>,
274 },
275 PeerWriteClosed {
276 stream_offset: u64,
277 },
278 SocketError {
280 stream_offset: u64,
281 errno: i32,
282 },
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(deny_unknown_fields)]
288pub struct NetworkInputEventV1 {
289 pub ordinal: u64,
290 pub channel: OpenFileId,
291 pub release: NetworkReleaseV1,
292 pub event: NetworkInputKindV1,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
300#[serde(deny_unknown_fields)]
301pub struct NetworkOutputV1 {
302 pub channel: OpenFileId,
303 pub stream_offset: u64,
304 pub bytes: Vec<u8>,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
315#[serde(deny_unknown_fields)]
316pub struct NetworkSendWaitV4 {
317 pub waits: u64,
320 pub not_before_global_time: LogicalTime,
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
328#[serde(deny_unknown_fields)]
329pub struct NetworkOutputV4 {
330 pub channel: OpenFileId,
331 pub stream_offset: u64,
332 pub bytes: Vec<u8>,
333 pub wait: Option<NetworkSendWaitV4>,
334}
335
336impl From<NetworkOutputV1> for NetworkOutputV4 {
337 fn from(output: NetworkOutputV1) -> Self {
338 Self {
339 channel: output.channel,
340 stream_offset: output.stream_offset,
341 bytes: output.bytes,
342 wait: None,
343 }
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
349#[serde(deny_unknown_fields)]
350pub struct NetworkTraceV1 {
351 pub epoch: DateTime<Utc>,
352 pub channels: Vec<NetworkChannelV1>,
353 pub inputs: Vec<NetworkInputEventV1>,
354 pub outputs: Vec<NetworkOutputV1>,
355}
356
357impl NetworkTraceV1 {
358 pub fn epoch_global_time(&self) -> Result<LogicalTime, NetworkTraceValidationError> {
363 epoch_global_time(&self.epoch)
364 }
365
366 pub fn validate(&self) -> Result<(), NetworkTraceValidationError> {
368 if self.channels.len() != 1 {
369 return Err(NetworkTraceValidationError::ChannelCount(
370 self.channels.len(),
371 ));
372 }
373 let channel = &self.channels[0];
374 if !channel.id.is_socket() {
375 return Err(NetworkTraceValidationError::NonSocketChannelIdentity);
376 }
377 if !channel.created_before_competing_threads {
378 return Err(NetworkTraceValidationError::ScheduleDependentChannelIdentity);
379 }
380 if !channel.local_address.same_family(&channel.peer_address) {
381 return Err(NetworkTraceValidationError::AddressFamilyMismatch);
382 }
383 if !channel.peer_address.is_supported_external_peer() {
384 return Err(NetworkTraceValidationError::UnsupportedPeerAddress);
385 }
386
387 let mut inbound_offset = 0u64;
388 let epoch_start = self.epoch_global_time()?;
389 let mut previous_time = epoch_start;
390 let mut previous_tx_watermark = 0u64;
391 let mut terminal = false;
392
393 let total_output = validate_outputs(&self.outputs, channel.id)?;
394 for (index, input) in self.inputs.iter().enumerate() {
395 if input.ordinal != index as u64 {
396 return Err(NetworkTraceValidationError::NonCanonicalOrdinal);
397 }
398 if input.channel != channel.id {
399 return Err(NetworkTraceValidationError::UnknownChannel);
400 }
401 if input.release.not_before_global_time < epoch_start {
402 return Err(NetworkTraceValidationError::ReleaseBeforeEpoch);
403 }
404 if input.release.not_before_global_time < previous_time
405 || input.release.after_transmitted_offset < previous_tx_watermark
406 {
407 return Err(NetworkTraceValidationError::NonMonotonicRelease);
408 }
409 if input.release.after_transmitted_offset > total_output {
410 return Err(NetworkTraceValidationError::UnreachableTransmitWatermark);
411 }
412 if terminal {
413 return Err(NetworkTraceValidationError::EventAfterTerminal);
414 }
415
416 match &input.event {
417 NetworkInputKindV1::InboundBytes {
418 stream_offset,
419 bytes,
420 } => {
421 if bytes.is_empty() {
422 return Err(NetworkTraceValidationError::EmptyByteChunk);
423 }
424 if *stream_offset != inbound_offset {
425 return Err(NetworkTraceValidationError::NonContiguousInput);
426 }
427 inbound_offset = inbound_offset
428 .checked_add(bytes.len() as u64)
429 .ok_or(NetworkTraceValidationError::StreamOffsetOverflow)?;
430 }
431 NetworkInputKindV1::PeerWriteClosed { stream_offset } => {
432 if *stream_offset != inbound_offset {
433 return Err(NetworkTraceValidationError::NonContiguousInput);
434 }
435 terminal = true;
436 }
437 NetworkInputKindV1::SocketError {
438 stream_offset,
439 errno,
440 } => {
441 if *stream_offset != inbound_offset {
442 return Err(NetworkTraceValidationError::NonContiguousInput);
443 }
444 if !(1..=4095).contains(errno) {
445 return Err(NetworkTraceValidationError::InvalidErrno);
446 }
447 if matches!(*errno, libc::EAGAIN | libc::EINTR) {
451 return Err(NetworkTraceValidationError::NonTerminalSocketError);
452 }
453 terminal = true;
454 }
455 }
456 previous_time = input.release.not_before_global_time;
457 previous_tx_watermark = input.release.after_transmitted_offset;
458 }
459 Ok(())
460 }
461
462 pub fn write_framed<W: Write>(&self, writer: W) -> Result<(), NetworkTraceCodecError> {
464 self.validate()?;
465 write_frame(writer, NETWORK_TRACE_VERSION_V1, self)
466 }
467
468 pub fn read_framed<R: Read>(reader: R) -> Result<Self, NetworkTraceCodecError> {
471 let (version, payload) = read_frame(reader)?;
472 if version != NETWORK_TRACE_VERSION_V1 {
473 return Err(NetworkTraceCodecError::UnsupportedVersion(version));
474 }
475 let trace: Self = decode_payload(&payload)?;
476 trace.validate()?;
477 Ok(trace)
478 }
479}
480
481fn read_frame<R: Read>(mut reader: R) -> Result<(u32, Vec<u8>), NetworkTraceCodecError> {
483 let mut header = [0u8; FRAME_HEADER_LEN];
484 read_exact_or_truncated(&mut reader, &mut header)?;
485 if header[..NETWORK_TRACE_MAGIC.len()] != NETWORK_TRACE_MAGIC {
486 return Err(NetworkTraceCodecError::BadMagic);
487 }
488 let version_start = NETWORK_TRACE_MAGIC.len();
489 let version = u32::from_le_bytes(
490 header[version_start..version_start + 4]
491 .try_into()
492 .expect("fixed-size version field"),
493 );
494 if ![
495 NETWORK_TRACE_VERSION_V1,
496 NETWORK_TRACE_VERSION_V2,
497 NETWORK_TRACE_VERSION_V3,
498 NETWORK_TRACE_VERSION_V4,
499 ]
500 .contains(&version)
501 {
502 return Err(NetworkTraceCodecError::UnsupportedVersion(version));
503 }
504 let len_start = version_start + 4;
505 let payload_len = u64::from_le_bytes(
506 header[len_start..len_start + 8]
507 .try_into()
508 .expect("fixed-size length field"),
509 );
510 let mut payload = Vec::new();
513 let read = reader
514 .by_ref()
515 .take(payload_len)
516 .read_to_end(&mut payload)?;
517 if read as u64 != payload_len {
518 return Err(NetworkTraceCodecError::Truncated);
519 }
520 let mut trailing = [0u8; 1];
521 if reader.read(&mut trailing)? != 0 {
522 return Err(NetworkTraceCodecError::TrailingData);
523 }
524 Ok((version, payload))
525}
526
527fn decode_payload<T: serde::de::DeserializeOwned>(
528 payload: &[u8],
529) -> Result<T, NetworkTraceCodecError> {
530 let (value, consumed): (T, usize) =
531 bincode::serde::decode_from_slice(payload, bincode::config::standard())
532 .map_err(NetworkTraceCodecError::Decode)?;
533 if consumed != payload.len() {
534 return Err(NetworkTraceCodecError::TrailingPayloadData);
535 }
536 Ok(value)
537}
538
539fn encode_frame<T: Serialize>(version: u32, value: &T) -> Result<Vec<u8>, NetworkTraceCodecError> {
541 let mut frame = Vec::with_capacity(FRAME_HEADER_LEN);
542 frame.extend_from_slice(&NETWORK_TRACE_MAGIC);
543 frame.extend_from_slice(&version.to_le_bytes());
544 frame.extend_from_slice(&[0; 8]);
545 bincode::serde::encode_into_std_write(value, &mut frame, bincode::config::standard())
546 .map_err(NetworkTraceCodecError::Encode)?;
547 let payload_len = (frame.len() - FRAME_HEADER_LEN) as u64;
548 frame[FRAME_HEADER_LEN - 8..FRAME_HEADER_LEN].copy_from_slice(&payload_len.to_le_bytes());
549 Ok(frame)
550}
551
552fn write_frame<W: Write, T: Serialize>(
553 mut writer: W,
554 version: u32,
555 value: &T,
556) -> Result<(), NetworkTraceCodecError> {
557 writer.write_all(&encode_frame(version, value)?)?;
558 Ok(())
559}
560
561pub const NETWORK_TRACE_VERSION_V2: u32 = 2;
564pub const NETWORK_TRACE_VERSION_V3: u32 = 3;
568pub const NETWORK_TRACE_VERSION_V4: u32 = 4;
572
573#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
580#[serde(deny_unknown_fields)]
581pub struct NetworkChannelV2 {
582 pub id: OpenFileId,
584 pub transport: NetworkTransportV1,
585 pub role: NetworkEndpointRoleV1,
586 pub local_address: Option<NetworkAddressV1>,
588 pub peer_address: NetworkAddressV1,
589 pub connect_errno: i32,
592}
593
594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
597#[serde(deny_unknown_fields)]
598pub struct NetworkTraceV2 {
599 pub epoch: DateTime<Utc>,
600 pub channels: Vec<NetworkChannelV2>,
601 pub inputs: Vec<NetworkInputEventV1>,
603 pub outputs: Vec<NetworkOutputV4>,
605}
606
607#[derive(Serialize, Deserialize)]
610#[serde(deny_unknown_fields)]
611struct NetworkTraceV2Wire {
612 epoch: DateTime<Utc>,
613 channels: Vec<NetworkChannelV2>,
614 inputs: Vec<NetworkInputEventV1>,
615 outputs: Vec<NetworkOutputV1>,
616}
617
618impl From<NetworkTraceV2Wire> for NetworkTraceV2 {
619 fn from(trace: NetworkTraceV2Wire) -> Self {
620 Self {
621 epoch: trace.epoch,
622 channels: trace.channels,
623 inputs: trace.inputs,
624 outputs: trace.outputs.into_iter().map(Into::into).collect(),
625 }
626 }
627}
628
629impl From<NetworkTraceV1> for NetworkTraceV2 {
630 fn from(trace: NetworkTraceV1) -> Self {
631 Self {
632 epoch: trace.epoch,
633 channels: trace
634 .channels
635 .into_iter()
636 .map(|channel| NetworkChannelV2 {
637 id: channel.id,
638 transport: channel.transport,
639 role: channel.role,
640 local_address: Some(channel.local_address),
641 peer_address: channel.peer_address,
642 connect_errno: 0,
643 })
644 .collect(),
645 inputs: trace.inputs,
646 outputs: trace.outputs.into_iter().map(Into::into).collect(),
647 }
648 }
649}
650
651impl NetworkTraceV2 {
652 pub fn validate(&self) -> Result<(), NetworkTraceValidationError> {
654 let epoch_start = epoch_global_time(&self.epoch)?;
655 let mut channels = BTreeMap::new();
656 for channel in &self.channels {
657 if !channel.id.is_socket() {
658 return Err(NetworkTraceValidationError::NonSocketChannelIdentity);
659 }
660 if !channel.peer_address.is_traceable_peer() {
661 return Err(NetworkTraceValidationError::UnsupportedPeerAddress);
662 }
663 if let Some(local) = &channel.local_address
664 && !local.same_family(&channel.peer_address)
665 {
666 return Err(NetworkTraceValidationError::AddressFamilyMismatch);
667 }
668 if !(0..=4095).contains(&channel.connect_errno)
669 || (channel.connect_errno == 0) != channel.local_address.is_some()
670 {
671 return Err(NetworkTraceValidationError::InvalidErrno);
672 }
673 if channels.insert(channel.id, channel).is_some() {
674 return Err(NetworkTraceValidationError::DuplicateChannel);
675 }
676 }
677
678 let mut total_output: BTreeMap<OpenFileId, u64> = BTreeMap::new();
679 let mut last_wait: BTreeMap<OpenFileId, LogicalTime> = BTreeMap::new();
680 for output in &self.outputs {
681 let channel = channels
682 .get(&output.channel)
683 .ok_or(NetworkTraceValidationError::UnknownChannel)?;
684 if channel.connect_errno != 0 {
685 return Err(NetworkTraceValidationError::TrafficOnFailedChannel);
686 }
687 let offset = total_output.entry(output.channel).or_default();
688 if output.stream_offset != *offset {
689 return Err(NetworkTraceValidationError::NonContiguousOutput);
690 }
691 *offset = offset
692 .checked_add(output.bytes.len() as u64)
693 .ok_or(NetworkTraceValidationError::StreamOffsetOverflow)?;
694 if let Some(wait) = &output.wait {
695 if output.bytes.is_empty() {
697 return Err(NetworkTraceValidationError::WaitOnRefusedSend);
698 }
699 if wait.waits == 0 {
700 return Err(NetworkTraceValidationError::ZeroSendWaits);
701 }
702 if wait.not_before_global_time < epoch_start {
703 return Err(NetworkTraceValidationError::SendWaitBeforeEpoch);
704 }
705 let previous = last_wait.entry(output.channel).or_insert(epoch_start);
706 if wait.not_before_global_time < *previous {
707 return Err(NetworkTraceValidationError::NonMonotonicSendWait);
708 }
709 *previous = wait.not_before_global_time;
710 }
711 }
712
713 #[derive(Default)]
714 struct InboundCursor {
715 offset: u64,
716 tx_watermark: u64,
717 terminal: bool,
718 }
719 let mut cursors: BTreeMap<OpenFileId, InboundCursor> = BTreeMap::new();
720 let mut previous_time = epoch_start;
721 for (index, input) in self.inputs.iter().enumerate() {
722 if input.ordinal != index as u64 {
723 return Err(NetworkTraceValidationError::NonCanonicalOrdinal);
724 }
725 let channel = channels
726 .get(&input.channel)
727 .ok_or(NetworkTraceValidationError::UnknownChannel)?;
728 if channel.connect_errno != 0 {
729 return Err(NetworkTraceValidationError::TrafficOnFailedChannel);
730 }
731 if input.release.not_before_global_time < epoch_start {
732 return Err(NetworkTraceValidationError::ReleaseBeforeEpoch);
733 }
734 let cursor = cursors.entry(input.channel).or_default();
735 if input.release.not_before_global_time < previous_time
736 || input.release.after_transmitted_offset < cursor.tx_watermark
737 {
738 return Err(NetworkTraceValidationError::NonMonotonicRelease);
739 }
740 if input.release.after_transmitted_offset
741 > total_output.get(&input.channel).copied().unwrap_or(0)
742 {
743 return Err(NetworkTraceValidationError::UnreachableTransmitWatermark);
744 }
745 if cursor.terminal {
746 return Err(NetworkTraceValidationError::EventAfterTerminal);
747 }
748 cursor.offset = validate_input_kind(&input.event, cursor.offset)?;
749 cursor.terminal = !matches!(input.event, NetworkInputKindV1::InboundBytes { .. });
750 cursor.tx_watermark = input.release.after_transmitted_offset;
751 previous_time = input.release.not_before_global_time;
752 }
753 Ok(())
754 }
755
756 pub fn records_refused_sends(&self) -> bool {
759 self.outputs.iter().any(|output| output.bytes.is_empty())
760 }
761
762 pub fn records_send_waits(&self) -> bool {
764 self.outputs.iter().any(|output| output.wait.is_some())
765 }
766
767 pub fn write_framed<W: Write>(&self, mut writer: W) -> Result<(), NetworkTraceCodecError> {
770 writer.write_all(&self.encode_framed()?)?;
771 Ok(())
772 }
773
774 pub fn encode_framed(&self) -> Result<Vec<u8>, NetworkTraceCodecError> {
777 self.validate()?;
778 if self.records_send_waits() {
779 return encode_frame(NETWORK_TRACE_VERSION_V4, self);
780 }
781 let version = if self.records_refused_sends() {
782 NETWORK_TRACE_VERSION_V3
783 } else {
784 NETWORK_TRACE_VERSION_V2
785 };
786 let wire = NetworkTraceV2Wire {
787 epoch: self.epoch,
788 channels: self.channels.clone(),
789 inputs: self.inputs.clone(),
790 outputs: self
791 .outputs
792 .iter()
793 .map(|output| NetworkOutputV1 {
794 channel: output.channel,
795 stream_offset: output.stream_offset,
796 bytes: output.bytes.clone(),
797 })
798 .collect(),
799 };
800 encode_frame(version, &wire)
801 }
802
803 pub fn read_framed<R: Read>(reader: R) -> Result<Self, NetworkTraceCodecError> {
808 let (version, payload) = read_frame(reader)?;
809 let trace = match version {
810 NETWORK_TRACE_VERSION_V1 => {
811 let trace: NetworkTraceV1 = decode_payload(&payload)?;
812 trace.validate()?;
813 Self::from(trace)
814 }
815 NETWORK_TRACE_VERSION_V4 => decode_payload(&payload)?,
816 _ => Self::from(decode_payload::<NetworkTraceV2Wire>(&payload)?),
817 };
818 if version < NETWORK_TRACE_VERSION_V3 && trace.records_refused_sends() {
819 return Err(NetworkTraceValidationError::EmptyByteChunk.into());
820 }
821 trace.validate()?;
822 Ok(trace)
823 }
824}
825
826fn validate_input_kind(
828 event: &NetworkInputKindV1,
829 offset: u64,
830) -> Result<u64, NetworkTraceValidationError> {
831 match event {
832 NetworkInputKindV1::InboundBytes {
833 stream_offset,
834 bytes,
835 } => {
836 if bytes.is_empty() {
837 return Err(NetworkTraceValidationError::EmptyByteChunk);
838 }
839 if *stream_offset != offset {
840 return Err(NetworkTraceValidationError::NonContiguousInput);
841 }
842 offset
843 .checked_add(bytes.len() as u64)
844 .ok_or(NetworkTraceValidationError::StreamOffsetOverflow)
845 }
846 NetworkInputKindV1::PeerWriteClosed { stream_offset } => {
847 if *stream_offset != offset {
848 return Err(NetworkTraceValidationError::NonContiguousInput);
849 }
850 Ok(offset)
851 }
852 NetworkInputKindV1::SocketError {
853 stream_offset,
854 errno,
855 } => {
856 if *stream_offset != offset {
857 return Err(NetworkTraceValidationError::NonContiguousInput);
858 }
859 if !(1..=4095).contains(errno) {
860 return Err(NetworkTraceValidationError::InvalidErrno);
861 }
862 if matches!(*errno, libc::EAGAIN | libc::EINTR) {
863 return Err(NetworkTraceValidationError::NonTerminalSocketError);
864 }
865 Ok(offset)
866 }
867 }
868}
869
870pub fn epoch_global_time(
873 epoch: &DateTime<Utc>,
874) -> Result<LogicalTime, NetworkTraceValidationError> {
875 let seconds = u64::try_from(epoch.timestamp())
876 .map_err(|_| NetworkTraceValidationError::EpochOutOfRange)?;
877 let whole_seconds = seconds
878 .checked_mul(1_000_000_000)
879 .ok_or(NetworkTraceValidationError::EpochOutOfRange)?;
880 let fractional_micros = u64::from(epoch.timestamp_subsec_micros())
881 .checked_mul(1_000)
882 .ok_or(NetworkTraceValidationError::EpochOutOfRange)?;
883 whole_seconds
884 .checked_add(fractional_micros)
885 .map(LogicalTime::from_nanos)
886 .ok_or(NetworkTraceValidationError::EpochOutOfRange)
887}
888
889fn validate_outputs(
890 outputs: &[NetworkOutputV1],
891 channel: OpenFileId,
892) -> Result<u64, NetworkTraceValidationError> {
893 let mut expected_offset = 0u64;
894 for output in outputs {
895 if output.channel != channel {
896 return Err(NetworkTraceValidationError::UnknownChannel);
897 }
898 if output.bytes.is_empty() {
899 return Err(NetworkTraceValidationError::EmptyByteChunk);
900 }
901 if output.stream_offset != expected_offset {
902 return Err(NetworkTraceValidationError::NonContiguousOutput);
903 }
904 expected_offset = expected_offset
905 .checked_add(output.bytes.len() as u64)
906 .ok_or(NetworkTraceValidationError::StreamOffsetOverflow)?;
907 }
908 Ok(expected_offset)
909}
910
911fn read_exact_or_truncated<R: Read>(
912 reader: &mut R,
913 bytes: &mut [u8],
914) -> Result<(), NetworkTraceCodecError> {
915 reader.read_exact(bytes).map_err(|error| {
916 if error.kind() == io::ErrorKind::UnexpectedEof {
917 NetworkTraceCodecError::Truncated
918 } else {
919 NetworkTraceCodecError::Io(error)
920 }
921 })
922}
923
924#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
926pub enum NetworkTraceValidationError {
927 ChannelCount(usize),
928 EpochOutOfRange,
929 NonSocketChannelIdentity,
930 ScheduleDependentChannelIdentity,
931 AddressFamilyMismatch,
932 UnsupportedPeerAddress,
933 UnknownChannel,
934 NonCanonicalOrdinal,
935 ReleaseBeforeEpoch,
936 NonMonotonicRelease,
937 UnreachableTransmitWatermark,
938 EmptyByteChunk,
939 NonContiguousInput,
940 NonContiguousOutput,
941 StreamOffsetOverflow,
942 InvalidErrno,
943 NonTerminalSocketError,
944 EventAfterTerminal,
945 DuplicateChannel,
946 TrafficOnFailedChannel,
947 WaitOnRefusedSend,
948 ZeroSendWaits,
949 SendWaitBeforeEpoch,
950 NonMonotonicSendWait,
951}
952
953impl fmt::Display for NetworkTraceValidationError {
954 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955 write!(f, "invalid network trace: {self:?}")
956 }
957}
958
959impl Error for NetworkTraceValidationError {}
960
961#[derive(Debug)]
963pub enum NetworkTraceCodecError {
964 Io(io::Error),
965 Truncated,
966 BadMagic,
967 UnsupportedVersion(u32),
968 TrailingData,
969 TrailingPayloadData,
970 Encode(bincode::error::EncodeError),
971 Decode(bincode::error::DecodeError),
972 Validation(NetworkTraceValidationError),
973}
974
975impl fmt::Display for NetworkTraceCodecError {
976 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
977 write!(f, "network trace codec error: {self:?}")
978 }
979}
980
981impl Error for NetworkTraceCodecError {}
982
983impl From<io::Error> for NetworkTraceCodecError {
984 fn from(error: io::Error) -> Self {
985 Self::Io(error)
986 }
987}
988
989impl From<NetworkTraceValidationError> for NetworkTraceCodecError {
990 fn from(error: NetworkTraceValidationError) -> Self {
991 Self::Validation(error)
992 }
993}
994
995#[cfg(test)]
996mod tests {
997 use std::io::Cursor;
998 use std::net::Ipv4Addr;
999 use std::time::Duration;
1000
1001 use chrono::TimeZone;
1002
1003 use super::*;
1004 use crate::config::Config;
1005 use crate::pid::DetTid;
1006 use crate::time::GlobalTime;
1007
1008 fn channel_id() -> OpenFileId {
1009 OpenFileId::new_socket(DetTid::from_raw(1), 0)
1010 }
1011
1012 fn trace_epoch() -> DateTime<Utc> {
1013 Utc.timestamp_opt(1_790_000_000, 0).unwrap()
1014 }
1015
1016 fn global_time_after_epoch(nanos: u64) -> LogicalTime {
1017 let config = Config {
1018 epoch: trace_epoch(),
1019 ..Config::default()
1020 };
1021 GlobalTime::new(&config).as_nanos() + LogicalTime::from_nanos(nanos)
1022 }
1023
1024 fn valid_trace() -> NetworkTraceV1 {
1025 let channel = channel_id();
1026 NetworkTraceV1 {
1027 epoch: trace_epoch(),
1028 channels: vec![NetworkChannelV1 {
1029 id: channel,
1030 transport: NetworkTransportV1::Tcp,
1031 role: NetworkEndpointRoleV1::OutboundClient,
1032 local_address: NetworkAddressV1::Inet4 {
1033 address: [10, 0, 0, 2],
1034 port: 40_000,
1035 },
1036 peer_address: NetworkAddressV1::Inet4 {
1037 address: [192, 0, 2, 10],
1038 port: 443,
1039 },
1040 created_before_competing_threads: true,
1041 }],
1042 inputs: vec![
1043 NetworkInputEventV1 {
1044 ordinal: 0,
1045 channel,
1046 release: NetworkReleaseV1 {
1047 not_before_global_time: global_time_after_epoch(10),
1048 after_transmitted_offset: 3,
1049 },
1050 event: NetworkInputKindV1::InboundBytes {
1051 stream_offset: 0,
1052 bytes: b"response".to_vec(),
1053 },
1054 },
1055 NetworkInputEventV1 {
1056 ordinal: 1,
1057 channel,
1058 release: NetworkReleaseV1 {
1059 not_before_global_time: global_time_after_epoch(20),
1060 after_transmitted_offset: 3,
1061 },
1062 event: NetworkInputKindV1::PeerWriteClosed { stream_offset: 8 },
1063 },
1064 ],
1065 outputs: vec![NetworkOutputV1 {
1066 channel,
1067 stream_offset: 0,
1068 bytes: b"req".to_vec(),
1069 }],
1070 }
1071 }
1072
1073 fn framed(trace: &NetworkTraceV1) -> Vec<u8> {
1074 let mut bytes = Vec::new();
1075 trace.write_framed(&mut bytes).unwrap();
1076 bytes
1077 }
1078
1079 fn framed_without_validation(trace: &NetworkTraceV1) -> Vec<u8> {
1080 let payload = bincode::serde::encode_to_vec(trace, bincode::config::standard()).unwrap();
1081 let mut bytes = Vec::new();
1082 bytes.extend_from_slice(&NETWORK_TRACE_MAGIC);
1083 bytes.extend_from_slice(&NETWORK_TRACE_VERSION_V1.to_le_bytes());
1084 bytes.extend_from_slice(&(payload.len() as u64).to_le_bytes());
1085 bytes.extend_from_slice(&payload);
1086 bytes
1087 }
1088
1089 #[test]
1090 fn traceable_peer_names_one_host_including_ipv4_mapped_addresses() {
1091 let inet6 = |address: Ipv6Addr| NetworkAddressV1::Inet6 {
1092 port: 80,
1093 flowinfo: 0,
1094 address: address.octets(),
1095 scope_id: 0,
1096 };
1097 let mapped = |v4: Ipv4Addr| inet6(v4.to_ipv6_mapped());
1098 assert!(mapped(Ipv4Addr::LOCALHOST).is_traceable_peer());
1099 assert!(inet6(Ipv6Addr::LOCALHOST).is_traceable_peer());
1100 for v4 in [
1101 Ipv4Addr::UNSPECIFIED,
1102 Ipv4Addr::BROADCAST,
1103 Ipv4Addr::new(224, 0, 0, 1),
1104 ] {
1105 assert!(!mapped(v4).is_traceable_peer(), "{v4} names no single host");
1106 }
1107 assert!(!inet6(Ipv6Addr::UNSPECIFIED).is_traceable_peer());
1108 }
1109
1110 #[test]
1111 fn network_trace_v1_round_trips_exactly() {
1112 let trace = valid_trace();
1113 let decoded = NetworkTraceV1::read_framed(Cursor::new(framed(&trace))).unwrap();
1114 assert_eq!(decoded, trace);
1115 }
1116
1117 #[test]
1118 fn transient_socket_errors_are_rejected_by_validation_writer_and_reader() {
1119 assert_eq!(libc::EWOULDBLOCK, libc::EAGAIN);
1120 for errno in [libc::EAGAIN, libc::EINTR] {
1121 let mut trace = valid_trace();
1122 trace.inputs[1].event = NetworkInputKindV1::SocketError {
1123 stream_offset: 8,
1124 errno,
1125 };
1126 assert_eq!(
1127 trace.validate(),
1128 Err(NetworkTraceValidationError::NonTerminalSocketError),
1129 "transient errno {errno} cannot terminate the stream"
1130 );
1131
1132 let mut output = b"existing output".to_vec();
1133 assert!(matches!(
1134 trace.write_framed(&mut output),
1135 Err(NetworkTraceCodecError::Validation(
1136 NetworkTraceValidationError::NonTerminalSocketError
1137 ))
1138 ));
1139 assert_eq!(output, b"existing output");
1140 assert!(matches!(
1143 NetworkTraceV1::read_framed(Cursor::new(framed_without_validation(&trace))),
1144 Err(NetworkTraceCodecError::Validation(
1145 NetworkTraceValidationError::NonTerminalSocketError
1146 ))
1147 ));
1148 }
1149 }
1150
1151 #[test]
1152 fn terminal_socket_errors_preserve_framing_and_the_stream_boundary() {
1153 for errno in [libc::ECONNRESET, libc::ETIMEDOUT] {
1154 let mut trace = valid_trace();
1155 trace.inputs[1].event = NetworkInputKindV1::SocketError {
1156 stream_offset: 8,
1157 errno,
1158 };
1159 assert_eq!(trace.validate(), Ok(()));
1160 let bytes = framed(&trace);
1161 assert_eq!(bytes, framed_without_validation(&trace));
1162 assert_eq!(
1163 NetworkTraceV1::read_framed(Cursor::new(bytes)).unwrap(),
1164 trace
1165 );
1166
1167 let mut late = trace.inputs[0].clone();
1168 late.ordinal = 2;
1169 late.release = trace.inputs[1].release;
1170 late.event = NetworkInputKindV1::InboundBytes {
1171 stream_offset: 8,
1172 bytes: b"late".to_vec(),
1173 };
1174 trace.inputs.push(late);
1175 assert_eq!(
1176 trace.validate(),
1177 Err(NetworkTraceValidationError::EventAfterTerminal)
1178 );
1179 }
1180 }
1181
1182 #[test]
1183 fn socket_errno_range_is_still_checked_before_framing() {
1184 for errno in [-1, 0, 4096] {
1185 let mut trace = valid_trace();
1186 trace.inputs[1].event = NetworkInputKindV1::SocketError {
1187 stream_offset: 8,
1188 errno,
1189 };
1190 assert_eq!(
1191 trace.validate(),
1192 Err(NetworkTraceValidationError::InvalidErrno)
1193 );
1194 assert!(matches!(
1195 trace.write_framed(Vec::new()),
1196 Err(NetworkTraceCodecError::Validation(
1197 NetworkTraceValidationError::InvalidErrno
1198 ))
1199 ));
1200 assert!(matches!(
1201 NetworkTraceV1::read_framed(Cursor::new(framed_without_validation(&trace))),
1202 Err(NetworkTraceCodecError::Validation(
1203 NetworkTraceValidationError::InvalidErrno
1204 ))
1205 ));
1206 }
1207 }
1208
1209 #[test]
1210 fn every_strict_prefix_is_reported_as_truncated() {
1211 let bytes = framed(&valid_trace());
1212 for end in 0..bytes.len() {
1213 assert!(
1214 matches!(
1215 NetworkTraceV1::read_framed(Cursor::new(&bytes[..end])),
1216 Err(NetworkTraceCodecError::Truncated)
1217 ),
1218 "prefix of length {end} was not rejected as truncation"
1219 );
1220 }
1221 }
1222
1223 #[test]
1224 fn unknown_versions_are_rejected_before_payload_decode() {
1225 let mut bytes = framed(&valid_trace());
1226 let start = NETWORK_TRACE_MAGIC.len();
1227 bytes[start..start + 4].copy_from_slice(&2u32.to_le_bytes());
1228 assert!(matches!(
1229 NetworkTraceV1::read_framed(Cursor::new(bytes)),
1230 Err(NetworkTraceCodecError::UnsupportedVersion(2))
1231 ));
1232 }
1233
1234 #[test]
1235 fn hostile_length_is_truncation_without_allocating_it() {
1236 let mut bytes = Vec::new();
1238 bytes.extend_from_slice(&NETWORK_TRACE_MAGIC);
1239 bytes.extend_from_slice(&NETWORK_TRACE_VERSION_V1.to_le_bytes());
1240 bytes.extend_from_slice(&u64::MAX.to_le_bytes());
1241 bytes.extend_from_slice(b"short");
1242 assert!(matches!(
1243 NetworkTraceV1::read_framed(Cursor::new(bytes)),
1244 Err(NetworkTraceCodecError::Truncated)
1245 ));
1246 }
1247
1248 #[test]
1249 fn trailing_file_data_is_rejected() {
1250 let mut bytes = framed(&valid_trace());
1251 bytes.push(0);
1252 assert!(matches!(
1253 NetworkTraceV1::read_framed(Cursor::new(bytes)),
1254 Err(NetworkTraceCodecError::TrailingData)
1255 ));
1256 }
1257
1258 #[test]
1259 fn reader_and_writer_both_refuse_semantically_invalid_traces() {
1260 let mut trace = valid_trace();
1261 trace.inputs[0].ordinal = 7;
1262
1263 assert!(matches!(
1264 trace.write_framed(Vec::new()),
1265 Err(NetworkTraceCodecError::Validation(
1266 NetworkTraceValidationError::NonCanonicalOrdinal
1267 ))
1268 ));
1269 assert!(matches!(
1270 NetworkTraceV1::read_framed(Cursor::new(framed_without_validation(&trace))),
1271 Err(NetworkTraceCodecError::Validation(
1272 NetworkTraceValidationError::NonCanonicalOrdinal
1273 ))
1274 ));
1275 }
1276
1277 #[test]
1278 fn deserialized_pre_unix_epoch_is_a_typed_validation_error() {
1279 let mut trace = valid_trace();
1280 trace.epoch = Utc.timestamp_opt(-1, 0).unwrap();
1281 assert!(matches!(
1282 NetworkTraceV1::read_framed(Cursor::new(framed_without_validation(&trace))),
1283 Err(NetworkTraceCodecError::Validation(
1284 NetworkTraceValidationError::EpochOutOfRange
1285 ))
1286 ));
1287 }
1288
1289 #[test]
1290 fn deserialized_epoch_beyond_global_time_is_a_typed_validation_error() {
1291 let mut trace = valid_trace();
1292 trace.epoch = Utc.with_ymd_and_hms(9999, 1, 1, 0, 0, 0).unwrap();
1293 assert!(matches!(
1294 NetworkTraceV1::read_framed(Cursor::new(framed_without_validation(&trace))),
1295 Err(NetworkTraceCodecError::Validation(
1296 NetworkTraceValidationError::EpochOutOfRange
1297 ))
1298 ));
1299 }
1300
1301 #[test]
1302 fn config_is_off_by_default_and_perturbation_has_no_seed_fallback() {
1303 let config = NetworkTraceConfig::default();
1304 assert_eq!(config.mode, NetworkTraceMode::Off);
1305 assert_eq!(config.network_perturb_seed, None);
1306 assert_eq!(config.validate(), Ok(()));
1307
1308 let replay = NetworkTraceConfig {
1309 mode: NetworkTraceMode::Replay,
1310 path: Some("trace.net".into()),
1311 network_perturb_seed: Some(17),
1312 };
1313 assert_eq!(replay.network_perturb_seed, Some(17));
1314 assert_eq!(replay.validate(), Ok(()));
1315
1316 let encoded = serde_json::to_string(&replay).unwrap();
1317 assert_eq!(
1318 serde_json::from_str::<NetworkTraceConfig>(&encoded).unwrap(),
1319 replay
1320 );
1321 assert!(
1322 serde_json::from_str::<NetworkTraceConfig>(
1323 r#"{"mode":"off","path":null,"network_perturb_seed":null,"unexpected":true}"#
1324 )
1325 .is_err()
1326 );
1327 }
1328
1329 #[test]
1330 fn config_rejects_ambiguous_or_record_perturbation_combinations() {
1331 assert_eq!(
1332 NetworkTraceConfig {
1333 path: Some("trace.net".into()),
1334 ..NetworkTraceConfig::default()
1335 }
1336 .validate(),
1337 Err(NetworkTraceConfigError::OptionsWhileOff)
1338 );
1339 assert_eq!(
1340 NetworkTraceConfig {
1341 mode: NetworkTraceMode::Replay,
1342 ..NetworkTraceConfig::default()
1343 }
1344 .validate(),
1345 Err(NetworkTraceConfigError::MissingPath)
1346 );
1347 assert_eq!(
1348 NetworkTraceConfig {
1349 mode: NetworkTraceMode::Record,
1350 path: Some("trace.net".into()),
1351 network_perturb_seed: Some(1),
1352 }
1353 .validate(),
1354 Err(NetworkTraceConfigError::PerturbationDuringRecord)
1355 );
1356 }
1357
1358 #[test]
1359 fn release_eligibility_uses_the_absolute_global_time_domain() {
1360 let mut config = Config {
1361 epoch: trace_epoch(),
1362 ..Config::default()
1363 };
1364 config.sched_seed = Some(999);
1366 let mut global_time = GlobalTime::new(&config);
1367 let start = global_time.as_nanos();
1368 assert_eq!(valid_trace().epoch_global_time(), Ok(start));
1369 let release = NetworkReleaseV1 {
1370 not_before_global_time: start + LogicalTime::from_nanos(10),
1371 after_transmitted_offset: 3,
1372 };
1373
1374 assert!(!release.is_eligible(global_time.as_nanos(), 3));
1375 global_time.add_extra_time(Duration::from_nanos(10));
1376 assert_eq!(global_time.as_nanos(), release.not_before_global_time);
1377 assert!(!release.is_eligible(global_time.as_nanos(), 2));
1378 assert!(release.is_eligible(global_time.as_nanos(), 3));
1379 }
1380
1381 #[test]
1382 fn v1_validation_rejects_channels_outside_the_supported_envelope() {
1383 let mut trace = valid_trace();
1384 trace.channels.push(trace.channels[0].clone());
1385 assert_eq!(
1386 trace.validate(),
1387 Err(NetworkTraceValidationError::ChannelCount(2))
1388 );
1389
1390 let mut trace = valid_trace();
1391 let non_socket = OpenFileId::new(DetTid::from_raw(1), 0);
1392 trace.channels[0].id = non_socket;
1393 for input in &mut trace.inputs {
1394 input.channel = non_socket;
1395 }
1396 for output in &mut trace.outputs {
1397 output.channel = non_socket;
1398 }
1399 assert_eq!(
1400 trace.validate(),
1401 Err(NetworkTraceValidationError::NonSocketChannelIdentity)
1402 );
1403
1404 let mut trace = valid_trace();
1405 trace.channels[0].peer_address = NetworkAddressV1::Inet4 {
1406 address: [127, 0, 0, 1],
1407 port: 443,
1408 };
1409 assert_eq!(
1410 trace.validate(),
1411 Err(NetworkTraceValidationError::UnsupportedPeerAddress)
1412 );
1413
1414 for mapped in [
1415 Ipv4Addr::LOCALHOST,
1416 Ipv4Addr::UNSPECIFIED,
1417 Ipv4Addr::new(224, 0, 0, 1),
1418 ] {
1419 let mut trace = valid_trace();
1420 trace.channels[0].local_address = NetworkAddressV1::Inet6 {
1421 address: Ipv6Addr::LOCALHOST.octets(),
1422 port: 40_000,
1423 flowinfo: 0,
1424 scope_id: 0,
1425 };
1426 trace.channels[0].peer_address = NetworkAddressV1::Inet6 {
1427 address: mapped.to_ipv6_mapped().octets(),
1428 port: 443,
1429 flowinfo: 0,
1430 scope_id: 0,
1431 };
1432 assert_eq!(
1433 trace.validate(),
1434 Err(NetworkTraceValidationError::UnsupportedPeerAddress),
1435 "IPv4-mapped {mapped} escaped the IPv4 policy"
1436 );
1437 }
1438
1439 let mut trace = valid_trace();
1440 trace.channels[0].created_before_competing_threads = false;
1441 assert_eq!(
1442 trace.validate(),
1443 Err(NetworkTraceValidationError::ScheduleDependentChannelIdentity)
1444 );
1445 }
1446
1447 #[test]
1448 fn v1_validation_rejects_noncanonical_streams_and_release_conditions() {
1449 let mut trace = valid_trace();
1450 trace.inputs[0].release.not_before_global_time = LogicalTime::ZERO;
1451 assert_eq!(
1452 trace.validate(),
1453 Err(NetworkTraceValidationError::ReleaseBeforeEpoch)
1454 );
1455
1456 let mut trace = valid_trace();
1457 trace.inputs[0].ordinal = 1;
1458 assert_eq!(
1459 trace.validate(),
1460 Err(NetworkTraceValidationError::NonCanonicalOrdinal)
1461 );
1462
1463 let mut trace = valid_trace();
1464 trace.inputs[0].release.after_transmitted_offset = 4;
1465 assert_eq!(
1466 trace.validate(),
1467 Err(NetworkTraceValidationError::UnreachableTransmitWatermark)
1468 );
1469
1470 let mut trace = valid_trace();
1471 trace.outputs[0].stream_offset = 1;
1472 assert_eq!(
1473 trace.validate(),
1474 Err(NetworkTraceValidationError::NonContiguousOutput)
1475 );
1476
1477 let mut trace = valid_trace();
1478 trace.inputs.push(NetworkInputEventV1 {
1479 ordinal: 2,
1480 channel: channel_id(),
1481 release: NetworkReleaseV1 {
1482 not_before_global_time: global_time_after_epoch(21),
1483 after_transmitted_offset: 3,
1484 },
1485 event: NetworkInputKindV1::InboundBytes {
1486 stream_offset: 8,
1487 bytes: b"late".to_vec(),
1488 },
1489 });
1490 assert_eq!(
1491 trace.validate(),
1492 Err(NetworkTraceValidationError::EventAfterTerminal)
1493 );
1494 }
1495
1496 fn second_channel_id() -> OpenFileId {
1497 OpenFileId::new_socket(DetTid::from_raw(1), 1)
1498 }
1499
1500 fn loopback(port: u16) -> NetworkAddressV1 {
1501 NetworkAddressV1::Inet4 {
1502 address: [127, 0, 0, 1],
1503 port,
1504 }
1505 }
1506
1507 fn valid_trace_v2() -> NetworkTraceV2 {
1509 let first = channel_id();
1510 let second = second_channel_id();
1511 let refused = OpenFileId::new_socket(DetTid::from_raw(2), 0);
1512 let channel = |id, local: Option<u16>, errno| NetworkChannelV2 {
1513 id,
1514 transport: NetworkTransportV1::Tcp,
1515 role: NetworkEndpointRoleV1::OutboundClient,
1516 local_address: local.map(loopback),
1517 peer_address: loopback(8080),
1518 connect_errno: errno,
1519 };
1520 let input = |ordinal, channel, time, tx, event| NetworkInputEventV1 {
1521 ordinal,
1522 channel,
1523 release: NetworkReleaseV1 {
1524 not_before_global_time: global_time_after_epoch(time),
1525 after_transmitted_offset: tx,
1526 },
1527 event,
1528 };
1529 NetworkTraceV2 {
1530 epoch: trace_epoch(),
1531 channels: vec![
1532 channel(first, Some(40_000), 0),
1533 channel(second, Some(40_001), 0),
1534 channel(refused, None, libc::ECONNREFUSED),
1535 ],
1536 inputs: vec![
1537 input(
1538 0,
1539 second,
1540 10,
1541 2,
1542 NetworkInputKindV1::InboundBytes {
1543 stream_offset: 0,
1544 bytes: b"xy".to_vec(),
1545 },
1546 ),
1547 input(
1548 1,
1549 first,
1550 10,
1551 3,
1552 NetworkInputKindV1::InboundBytes {
1553 stream_offset: 0,
1554 bytes: b"abc".to_vec(),
1555 },
1556 ),
1557 input(
1558 2,
1559 first,
1560 30,
1561 3,
1562 NetworkInputKindV1::PeerWriteClosed { stream_offset: 3 },
1563 ),
1564 ],
1565 outputs: vec![
1566 NetworkOutputV4 {
1567 channel: first,
1568 stream_offset: 0,
1569 bytes: b"req".to_vec(),
1570 wait: None,
1571 },
1572 NetworkOutputV4 {
1573 channel: second,
1574 stream_offset: 0,
1575 bytes: b"hi".to_vec(),
1576 wait: None,
1577 },
1578 ],
1579 }
1580 }
1581
1582 #[test]
1583 fn network_trace_v2_round_trips_exactly() {
1584 let trace = valid_trace_v2();
1585 let mut bytes = Vec::new();
1586 trace.write_framed(&mut bytes).unwrap();
1587 let start = NETWORK_TRACE_MAGIC.len();
1588 assert_eq!(
1589 bytes[start..start + 4],
1590 NETWORK_TRACE_VERSION_V2.to_le_bytes()
1591 );
1592 assert_eq!(
1593 NetworkTraceV2::read_framed(Cursor::new(bytes)).unwrap(),
1594 trace
1595 );
1596 }
1597
1598 #[test]
1599 fn frames_are_the_header_then_the_bincode_payload() {
1600 let v1 = valid_trace();
1603 assert_eq!(framed(&v1), framed_without_validation(&v1));
1604 let trace = valid_trace_v2();
1605 let wire = NetworkTraceV2Wire {
1606 epoch: trace.epoch,
1607 channels: trace.channels.clone(),
1608 inputs: trace.inputs.clone(),
1609 outputs: trace
1610 .outputs
1611 .iter()
1612 .map(|output| NetworkOutputV1 {
1613 channel: output.channel,
1614 stream_offset: output.stream_offset,
1615 bytes: output.bytes.clone(),
1616 })
1617 .collect(),
1618 };
1619 let payload = bincode::serde::encode_to_vec(&wire, bincode::config::standard()).unwrap();
1620 let mut expected = Vec::new();
1622 expected.extend_from_slice(b"HERMIT-NET-TRACE");
1623 expected.extend_from_slice(&[2, 0, 0, 0]);
1624 expected.extend_from_slice(&(payload.len() as u64).to_le_bytes());
1625 expected.extend_from_slice(&payload);
1626 assert_eq!(trace.encode_framed().unwrap(), expected);
1627 }
1628
1629 #[test]
1630 fn network_trace_above_64_mib_round_trips() {
1631 let mut trace = valid_trace_v2();
1634 let len = 64 * 1024 * 1024 + 1;
1635 trace.inputs[1].event = NetworkInputKindV1::InboundBytes {
1636 stream_offset: 0,
1637 bytes: vec![0xa5; len],
1638 };
1639 trace.inputs[2].event = NetworkInputKindV1::PeerWriteClosed {
1640 stream_offset: len as u64,
1641 };
1642 let mut framed = Vec::new();
1643 trace.write_framed(&mut framed).unwrap();
1644 assert!(framed.len() > len);
1645 assert!(NetworkTraceV2::read_framed(Cursor::new(framed)).unwrap() == trace);
1646 }
1647
1648 #[test]
1649 fn network_trace_v2_reader_upgrades_v1() {
1650 let v1 = valid_trace();
1651 let upgraded = NetworkTraceV2::read_framed(Cursor::new(framed(&v1))).unwrap();
1652 assert_eq!(upgraded, NetworkTraceV2::from(v1.clone()));
1653 assert_eq!(upgraded.channels.len(), 1);
1654 assert_eq!(upgraded.channels[0].connect_errno, 0);
1655 assert_eq!(
1656 upgraded.channels[0].local_address,
1657 Some(v1.channels[0].local_address.clone())
1658 );
1659 assert_eq!(upgraded.inputs, v1.inputs);
1660 let v1_outputs: Vec<NetworkOutputV4> = v1.outputs.into_iter().map(Into::into).collect();
1661 assert_eq!(upgraded.outputs, v1_outputs);
1662 }
1663
1664 #[test]
1665 fn network_trace_v2_reader_keeps_v1_validation() {
1666 let mut v1 = valid_trace();
1667 v1.channels[0].created_before_competing_threads = false;
1668 assert!(matches!(
1669 NetworkTraceV2::read_framed(Cursor::new(framed_without_validation(&v1))),
1670 Err(NetworkTraceCodecError::Validation(_))
1671 ));
1672 }
1673
1674 #[test]
1675 fn network_trace_v2_reader_rejects_unknown_versions() {
1676 let mut bytes = Vec::new();
1677 valid_trace_v2().write_framed(&mut bytes).unwrap();
1678 let start = NETWORK_TRACE_MAGIC.len();
1679 bytes[start..start + 4].copy_from_slice(&5u32.to_le_bytes());
1680 assert!(matches!(
1681 NetworkTraceV2::read_framed(Cursor::new(bytes)),
1682 Err(NetworkTraceCodecError::UnsupportedVersion(5))
1683 ));
1684 }
1685
1686 fn waited_trace_v4() -> NetworkTraceV2 {
1689 let mut trace = valid_trace_v2();
1690 trace.outputs.push(NetworkOutputV4 {
1691 channel: trace.channels[0].id,
1692 stream_offset: 3,
1693 bytes: b"more".to_vec(),
1694 wait: Some(NetworkSendWaitV4 {
1695 waits: 2,
1696 not_before_global_time: global_time_after_epoch(20),
1697 }),
1698 });
1699 trace
1700 }
1701
1702 #[test]
1703 fn network_trace_v4_holds_send_waits_that_v2_and_v3_cannot() {
1704 let trace = waited_trace_v4();
1705 let mut bytes = Vec::new();
1706 trace.write_framed(&mut bytes).unwrap();
1707 let start = NETWORK_TRACE_MAGIC.len();
1708 assert_eq!(
1709 bytes[start..start + 4],
1710 NETWORK_TRACE_VERSION_V4.to_le_bytes()
1711 );
1712 assert_eq!(
1713 NetworkTraceV2::read_framed(Cursor::new(bytes.clone())).unwrap(),
1714 trace
1715 );
1716 for older in [NETWORK_TRACE_VERSION_V2, NETWORK_TRACE_VERSION_V3] {
1719 let mut relabelled = bytes.clone();
1720 relabelled[start..start + 4].copy_from_slice(&older.to_le_bytes());
1721 assert!(
1722 NetworkTraceV2::read_framed(Cursor::new(relabelled)).is_err(),
1723 "a v4 payload labelled v{older} decoded"
1724 );
1725 }
1726 let mut unwaited = Vec::new();
1728 valid_trace_v2().write_framed(&mut unwaited).unwrap();
1729 assert_eq!(
1730 unwaited[start..start + 4],
1731 NETWORK_TRACE_VERSION_V2.to_le_bytes()
1732 );
1733 }
1734
1735 #[test]
1736 fn network_trace_v4_validation_rejects_each_malformed_wait() {
1737 let check = |mutate: &dyn Fn(&mut NetworkTraceV2), expected| {
1738 let mut trace = waited_trace_v4();
1739 mutate(&mut trace);
1740 assert_eq!(trace.validate(), Err(expected));
1741 };
1742 use NetworkTraceValidationError as E;
1743 let wait_at = |nanos| {
1744 Some(NetworkSendWaitV4 {
1745 waits: 1,
1746 not_before_global_time: global_time_after_epoch(nanos),
1747 })
1748 };
1749 check(
1750 &|t| {
1751 t.outputs.push(NetworkOutputV4 {
1752 channel: t.channels[0].id,
1753 stream_offset: 7,
1754 bytes: Vec::new(),
1755 wait: wait_at(30),
1756 })
1757 },
1758 E::WaitOnRefusedSend,
1759 );
1760 check(
1761 &|t| t.outputs[2].wait.as_mut().unwrap().waits = 0,
1762 E::ZeroSendWaits,
1763 );
1764 check(
1765 &|t| {
1766 let epoch = epoch_global_time(&t.epoch).unwrap().as_nanos();
1767 t.outputs[2].wait.as_mut().unwrap().not_before_global_time =
1768 LogicalTime::from_nanos(epoch - 1);
1769 },
1770 E::SendWaitBeforeEpoch,
1771 );
1772 check(
1773 &|t| {
1774 t.outputs.push(NetworkOutputV4 {
1775 channel: t.channels[0].id,
1776 stream_offset: 7,
1777 bytes: b"x".to_vec(),
1778 wait: wait_at(19),
1779 })
1780 },
1781 E::NonMonotonicSendWait,
1782 );
1783 let mut trace = waited_trace_v4();
1786 trace.outputs.push(NetworkOutputV4 {
1787 channel: trace.channels[1].id,
1788 stream_offset: 2,
1789 bytes: b"x".to_vec(),
1790 wait: wait_at(5),
1791 });
1792 assert_eq!(trace.validate(), Ok(()));
1793 }
1794
1795 #[test]
1796 fn network_trace_v3_holds_refused_sends_that_v2_rejects() {
1797 let mut trace = valid_trace_v2();
1798 let first = trace.channels[0].id;
1799 trace.outputs.insert(
1800 0,
1801 NetworkOutputV4 {
1802 channel: first,
1803 stream_offset: 0,
1804 bytes: Vec::new(),
1805 wait: None,
1806 },
1807 );
1808 let mut bytes = Vec::new();
1809 trace.write_framed(&mut bytes).unwrap();
1810 let start = NETWORK_TRACE_MAGIC.len();
1811 assert_eq!(
1812 bytes[start..start + 4],
1813 NETWORK_TRACE_VERSION_V3.to_le_bytes()
1814 );
1815 assert_eq!(
1816 NetworkTraceV2::read_framed(Cursor::new(bytes.clone())).unwrap(),
1817 trace
1818 );
1819 bytes[start..start + 4].copy_from_slice(&NETWORK_TRACE_VERSION_V2.to_le_bytes());
1820 assert!(matches!(
1821 NetworkTraceV2::read_framed(Cursor::new(bytes)),
1822 Err(NetworkTraceCodecError::Validation(
1823 NetworkTraceValidationError::EmptyByteChunk
1824 ))
1825 ));
1826 }
1827
1828 #[test]
1829 fn network_trace_v2_validation_rejects_each_malformation() {
1830 let check = |mutate: &dyn Fn(&mut NetworkTraceV2), expected| {
1831 let mut trace = valid_trace_v2();
1832 mutate(&mut trace);
1833 assert_eq!(trace.validate(), Err(expected));
1834 };
1835 use NetworkTraceValidationError as E;
1836 check(&|t| t.channels[1].id = channel_id(), E::DuplicateChannel);
1837 check(
1838 &|t| t.channels[0].id = OpenFileId::new(DetTid::from_raw(1), 9),
1839 E::NonSocketChannelIdentity,
1840 );
1841 check(
1842 &|t| t.channels[0].peer_address = loopback(0),
1843 E::UnsupportedPeerAddress,
1844 );
1845 check(
1846 &|t| {
1847 t.channels[0].peer_address = NetworkAddressV1::Inet4 {
1848 address: [0, 0, 0, 0],
1849 port: 80,
1850 }
1851 },
1852 E::UnsupportedPeerAddress,
1853 );
1854 check(
1855 &|t| {
1856 t.channels[0].peer_address = NetworkAddressV1::Inet6 {
1857 address: [0; 16],
1858 port: 80,
1859 flowinfo: 0,
1860 scope_id: 0,
1861 }
1862 },
1863 E::UnsupportedPeerAddress,
1864 );
1865 check(
1866 &|t| {
1867 t.channels[0].peer_address = NetworkAddressV1::Inet6 {
1868 address: Ipv6Addr::LOCALHOST.octets(),
1869 port: 80,
1870 flowinfo: 0,
1871 scope_id: 0,
1872 }
1873 },
1874 E::AddressFamilyMismatch,
1875 );
1876 check(&|t| t.channels[0].connect_errno = -1, E::InvalidErrno);
1877 check(&|t| t.channels[0].local_address = None, E::InvalidErrno);
1878 check(
1879 &|t| t.channels[2].local_address = Some(loopback(1)),
1880 E::InvalidErrno,
1881 );
1882 check(
1883 &|t| t.outputs[1].channel = t.channels[2].id,
1884 E::TrafficOnFailedChannel,
1885 );
1886 check(
1887 &|t| t.inputs[0].channel = t.channels[2].id,
1888 E::TrafficOnFailedChannel,
1889 );
1890 check(
1891 &|t| t.inputs[0].channel = OpenFileId::new_socket(DetTid::from_raw(9), 0),
1892 E::UnknownChannel,
1893 );
1894 check(&|t| t.outputs[1].stream_offset = 1, E::NonContiguousOutput);
1895 check(&|t| t.inputs[2].ordinal = 7, E::NonCanonicalOrdinal);
1896 check(
1897 &|t| t.inputs[1].release.not_before_global_time = global_time_after_epoch(5),
1898 E::NonMonotonicRelease,
1899 );
1900 check(
1901 &|t| t.inputs[2].release.after_transmitted_offset = 2,
1902 E::NonMonotonicRelease,
1903 );
1904 check(
1905 &|t| t.inputs[0].release.after_transmitted_offset = 3,
1906 E::UnreachableTransmitWatermark,
1907 );
1908 check(
1909 &|t| {
1910 t.inputs[2].event = NetworkInputKindV1::PeerWriteClosed { stream_offset: 2 };
1911 },
1912 E::NonContiguousInput,
1913 );
1914 check(
1915 &|t| {
1916 let mut late = t.inputs[2].clone();
1917 late.ordinal = 3;
1918 late.release.not_before_global_time = global_time_after_epoch(40);
1919 late.event = NetworkInputKindV1::InboundBytes {
1920 stream_offset: 3,
1921 bytes: b"z".to_vec(),
1922 };
1923 t.inputs.push(late);
1924 },
1925 E::EventAfterTerminal,
1926 );
1927 }
1928
1929 #[test]
1930 fn network_trace_v2_offsets_are_per_channel() {
1931 let mut trace = valid_trace_v2();
1934 trace.inputs.push(NetworkInputEventV1 {
1935 ordinal: 3,
1936 channel: second_channel_id(),
1937 release: NetworkReleaseV1 {
1938 not_before_global_time: global_time_after_epoch(30),
1939 after_transmitted_offset: 2,
1940 },
1941 event: NetworkInputKindV1::InboundBytes {
1942 stream_offset: 2,
1943 bytes: b"z".to_vec(),
1944 },
1945 });
1946 assert_eq!(trace.validate(), Ok(()));
1947 }
1948}