1use crate::{RevisionIndex, Store};
2use std::io;
3
4pub(crate) fn read_exact(
5 read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>,
6 mut offset: u64,
7 mut output: &mut [u8],
8) -> io::Result<()> {
9 while !output.is_empty() {
10 match read(offset, output) {
11 Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()),
12 Ok(count) if count <= output.len() => {
13 offset += count as u64;
14 output = &mut output[count..];
15 }
16 Ok(_) => return Err(io::ErrorKind::InvalidData.into()),
17 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
18 Err(error) => return Err(error),
19 }
20 }
21 Ok(())
22}
23
24/// Reads a bounded snapshot; the caller must provide fresh I/O and exclude in-place maintenance.
25/// Includes unpublished trailing bytes so subsequent commits can validate the physical file.
26/// `None` is a torn read or storage that does not validate.
27pub fn read_snapshot(
28 read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>,
29 limit: usize,
30) -> io::Result<Option<Vec<u8>>> {
31 let Some(bytes) = image(read, limit)? else {
32 return Ok(None);
33 };
34 let valid = Store::parse(&bytes).is_ok_and(|store| {
35 store.checksum_mismatches.is_empty()
36 && RevisionIndex::parse(&store).is_ok_and(|index| index.validate_current().is_ok())
37 });
38 Ok(valid.then_some(bytes))
39}
40
41/// Reads stable storage and checksums without requiring traversable property references.
42/// Used to inspect encrypted or incomplete documents; this does not establish edit readiness.
43/// The caller must provide fresh I/O and exclude in-place maintenance, as for `read_snapshot`.
44/// `None` is a torn read; stable storage that fails to parse or checksum is `InvalidData`.
45pub fn read_storage_snapshot(
46 read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>,
47 limit: usize,
48) -> io::Result<Option<Vec<u8>>> {
49 let Some(bytes) = image(read, limit)? else {
50 return Ok(None);
51 };
52 let store =
53 Store::parse(&bytes).map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
54 if !store.checksum_mismatches.is_empty() {
55 return Err(io::Error::new(
56 io::ErrorKind::InvalidData,
57 "Checksum mismatch",
58 ));
59 }
60 Ok(Some(bytes))
61}
62
63/// The file's bytes, or `None` where its header changed or its storage ended early meanwhile.
64fn image(
65 mut read: impl FnMut(u64, &mut [u8]) -> io::Result<usize>,
66 limit: usize,
67) -> io::Result<Option<Vec<u8>>> {
68 let mut header = [0; 1024];
69 read_exact(&mut read, 0, &mut header)?;
70 let length = u64::from_le_bytes(header[196..204].try_into().unwrap());
71 let length = usize::try_from(length)
72 .ok()
73 .filter(|length| (1024..=limit).contains(length))
74 .ok_or(io::ErrorKind::InvalidData)?;
75 let mut bytes = Vec::new();
76 bytes.try_reserve_exact(length).map_err(io::Error::other)?;
77 bytes.extend_from_slice(&header);
78 bytes.resize(length, 0);
79 if let Err(error) = read_exact(&mut read, 1024, &mut bytes[1024..]) {
80 // Native writers can shorten unused storage before publishing the new expected length.
81 return if error.kind() == io::ErrorKind::UnexpectedEof {
82 Ok(None)
83 } else {
84 Err(error)
85 };
86 }
87 let mut tail = [0; 65536];
88 loop {
89 let size = (limit - bytes.len()).clamp(1, tail.len());
90 match read(bytes.len() as u64, &mut tail[..size]) {
91 Ok(0) => break,
92 Ok(count) if count <= size && count <= limit - bytes.len() => {
93 bytes.try_reserve_exact(count).map_err(io::Error::other)?;
94 bytes.extend_from_slice(&tail[..count]);
95 }
96 Ok(_) => return Err(io::ErrorKind::InvalidData.into()),
97 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
98 Err(error) => return Err(error),
99 }
100 }
101 let mut after = [0; 1024];
102 read_exact(&mut read, 0, &mut after)?;
103 Ok((header == after).then_some(bytes))
104}