//! Structurally traversed typed session frames (`SMH-SPEC-SPEC0001`, Sessions). //! //! File: `[SMH3, format=1, reserved=0: 8 bytes][frames...]`. //! Frame, little endian: `[body_len u32][kind u16][version u16][header_crc u32]` //! `[body_crc u32][payload][body_crc XOR 0x534d4833 u32]`. //! `body_len` counts body CRC, payload, and completion marker (not the 12-byte //! header). Header CRC-32 covers the first eight header bytes; body CRC-32 //! covers kind, version, and payload. Complete intact-boundary records with //! damaged bodies are skipped by length; an untrustworthy header stops the //! walk rather than scanning payload for marker-shaped bytes. Entries carry //! 62 bytes of tightly packed fixed metadata followed by length-checked typed //! content; format has no implicit padding, absent parent IDs are zero-filled. use crate::session::{Entry, EntryFrame, EntryKind, SessionHeader}; use smith::error::{FrameFault, Result, SmithError}; use smith::id::{BranchId, EntryId, MessageId, SessionId, ToolCallId}; use smith::message::Role; use std::io::{Read, Write}; /// Fixed file prologue, written once before the first framed record. pub const FILE_PROLOGUE: [u8; 8] = *b"SMH3\x01\0\0\0"; /// Default frame size ceiling when no configuration is supplied. pub const DEFAULT_MAX_FRAME_BYTES: usize = 1024 * 1024; // Permit brief signal bursts; a reader that never progresses must fail closed. const INTERRUPTED_READS_MAX: usize = 16; /// Length of the independently checked frame header. pub const HEADER_LEN: usize = 12; /// Body checksum and completion marker bytes excluding payload. pub const BODY_PREFIX_LEN: usize = 8; /// Frame kind of a session entry payload. pub const KIND_ENTRY: u16 = 1; /// Supported version of the [`KIND_ENTRY`] payload schema. /// Version 4 uses fixed metadata followed by typed binary content and /// accompanies durable navigation records in the session protocol. pub const VERSION_ENTRY: u16 = 4; /// Frame kind of the session header payload. pub const KIND_SESSION_HEADER: u16 = 2; /// Supported version of the [`KIND_SESSION_HEADER`] payload schema. pub const VERSION_SESSION_HEADER: u16 = 2; /// An embedded, reconstructible entry-location hint. pub const KIND_INDEX: u16 = 3; /// Durable selection of a branch tip. pub const KIND_SELECTION: u16 = 4; /// Reconstructible selected-tip checkpoint kind. pub const KIND_CHECKPOINT: u16 = 5; /// Version of index, selection and checkpoint records. pub const VERSION_NAV: u16 = 1; /// A decoded session frame. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Frame { /// Durable session identity; the first frame of a session file. Header { /// Decoded header. header: SessionHeader, }, /// A known session entry. Known { /// Decoded entry frame; its content stays typed binary bytes. entry: EntryFrame, }, /// Derived location hint; authoritative entry frames remain discoverable without it. Index { /// Entry identifier. id: EntryId, /// Absolute byte offset of its entry frame. offset: u64, /// Parent identity, independent of physical address. parent: Option, /// Absolute byte offset of its parent entry frame, if known. parent_offset: Option, }, /// Append-only selection of an already recorded entry. Selection { /// Selected entry identifier. id: EntryId, }, /// Reconstructible selected-tip location hint; never authoritative. Checkpoint { /// Selected entry identifier. id: EntryId, /// Its absolute entry-frame boundary. offset: u64, }, /// A checksum-valid frame of a kind this build does not know. /// Kind, version, and payload are preserved so it round-trips unchanged. Unknown { /// Frame kind tag. kind: u16, /// Frame version tag. version: u16, /// Exact payload bytes as read. payload: Vec, }, } impl Frame { /// Encode the full frame (length prefix, body prefix, payload) enforcing /// `max_frame_bytes` on the payload. /// /// # Errors /// /// Returns a [`FrameFault::TooLarge`] or [`FrameFault::Encode`] error. pub fn encode(&self, frame_bytes_max: usize) -> Result> { match self { Self::Header { header } => seal( KIND_SESSION_HEADER, VERSION_SESSION_HEADER, header_bytes(header), frame_bytes_max, ), Self::Known { entry } => encode_entry_fields( entry.id, entry.parent, entry.timestamp_ms, entry.kind, &entry.content, frame_bytes_max, ), Self::Index { id, offset, parent, parent_offset, } => { if frame_bytes_max < 48 { return Err(too_large(frame_bytes_max, 48)); } Ok(index_bytes(*id, *offset, *parent, *parent_offset).to_vec()) } Self::Selection { id } => { let mut out = Vec::with_capacity(FRAME_PREFIX_LEN + 16 + 4); out.resize(FRAME_PREFIX_LEN, 0); out.extend_from_slice(&id.as_u128().to_le_bytes()); seal(KIND_SELECTION, VERSION_NAV, out, frame_bytes_max) } Self::Checkpoint { id, offset } => { let mut out = Vec::with_capacity(FRAME_PREFIX_LEN + 24 + 4); out.resize(FRAME_PREFIX_LEN, 0); out.extend_from_slice(&id.as_u128().to_le_bytes()); out.extend_from_slice(&offset.to_le_bytes()); seal(KIND_CHECKPOINT, VERSION_NAV, out, frame_bytes_max) } Self::Unknown { kind, version, payload, } => { if payload.len() > frame_bytes_max { return Err(too_large(frame_bytes_max, payload.len())); } let mut out = Vec::with_capacity(FRAME_PREFIX_LEN + payload.len() + 4); out.resize(FRAME_PREFIX_LEN, 0); out.extend_from_slice(payload); seal(*kind, *version, out, frame_bytes_max) } } } } /// Encode one fixed-width location hint without allocating. #[must_use] pub fn index_bytes( id: EntryId, offset: u64, parent: Option, parent_offset: Option, ) -> [u8; 68] { let mut out = [0u8; 68]; out[4..6].copy_from_slice(&KIND_INDEX.to_le_bytes()); out[6..8].copy_from_slice(&VERSION_NAV.to_le_bytes()); out[16..32].copy_from_slice(&id.as_u128().to_le_bytes()); out[32..40].copy_from_slice(&offset.to_le_bytes()); out[40..56].copy_from_slice(&parent.map_or(0, |id| id.as_u128()).to_le_bytes()); out[56..64].copy_from_slice(&parent_offset.unwrap_or(0).to_le_bytes()); out[..4].copy_from_slice(&56_u32.to_le_bytes()); let header_crc = crc32fast::hash(&out[..8]); out[8..12].copy_from_slice(&header_crc.to_le_bytes()); let checksum = crc32(KIND_INDEX, VERSION_NAV, &out[16..64]); out[12..16].copy_from_slice(&checksum.to_le_bytes()); out[64..68].copy_from_slice(&(checksum ^ COMPLETION).to_le_bytes()); out } /// Encode a session record and its arena content bytes as a full entry /// frame, without copying the content out of the arena first. /// /// # Errors /// /// Returns a [`FrameFault::TooLarge`] or [`FrameFault::Encode`] error. pub fn encode_entry(entry: &Entry, content: &[u8], frame_bytes_max: usize) -> Result> { encode_entry_fields( entry.id, entry.parent, entry.timestamp_ms, entry.kind, content, frame_bytes_max, ) } /// Bytes before a frame payload: length prefix, kind, version, checksum. const FRAME_PREFIX_LEN: usize = HEADER_LEN + 4; /// Fixed-size entry metadata is independent of the variable content span. const ENTRY_METADATA_LEN: usize = 16 + 16 + 8 + 1 + 16 + 1 + 4; const _: () = assert!(HEADER_LEN == 12, "frame header wire width changed"); const _: () = assert!(FRAME_PREFIX_LEN == 16, "frame prefix wire width changed"); const _: () = assert!( ENTRY_METADATA_LEN == 62, "entry metadata wire width changed" ); fn header_bytes(header: &SessionHeader) -> Vec { let mut bytes = Vec::with_capacity(FRAME_PREFIX_LEN + 32 + 4); bytes.resize(FRAME_PREFIX_LEN, 0); bytes.extend_from_slice(&header.session_id.as_u128().to_le_bytes()); bytes.extend_from_slice(&header.branch_id.as_u128().to_le_bytes()); bytes } #[expect( clippy::large_types_passed_by_value, reason = "EntryKind is a compact Copy wire tag passed once to the encoder" )] fn encode_entry_fields( id: EntryId, parent: Option, timestamp_ms: i64, kind: EntryKind, content: &[u8], frame_bytes_max: usize, ) -> Result> { let payload_len = ENTRY_METADATA_LEN .checked_add(content.len()) .ok_or_else(|| too_large(frame_bytes_max, usize::MAX))?; if payload_len > frame_bytes_max { return Err(too_large(frame_bytes_max, payload_len)); } let length = u32::try_from(content.len()).map_err(|_| too_large(frame_bytes_max, payload_len))?; let mut bytes = Vec::with_capacity(FRAME_PREFIX_LEN + payload_len + 4); bytes.resize(FRAME_PREFIX_LEN, 0); bytes.extend_from_slice(&id.as_u128().to_le_bytes()); bytes.extend_from_slice(&parent.map_or(0, |p| p.as_u128()).to_le_bytes()); bytes.extend_from_slice(×tamp_ms.to_le_bytes()); let (tag, bits, flag) = match kind { EntryKind::Message { id, role } => ( 0, id.as_u128(), match role { Role::User => 0, Role::Assistant => 1, Role::System => 2, Role::Tool => 3, }, ), EntryKind::ToolCall { call_id } => (1, call_id.as_u128(), 0), EntryKind::ToolResult { call_id, ok } => (2, call_id.as_u128(), u8::from(ok)), EntryKind::Meta { compaction } => (3, 0, u8::from(compaction)), }; bytes.push(tag); bytes.extend_from_slice(&bits.to_le_bytes()); bytes.push(flag); bytes.extend_from_slice(&length.to_le_bytes()); bytes.extend_from_slice(content); seal(KIND_ENTRY, VERSION_ENTRY, bytes, frame_bytes_max) } fn decode_header(payload: &[u8]) -> std::result::Result { let bytes: [u8; 32] = payload .try_into() .map_err(|_| schema("invalid session header length"))?; Ok(SessionHeader { session_id: SessionId::from_u128(u128::from_le_bytes( bytes[..16] .try_into() .map_err(|_| schema("invalid session id"))?, )), branch_id: BranchId::from_u128(u128::from_le_bytes( bytes[16..] .try_into() .map_err(|_| schema("invalid branch id"))?, )), }) } fn schema(message: &str) -> FrameFault { FrameFault::SchemaDecode { message: message.into(), } } #[expect( clippy::expect_used, reason = "fixed metadata width checked before slicing" )] fn decode_entry(payload: &[u8]) -> std::result::Result { let metadata = payload .get(..ENTRY_METADATA_LEN) .ok_or_else(|| schema("short entry metadata"))?; let bits = |slice: std::ops::Range| -> u128 { u128::from_le_bytes(metadata[slice].try_into().expect("fixed metadata width")) }; let id = EntryId::from_u128(bits(0..16)); let parent = match bits(16..32) { 0 => None, bits => Some(EntryId::from_u128(bits)), }; let timestamp_ms = i64::from_le_bytes(metadata[32..40].try_into().expect("fixed timestamp width")); let flag = metadata[57]; let bool_flag = || match flag { 0 => Ok(false), 1 => Ok(true), _ => Err(schema("invalid metadata boolean")), }; let kind = match metadata[40] { 0 => EntryKind::Message { id: MessageId::from_u128(bits(41..57)), role: match flag { 0 => Role::User, 1 => Role::Assistant, 2 => Role::System, 3 => Role::Tool, _ => return Err(schema("invalid role")), }, }, 1 if flag == 0 => EntryKind::ToolCall { call_id: ToolCallId::from_u128(bits(41..57)), }, 2 => EntryKind::ToolResult { call_id: ToolCallId::from_u128(bits(41..57)), ok: bool_flag()?, }, 3 if bits(41..57) == 0 => EntryKind::Meta { compaction: bool_flag()?, }, _ => return Err(schema("invalid entry kind metadata")), }; let content_len = u32::from_le_bytes(metadata[58..62].try_into().expect("fixed length width")) as usize; let content = payload .get(ENTRY_METADATA_LEN..) .ok_or_else(|| schema("missing content"))?; if content.len() != content_len { return Err(schema("entry content length mismatch")); } Ok(EntryFrame { id, parent, timestamp_ms, kind, content: content.to_vec(), }) } /// Fill the reserved prefix of `out` (length, kind, version, checksum) for /// the payload that follows it. fn seal(kind: u16, version: u16, mut out: Vec, frame_bytes_max: usize) -> Result> { assert!( out.len() >= FRAME_PREFIX_LEN, "encoder must reserve the complete frame prefix before sealing" ); let payload_len = out.len() - FRAME_PREFIX_LEN; if payload_len > frame_bytes_max { return Err(too_large(frame_bytes_max, payload_len)); } let Ok(declared) = u32::try_from(BODY_PREFIX_LEN + payload_len) else { return Err(too_large(frame_bytes_max, payload_len)); }; let checksum = crc32(kind, version, &out[FRAME_PREFIX_LEN..]); out[..4].copy_from_slice(&declared.to_le_bytes()); out[4..6].copy_from_slice(&kind.to_le_bytes()); out[6..8].copy_from_slice(&version.to_le_bytes()); let header_checksum = crc32fast::hash(&out[..8]); out[8..HEADER_LEN].copy_from_slice(&header_checksum.to_le_bytes()); out[HEADER_LEN..FRAME_PREFIX_LEN].copy_from_slice(&checksum.to_le_bytes()); out.extend_from_slice(&(checksum ^ COMPLETION).to_le_bytes()); Ok(out) } /// Classify one frame body: verify the checksum first, then the kind and /// version, then the payload schema. const COMPLETION: u32 = 0x53_4d_48_33; fn decode_body(kind: u16, version: u16, body: &[u8]) -> std::result::Result { if body.len() < BODY_PREFIX_LEN { return Err(FrameFault::Malformed { declared_length: body.len(), }); } let checksum = u32::from_le_bytes( body[..4] .try_into() .map_err(|_| schema("missing body checksum"))?, ); let end = body.len() - 4; let published = u32::from_le_bytes( body[end..] .try_into() .map_err(|_| schema("missing completion"))?, ); let payload = &body[4..end]; if published != checksum ^ COMPLETION || checksum != crc32(kind, version, payload) { return Err(FrameFault::Checksum { declared_length: body.len(), }); } match (kind, version) { (KIND_ENTRY, VERSION_ENTRY) => decode_entry(payload).map(|entry| Frame::Known { entry }), (KIND_SESSION_HEADER, VERSION_SESSION_HEADER) => { decode_header(payload).map(|header| Frame::Header { header }) } (KIND_INDEX, VERSION_NAV) if payload.len() == 48 => { let bits = |range: std::ops::Range| -> u128 { // Fixed width is checked by the payload length guard. u128::from_le_bytes(payload[range].try_into().unwrap_or([0; 16])) }; let word = |range: std::ops::Range| -> u64 { u64::from_le_bytes(payload[range].try_into().unwrap_or([0; 8])) }; let parent = bits(24..40); let parent_offset = word(40..48); if (parent == 0) != (parent_offset == 0) { return Err(schema("index parent address mismatch")); } Ok(Frame::Index { id: EntryId::from_u128(bits(0..16)), offset: word(16..24), parent: (parent != 0).then(|| EntryId::from_u128(parent)), parent_offset: (parent_offset != 0).then_some(parent_offset), }) } (KIND_SELECTION, VERSION_NAV) if payload.len() == 16 => Ok(Frame::Selection { id: EntryId::from_u128(u128::from_le_bytes( payload.try_into().map_err(|_| schema("selection length"))?, )), }), (KIND_CHECKPOINT, VERSION_NAV) if payload.len() == 24 => Ok(Frame::Checkpoint { id: EntryId::from_u128(u128::from_le_bytes( payload[..16] .try_into() .map_err(|_| schema("checkpoint id"))?, )), offset: u64::from_le_bytes( payload[16..] .try_into() .map_err(|_| schema("checkpoint offset"))?, ), }), (KIND_INDEX | KIND_SELECTION | KIND_CHECKPOINT, VERSION_NAV) => { Err(schema("navigation length")) } _ => Ok(Frame::Unknown { kind, version, payload: payload.to_vec(), }), } } /// Frames recovered from a frame stream, plus explicit damage. /// /// Damage never discards the surrounding frames: a corrupt frame is reported /// and skipped by its declared length, and the frames after it stay readable. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct Recovery { /// Frames recovered in stream order. pub frames: Vec, /// Damage reports, each locating its frame header by byte offset. /// Bounded repair and reporting scans retain at most their first report. pub damage: Vec, /// Total complete damaged frames observed, including reports not retained. pub damage_count: u64, /// Offset just past the last complete frame. pub good_end: u64, /// Offset where an incomplete trailing frame starts, if any. pub torn_tail_at: Option, } /// Read every complete frame from `reader` with per-frame bounded allocation. /// /// A torn tail ends the walk and is reported through /// [`Recovery::torn_tail_at`]; complete frames before it are always returned. /// /// # Errors /// Returns `FRAME_READ` when the reader fails for a reason other than EOF. pub fn read_frames(reader: &mut R, frame_bytes_max: usize) -> Result { let mut frames = Vec::new(); let mut recovery = visit_frames(reader, frame_bytes_max, |frame| { frames.push(frame); true })?; recovery.frames = frames; Ok(recovery) } /// Walk complete frames with one bounded buffer, without retaining earlier frames. /// /// Returning `false` from `visit` ends the walk at the last complete boundary. /// Damage and incomplete tails are reported separately from the visited records. /// Read failures other than EOF return explicitly, never as torn tails. /// /// # Errors /// Returns `FRAME_READ` when the reader fails for a reason other than EOF. pub fn visit_frames( reader: &mut R, frame_bytes_max: usize, visit: impl FnMut(Frame) -> bool, ) -> Result { let mut damage = Vec::new(); let mut recovery = visit_frames_reporting(reader, frame_bytes_max, visit, |fault| { damage.push(fault); true })?; recovery.damage = damage; Ok(recovery) } /// Walk frames while reporting damage immediately instead of retaining it. /// /// Returning `false` from either callback ends the walk at the last complete /// boundary. The returned recovery never retains damage, so streaming callers /// can fail or emit each fault with bounded memory. /// /// # Errors /// Returns `FRAME_READ` when the reader fails for a reason other than EOF. pub fn visit_frames_reporting( reader: &mut R, frame_bytes_max: usize, mut visit: impl FnMut(Frame) -> bool, mut report_damage: impl FnMut(SmithError) -> bool, ) -> Result { let mut recovery = Recovery::default(); let declared_limit = frame_bytes_max.saturating_add(BODY_PREFIX_LEN); loop { let offset = recovery.good_end; let mut header = [0u8; HEADER_LEN]; match read_exact_or_eof(reader, &mut header) { Ok(true) => {} Ok(false) => break, Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => { recovery.torn_tail_at = Some(offset); break; } Err(error) => return Err(read_error(&error)), } let header_crc = u32::from_le_bytes(header[8..12].try_into().map_err(|_| boundary(offset))?); if header_crc != crc32fast::hash(&header[..8]) { return Err(boundary(offset)); } let declared = u32::from_le_bytes(header[..4].try_into().map_err(|_| boundary(offset))?) as usize; let kind = u16::from_le_bytes(header[4..6].try_into().map_err(|_| boundary(offset))?); let version = u16::from_le_bytes(header[6..8].try_into().map_err(|_| boundary(offset))?); let total = HEADER_LEN as u64 + declared as u64; if declared > declared_limit { match skip_exact(reader, declared) { Ok(true) => {} Ok(false) => { recovery.torn_tail_at = Some(offset); break; } Err(error) => return Err(read_error(&error)), } recovery.good_end = offset + total; recovery.damage_count += 1; if !report_damage(located( FrameFault::TooLarge { bytes_max: frame_bytes_max, actual: declared.saturating_sub(BODY_PREFIX_LEN), }, offset, )) { break; } continue; } let mut body = vec![0u8; declared]; match reader.read_exact(&mut body) { Ok(()) => {} Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => { recovery.torn_tail_at = Some(offset); break; } Err(error) => return Err(read_error(&error)), } let decoded = decode_body(kind, version, &body); recovery.good_end = offset + total; match decoded { Ok(frame) => { if !visit(frame) { break; } } Err(f) => { recovery.damage_count += 1; if !report_damage(located(f, offset)) { break; } } } } Ok(recovery) } /// Visit boundaries and fixed metadata with bounded checksum chunks. /// /// Known frames carry empty content; read validated offsets for payloads. /// /// # Errors /// Returns an I/O or boundary error; damaged complete frames are reported. pub fn visit_metadata_reporting( reader: &mut impl Read, max: usize, mut visit: impl FnMut(u64, Frame) -> Result<()>, mut damage: impl FnMut(SmithError) -> Result<()>, ) -> Result { let mut recovery = Recovery::default(); let mut chunk = [0u8; 8192]; 'frames: loop { let at = recovery.good_end; let mut header = [0; HEADER_LEN]; match read_exact_or_eof(reader, &mut header) { Ok(true) => {} Ok(false) => break, Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { recovery.torn_tail_at = Some(at); break; } Err(e) => return Err(read_error(&e)), } if crc32fast::hash(&header[..8]) != u32::from_le_bytes(header[8..12].try_into().map_err(|_| boundary(at))?) { return Err(boundary(at)); } let len = u32::from_le_bytes(header[..4].try_into().map_err(|_| boundary(at))?) as usize; let kind = u16::from_le_bytes(header[4..6].try_into().map_err(|_| boundary(at))?); let version = u16::from_le_bytes(header[6..8].try_into().map_err(|_| boundary(at))?); if len < BODY_PREFIX_LEN || len > max.saturating_add(BODY_PREFIX_LEN) { let skipped = std::io::copy(&mut reader.take(len as u64), &mut std::io::sink()) .map_err(|e| read_error(&e))?; if skipped != len as u64 { recovery.torn_tail_at = Some(at); break; } recovery.good_end += HEADER_LEN as u64 + len as u64; metadata_fault( &mut recovery, &mut damage, FrameFault::TooLarge { bytes_max: max, actual: len.saturating_sub(BODY_PREFIX_LEN), }, at, )?; continue; } let mut checksum = [0; 4]; if !read_part(reader, &mut checksum, &mut recovery)? { break; } let expected = u32::from_le_bytes(checksum); let payload_len = len - BODY_PREFIX_LEN; let fixed = match (kind, version) { (KIND_ENTRY, VERSION_ENTRY) => payload_len.min(ENTRY_METADATA_LEN), (KIND_SESSION_HEADER, VERSION_SESSION_HEADER) => payload_len.min(32), (KIND_INDEX, VERSION_NAV) => payload_len.min(48), (KIND_SELECTION, VERSION_NAV) => payload_len.min(16), (KIND_CHECKPOINT, VERSION_NAV) => payload_len.min(24), _ => 0, }; let mut prefix = [0u8; ENTRY_METADATA_LEN]; if !read_part(reader, &mut prefix[..fixed], &mut recovery)? { break; } let mut hasher = crc32fast::Hasher::new(); hasher.update(&header[4..8]); hasher.update(&prefix[..fixed]); let mut left = payload_len - fixed; while left > 0 { let n = metadata_chunk_size(left, chunk.len()); if !read_part(reader, &mut chunk[..n], &mut recovery)? { break 'frames; } hasher.update(&chunk[..n]); left -= n; } let mut marker = [0; 4]; if !read_part(reader, &mut marker, &mut recovery)? { break; } recovery.good_end += HEADER_LEN as u64 + len as u64; if hasher.finalize() != expected || u32::from_le_bytes(marker) != expected ^ COMPLETION { metadata_fault( &mut recovery, &mut damage, FrameFault::Checksum { declared_length: len, }, at, )?; continue; } match decode_metadata_frame( kind, version, payload_len, &prefix[..fixed], checksum, marker, ) { Ok(frame) => visit(at, frame)?, Err(fault) => metadata_fault(&mut recovery, &mut damage, fault, at)?, } } Ok(recovery) } fn metadata_chunk_size(left: usize, buffer_len: usize) -> usize { let size = left.min(buffer_len); assert!( size > 0, "nonempty metadata remainder yields a positive chunk" ); size } fn metadata_fault( recovery: &mut Recovery, report: &mut impl FnMut(SmithError) -> Result<()>, fault: FrameFault, at: u64, ) -> Result<()> { recovery.damage_count += 1; report(located(fault, at)) } fn read_part(reader: &mut impl Read, bytes: &mut [u8], recovery: &mut Recovery) -> Result { match reader.read_exact(bytes) { Ok(()) => Ok(true), Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { recovery.torn_tail_at = Some(recovery.good_end); Ok(false) } Err(e) => Err(read_error(&e)), } } fn decode_metadata_frame( kind: u16, version: u16, payload_len: usize, prefix: &[u8], checksum: [u8; 4], marker: [u8; 4], ) -> std::result::Result { if kind == KIND_ENTRY && version == VERSION_ENTRY { if prefix.len() != ENTRY_METADATA_LEN { return Err(schema("short entry metadata")); } let actual = u32::from_le_bytes( prefix[58..62] .try_into() .map_err(|_| schema("entry length"))?, ) as usize; if actual != payload_len - ENTRY_METADATA_LEN { return Err(schema("entry content length mismatch")); } let mut metadata = [0; ENTRY_METADATA_LEN]; metadata.copy_from_slice(prefix); metadata[58..62].fill(0); return decode_entry(&metadata).map(|entry| Frame::Known { entry }); } if !prefix.is_empty() && prefix.len() == payload_len { return decode_body( kind, version, &[&checksum[..], prefix, &marker[..]].concat(), ); } if matches!( kind, KIND_SESSION_HEADER | KIND_INDEX | KIND_SELECTION | KIND_CHECKPOINT ) { Err(schema("navigation or header length")) } else { Ok(Frame::Unknown { kind, version, payload: Vec::new(), }) } } /// Write a full frame to a writer with an enforced payload size limit. /// /// # Errors /// /// Returns a framing error when encoding or writing fails. pub fn write_frame(writer: &mut impl Write, frame: &Frame, frame_bytes_max: usize) -> Result<()> { let bytes = frame.encode(frame_bytes_max)?; writer.write_all(&bytes).map_err(|e| { fault(FrameFault::Encode { message: e.to_string(), }) })?; Ok(()) } const fn fault(f: FrameFault) -> SmithError { SmithError::Frame { fault: f, offset: None, } } const fn located(f: FrameFault, offset: u64) -> SmithError { SmithError::Frame { fault: f, offset: Some(offset), } } const fn too_large(bytes_max: usize, actual: usize) -> SmithError { fault(FrameFault::TooLarge { bytes_max, actual }) } fn boundary(offset: u64) -> SmithError { SmithError::Session { code: smith::error::SessionCode::FrameBoundary, message: format!( "untrustworthy frame header at byte {offset}; cannot locate the next record" ), } } fn read_error(error: &std::io::Error) -> SmithError { SmithError::Session { code: smith::error::SessionCode::FrameRead, message: error.to_string(), } } /// Read exactly `buf.len()` bytes. `Ok(false)` means clean end of stream, /// `Err` means a partial read, which is a torn tail. fn read_exact_or_eof(reader: &mut R, buffer: &mut [u8]) -> std::io::Result { let mut filled = 0; let mut interrupted_reads = 0; while filled < buffer.len() { match reader.read(&mut buffer[filled..]) { Ok(0) => { if filled == 0 { return Ok(false); } return Err(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "torn frame", )); } Ok(n) => { assert!(n > 0, "successful nonempty read makes positive progress"); filled += n; interrupted_reads = 0; } Err(e) if e.kind() == std::io::ErrorKind::Interrupted => { if interrupted_reads == INTERRUPTED_READS_MAX { return Err(e); } interrupted_reads += 1; } Err(e) => return Err(e), } } Ok(true) } /// Discard `len` bytes through a bounded copy buffer. `false` means the /// stream ended early; other read errors propagate. fn skip_exact(reader: &mut R, len: usize) -> std::io::Result { let len = len as u64; std::io::copy(&mut reader.take(len), &mut std::io::sink()).map(|skipped| skipped == len) } /// CRC-32 (IEEE 802.3, reflected) over kind, version, and payload. /// /// `crc32fast` is pure Rust with runtime CPU dispatch (SSE4.2 PCLMULQDQ /// where present); the table loop it replaces measured 60-80x slower on /// large frames, and this checksum covers every frame twice. fn crc32(kind: u16, version: u16, payload: &[u8]) -> u32 { let mut hasher = crc32fast::Hasher::new(); hasher.update(&kind.to_le_bytes()); hasher.update(&version.to_le_bytes()); hasher.update(payload); hasher.finalize() } #[cfg(test)] #[expect( clippy::unwrap_used, clippy::panic, reason = "tests may panic on invariant violations" )] mod tests { use super::*; use crate::session::{EntryContent, EntryKind, Session}; use smith::id::ToolCallId; use smith::message::{ContentBlock, Message, Role}; use smith::structured::ToolArguments; fn message_frame(message: Message) -> EntryFrame { EntryFrame::new(None, &EntryContent::Message(message)).unwrap() } fn sample_entry(thinking: bool) -> EntryFrame { let mut msg = Message::with_text(Role::User, "hello"); if thinking { msg.add_block(ContentBlock::thinking("internal")); } message_frame(msg) } fn known_bytes(text: &str) -> Vec { Frame::Known { entry: message_frame(Message::with_text(Role::User, text)), } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap() } fn text_of(frame: &Frame) -> String { match frame { Frame::Known { entry } => match EntryContent::decode(&entry.content).unwrap() { EntryContent::Message(m) => match &m.blocks[0] { ContentBlock::Text(t) => t.clone(), other => panic!("unexpected block {other:?}"), }, other => panic!("unexpected content {other:?}"), }, other => panic!("unexpected frame {other:?}"), } } fn read_all(bytes: &[u8], frame_bytes_max: usize) -> Recovery { read_frames(&mut &bytes[..], frame_bytes_max).unwrap() } struct FailingReader; impl Read for FailingReader { fn read(&mut self, _: &mut [u8]) -> std::io::Result { Err(std::io::Error::from(std::io::ErrorKind::Other)) } } struct CountedBrokenReader { reads: usize, } impl Read for CountedBrokenReader { fn read(&mut self, _: &mut [u8]) -> std::io::Result { self.reads += 1; Err(std::io::ErrorKind::BrokenPipe.into()) } } struct AlwaysInterruptedReader { reads: usize, } impl Read for AlwaysInterruptedReader { fn read(&mut self, _: &mut [u8]) -> std::io::Result { self.reads += 1; Err(std::io::ErrorKind::Interrupted.into()) } } struct InterruptedOnceThenData { interrupted: bool, bytes: &'static [u8], } impl Read for InterruptedOnceThenData { fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { if !self.interrupted { self.interrupted = true; return Err(std::io::ErrorKind::Interrupted.into()); } let count = buffer.len().min(self.bytes.len()); buffer[..count].copy_from_slice(&self.bytes[..count]); self.bytes = &self.bytes[count..]; Ok(count) } } #[test] fn non_interruption_errors_propagate_without_retry() { let mut reader = CountedBrokenReader { reads: 0 }; let error = read_exact_or_eof(&mut reader, &mut [0; 1]).unwrap_err(); assert_eq!(error.kind(), std::io::ErrorKind::BrokenPipe); assert_eq!(reader.reads, 1); } #[test] fn repeated_interruptions_are_bounded_and_propagated() { let mut reader = AlwaysInterruptedReader { reads: 0 }; let error = read_exact_or_eof(&mut reader, &mut [0; 1]).unwrap_err(); assert_eq!(error.kind(), std::io::ErrorKind::Interrupted); assert_eq!(reader.reads, INTERRUPTED_READS_MAX + 1); } #[test] fn metadata_reader_accepts_frame_with_no_variable_payload_remainder() { let frame = Frame::Unknown { kind: u16::MAX, version: u16::MAX, payload: Vec::new(), }; let bytes = frame.encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); let mut visited = Vec::new(); let recovery = visit_metadata_reporting( &mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES, |offset, frame| { visited.push((offset, frame)); Ok(()) }, |_| Ok(()), ) .unwrap(); assert_eq!(visited, vec![(0, frame)]); assert_eq!(recovery.good_end, bytes.len() as u64); } #[test] fn interruption_before_data_retries_successfully() { let mut reader = InterruptedOnceThenData { interrupted: false, bytes: b"ok", }; let mut bytes = [0; 2]; assert!(read_exact_or_eof(&mut reader, &mut bytes).unwrap()); assert_eq!(&bytes, b"ok"); } #[test] fn read_errors_are_not_torn_tails() { let error = visit_frames(&mut FailingReader, DEFAULT_MAX_FRAME_BYTES, |_| true).unwrap_err(); assert_eq!(error.code(), "FRAME_READ"); } #[test] fn corrupt_frame_is_reported_and_neighbours_survive() { let mut bytes = known_bytes("before"); let corrupt_at = bytes.len() as u64; let mut corrupt = known_bytes("corrupt"); // Flip one payload byte: the length prefix stays intact, the checksum fails. let last = corrupt.len() - 1; corrupt[last] ^= 0xFF; bytes.extend_from_slice(&corrupt); bytes.extend_from_slice(&known_bytes("after")); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); let texts: Vec = recovery.frames.iter().map(text_of).collect(); assert_eq!(texts, vec!["before".to_string(), "after".to_string()]); assert_eq!(recovery.damage.len(), 1); assert_eq!(recovery.damage[0].code(), "FRAME_CHECKSUM"); assert_eq!( recovery.damage[0], SmithError::Frame { fault: FrameFault::Checksum { declared_length: corrupt.len() - HEADER_LEN }, offset: Some(corrupt_at), } ); assert_eq!(recovery.good_end, bytes.len() as u64); assert_eq!(recovery.torn_tail_at, None); } #[test] fn damaged_header_never_scans_payload_for_a_frame_boundary() { let nested = known_bytes("spoofed boundary"); let mut bytes = Frame::Unknown { kind: 99, version: 1, payload: nested, } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap(); bytes[0] ^= 1; bytes.extend_from_slice(&known_bytes("after")); assert_eq!( read_frames(&mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES) .unwrap_err() .code(), "FRAME_BOUNDARY" ); } #[test] fn future_frame_round_trips_byte_for_byte() { let future = Frame::Unknown { kind: 0xBEEF, version: 7, payload: vec![0xDE, 0xAD, 0x01, 0x02], }; let bytes = future.encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert!(recovery.damage.is_empty()); assert_eq!(recovery.frames, vec![future]); assert_eq!( recovery.frames[0].encode(DEFAULT_MAX_FRAME_BYTES).unwrap(), bytes ); } #[test] fn known_frame_round_trips_through_fixed_metadata() { let entry = sample_entry(true); let bytes = Frame::Known { entry: entry.clone(), } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap(); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert_eq!(recovery.frames, vec![Frame::Known { entry }]); assert_eq!(recovery.good_end, bytes.len() as u64); } #[test] fn entry_frame_carries_fixed_metadata_and_raw_content() { let call_id = ToolCallId::new(); let content = EntryContent::ToolResult { ok: true, output: "done".to_string(), call_id, }; let entry = EntryFrame::new(None, &content).unwrap(); assert_eq!(entry.kind, EntryKind::ToolResult { call_id, ok: true }); assert_eq!(EntryContent::decode(&entry.content).unwrap(), content); let bytes = Frame::Known { entry: entry.clone(), } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap(); assert_eq!( &bytes[FRAME_PREFIX_LEN + ENTRY_METADATA_LEN..bytes.len() - 4], entry.content ); assert_eq!( u32::from_le_bytes( bytes[FRAME_PREFIX_LEN + 58..FRAME_PREFIX_LEN + 62] .try_into() .unwrap() ) as usize, entry.content.len() ); // A session record frames straight from arena bytes, identically. let mut session = Session::new(); let record = *session.append(&content).unwrap(); let from_arena = encode_entry(&record, session.bytes(&record), DEFAULT_MAX_FRAME_BYTES).unwrap(); let owned = Frame::Known { entry: EntryFrame { id: record.id, parent: record.parent, timestamp_ms: record.timestamp_ms, kind: record.kind, content: session.bytes(&record).to_vec(), }, }; assert_eq!(from_arena, owned.encode(DEFAULT_MAX_FRAME_BYTES).unwrap()); } #[test] fn torn_tail_is_located_and_earlier_frames_kept() { let mut bytes = known_bytes("kept"); let tail_at = bytes.len() as u64; bytes.extend_from_slice(&known_bytes("lost")[..6]); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert_eq!( recovery.frames.iter().map(text_of).collect::>(), vec!["kept".to_string()] ); assert_eq!(recovery.torn_tail_at, Some(tail_at)); assert_eq!(recovery.good_end, tail_at); // A partial length prefix is torn too, never a fabricated header. let mut short = known_bytes("kept"); let short_tail = short.len() as u64; short.extend_from_slice(&[0xFF, 0xFF]); let recovery = read_all(&short, DEFAULT_MAX_FRAME_BYTES); assert_eq!(recovery.frames.len(), 1); assert_eq!(recovery.torn_tail_at, Some(short_tail)); } #[test] fn oversized_complete_frame_is_skipped_by_length() { let mut bytes = Frame::Unknown { kind: 9, version: 1, payload: vec![0xAB; 5000], } .encode(6000) .unwrap(); let skipped = bytes.len() as u64; bytes.extend_from_slice(&known_bytes("tail")); let recovery = read_all(&bytes, 1024); assert_eq!( recovery.frames.iter().map(text_of).collect::>(), vec!["tail".to_string()] ); assert_eq!(recovery.damage.len(), 1); assert_eq!(recovery.damage[0].code(), "FRAME_TOO_LARGE"); assert_eq!( recovery.damage[0], SmithError::Frame { fault: FrameFault::TooLarge { bytes_max: 1024, actual: 5000 }, offset: Some(0), } ); assert_eq!(recovery.good_end, bytes.len() as u64); assert_eq!(skipped, (HEADER_LEN + BODY_PREFIX_LEN + 5000) as u64); } #[test] fn oversized_truncated_frame_is_torn_not_scanned() { let mut bytes = Frame::Unknown { kind: 9, version: 1, payload: vec![0; 200], } .encode(300) .unwrap(); bytes.truncate(HEADER_LEN + 10); let recovery = read_all(&bytes, 50); assert!(recovery.frames.is_empty()); assert!(recovery.damage.is_empty()); assert_eq!(recovery.torn_tail_at, Some(0)); } #[test] fn declared_length_below_body_prefix_is_malformed() { let mut bytes = Frame::Unknown { kind: 9, version: 1, payload: vec![], } .encode(100) .unwrap(); bytes[..4].copy_from_slice(&3_u32.to_le_bytes()); let checksum = crc32fast::hash(&bytes[..8]); bytes[8..12].copy_from_slice(&checksum.to_le_bytes()); bytes.truncate(HEADER_LEN + 3); bytes.extend_from_slice(&known_bytes("after")); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert_eq!(recovery.damage.len(), 1); assert_eq!(recovery.damage[0].code(), "FRAME_MALFORMED"); assert_eq!(recovery.frames.len(), 1); } #[test] fn future_entry_version_is_preserved_verbatim() { let payload = vec![0xA0]; // empty CBOR map let frame = Frame::Unknown { kind: KIND_ENTRY, version: VERSION_ENTRY + 1, payload, }; let bytes = frame.encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert!(recovery.damage.is_empty()); assert_eq!(recovery.frames, vec![frame]); assert_eq!( recovery.frames[0].encode(DEFAULT_MAX_FRAME_BYTES).unwrap(), bytes ); } #[test] fn checksum_valid_entry_with_bad_schema_is_schema_damage() { let frame = Frame::Unknown { kind: KIND_ENTRY, version: VERSION_ENTRY, payload: vec![0xA0], // valid CBOR, not an Entry }; let bytes = frame.encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert!(recovery.frames.is_empty()); assert_eq!(recovery.damage[0].code(), "FRAME_SCHEMA"); } #[test] fn old_frames_without_header_checksum_are_rejected_as_ambiguous() { // Old layout: [len][CBOR payload] with no kind, version, or checksum. let entry = sample_entry(false); let payload = Frame::Known { entry } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap(); let mut bytes = u32::try_from(payload.len()).unwrap().to_le_bytes().to_vec(); bytes.extend_from_slice(&payload); assert_eq!( read_frames(&mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES) .unwrap_err() .code(), "FRAME_BOUNDARY" ); } #[test] fn sequential_frames_consume_the_whole_stream() { let mut bytes = known_bytes("one"); bytes.extend_from_slice(&known_bytes("two")); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert_eq!( recovery.frames.iter().map(text_of).collect::>(), vec!["one".to_string(), "two".to_string()] ); assert_eq!(recovery.good_end, bytes.len() as u64); } #[test] fn encoding_rejects_frames_above_limit() { let big = message_frame({ let mut m = Message::with_text(Role::User, "x"); m.add_block(ContentBlock::text("y".repeat(4096))); m }); let error = Frame::Known { entry: big }.encode(64).unwrap_err(); assert!( matches!(error, SmithError::Frame { fault: FrameFault::TooLarge { bytes_max: 64, actual }, .. } if actual > 64) ); } #[test] fn default_ceiling_admits_one_mebibyte_payloads_and_no_more() { let frame = |len| Frame::Unknown { kind: 9, version: 1, payload: vec![0; len], }; let at_ceiling = frame(1 << 20).encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); assert_eq!(at_ceiling.len(), HEADER_LEN + BODY_PREFIX_LEN + (1 << 20)); assert_eq!( frame((1 << 20) + 1).encode(DEFAULT_MAX_FRAME_BYTES), Err(too_large(1 << 20, (1 << 20) + 1)) ); } #[test] fn equivalent_inputs_encode_identically_despite_key_order() { let a = serde_json::json!({"path": "p", "content": {"b": 1, "a": 2}}); let b = serde_json::json!({"content": {"a": 2, "b": 1}, "path": "p"}); let call_id = ToolCallId::new(); let e1 = EntryFrame::new( None, &EntryContent::ToolCall { name: "write".into(), input: ToolArguments::from_serializable(&a).unwrap(), call_id, }, ) .unwrap(); let mut e2 = EntryFrame::new( None, &EntryContent::ToolCall { name: "write".into(), input: ToolArguments::from_serializable(&b).unwrap(), call_id, }, ) .unwrap(); // Force identical ids/timestamps so only key order could differ. e2.id = e1.id; e2.timestamp_ms = e1.timestamp_ms; let b1 = Frame::Known { entry: e1 }.encode(1 << 20).unwrap(); let b2 = Frame::Known { entry: e2 }.encode(1 << 20).unwrap(); assert_eq!(b1, b2); } #[test] fn navigation_frames_round_trip_and_fixed_index_matches_heap_encoder() { let id = EntryId::from_u128(13); let parent = EntryId::from_u128(7); let index = Frame::Index { id, offset: 4096, parent: Some(parent), parent_offset: Some(128), }; let bytes = index.encode(DEFAULT_MAX_FRAME_BYTES).unwrap(); assert_eq!(bytes, index_bytes(id, 4096, Some(parent), Some(128))); let selection = Frame::Selection { id }; let mut stream = bytes; stream.extend(selection.encode(DEFAULT_MAX_FRAME_BYTES).unwrap()); let checkpoint = Frame::Checkpoint { id, offset: 4096 }; stream.extend(checkpoint.encode(DEFAULT_MAX_FRAME_BYTES).unwrap()); assert_eq!( read_all(&stream, DEFAULT_MAX_FRAME_BYTES).frames, vec![index, selection, checkpoint] ); } #[test] fn header_frame_round_trips() { let header = SessionHeader { session_id: smith::id::SessionId::new(), branch_id: smith::id::BranchId::new(), }; let bytes = Frame::Header { header } .encode(DEFAULT_MAX_FRAME_BYTES) .unwrap(); let recovery = read_all(&bytes, DEFAULT_MAX_FRAME_BYTES); assert_eq!(recovery.frames, vec![Frame::Header { header }]); assert!(recovery.damage.is_empty()); } #[test] fn checksum_matches_known_reference_value() { // CRC-32 of "123456789" is a published check value. assert_eq!(crc32(0x3231, 0x3433, b"56789"), 0xCBF4_3926); } } /// Frame stream round-trip law (`SMH-SPEC-SPEC0001`, Sessions). #[cfg(test)] mod properties { use super::*; use crate::session::EntryKind; use crate::session::properties::role; use proptest::collection::vec; use proptest::prelude::*; use smith::id::{BranchId, EntryId, MessageId, SessionId, ToolCallId}; fn kind() -> impl Strategy { prop_oneof![ (any::(), role()).prop_map(|(id, role)| EntryKind::Message { id: MessageId::from_u128(id), role, }), any::().prop_map(|id| EntryKind::ToolCall { call_id: ToolCallId::from_u128(id), }), (any::(), any::()).prop_map(|(id, ok)| EntryKind::ToolResult { call_id: ToolCallId::from_u128(id), ok, }), any::().prop_map(|compaction| EntryKind::Meta { compaction }), ] } /// Any frame this build writes: header, entry with opaque content bytes, /// or a frame of a kind it does not know. fn frame() -> impl Strategy { prop_oneof![ (any::(), any::()).prop_map(|(session, branch)| Frame::Header { header: SessionHeader { session_id: SessionId::from_u128(session), branch_id: BranchId::from_u128(branch), }, }), ( any::(), proptest::option::of(any::()), any::(), kind(), vec(any::(), 0..64), ) .prop_map(|(id, parent, timestamp_ms, kind, content)| Frame::Known { entry: EntryFrame { id: EntryId::from_u128(id), parent: parent.map(EntryId::from_u128), timestamp_ms, kind, content, }, }), ( any::(), any::(), any::(), any::(), any::(), ) .prop_map(|(id, offset, parent, parent_offset, has_parent)| { Frame::Index { id: EntryId::from_u128(id), offset, parent: has_parent.then(|| EntryId::from_u128(parent | 1)), parent_offset: has_parent.then_some(parent_offset | 1), } }), any::().prop_map(|id| Frame::Selection { id: EntryId::from_u128(id), }), (any::(), any::()).prop_map(|(id, offset)| Frame::Checkpoint { id: EntryId::from_u128(id), offset, }), ( (any::(), any::()).prop_filter( "known frame versions decode through their schemas", |(kind, version)| { !matches!( (*kind, *version), (KIND_ENTRY, VERSION_ENTRY) | (KIND_SESSION_HEADER, VERSION_SESSION_HEADER) | (KIND_INDEX, VERSION_NAV) | (KIND_SELECTION, VERSION_NAV) | (KIND_CHECKPOINT, VERSION_NAV) ) }, ), vec(any::(), 0..64), ) .prop_map(|((kind, version), payload)| Frame::Unknown { kind, version, payload, }), ] } proptest! { #[test] fn visitor_and_eager_reader_agree_on_every_frame(frames in vec(frame(), 0..8)) { // SMH-SPEC-SPEC0001: Sessions, bounded sequential reads preserve semantics. let mut bytes = Vec::new(); for frame in &frames { bytes.extend(frame.encode(DEFAULT_MAX_FRAME_BYTES).map_err(|e| TestCaseError::fail(e.to_string()))?); } let eager = read_frames(&mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES).unwrap(); let mut visited = Vec::new(); let scan = visit_frames(&mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES, |frame| { visited.push(frame); true }).unwrap(); prop_assert_eq!(visited, eager.frames); prop_assert_eq!(scan.damage, eager.damage); prop_assert_eq!(scan.good_end, eager.good_end); prop_assert_eq!(scan.torn_tail_at, eager.torn_tail_at); } #[test] fn encoded_frame_streams_decode_to_the_same_frames(frames in vec(frame(), 0..8)) { let mut bytes = Vec::new(); for frame in &frames { bytes.extend( frame .encode(DEFAULT_MAX_FRAME_BYTES) .map_err(|e| TestCaseError::fail(e.to_string()))?, ); } let recovery = read_frames(&mut bytes.as_slice(), DEFAULT_MAX_FRAME_BYTES).unwrap(); prop_assert!(recovery.damage.is_empty(), "damage: {:?}", recovery.damage); prop_assert_eq!(recovery.torn_tail_at, None); prop_assert_eq!(recovery.good_end, bytes.len() as u64); let mut again = Vec::new(); for frame in &recovery.frames { again.extend( frame .encode(DEFAULT_MAX_FRAME_BYTES) .map_err(|e| TestCaseError::fail(e.to_string()))?, ); } prop_assert_eq!(recovery.frames, frames); prop_assert_eq!(again, bytes); } #[test] fn frame_encoding_is_deterministic_and_zero_fills_absent_fields(frame in frame()) { // SMH-SPEC-SPEC0001: Sessions, deterministic encoding and reserved bytes. let first = frame .encode(DEFAULT_MAX_FRAME_BYTES) .map_err(|error| TestCaseError::fail(error.to_string()))?; let second = frame .encode(DEFAULT_MAX_FRAME_BYTES) .map_err(|error| TestCaseError::fail(error.to_string()))?; prop_assert_eq!(&first, &second); prop_assert_eq!(&FILE_PROLOGUE[5..], &[0, 0, 0]); match frame { Frame::Known { ref entry } if entry.parent.is_none() => { prop_assert_eq!( &first[FRAME_PREFIX_LEN + 16..FRAME_PREFIX_LEN + 32], &[0; 16] ); } Frame::Index { parent: None, .. } => { prop_assert_eq!(&first[40..64], &[0; 24]); } _ => {} } } #[test] fn a_frame_exactly_at_the_limit_round_trips_and_one_byte_less_rejects_it(frame in frame()) { let unlimited = frame .encode(DEFAULT_MAX_FRAME_BYTES) .map_err(|e| TestCaseError::fail(e.to_string()))?; let payload_len = unlimited.len() - HEADER_LEN - BODY_PREFIX_LEN; let declared = u32::from_le_bytes(unlimited[..4].try_into().unwrap()); prop_assert_eq!(declared as usize, BODY_PREFIX_LEN + payload_len); let at_limit = frame .encode(payload_len) .map_err(|e| TestCaseError::fail(e.to_string()))?; prop_assert_eq!(&at_limit, &unlimited); let recovery = read_frames(&mut at_limit.as_slice(), payload_len).unwrap(); prop_assert!(recovery.damage.is_empty(), "damage: {:?}", recovery.damage); prop_assert_eq!(recovery.frames, vec![frame.clone()]); if let Some(below) = payload_len.checked_sub(1) { let result = frame.encode(below); prop_assert!(result.is_err(), "below-limit frame was accepted"); let recovery = read_frames(&mut at_limit.as_slice(), below).unwrap(); prop_assert!(recovery.frames.is_empty()); prop_assert_eq!(recovery.good_end, at_limit.len() as u64); } } } }