Skip to main content

lace_sans_io/records/
record.rs

1//! Validated records over one retained byte source.
2
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::io;
6use std::sync::Arc;
7
8#[cfg(all(target_pointer_width = "64", any(unix, windows)))]
9use crate::runtime_support::native_record_arena::ArenaMapping;
10
11use super::file::RecordFile;
12use super::metadata::{RecordMetadata, trusted_data_offset, trusted_head_data_offset};
13use super::validator::{RecordError, RecordValidator, ValidatedHeader};
14use crate::hash::{RecordHash, RecordKind};
15use crate::util::MAX_RECORD_METADATA_BYTES;
16
17/// Bytes a trusted file-range load reads first; most prefixes fit, longer
18/// ones take one more read of at most the maximum prefix.
19const FIRST_PREFIX_READ: usize = 1024;
20
21/// The immutable canonical bytes a record retains.
22#[derive(Clone)]
23enum RecordSource {
24    Memory(Arc<[u8]>),
25    /// A committed native arena range. Only an accepted record-log row
26    /// constructs it; the mapping holds the shared arena lock.
27    #[cfg(all(target_pointer_width = "64", any(unix, windows)))]
28    Mmap {
29        mmap: Arc<ArenaMapping>,
30        offset: usize,
31        len: usize,
32    },
33    /// A range of an immutable record file, read in bounded pieces. Its
34    /// canonical prefix is held in memory.
35    File {
36        file: Arc<RecordFile>,
37        offset: u64,
38        len: usize,
39        prefix: Arc<[u8]>,
40    },
41}
42
43impl RecordSource {
44    fn contiguous(&self) -> Option<&[u8]> {
45        Some(match self {
46            Self::Memory(bytes) => bytes,
47            #[cfg(all(target_pointer_width = "64", any(unix, windows)))]
48            Self::Mmap { mmap, offset, len } => {
49                // SAFETY: the range was bounds-checked inside a permanent
50                // fixed-size mapping when an accepted committed row
51                // constructed it. Published ranges are immutable while any
52                // mapping owner holds its shared arena lock, which excludes
53                // compaction.
54                unsafe { std::slice::from_raw_parts(mmap.as_ptr().add(*offset), *len) }
55            }
56            Self::File { .. } => return None,
57        })
58    }
59}
60
61/// A validated record: verified canonical bytes from one retained source.
62///
63/// Its metadata (fields, links, `By`/Mark, lengths, containment) is always in
64/// memory. The complete bytes are available as one slice only where the
65/// source is contiguous; [`Record::read_at`] reads any source in bounded
66/// pieces. Clones share the source. Equality and hashing use the record hash.
67#[derive(Clone)]
68pub struct Record {
69    source: RecordSource,
70    data_offset: usize,
71}
72
73impl fmt::Debug for Record {
74    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75        formatter
76            .debug_struct("Record")
77            .field("hash", &self.hash())
78            .field("canonical_len", &self.canonical_len())
79            .finish()
80    }
81}
82
83impl PartialEq for Record {
84    fn eq(&self, other: &Self) -> bool {
85        self.hash() == other.hash()
86    }
87}
88impl Eq for Record {}
89impl Hash for Record {
90    fn hash<H: Hasher>(&self, state: &mut H) {
91        self.hash().hash(state);
92    }
93}
94
95/// A record could not be materialised.
96#[derive(Debug)]
97pub enum RecordReadError {
98    /// The requested bytes exceed the caller's limit.
99    TooLarge { length: usize, limit: usize },
100    /// The source could not be read.
101    Io(io::Error),
102}
103
104impl fmt::Display for RecordReadError {
105    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            Self::TooLarge { length, limit } => {
108                write!(
109                    formatter,
110                    "record bytes ({length}) exceed the limit ({limit})"
111                )
112            }
113            Self::Io(error) => write!(formatter, "record bytes could not be read: {error}"),
114        }
115    }
116}
117
118impl std::error::Error for RecordReadError {
119    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
120        match self {
121            Self::TooLarge { .. } => None,
122            Self::Io(error) => Some(error),
123        }
124    }
125}
126
127impl Record {
128    pub(super) fn from_constructed(bytes: Vec<u8>, data_offset: usize) -> Self {
129        Self {
130            source: RecordSource::Memory(Arc::from(bytes)),
131            data_offset,
132        }
133    }
134
135    /// Own the complete canonical bytes of a record whose header the
136    /// validator verified from these same bytes.
137    pub(crate) fn from_validated(bytes: Arc<[u8]>, header: &ValidatedHeader) -> Self {
138        debug_assert_eq!(bytes.len(), header.canonical_len());
139        Self {
140            data_offset: header.prefix().len(),
141            source: RecordSource::Memory(bytes),
142        }
143    }
144
145    /// A record over a range of `file` that the validator verified,
146    /// `header` describing the bytes at `offset`.
147    pub(crate) fn from_validated_file(
148        file: Arc<RecordFile>,
149        offset: u64,
150        header: &ValidatedHeader,
151    ) -> Self {
152        Self {
153            data_offset: header.prefix().len(),
154            source: RecordSource::File {
155                file,
156                offset,
157                len: header.canonical_len(),
158                prefix: Arc::from(header.prefix()),
159            },
160        }
161    }
162
163    /// Validate complete canonical record bytes (Lace-010) and own them.
164    pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Result<Self, RecordError> {
165        let bytes = bytes.into();
166        let mut validator = RecordValidator::new();
167        if validator.feed(&bytes)? != bytes.len() {
168            return Err(RecordError::InvalidFormat(
169                "trailing bytes after a complete record".into(),
170            ));
171        }
172        let header = validator.finish()?;
173        Ok(Self {
174            data_offset: header.prefix().len(),
175            source: RecordSource::Memory(Arc::from(bytes)),
176        })
177    }
178
179    /// # Safety
180    ///
181    /// The bytes must be immutable canonical bytes admitted after full validation.
182    #[doc(hidden)]
183    pub unsafe fn try_from_trusted_record_bytes(
184        bytes: impl Into<Vec<u8>>,
185    ) -> Result<Self, RecordError> {
186        let bytes = bytes.into();
187        Ok(Self {
188            data_offset: trusted_data_offset(&bytes)?,
189            source: RecordSource::Memory(Arc::from(bytes)),
190        })
191    }
192
193    /// A committed record of `len` canonical bytes at `offset` in a store
194    /// file. Its prefix is read in at most two bounded reads; hashes and
195    /// Marks are not rerun. Malformed trusted bytes are `InvalidData`.
196    ///
197    /// # Safety
198    ///
199    /// The range must come from an accepted committed row and hold
200    /// immutable canonical bytes admitted after full validation.
201    pub(crate) unsafe fn try_from_trusted_file(
202        file: Arc<RecordFile>,
203        offset: u64,
204        len: usize,
205    ) -> io::Result<Self> {
206        let invalid = |error: RecordError| io::Error::new(io::ErrorKind::InvalidData, error);
207        let mut head = vec![0; len.min(FIRST_PREFIX_READ)];
208        file.read_exact_at(&mut head, offset)?;
209        let data_offset = match trusted_head_data_offset(&head, len).map_err(invalid)? {
210            Some(data_offset) => data_offset,
211            None => {
212                head.resize(len.min(MAX_RECORD_METADATA_BYTES), 0);
213                file.read_exact_at(
214                    &mut head[FIRST_PREFIX_READ..],
215                    offset + FIRST_PREFIX_READ as u64,
216                )?;
217                trusted_head_data_offset(&head, len)
218                    .map_err(invalid)?
219                    .ok_or_else(|| invalid(RecordError::InvalidFormat("no record prefix".into())))?
220            }
221        };
222        head.truncate(data_offset);
223        Ok(Self {
224            data_offset,
225            source: RecordSource::File {
226                file,
227                offset,
228                len,
229                prefix: Arc::from(head),
230            },
231        })
232    }
233
234    /// # Safety
235    ///
236    /// The raw mmap range must come from an accepted committed record-log row.
237    /// The mapped file must retain its fixed length and identity, and the range
238    /// must remain immutable while `mmap` holds its shared arena lock.
239    #[cfg(all(target_pointer_width = "64", any(unix, windows)))]
240    pub(crate) unsafe fn try_from_trusted_raw_mmap_record_bytes(
241        mmap: Arc<ArenaMapping>,
242        offset: usize,
243        len: usize,
244    ) -> Result<Self, RecordError> {
245        let end = offset.checked_add(len).ok_or_else(|| {
246            RecordError::InvalidFormat("mapped record byte range overflow".into())
247        })?;
248        if end > mmap.len() {
249            return Err(RecordError::InvalidFormat(
250                "mapped record byte range exceeds mapping".into(),
251            ));
252        }
253        let source = RecordSource::Mmap { mmap, offset, len };
254        Ok(Self {
255            data_offset: trusted_data_offset(source.contiguous().expect("mapped bytes"))?,
256            source,
257        })
258    }
259
260    pub fn hash(&self) -> RecordHash {
261        self.metadata().hash()
262    }
263
264    pub fn kind(&self) -> RecordKind {
265        self.hash().kind()
266    }
267
268    /// The complete canonical length in bytes.
269    pub fn canonical_len(&self) -> usize {
270        match &self.source {
271            RecordSource::Memory(bytes) => bytes.len(),
272            #[cfg(all(target_pointer_width = "64", any(unix, windows)))]
273            RecordSource::Mmap { len, .. } => *len,
274            RecordSource::File { len, .. } => *len,
275        }
276    }
277
278    /// The canonical non-payload prefix and everything derived from it.
279    pub fn metadata(&self) -> RecordMetadata<'_> {
280        let prefix = match &self.source {
281            RecordSource::File { prefix, .. } => prefix,
282            source => &source.contiguous().expect("contiguous source")[..self.data_offset],
283        };
284        RecordMetadata::new_trusted(prefix)
285    }
286
287    /// The complete canonical bytes, when the source holds them contiguously:
288    /// memory and native mmap records, not file ranges.
289    pub fn contiguous_bytes(&self) -> Option<&[u8]> {
290        self.source.contiguous()
291    }
292
293    /// The Blob data, when the source holds it contiguously.
294    pub fn contiguous_data(&self) -> Option<&[u8]> {
295        Some(&self.source.contiguous()?[self.data_offset..])
296    }
297
298    /// Copy canonical bytes starting at `offset` into `buffer` and return how
299    /// many were copied: fewer than `buffer.len()` only at the record's end.
300    /// A file-range record reads its file at the matching position.
301    pub fn read_at(&self, offset: usize, buffer: &mut [u8]) -> io::Result<usize> {
302        let len = self.canonical_len();
303        if offset > len {
304            return Err(io::Error::new(
305                io::ErrorKind::InvalidInput,
306                "read offset is beyond the record",
307            ));
308        }
309        let count = (len - offset).min(buffer.len());
310        match &self.source {
311            RecordSource::File {
312                file,
313                offset: start,
314                ..
315            } => file.read_exact_at(&mut buffer[..count], start + offset as u64)?,
316            source => {
317                let bytes = source.contiguous().expect("contiguous source");
318                buffer[..count].copy_from_slice(&bytes[offset..offset + count]);
319            }
320        }
321        Ok(count)
322    }
323
324    /// The complete canonical bytes, if at most `limit` bytes long.
325    pub fn to_vec(&self, limit: usize) -> Result<Vec<u8>, RecordReadError> {
326        self.read_range_to_vec(0, limit)
327    }
328
329    /// The Blob data, if at most `limit` bytes long.
330    pub fn data_to_vec(&self, limit: usize) -> Result<Vec<u8>, RecordReadError> {
331        self.read_range_to_vec(self.data_offset, limit)
332    }
333
334    fn read_range_to_vec(&self, start: usize, limit: usize) -> Result<Vec<u8>, RecordReadError> {
335        let length = self.canonical_len() - start;
336        if length > limit {
337            return Err(RecordReadError::TooLarge { length, limit });
338        }
339        let mut bytes = vec![0; length];
340        self.read_at(start, &mut bytes)
341            .map_err(RecordReadError::Io)?;
342        Ok(bytes)
343    }
344}