1use std::fs::File;
4#[cfg(unix)]
5use std::os::fd::AsRawFd;
6use std::path::Path;
7#[cfg(any(test, feature = "test-support"))]
8use std::path::PathBuf;
9#[cfg(unix)]
10use std::ptr::NonNull;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::{Arc, Mutex, OnceLock};
13
14use xxhash_rust::xxh3::xxh3_64;
15
16use crate::format::frame::{
17 FILE_HEADER_LEN, FILE_TRAILER_LEN, FormatCheck, FormatError, decode_header, read_u16, read_u32,
18 read_u64,
19};
20use crate::format::{FormatFamily, FormatRegistry};
21use crate::graph::block::{GraphNodeBlocks, ValidatedGraphNodeBlocks, decode_node_blocks};
22use crate::ingest::{DocId, DocumentVersion, Revision};
23use crate::meta::{AliveSet, ColumnStore};
24use crate::quant::Bit4Factors;
25use crate::vfs::Vfs;
26
27use super::layout::{
28 CHECKSUM_CHUNK_BYTES, Cursor, Int8Factors, REGION_ALIGNMENT, REGION_ENTRY_LEN, RegionEntry,
29 RegionKind, SEGMENT_PREFIX_LEN, VECTOR_HEADER_LEN, align_up, decode_alive, decode_columns,
30 decode_vector_header,
31};
32use super::{SegmentError, SegmentId, SegmentMeta};
33
34const MAX_SEGMENT_HEADER_BYTES: usize = 1024 * 1024;
35
36thread_local! {
37 static DATA_READ_AUDIT: std::cell::RefCell<Option<Arc<AtomicU64>>> = const {
38 std::cell::RefCell::new(None)
39 };
40}
41
42#[cfg(any(test, feature = "test-support"))]
43thread_local! {
44 static SEGMENT_COST_AUDIT: std::cell::RefCell<Option<Arc<SegmentCostCounters>>> = const {
45 std::cell::RefCell::new(None)
46 };
47}
48
49#[cfg(any(test, feature = "test-support"))]
50#[derive(Default)]
51struct SegmentCostCounters {
52 identity_hash_bytes: AtomicU64,
53 rescore_hash_bytes: AtomicU64,
54 int8_factor_decode_bytes: AtomicU64,
55 postings_hash_bytes: AtomicU64,
56 postings_decode_bytes: AtomicU64,
57 columns_hash_bytes: AtomicU64,
58 columns_decode_bytes: AtomicU64,
59 alive_hash_bytes: AtomicU64,
60 alive_decode_bytes: AtomicU64,
61}
62
63#[cfg(any(test, feature = "test-support"))]
65#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66pub struct SegmentCostSnapshot {
67 pub identity_hash_bytes: u64,
69 pub rescore_hash_bytes: u64,
71 pub int8_factor_decode_bytes: u64,
73 pub postings_hash_bytes: u64,
75 pub postings_decode_bytes: u64,
77 pub columns_hash_bytes: u64,
79 pub columns_decode_bytes: u64,
81 pub alive_hash_bytes: u64,
83 pub alive_decode_bytes: u64,
85}
86
87#[cfg(any(test, feature = "test-support"))]
89#[derive(Clone, Default)]
90pub struct SegmentCostAudit {
91 counters: Arc<SegmentCostCounters>,
92}
93
94#[cfg(any(test, feature = "test-support"))]
95struct SegmentCostAuditGuard {
96 previous: Option<Arc<SegmentCostCounters>>,
97}
98
99#[cfg(any(test, feature = "test-support"))]
100impl Drop for SegmentCostAuditGuard {
101 fn drop(&mut self) {
102 let previous = self.previous.take();
103 SEGMENT_COST_AUDIT.with(|slot| {
104 slot.replace(previous);
105 });
106 }
107}
108
109#[cfg(any(test, feature = "test-support"))]
110impl SegmentCostAudit {
111 #[must_use]
113 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn measure<T>(&self, operation: impl FnOnce() -> T) -> T {
119 let previous =
120 SEGMENT_COST_AUDIT.with(|slot| slot.replace(Some(Arc::clone(&self.counters))));
121 let _guard = SegmentCostAuditGuard { previous };
122 operation()
123 }
124
125 #[must_use]
127 pub fn snapshot(&self) -> SegmentCostSnapshot {
128 SegmentCostSnapshot {
129 identity_hash_bytes: self.counters.identity_hash_bytes.load(Ordering::Relaxed),
130 rescore_hash_bytes: self.counters.rescore_hash_bytes.load(Ordering::Relaxed),
131 int8_factor_decode_bytes: self
132 .counters
133 .int8_factor_decode_bytes
134 .load(Ordering::Relaxed),
135 postings_hash_bytes: self.counters.postings_hash_bytes.load(Ordering::Relaxed),
136 postings_decode_bytes: self.counters.postings_decode_bytes.load(Ordering::Relaxed),
137 columns_hash_bytes: self.counters.columns_hash_bytes.load(Ordering::Relaxed),
138 columns_decode_bytes: self.counters.columns_decode_bytes.load(Ordering::Relaxed),
139 alive_hash_bytes: self.counters.alive_hash_bytes.load(Ordering::Relaxed),
140 alive_decode_bytes: self.counters.alive_decode_bytes.load(Ordering::Relaxed),
141 }
142 }
143}
144
145#[cfg(any(test, feature = "test-support"))]
146fn account_region_hash(kind: RegionKind, bytes: usize) {
147 let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
148 SEGMENT_COST_AUDIT.with(|slot| {
149 let Some(counters) = slot.borrow().as_ref().cloned() else {
150 return;
151 };
152 let counter = match kind {
153 RegionKind::DocumentVersions => &counters.identity_hash_bytes,
154 RegionKind::VectorRescore => &counters.rescore_hash_bytes,
155 RegionKind::Postings => &counters.postings_hash_bytes,
156 RegionKind::Columns => &counters.columns_hash_bytes,
157 RegionKind::Alive => &counters.alive_hash_bytes,
158 _ => return,
159 };
160 let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
161 Some(current.saturating_add(bytes))
162 });
163 });
164}
165
166#[cfg(any(test, feature = "test-support"))]
167fn account_region_decode(kind: RegionKind, bytes: usize) {
168 let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
169 SEGMENT_COST_AUDIT.with(|slot| {
170 let Some(counters) = slot.borrow().as_ref().cloned() else {
171 return;
172 };
173 let counter = match kind {
174 RegionKind::VectorFactors => &counters.int8_factor_decode_bytes,
175 RegionKind::Postings => &counters.postings_decode_bytes,
176 RegionKind::Columns => &counters.columns_decode_bytes,
177 RegionKind::Alive => &counters.alive_decode_bytes,
178 _ => return,
179 };
180 let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
181 Some(current.saturating_add(bytes))
182 });
183 });
184}
185
186#[cfg(any(test, feature = "test-support"))]
187pub(crate) fn account_active_postings_decode(bytes: usize) {
188 let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
189 SEGMENT_COST_AUDIT.with(|slot| {
190 if let Some(counters) = slot.borrow().as_ref() {
191 let _ = counters.postings_decode_bytes.fetch_update(
192 Ordering::Relaxed,
193 Ordering::Relaxed,
194 |current| Some(current.saturating_add(bytes)),
195 );
196 }
197 });
198}
199
200fn region_hash(_kind: RegionKind, bytes: &[u8]) -> u64 {
201 #[cfg(any(test, feature = "test-support"))]
202 account_region_hash(_kind, bytes.len());
203 xxh3_64(bytes)
204}
205
206pub(crate) struct DataReadAuditGuard {
207 previous: Option<Arc<AtomicU64>>,
208}
209
210impl Drop for DataReadAuditGuard {
211 fn drop(&mut self) {
212 let previous = self.previous.take();
213 DATA_READ_AUDIT.with(|slot| {
214 slot.replace(previous);
215 });
216 }
217}
218
219pub(crate) fn install_data_read_audit(counter: Option<Arc<AtomicU64>>) -> DataReadAuditGuard {
220 let previous = DATA_READ_AUDIT.with(|slot| slot.replace(counter));
221 DataReadAuditGuard { previous }
222}
223
224fn account_data_read(bytes: usize) {
225 let bytes = u64::try_from(bytes).unwrap_or(u64::MAX);
226 DATA_READ_AUDIT.with(|slot| {
227 if let Some(counter) = slot.borrow().as_ref() {
228 let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
229 Some(current.saturating_add(bytes))
230 });
231 }
232 });
233}
234
235#[cfg(unix)]
236struct MappedFile {
237 _file: File,
238 pointer: NonNull<u8>,
239 length: usize,
240}
241
242#[cfg(unix)]
243impl MappedFile {
244 fn open(file: File, path: &Path) -> Result<Self, SegmentError> {
245 let length = usize::try_from(
246 file.metadata()
247 .map_err(|error| SegmentError::io(path, error))?
248 .len(),
249 )
250 .map_err(|_| SegmentError::Geometry("mapped file length exceeds usize".to_owned()))?;
251 if length == 0 {
252 return Err(FormatError::new(
253 path.display().to_string(),
254 FormatCheck::Length,
255 "empty segment file",
256 )
257 .into());
258 }
259 let mapped = unsafe {
263 libc::mmap(
264 std::ptr::null_mut(),
265 length,
266 libc::PROT_READ,
267 libc::MAP_PRIVATE,
268 file.as_raw_fd(),
269 0,
270 )
271 };
272 if mapped == libc::MAP_FAILED {
273 return Err(SegmentError::io(path, std::io::Error::last_os_error()));
274 }
275 let pointer = NonNull::new(mapped.cast::<u8>()).ok_or_else(|| {
276 SegmentError::Geometry("mmap returned a null non-failure pointer".to_owned())
277 })?;
278 Ok(Self {
279 _file: file,
280 pointer,
281 length,
282 })
283 }
284
285 fn as_bytes(&self) -> &[u8] {
286 unsafe { std::slice::from_raw_parts(self.pointer.as_ptr(), self.length) }
289 }
290
291 const fn length(&self) -> usize {
293 self.length
294 }
295
296 fn resident_bytes(&self) -> std::io::Result<u64> {
298 crate::sys::memory::mincore_resident_bytes(self.as_bytes())
299 }
300
301 #[cfg(any(test, feature = "test-support"))]
307 fn corrupt_byte(&mut self, offset: usize, mask: u8) -> std::io::Result<()> {
308 if offset >= self.length {
309 return Err(std::io::Error::other(
310 "corruption offset is outside the mapping",
311 ));
312 }
313 let page_size_raw = unsafe {
314 libc::sysconf(libc::_SC_PAGESIZE)
316 };
317 let page_size = usize::try_from(page_size_raw)
318 .ok()
319 .filter(|size| *size != 0)
320 .ok_or_else(|| std::io::Error::other("sysconf returned an invalid page size"))?;
321 let page_offset = offset - offset % page_size;
322 let page = self
323 .pointer
324 .as_ptr()
325 .wrapping_add(page_offset)
326 .cast::<libc::c_void>();
327 if unsafe { libc::mprotect(page, page_size, libc::PROT_READ | libc::PROT_WRITE) } != 0 {
330 return Err(std::io::Error::last_os_error());
331 }
332 unsafe {
335 let byte = self.pointer.as_ptr().add(offset);
336 byte.write(byte.read() ^ mask);
337 }
338 if unsafe { libc::mprotect(page, page_size, libc::PROT_READ) } != 0 {
340 return Err(std::io::Error::last_os_error());
341 }
342 Ok(())
343 }
344}
345
346#[cfg(unix)]
347impl Drop for MappedFile {
348 fn drop(&mut self) {
349 let _ = unsafe { libc::munmap(self.pointer.as_ptr().cast(), self.length) };
352 }
353}
354
355#[cfg(unix)]
357unsafe impl Send for MappedFile {}
358#[cfg(unix)]
360unsafe impl Sync for MappedFile {}
361
362#[cfg(windows)]
370struct MappedFile {
371 inner: crate::sys::windows::FileMapping,
372}
373
374#[cfg(windows)]
375impl MappedFile {
376 fn open(file: File, path: &Path) -> Result<Self, SegmentError> {
377 let length = file
378 .metadata()
379 .map_err(|error| SegmentError::io(path, error))?
380 .len();
381 if length == 0 {
382 return Err(FormatError::new(
383 path.display().to_string(),
384 FormatCheck::Length,
385 "empty segment file",
386 )
387 .into());
388 }
389 let inner = crate::sys::windows::FileMapping::from_file(file, length)
390 .map_err(|error| SegmentError::io(path, error))?;
391 Ok(Self { inner })
392 }
393
394 fn as_bytes(&self) -> &[u8] {
395 self.inner.as_bytes()
396 }
397
398 const fn length(&self) -> usize {
400 self.inner.length()
401 }
402
403 fn resident_bytes(&self) -> std::io::Result<u64> {
406 self.inner.resident_bytes()
407 }
408
409 #[cfg(any(test, feature = "test-support"))]
412 fn corrupt_byte(&mut self, offset: usize, mask: u8) -> std::io::Result<()> {
413 self.inner.corrupt_byte(offset, mask)
414 }
415}
416
417impl crate::graph::search::RescoreValidator for SegmentReader {
418 fn validate_rows(&self, rows: &[u32]) -> Result<(), String> {
419 self.validate_rescore_rows(rows).map_err(|source| {
420 format!(
421 "exact scores unavailable for segment {}: {source}",
422 self.meta.id
423 )
424 })
425 }
426}
427
428struct ParsedHeader {
429 meta: SegmentMeta,
430 header_length: usize,
431 entries: Vec<RegionEntry>,
432}
433
434pub struct SegmentReader {
436 mapping: MappedFile,
437 meta: SegmentMeta,
438 #[cfg(any(test, feature = "test-support"))]
439 storage_store_directory: PathBuf,
440 header_length: usize,
441 entries: Vec<RegionEntry>,
442 document_versions_validation: OnceLock<Result<(), DocumentVersionValidationError>>,
443 stored_text_validated: OnceLock<()>,
447 stored_metadata_validated: OnceLock<()>,
450 vector_codes_validated: OnceLock<()>,
451 vector_factors_validated: OnceLock<()>,
452 rescore_valid_chunks: Mutex<Box<[u64]>>,
453 query_accounting: Option<Arc<crate::lifecycle::stats::Accounting>>,
454 int8_factors_cache: OnceLock<CachedQueryValue<Arc<Vec<crate::scan::Int8Factors>>>>,
455 postings_cache: OnceLock<CachedQueryValue<Arc<crate::fts::sealed::SealedSegment>>>,
456 columns_cache: OnceLock<CachedQueryValue<Arc<ColumnStore>>>,
457 alive_cache: OnceLock<CachedQueryValue<Arc<AliveSet>>>,
458 document_version_index_cache: OnceLock<CachedQueryValue<Arc<Box<[u32]>>>>,
461 pub(crate) graph_search_cache: crate::lifecycle::graph_cache::SegmentGraphSearchCache,
464 vector_ceiling_norm_range: OnceLock<crate::graph::search::GraphSegmentNormRange>,
469}
470
471struct CachedQueryValue<T> {
472 value: T,
473 _accounting: Option<crate::lifecycle::stats::AccountedCounter>,
474}
475
476#[derive(Clone, Debug)]
477enum DocumentVersionValidationError {
478 Geometry(String),
479 Checksum,
480}
481
482impl DocumentVersionValidationError {
483 fn into_segment_error(self, segment_id: SegmentId) -> SegmentError {
484 match self {
485 Self::Geometry(detail) => SegmentError::Geometry(detail),
486 Self::Checksum => FormatError::new(
487 format!("segment:{segment_id}:document-versions"),
488 FormatCheck::BlockChecksum,
489 "document-version region checksum mismatch",
490 )
491 .into(),
492 }
493 }
494}
495
496#[derive(Clone, Copy, Debug)]
498pub struct StoredMetadataRows<'a> {
499 offsets: &'a [u8],
500 bytes: &'a [u8],
501 row_count: usize,
502}
503
504impl<'a> StoredMetadataRows<'a> {
505 #[must_use]
507 pub const fn row_count(self) -> usize {
508 self.row_count
509 }
510
511 #[must_use]
513 pub fn row(self, row: usize) -> Option<&'a [u8]> {
514 if row >= self.row_count {
515 return None;
516 }
517 let start_index = row.checked_mul(8)?;
518 let end_index = row.checked_add(1)?.checked_mul(8)?;
519 let start = self
520 .offsets
521 .get(start_index..start_index.checked_add(8)?)?
522 .try_into()
523 .ok()
524 .map(u64::from_le_bytes)
525 .and_then(|value| usize::try_from(value).ok())?;
526 let end = self
527 .offsets
528 .get(end_index..end_index.checked_add(8)?)?
529 .try_into()
530 .ok()
531 .map(u64::from_le_bytes)
532 .and_then(|value| usize::try_from(value).ok())?;
533 self.bytes.get(start..end)
534 }
535}
536
537#[derive(Clone, Copy, Debug)]
539pub struct StoredTextRows<'a> {
540 present: &'a [u8],
541 offsets: &'a [u8],
542 bytes: &'a [u8],
543 row_count: usize,
544}
545
546impl<'a> StoredTextRows<'a> {
547 #[must_use]
549 pub const fn row_count(self) -> usize {
550 self.row_count
551 }
552
553 #[must_use]
556 pub fn row(self, row: usize) -> Option<Option<&'a str>> {
557 if row >= self.row_count {
558 return None;
559 }
560 let end_index = row.checked_mul(8)?;
561 let end = self
562 .offsets
563 .get(end_index..end_index.checked_add(8)?)?
564 .try_into()
565 .ok()
566 .map(u64::from_le_bytes)
567 .and_then(|value| usize::try_from(value).ok())?;
568 let start = if row == 0 {
569 0
570 } else {
571 let start_index = row.checked_sub(1)?.checked_mul(8)?;
572 self.offsets
573 .get(start_index..start_index.checked_add(8)?)?
574 .try_into()
575 .ok()
576 .map(u64::from_le_bytes)
577 .and_then(|value| usize::try_from(value).ok())?
578 };
579 let byte = *self.present.get(row / 8)?;
580 if byte & (1 << (row % 8)) == 0 {
581 return (start == end).then_some(None);
582 }
583 let text = std::str::from_utf8(self.bytes.get(start..end)?).ok()?;
584 Some(Some(text))
585 }
586}
587
588static QUERY_CHECKSUMS: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
591
592#[cfg(any(test, feature = "test-support"))]
597pub fn set_query_checksum_verification(enabled: bool) {
598 QUERY_CHECKSUMS.store(
599 if enabled { 2 } else { 1 },
600 std::sync::atomic::Ordering::Relaxed,
601 );
602}
603
604impl SegmentReader {
605 pub fn open(vfs: &dyn Vfs, path: &Path, expected_id: SegmentId) -> Result<Self, SegmentError> {
607 let file = vfs
608 .open_for_map(path)
609 .map_err(|error| SegmentError::io(path, error))?;
610 let mapping = MappedFile::open(file, path)?;
611 let artifact = path.display().to_string();
612 let parsed = parse_segment_header(
613 &artifact,
614 mapping.as_bytes(),
615 mapping.length() as u64,
616 expected_id,
617 )?;
618 let rescore_valid_chunks = rescore_validation_words(&parsed.entries)?;
619 Ok(Self {
620 mapping,
621 meta: parsed.meta,
622 #[cfg(any(test, feature = "test-support"))]
623 storage_store_directory: path.parent().unwrap_or(Path::new("")).to_path_buf(),
624 header_length: parsed.header_length,
625 entries: parsed.entries,
626 document_versions_validation: OnceLock::new(),
627 stored_text_validated: OnceLock::new(),
628 stored_metadata_validated: OnceLock::new(),
629 vector_codes_validated: OnceLock::new(),
630 vector_factors_validated: OnceLock::new(),
631 rescore_valid_chunks: Mutex::new(vec![0_u64; rescore_valid_chunks].into_boxed_slice()),
632 query_accounting: None,
633 int8_factors_cache: OnceLock::new(),
634 postings_cache: OnceLock::new(),
635 columns_cache: OnceLock::new(),
636 alive_cache: OnceLock::new(),
637 document_version_index_cache: OnceLock::new(),
638 graph_search_cache: crate::lifecycle::graph_cache::SegmentGraphSearchCache::new(),
639 vector_ceiling_norm_range: OnceLock::new(),
640 })
641 }
642
643 pub(crate) fn open_accounted(
644 vfs: &dyn Vfs,
645 path: &Path,
646 expected: &SegmentMeta,
647 accounting: &Arc<crate::lifecycle::stats::Accounting>,
648 mut before_reader_allocation: impl FnMut(usize) -> Result<(), crate::lifecycle::StoreError>,
649 ) -> Result<Self, crate::lifecycle::StoreError> {
650 let file = vfs
651 .open_for_map(path)
652 .map_err(|error| SegmentError::io(path, error))
653 .map_err(crate::lifecycle::StoreError::Segment)?;
654 let mapping =
655 MappedFile::open(file, path).map_err(crate::lifecycle::StoreError::Segment)?;
656 let artifact = path.display().to_string();
657 let region_count =
658 preflight_region_count(&artifact, mapping.as_bytes(), mapping.length() as u64)
659 .map_err(crate::lifecycle::StoreError::Segment)?;
660 let directory_bytes = region_count
661 .checked_mul(std::mem::size_of::<RegionEntry>())
662 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
663 needed: u64::MAX,
664 budget: u64::MAX,
665 component: "snapshot",
666 })?;
667 before_reader_allocation(directory_bytes)?;
668 let parsed = parse_segment_header(
669 &artifact,
670 mapping.as_bytes(),
671 mapping.length() as u64,
672 expected.id,
673 )
674 .map_err(crate::lifecycle::StoreError::Segment)?;
675 if !parsed.meta.same_segment_file(expected) {
676 return Err(crate::lifecycle::StoreError::Segment(
677 SegmentError::Geometry(format!(
678 "manifest metadata {expected:?}, mapped header metadata {:?}",
679 parsed.meta
680 )),
681 ));
682 }
683 let rescore_valid_chunks = rescore_validation_words(&parsed.entries)
684 .map_err(crate::lifecycle::StoreError::Segment)?;
685 let validation_bytes = rescore_valid_chunks
686 .checked_mul(std::mem::size_of::<u64>())
687 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
688 needed: u64::MAX,
689 budget: u64::MAX,
690 component: "snapshot",
691 })?;
692 before_reader_allocation(validation_bytes)?;
693 Ok(Self {
694 mapping,
695 meta: expected.clone(),
696 #[cfg(any(test, feature = "test-support"))]
697 storage_store_directory: path.parent().unwrap_or(Path::new("")).to_path_buf(),
698 header_length: parsed.header_length,
699 entries: parsed.entries,
700 document_versions_validation: OnceLock::new(),
701 stored_text_validated: OnceLock::new(),
702 stored_metadata_validated: OnceLock::new(),
703 vector_codes_validated: OnceLock::new(),
704 vector_factors_validated: OnceLock::new(),
705 rescore_valid_chunks: Mutex::new(vec![0_u64; rescore_valid_chunks].into_boxed_slice()),
706 query_accounting: Some(Arc::clone(accounting)),
707 int8_factors_cache: OnceLock::new(),
708 postings_cache: OnceLock::new(),
709 columns_cache: OnceLock::new(),
710 alive_cache: OnceLock::new(),
711 document_version_index_cache: OnceLock::new(),
712 graph_search_cache: crate::lifecycle::graph_cache::SegmentGraphSearchCache::new(),
713 vector_ceiling_norm_range: OnceLock::new(),
714 })
715 }
716
717 #[must_use]
719 pub const fn meta(&self) -> &SegmentMeta {
720 &self.meta
721 }
722
723 #[must_use]
725 pub const fn header_length(&self) -> usize {
726 self.header_length
727 }
728
729 #[must_use]
731 pub(crate) const fn mapped_bytes(&self) -> usize {
732 self.mapping.length()
733 }
734
735 pub(crate) fn mapped_resident_bytes(&self) -> std::io::Result<u64> {
737 self.mapping.resident_bytes()
738 }
739
740 #[cfg(test)]
741 pub(crate) fn retained_validation_bytes(&self) -> usize {
742 self.rescore_valid_chunks
743 .lock()
744 .map_or(0, |words| std::mem::size_of_val(words.as_ref()))
745 }
746
747 #[cfg(any(test, feature = "test-support"))]
751 #[doc(hidden)]
752 pub fn corrupt_mapped_byte_for_test(&mut self, offset: usize, mask: u8) -> std::io::Result<()> {
753 self.mapping.corrupt_byte(offset, mask)
754 }
755
756 #[cfg(any(test, feature = "test-support"))]
757 #[doc(hidden)]
758 pub fn retained_query_view_bytes(&self) -> u64 {
759 let int8_factors = self.int8_factors_cache.get().and_then(|cached| {
760 cached
761 .value
762 .capacity()
763 .checked_mul(std::mem::size_of::<crate::scan::Int8Factors>())
764 .and_then(arc_resident_bytes::<Vec<crate::scan::Int8Factors>>)
765 });
766 let postings = self.postings_cache.get().and_then(|cached| {
767 cached
768 .value
769 .resident_bytes()
770 .ok()
771 .and_then(arc_resident_bytes::<crate::fts::sealed::SealedSegment>)
772 });
773 let columns = self.columns_cache.get().and_then(|cached| {
774 cached
775 .value
776 .resident_bytes()
777 .and_then(arc_resident_bytes::<ColumnStore>)
778 });
779 let alive = self.alive_cache.get().and_then(|cached| {
780 cached
781 .value
782 .resident_bytes()
783 .and_then(arc_resident_bytes::<AliveSet>)
784 });
785 let document_version_index = self.document_version_index_cache.get().and_then(|cached| {
786 cached
787 .value
788 .len()
789 .checked_mul(std::mem::size_of::<u32>())
790 .and_then(arc_resident_bytes::<Box<[u32]>>)
791 });
792 int8_factors
793 .into_iter()
794 .chain(postings)
795 .chain(columns)
796 .chain(alive)
797 .chain(document_version_index)
798 .fold(0_u64, |total, bytes| {
799 total.saturating_add(u64::try_from(bytes).unwrap_or(u64::MAX))
800 })
801 }
802
803 #[must_use]
805 pub fn directory(&self) -> &[RegionEntry] {
806 &self.entries
807 }
808
809 #[must_use]
811 pub fn unknown_region_ids(&self) -> Vec<u16> {
812 self.entries
813 .iter()
814 .filter_map(|entry| {
815 RegionKind::from_id(entry.kind)
816 .is_none()
817 .then_some(entry.kind)
818 })
819 .collect()
820 }
821
822 pub fn region(&self, kind: RegionKind) -> Result<&[u8], SegmentError> {
824 let entry = self.entry(kind)?;
825 let bytes = self.region_slice(entry)?;
826 let actual = region_hash(kind, bytes);
827 if actual != entry.checksum {
828 #[cfg(any(test, feature = "test-support"))]
829 crate::lifecycle::record_storage_segment_checksum_fault(
830 &self.storage_store_directory,
831 self.meta.id,
832 kind,
833 0,
834 entry.checksum,
835 actual,
836 );
837 return Err(FormatError::checksum_mismatch(
838 format!("segment:{}:{kind:?}", self.meta.id),
839 FormatCheck::BlockChecksum,
840 entry.checksum,
841 actual,
842 )
843 .into());
844 }
845 Ok(bytes)
846 }
847
848 pub(crate) fn region_by_id(&self, kind: u16) -> Result<&[u8], SegmentError> {
850 let entry = self
851 .entries
852 .iter()
853 .find(|entry| entry.kind == kind)
854 .ok_or_else(|| {
855 SegmentError::Geometry(format!("segment is missing region kind {kind}"))
856 })?;
857 let bytes = self.region_slice(entry)?;
858 let actual = xxh3_64(bytes);
859 if actual != entry.checksum {
860 return Err(FormatError::checksum_mismatch(
861 format!("segment:{}:region-{kind}", self.meta.id),
862 FormatCheck::BlockChecksum,
863 entry.checksum,
864 actual,
865 )
866 .into());
867 }
868 Ok(bytes)
869 }
870
871 pub fn region_chunk(&self, kind: RegionKind, chunk_index: u32) -> Result<&[u8], SegmentError> {
873 let entry = self.entry(kind)?;
874 let region = self.region_slice(entry)?;
875 let start = (chunk_index as usize)
876 .checked_mul(CHECKSUM_CHUNK_BYTES)
877 .ok_or_else(|| SegmentError::Geometry("chunk offset overflow".to_owned()))?;
878 let end = start.saturating_add(CHECKSUM_CHUNK_BYTES).min(region.len());
879 let chunk = region.get(start..end).ok_or_else(|| {
880 SegmentError::Geometry(format!("chunk {chunk_index} is outside {kind:?}"))
881 })?;
882 if chunk.is_empty() {
883 return Err(SegmentError::Geometry(format!(
884 "chunk {chunk_index} is outside {kind:?}"
885 )));
886 }
887 let expected = self.chunk_checksum(kind, chunk_index)?;
888 let actual = region_hash(kind, chunk);
889 if actual != expected {
890 #[cfg(any(test, feature = "test-support"))]
891 crate::lifecycle::record_storage_segment_checksum_fault(
892 &self.storage_store_directory,
893 self.meta.id,
894 kind,
895 chunk_index,
896 expected,
897 actual,
898 );
899 return Err(FormatError::checksum_mismatch(
900 format!("segment:{}:{kind:?}:chunk-{chunk_index}", self.meta.id),
901 FormatCheck::BlockChecksum,
902 expected,
903 actual,
904 )
905 .into());
906 }
907 Ok(chunk)
908 }
909
910 pub fn bit4_codes(&self) -> Result<&[u8], SegmentError> {
912 let (header, payload) = self.vector_payload(RegionKind::VectorCodes)?;
913 self.bit4_codes_from(header, payload)
914 }
915
916 fn bit4_codes_unchecked(&self) -> Result<&[u8], SegmentError> {
917 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorCodes)?;
918 self.bit4_codes_from(header, payload)
919 }
920
921 fn bit4_codes_from<'a>(
922 &'a self,
923 header: super::layout::VectorHeader,
924 payload: &'a [u8],
925 ) -> Result<&'a [u8], SegmentError> {
926 if self.meta.scheme != 4 {
927 return Err(SegmentError::Geometry(format!(
928 "Bit4 codes requested for scheme {}",
929 self.meta.scheme
930 )));
931 }
932 let expected = (self.meta.dims as usize)
933 .div_ceil(2)
934 .checked_mul(self.meta.row_count as usize)
935 .ok_or_else(|| SegmentError::Geometry("Bit4 code length overflow".to_owned()))?;
936 if header.row_stride_bytes as usize != (self.meta.dims as usize).div_ceil(2)
937 || payload.len() != expected
938 {
939 return Err(SegmentError::Geometry(format!(
940 "Bit4 code stride/length {}/{}, expected {}/{}",
941 header.row_stride_bytes,
942 payload.len(),
943 (self.meta.dims as usize).div_ceil(2),
944 expected
945 )));
946 }
947 Ok(payload)
948 }
949
950 pub fn f32_codes(&self) -> Result<&[f32], SegmentError> {
952 let (header, payload) = self.vector_payload(RegionKind::VectorCodes)?;
953 self.f32_codes_from(header, payload)
954 }
955
956 pub(crate) fn query_f32_codes(&self) -> Result<&[f32], SegmentError> {
958 self.validate_vector_region_once(RegionKind::VectorCodes, &self.vector_codes_validated)?;
959 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorCodes)?;
960 self.f32_codes_from(header, payload)
961 }
962
963 fn f32_codes_from<'a>(
964 &'a self,
965 header: super::layout::VectorHeader,
966 payload: &'a [u8],
967 ) -> Result<&'a [f32], SegmentError> {
968 if self.meta.scheme != 0 {
969 return Err(SegmentError::Geometry(format!(
970 "F32 codes requested for scheme {}",
971 self.meta.scheme
972 )));
973 }
974 let expected = (self.meta.dims as usize)
975 .checked_mul(self.meta.row_count as usize)
976 .ok_or_else(|| SegmentError::Geometry("F32 code length overflow".to_owned()))?;
977 let expected_bytes = expected
978 .checked_mul(std::mem::size_of::<f32>())
979 .ok_or_else(|| SegmentError::Geometry("F32 code byte length overflow".to_owned()))?;
980 let expected_stride = (self.meta.dims as usize)
981 .checked_mul(std::mem::size_of::<f32>())
982 .ok_or_else(|| SegmentError::Geometry("F32 code stride overflow".to_owned()))?;
983 if header.row_stride_bytes as usize != expected_stride || payload.len() != expected_bytes {
984 return Err(SegmentError::Geometry(format!(
985 "F32 code stride/length {}/{}, expected {expected_stride}/{expected_bytes}",
986 header.row_stride_bytes,
987 payload.len()
988 )));
989 }
990 cast_slice::<f32>(payload, expected, "F32 codes")
991 }
992
993 pub fn int8_codes(&self) -> Result<&[i8], SegmentError> {
995 let (header, payload) = self.vector_payload(RegionKind::VectorCodes)?;
996 self.int8_codes_from(header, payload)
997 }
998
999 fn int8_codes_unchecked(&self) -> Result<&[i8], SegmentError> {
1000 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorCodes)?;
1001 self.int8_codes_from(header, payload)
1002 }
1003
1004 fn int8_codes_from<'a>(
1005 &'a self,
1006 header: super::layout::VectorHeader,
1007 payload: &'a [u8],
1008 ) -> Result<&'a [i8], SegmentError> {
1009 if self.meta.scheme != 2 {
1010 return Err(SegmentError::Geometry(format!(
1011 "Int8 codes requested for scheme {}",
1012 self.meta.scheme
1013 )));
1014 }
1015 let expected = (self.meta.dims as usize)
1016 .checked_mul(self.meta.row_count as usize)
1017 .ok_or_else(|| SegmentError::Geometry("Int8 code length overflow".to_owned()))?;
1018 if header.row_stride_bytes as usize != self.meta.dims as usize || payload.len() != expected
1019 {
1020 return Err(SegmentError::Geometry(format!(
1021 "Int8 code stride/length {}/{}, expected {}/{}",
1022 header.row_stride_bytes,
1023 payload.len(),
1024 self.meta.dims,
1025 expected
1026 )));
1027 }
1028 cast_slice::<i8>(payload, expected, "Int8 codes")
1029 }
1030
1031 pub fn bit4_factors(&self) -> Result<&[Bit4Factors], SegmentError> {
1033 let (header, payload) = self.vector_payload(RegionKind::VectorFactors)?;
1034 self.bit4_factors_from(header, payload)
1035 }
1036
1037 fn bit4_factors_unchecked(&self) -> Result<&[Bit4Factors], SegmentError> {
1038 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorFactors)?;
1039 self.bit4_factors_from(header, payload)
1040 }
1041
1042 fn bit4_factors_from<'a>(
1043 &'a self,
1044 header: super::layout::VectorHeader,
1045 payload: &'a [u8],
1046 ) -> Result<&'a [Bit4Factors], SegmentError> {
1047 if self.meta.scheme != 4 {
1048 return Err(SegmentError::Geometry(format!(
1049 "Bit4 factors requested for scheme {}",
1050 self.meta.scheme
1051 )));
1052 }
1053 if header.factor_stride_bytes != 12 {
1054 return Err(SegmentError::Geometry(format!(
1055 "Bit4 factor stride {}, expected 12",
1056 header.factor_stride_bytes
1057 )));
1058 }
1059 cast_slice::<Bit4Factors>(payload, self.meta.row_count as usize, "Bit4 factors")
1060 }
1061
1062 pub fn int8_factors(&self) -> Result<&[Int8Factors], SegmentError> {
1064 let (header, payload) = self.vector_payload(RegionKind::VectorFactors)?;
1065 self.int8_factors_from(header, payload)
1066 }
1067
1068 fn int8_factors_unchecked(&self) -> Result<&[Int8Factors], SegmentError> {
1069 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorFactors)?;
1070 self.int8_factors_from(header, payload)
1071 }
1072
1073 fn int8_factors_from<'a>(
1074 &'a self,
1075 header: super::layout::VectorHeader,
1076 payload: &'a [u8],
1077 ) -> Result<&'a [Int8Factors], SegmentError> {
1078 if self.meta.scheme != 2 {
1079 return Err(SegmentError::Geometry(format!(
1080 "Int8 factors requested for scheme {}",
1081 self.meta.scheme
1082 )));
1083 }
1084 if header.factor_stride_bytes != 8 {
1085 return Err(SegmentError::Geometry(format!(
1086 "Int8 factor stride {}, expected 8",
1087 header.factor_stride_bytes
1088 )));
1089 }
1090 #[cfg(any(test, feature = "test-support"))]
1091 account_region_decode(RegionKind::VectorFactors, payload.len());
1092 cast_slice::<Int8Factors>(payload, self.meta.row_count as usize, "Int8 factors")
1093 }
1094
1095 fn query_checksums_enabled() -> bool {
1105 match QUERY_CHECKSUMS.load(std::sync::atomic::Ordering::Relaxed) {
1106 0 => {
1107 let enabled =
1108 std::env::var_os("ZE_VERIFY_QUERY_CHECKSUMS").is_some_and(|value| value != "0");
1109 QUERY_CHECKSUMS.store(
1110 if enabled { 2 } else { 1 },
1111 std::sync::atomic::Ordering::Relaxed,
1112 );
1113 enabled
1114 }
1115 1 => false,
1116 _ => true,
1117 }
1118 }
1119
1120 fn validate_vector_region_once(
1130 &self,
1131 kind: RegionKind,
1132 gate: &OnceLock<()>,
1133 ) -> Result<(), SegmentError> {
1134 if gate.get().is_some() || !Self::query_checksums_enabled() {
1135 return Ok(());
1136 }
1137 let _ = self.region(kind)?;
1138 let _ = gate.set(());
1139 Ok(())
1140 }
1141
1142 pub(crate) fn query_bit4_codes(&self) -> Result<&[u8], SegmentError> {
1144 self.validate_vector_region_once(RegionKind::VectorCodes, &self.vector_codes_validated)?;
1145 self.bit4_codes_unchecked()
1146 }
1147
1148 pub(crate) fn query_int8_codes(&self) -> Result<&[i8], SegmentError> {
1150 self.validate_vector_region_once(RegionKind::VectorCodes, &self.vector_codes_validated)?;
1151 self.int8_codes_unchecked()
1152 }
1153
1154 pub(crate) fn query_int8_factors_slice(&self) -> Result<&[Int8Factors], SegmentError> {
1156 self.validate_vector_region_once(
1157 RegionKind::VectorFactors,
1158 &self.vector_factors_validated,
1159 )?;
1160 self.int8_factors_unchecked()
1161 }
1162
1163 pub(crate) fn cached_vector_ceiling_norm_range(
1169 &self,
1170 ) -> Option<crate::graph::search::GraphSegmentNormRange> {
1171 self.vector_ceiling_norm_range.get().copied()
1172 }
1173
1174 pub(crate) fn remember_vector_ceiling_norm_range(
1176 &self,
1177 range: crate::graph::search::GraphSegmentNormRange,
1178 ) {
1179 if self.vector_ceiling_norm_range.set(range).is_err() {
1180 }
1183 }
1184
1185 pub(crate) fn query_bit4_factors(&self) -> Result<&[Bit4Factors], SegmentError> {
1187 self.validate_vector_region_once(
1188 RegionKind::VectorFactors,
1189 &self.vector_factors_validated,
1190 )?;
1191 self.bit4_factors_unchecked()
1192 }
1193
1194 pub(crate) fn query_int8_factors(
1195 &self,
1196 ) -> Result<Arc<Vec<crate::scan::Int8Factors>>, crate::lifecycle::StoreError> {
1197 if let Some(cached) = self.int8_factors_cache.get() {
1198 return Ok(Arc::clone(&cached.value));
1199 }
1200 let factors = self
1201 .int8_factors()
1202 .map_err(crate::lifecycle::StoreError::Segment)?
1203 .iter()
1204 .map(|factor| crate::scan::Int8Factors::new(factor.scale, factor.offset))
1205 .collect::<Result<Vec<_>, _>>()
1206 .map_err(|_| {
1207 crate::lifecycle::StoreError::Segment(SegmentError::Geometry(
1208 "Int8 factor is not finite and non-negative".to_owned(),
1209 ))
1210 })?;
1211 let resident_bytes = factors
1212 .capacity()
1213 .checked_mul(std::mem::size_of::<crate::scan::Int8Factors>())
1214 .and_then(arc_resident_bytes::<Vec<crate::scan::Int8Factors>>)
1215 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
1216 needed: u64::MAX,
1217 budget: u64::MAX,
1218 component: "snapshot",
1219 })?;
1220 let cached = CachedQueryValue {
1221 value: Arc::new(factors),
1222 _accounting: self.account_query_cache(resident_bytes)?,
1223 };
1224 if self.int8_factors_cache.set(cached).is_err() {
1225 }
1227 self.int8_factors_cache
1228 .get()
1229 .map(|cached| Arc::clone(&cached.value))
1230 .ok_or(crate::lifecycle::StoreError::Synchronization {
1231 component: "Int8 factors query cache",
1232 })
1233 }
1234
1235 pub fn rescore_f32(&self) -> Result<&[f32], SegmentError> {
1237 let (header, payload) = self.vector_payload(RegionKind::VectorRescore)?;
1238 if header.scheme != 0 {
1239 return Err(SegmentError::Geometry(format!(
1240 "v1 rescore scheme must be F32/0, got {}",
1241 header.scheme
1242 )));
1243 }
1244 let count = (self.meta.row_count as usize)
1245 .checked_mul(self.meta.dims as usize)
1246 .ok_or_else(|| SegmentError::Geometry("rescore count overflow".to_owned()))?;
1247 cast_slice::<f32>(payload, count, "f32 rescore")
1248 }
1249
1250 pub(crate) fn query_rescore_f32(&self) -> Result<&[f32], SegmentError> {
1251 self.validate_rescore_byte_range(0, VECTOR_HEADER_LEN)?;
1252 let (header, payload) = self.vector_payload_unchecked(RegionKind::VectorRescore)?;
1253 if header.scheme != 0 {
1254 return Err(SegmentError::Geometry(format!(
1255 "v1 rescore scheme must be F32/0, got {}",
1256 header.scheme
1257 )));
1258 }
1259 let count = (self.meta.row_count as usize)
1260 .checked_mul(self.meta.dims as usize)
1261 .ok_or_else(|| SegmentError::Geometry("rescore count overflow".to_owned()))?;
1262 cast_slice::<f32>(payload, count, "f32 rescore")
1263 }
1264
1265 pub(crate) fn validate_rescore_rows(&self, rows: &[u32]) -> Result<(), SegmentError> {
1266 let row_bytes = (self.meta.dims as usize)
1267 .checked_mul(std::mem::size_of::<f32>())
1268 .ok_or_else(|| SegmentError::Geometry("rescore row byte length overflow".to_owned()))?;
1269 for row in rows {
1270 if *row >= self.meta.row_count {
1271 return Err(SegmentError::Geometry(format!(
1272 "rescore row {row} is outside {} rows",
1273 self.meta.row_count
1274 )));
1275 }
1276 let start = (*row as usize)
1277 .checked_mul(row_bytes)
1278 .and_then(|offset| offset.checked_add(VECTOR_HEADER_LEN))
1279 .ok_or_else(|| SegmentError::Geometry("rescore row offset overflow".to_owned()))?;
1280 self.validate_rescore_byte_range(start, row_bytes)?;
1281 }
1282 Ok(())
1283 }
1284
1285 pub fn columns(&self) -> Result<ColumnStore, SegmentError> {
1287 let region = self.region(RegionKind::Columns)?;
1288 #[cfg(any(test, feature = "test-support"))]
1289 account_region_decode(RegionKind::Columns, region.len());
1290 let columns = decode_columns(region)?;
1291 if columns.row_count() != self.meta.row_count {
1292 return Err(SegmentError::Geometry(format!(
1293 "column rows {}, header rows {}",
1294 columns.row_count(),
1295 self.meta.row_count
1296 )));
1297 }
1298 Ok(columns)
1299 }
1300
1301 pub(crate) fn query_columns(&self) -> Result<Arc<ColumnStore>, crate::lifecycle::StoreError> {
1302 if let Some(cached) = self.columns_cache.get() {
1303 return Ok(Arc::clone(&cached.value));
1304 }
1305 let region = self
1306 .region(RegionKind::Columns)
1307 .map_err(crate::lifecycle::StoreError::Segment)?;
1308 #[cfg(any(test, feature = "test-support"))]
1309 account_region_decode(RegionKind::Columns, region.len());
1310 let mut columns = decode_columns(region).map_err(crate::lifecycle::StoreError::Segment)?;
1311 if columns.row_count() != self.meta.row_count {
1312 return Err(crate::lifecycle::StoreError::Segment(
1313 SegmentError::Geometry(format!(
1314 "column rows {}, header rows {}",
1315 columns.row_count(),
1316 self.meta.row_count
1317 )),
1318 ));
1319 }
1320 columns.compact_for_cache();
1321 let resident_bytes = columns
1322 .resident_bytes()
1323 .and_then(arc_resident_bytes::<ColumnStore>)
1324 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
1325 needed: u64::MAX,
1326 budget: u64::MAX,
1327 component: "snapshot",
1328 })?;
1329 let cached = CachedQueryValue {
1330 value: Arc::new(columns),
1331 _accounting: self.account_query_cache(resident_bytes)?,
1332 };
1333 if self.columns_cache.set(cached).is_err() {
1334 }
1336 self.columns_cache
1337 .get()
1338 .map(|cached| Arc::clone(&cached.value))
1339 .ok_or(crate::lifecycle::StoreError::Synchronization {
1340 component: "columns query cache",
1341 })
1342 }
1343
1344 pub fn postings(&self) -> Result<Option<crate::fts::sealed::SealedSegment>, SegmentError> {
1346 if !self
1347 .entries
1348 .iter()
1349 .any(|entry| entry.kind == RegionKind::Postings.id())
1350 {
1351 return Ok(None);
1352 }
1353 let region = self.region(RegionKind::Postings)?;
1354 #[cfg(any(test, feature = "test-support"))]
1355 account_region_decode(RegionKind::Postings, region.len());
1356 let postings = crate::fts::sealed::SealedSegment::decode_region(region)?;
1357 if postings.row_count() != self.meta.row_count {
1358 return Err(SegmentError::Geometry(format!(
1359 "postings rows {}, header rows {}",
1360 postings.row_count(),
1361 self.meta.row_count
1362 )));
1363 }
1364 Ok(Some(postings))
1365 }
1366
1367 pub(crate) fn lexical_postings_identity(&self) -> Result<(u64, u64), SegmentError> {
1370 let entry = self.entry(RegionKind::Postings)?;
1371 Ok((entry.length, entry.checksum))
1372 }
1373
1374 pub(crate) fn query_postings(
1375 &self,
1376 ) -> Result<Option<Arc<crate::fts::sealed::SealedSegment>>, crate::lifecycle::StoreError> {
1377 if !self
1378 .entries
1379 .iter()
1380 .any(|entry| entry.kind == RegionKind::Postings.id())
1381 {
1382 return Ok(None);
1383 }
1384 if let Some(cached) = self.postings_cache.get() {
1385 return Ok(Some(Arc::clone(&cached.value)));
1386 }
1387 let region = self
1388 .region(RegionKind::Postings)
1389 .map_err(crate::lifecycle::StoreError::Segment)?;
1390 #[cfg(any(test, feature = "test-support"))]
1391 account_region_decode(RegionKind::Postings, region.len());
1392 let postings = crate::fts::sealed::SealedSegment::decode_region(region)
1393 .map_err(SegmentError::from)
1394 .map_err(crate::lifecycle::StoreError::Segment)?;
1395 if postings.row_count() != self.meta.row_count {
1396 return Err(crate::lifecycle::StoreError::Segment(
1397 SegmentError::Geometry(format!(
1398 "postings rows {}, header rows {}",
1399 postings.row_count(),
1400 self.meta.row_count
1401 )),
1402 ));
1403 }
1404 let resident_bytes = postings
1405 .resident_bytes()
1406 .map_err(SegmentError::from)
1407 .map_err(crate::lifecycle::StoreError::Segment)?
1408 .checked_add(std::mem::size_of::<crate::fts::sealed::SealedSegment>())
1409 .and_then(|bytes| bytes.checked_add(2 * std::mem::size_of::<usize>()))
1410 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
1411 needed: u64::MAX,
1412 budget: u64::MAX,
1413 component: "snapshot",
1414 })?;
1415 let mut cache_accounting = self
1416 .query_accounting
1417 .as_ref()
1418 .map(|accounting| {
1419 crate::lifecycle::stats::AccountedCounter::new(
1420 accounting,
1421 crate::lifecycle::stats::AllocationComponent::Snapshot,
1422 )
1423 })
1424 .transpose()?;
1425 if let Some(counter) = cache_accounting.as_mut() {
1426 counter.set(resident_bytes)?;
1427 }
1428 let cached = CachedQueryValue {
1429 value: Arc::new(postings),
1430 _accounting: cache_accounting,
1431 };
1432 if self.postings_cache.set(cached).is_err() {
1433 }
1435 self.postings_cache
1436 .get()
1437 .map(|cached| Some(Arc::clone(&cached.value)))
1438 .ok_or(crate::lifecycle::StoreError::Synchronization {
1439 component: "postings query cache",
1440 })
1441 }
1442
1443 pub fn alive(&self) -> Result<AliveSet, SegmentError> {
1445 let region = self.region(RegionKind::Alive)?;
1446 #[cfg(any(test, feature = "test-support"))]
1447 account_region_decode(RegionKind::Alive, region.len());
1448 let alive = decode_alive(region)?;
1449 if alive.row_count() != self.meta.row_count {
1450 return Err(SegmentError::Geometry(format!(
1451 "alive rows {}, header rows {}",
1452 alive.row_count(),
1453 self.meta.row_count
1454 )));
1455 }
1456 Ok(alive)
1457 }
1458
1459 pub(crate) fn query_alive(&self) -> Result<Arc<AliveSet>, crate::lifecycle::StoreError> {
1460 if let Some(cached) = self.alive_cache.get() {
1461 return Ok(Arc::clone(&cached.value));
1462 }
1463 let region = self
1464 .region(RegionKind::Alive)
1465 .map_err(crate::lifecycle::StoreError::Segment)?;
1466 #[cfg(any(test, feature = "test-support"))]
1467 account_region_decode(RegionKind::Alive, region.len());
1468 let mut alive = decode_alive(region).map_err(crate::lifecycle::StoreError::Segment)?;
1469 if alive.row_count() != self.meta.row_count {
1470 return Err(crate::lifecycle::StoreError::Segment(
1471 SegmentError::Geometry(format!(
1472 "alive rows {}, header rows {}",
1473 alive.row_count(),
1474 self.meta.row_count
1475 )),
1476 ));
1477 }
1478 alive.compact_for_cache();
1479 let resident_bytes = alive
1480 .resident_bytes()
1481 .and_then(arc_resident_bytes::<AliveSet>)
1482 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
1483 needed: u64::MAX,
1484 budget: u64::MAX,
1485 component: "snapshot",
1486 })?;
1487 let cached = CachedQueryValue {
1488 value: Arc::new(alive),
1489 _accounting: self.account_query_cache(resident_bytes)?,
1490 };
1491 if self.alive_cache.set(cached).is_err() {
1492 }
1494 self.alive_cache
1495 .get()
1496 .map(|cached| Arc::clone(&cached.value))
1497 .ok_or(crate::lifecycle::StoreError::Synchronization {
1498 component: "alive query cache",
1499 })
1500 }
1501
1502 fn document_versions_region(&self) -> Result<Option<&[u8]>, SegmentError> {
1505 let Some(entry) = self
1506 .entries
1507 .iter()
1508 .find(|entry| entry.kind == RegionKind::DocumentVersions.id())
1509 else {
1510 return Ok(None);
1511 };
1512 let validation = self.document_versions_validation.get_or_init(|| {
1513 let bytes = self
1514 .region_slice(entry)
1515 .map_err(|error| DocumentVersionValidationError::Geometry(error.to_string()))?;
1516 if region_hash(RegionKind::DocumentVersions, bytes) != entry.checksum {
1517 return Err(DocumentVersionValidationError::Checksum);
1518 }
1519 let expected = (self.meta.row_count as usize)
1520 .checked_mul(24)
1521 .ok_or_else(|| {
1522 DocumentVersionValidationError::Geometry(
1523 "document-version length overflow".to_owned(),
1524 )
1525 })?;
1526 if bytes.len() != expected {
1527 return Err(DocumentVersionValidationError::Geometry(format!(
1528 "document-version bytes {}, expected {expected}",
1529 bytes.len()
1530 )));
1531 }
1532 Ok(())
1533 });
1534 if let Err(error) = validation {
1535 return Err(error.clone().into_segment_error(self.meta.id));
1536 }
1537 self.region_slice_unaccounted(entry).map(Some)
1538 }
1539
1540 pub fn document_version(&self, row: usize) -> Result<Option<DocumentVersion>, SegmentError> {
1542 let Some(bytes) = self.document_versions_region()? else {
1543 return Ok(None);
1544 };
1545 let start = row
1546 .checked_mul(24)
1547 .ok_or_else(|| SegmentError::Geometry("document-version row overflow".to_owned()))?;
1548 let doc_end = start
1549 .checked_add(16)
1550 .ok_or_else(|| SegmentError::Geometry("document id end overflow".to_owned()))?;
1551 let revision_end = doc_end
1552 .checked_add(8)
1553 .ok_or_else(|| SegmentError::Geometry("revision end overflow".to_owned()))?;
1554 let doc_id = bytes
1555 .get(start..doc_end)
1556 .and_then(|value| value.try_into().ok())
1557 .map(u128::from_le_bytes)
1558 .ok_or_else(|| SegmentError::Geometry(format!("document id row {row} is missing")))?;
1559 let revision = bytes
1560 .get(doc_end..revision_end)
1561 .and_then(|value| value.try_into().ok())
1562 .map(u64::from_le_bytes)
1563 .ok_or_else(|| SegmentError::Geometry(format!("revision row {row} is missing")))?;
1564 Ok(Some(DocumentVersion::new(
1565 DocId::new(doc_id),
1566 Revision::new(revision),
1567 )))
1568 }
1569
1570 pub fn row_for_document_version(
1575 &self,
1576 version: DocumentVersion,
1577 ) -> Result<Option<usize>, SegmentError> {
1578 let Some(bytes) = self.document_versions_region()? else {
1579 return Ok(None);
1580 };
1581 let needle = encode_document_version(version);
1582 Ok(bytes
1583 .chunks_exact(DOCUMENT_VERSION_BYTES)
1584 .position(|entry| entry == needle))
1585 }
1586
1587 pub(crate) fn query_row_for_document_version(
1592 &self,
1593 version: DocumentVersion,
1594 ) -> Result<Option<usize>, crate::lifecycle::StoreError> {
1595 let Some(bytes) = self
1596 .document_versions_region()
1597 .map_err(crate::lifecycle::StoreError::Segment)?
1598 else {
1599 return Ok(None);
1600 };
1601 let Some(index) = self.query_document_version_index(bytes)? else {
1602 return Ok(None);
1603 };
1604 let needle = encode_document_version(version);
1605 let position =
1606 index.partition_point(|&row| document_version_entry(bytes, row) < needle.as_slice());
1607 Ok(index
1608 .get(position)
1609 .copied()
1610 .filter(|&row| document_version_entry(bytes, row) == needle)
1611 .map(|row| row as usize))
1612 }
1613
1614 pub(crate) fn query_rows_for_doc_id(
1617 &self,
1618 doc_id: DocId,
1619 ) -> Result<Vec<usize>, crate::lifecycle::StoreError> {
1620 let Some(bytes) = self
1621 .document_versions_region()
1622 .map_err(crate::lifecycle::StoreError::Segment)?
1623 else {
1624 return Ok(Vec::new());
1625 };
1626 let Some(index) = self.query_document_version_index(bytes)? else {
1627 return Ok(Vec::new());
1628 };
1629 let needle = doc_id.get().to_le_bytes();
1630 let start = index.partition_point(|&row| document_id_entry(bytes, row) < needle.as_slice());
1631 let end = index.partition_point(|&row| document_id_entry(bytes, row) <= needle.as_slice());
1632 let matching = index.get(start..end).ok_or_else(|| {
1633 crate::lifecycle::StoreError::Segment(SegmentError::Geometry(
1634 "document-id index range is invalid".to_owned(),
1635 ))
1636 })?;
1637 let alive = self.query_alive()?;
1638 let mut rows = Vec::new();
1639 rows.try_reserve_exact(matching.len()).map_err(|_| {
1640 crate::lifecycle::StoreError::AllocationFailed {
1641 needed: matching
1642 .len()
1643 .checked_mul(std::mem::size_of::<usize>())
1644 .and_then(|bytes| u64::try_from(bytes).ok())
1645 .unwrap_or(u64::MAX),
1646 component: "document-id rows",
1647 }
1648 })?;
1649 for row in matching {
1650 if alive.is_alive(*row) {
1651 rows.push(*row as usize);
1652 }
1653 }
1654 Ok(rows)
1655 }
1656
1657 fn query_document_version_index(
1658 &self,
1659 bytes: &[u8],
1660 ) -> Result<Option<Arc<Box<[u32]>>>, crate::lifecycle::StoreError> {
1661 if let Some(cached) = self.document_version_index_cache.get() {
1662 return Ok(Some(Arc::clone(&cached.value)));
1663 }
1664 let mut rows: Vec<u32> = (0..self.meta.row_count).collect();
1665 rows.sort_unstable_by(|&left, &right| {
1666 document_version_entry(bytes, left)
1667 .cmp(document_version_entry(bytes, right))
1668 .then(left.cmp(&right))
1669 });
1670 let index = rows.into_boxed_slice();
1671 let resident_bytes = index
1672 .len()
1673 .checked_mul(std::mem::size_of::<u32>())
1674 .and_then(arc_resident_bytes::<Box<[u32]>>)
1675 .ok_or(crate::lifecycle::StoreError::BudgetExceeded {
1676 needed: u64::MAX,
1677 budget: u64::MAX,
1678 component: "snapshot",
1679 })?;
1680 let cached = CachedQueryValue {
1681 value: Arc::new(index),
1682 _accounting: self.account_query_cache(resident_bytes)?,
1683 };
1684 if self.document_version_index_cache.set(cached).is_err() {
1685 }
1687 self.document_version_index_cache
1688 .get()
1689 .map(|cached| Some(Arc::clone(&cached.value)))
1690 .ok_or(crate::lifecycle::StoreError::Synchronization {
1691 component: "document-version index cache",
1692 })
1693 }
1694
1695 pub fn stored_metadata(&self) -> Result<Option<StoredMetadataRows<'_>>, SegmentError> {
1697 let Some(entry) = self
1698 .entries
1699 .iter()
1700 .find(|entry| entry.kind == RegionKind::StoredMetadata.id())
1701 else {
1702 return Ok(None);
1703 };
1704 let region = self.region(RegionKind::StoredMetadata)?;
1705 let declared_rows = region
1706 .get(..4)
1707 .and_then(|value| value.try_into().ok())
1708 .map(u32::from_le_bytes)
1709 .ok_or_else(|| {
1710 SegmentError::Geometry("stored-metadata header is truncated".to_owned())
1711 })?;
1712 let reserved = region
1713 .get(4..8)
1714 .and_then(|value| value.try_into().ok())
1715 .map(u32::from_le_bytes)
1716 .ok_or_else(|| {
1717 SegmentError::Geometry("stored-metadata reserved field is truncated".to_owned())
1718 })?;
1719 if declared_rows != self.meta.row_count || reserved != 0 {
1720 return Err(SegmentError::Geometry(format!(
1721 "stored-metadata rows/reserved {declared_rows}/{reserved}, expected {}/0",
1722 self.meta.row_count
1723 )));
1724 }
1725 let row_count = declared_rows as usize;
1726 let offset_bytes = row_count
1727 .checked_add(1)
1728 .and_then(|count| count.checked_mul(8))
1729 .ok_or_else(|| SegmentError::Geometry("stored-metadata offsets overflow".to_owned()))?;
1730 let offsets_end = 8_usize.checked_add(offset_bytes).ok_or_else(|| {
1731 SegmentError::Geometry("stored-metadata offset end overflow".to_owned())
1732 })?;
1733 let offsets = region.get(8..offsets_end).ok_or_else(|| {
1734 SegmentError::Geometry("stored-metadata offsets are truncated".to_owned())
1735 })?;
1736 let bytes = region.get(offsets_end..).ok_or_else(|| {
1737 SegmentError::Geometry("stored-metadata payload is truncated".to_owned())
1738 })?;
1739 let view = StoredMetadataRows {
1740 offsets,
1741 bytes,
1742 row_count,
1743 };
1744 let first = view
1745 .offsets
1746 .get(..8)
1747 .and_then(|value| value.try_into().ok())
1748 .map(u64::from_le_bytes)
1749 .ok_or_else(|| {
1750 SegmentError::Geometry("stored-metadata first offset is missing".to_owned())
1751 })?;
1752 if first != 0 {
1753 return Err(SegmentError::Geometry(format!(
1754 "stored-metadata first offset is {first}, expected zero"
1755 )));
1756 }
1757 let mut previous = 0_usize;
1758 for row in 0..row_count {
1759 let value = view.row(row).ok_or_else(|| {
1760 SegmentError::Geometry(format!("stored-metadata row {row} is invalid"))
1761 })?;
1762 previous = previous.checked_add(value.len()).ok_or_else(|| {
1763 SegmentError::Geometry("stored-metadata length overflow".to_owned())
1764 })?;
1765 }
1766 if previous != bytes.len() {
1767 return Err(SegmentError::Geometry(format!(
1768 "stored-metadata final offset {previous}, bytes {}",
1769 bytes.len()
1770 )));
1771 }
1772 let _ = entry;
1773 Ok(Some(view))
1774 }
1775
1776 pub fn stored_text(&self) -> Result<Option<StoredTextRows<'_>>, SegmentError> {
1780 let Some(_) = self
1781 .entries
1782 .iter()
1783 .find(|entry| entry.kind == RegionKind::StoredText.id())
1784 else {
1785 return Ok(None);
1786 };
1787 let region = self.region(RegionKind::StoredText)?;
1788 let view = stored_text_view(region, self.meta.row_count)?;
1789 let StoredTextRows {
1790 offsets,
1791 bytes,
1792 row_count,
1793 ..
1794 } = view;
1795 let mut previous = 0_usize;
1796 for row in 0..row_count {
1797 let value = view.row(row).ok_or_else(|| {
1798 SegmentError::Geometry(format!("stored-text row {row} is invalid"))
1799 })?;
1800 let end_index = row.checked_mul(8).ok_or_else(|| {
1801 SegmentError::Geometry("stored-text offset index overflow".to_owned())
1802 })?;
1803 let end = offsets
1804 .get(end_index..end_index + 8)
1805 .and_then(|value| value.try_into().ok())
1806 .map(u64::from_le_bytes)
1807 .and_then(|value| usize::try_from(value).ok())
1808 .ok_or_else(|| {
1809 SegmentError::Geometry(format!("stored-text offset row {row} is invalid"))
1810 })?;
1811 if end < previous || end > bytes.len() {
1812 return Err(SegmentError::Geometry(format!(
1813 "stored-text offset row {row} is outside {previous}..={} bytes",
1814 bytes.len()
1815 )));
1816 }
1817 if value.is_none() && end != previous {
1818 return Err(SegmentError::Geometry(format!(
1819 "stored-text absent row {row} owns bytes {previous}..{end}"
1820 )));
1821 }
1822 previous = end;
1823 }
1824 if previous != bytes.len() {
1825 return Err(SegmentError::Geometry(format!(
1826 "stored-text final offset {previous}, bytes {}",
1827 bytes.len()
1828 )));
1829 }
1830 Ok(Some(view))
1831 }
1832
1833 pub(crate) fn query_stored_text(&self) -> Result<Option<StoredTextRows<'_>>, SegmentError> {
1839 if self.stored_text_validated.get().is_some() || !Self::query_checksums_enabled() {
1840 let Some(entry) = self
1841 .entries
1842 .iter()
1843 .find(|entry| entry.kind == RegionKind::StoredText.id())
1844 else {
1845 return Ok(None);
1846 };
1847 let region = self.region_slice(entry)?;
1848 return stored_text_view(region, self.meta.row_count).map(Some);
1849 }
1850 let view = self.stored_text()?;
1851 if view.is_some() {
1852 let _ = self.stored_text_validated.set(());
1854 }
1855 Ok(view)
1856 }
1857
1858 pub(crate) fn query_stored_metadata(
1861 &self,
1862 ) -> Result<Option<StoredMetadataRows<'_>>, SegmentError> {
1863 if self.stored_metadata_validated.get().is_some() || !Self::query_checksums_enabled() {
1864 let Some(entry) = self
1865 .entries
1866 .iter()
1867 .find(|entry| entry.kind == RegionKind::StoredMetadata.id())
1868 else {
1869 return Ok(None);
1870 };
1871 let region = self.region_slice(entry)?;
1872 return stored_metadata_view(region, self.meta.row_count).map(Some);
1873 }
1874 let view = self.stored_metadata()?;
1875 if view.is_some() {
1876 let _ = self.stored_metadata_validated.set(());
1877 }
1878 Ok(view)
1879 }
1880}
1881
1882fn stored_metadata_view(
1883 region: &[u8],
1884 header_rows: u32,
1885) -> Result<StoredMetadataRows<'_>, SegmentError> {
1886 let declared_rows = region
1887 .get(..4)
1888 .and_then(|value| value.try_into().ok())
1889 .map(u32::from_le_bytes)
1890 .ok_or_else(|| SegmentError::Geometry("stored-metadata header is truncated".to_owned()))?;
1891 let reserved = region
1892 .get(4..8)
1893 .and_then(|value| value.try_into().ok())
1894 .map(u32::from_le_bytes)
1895 .ok_or_else(|| {
1896 SegmentError::Geometry("stored-metadata reserved field is truncated".to_owned())
1897 })?;
1898 if declared_rows != header_rows || reserved != 0 {
1899 return Err(SegmentError::Geometry(format!(
1900 "stored-metadata rows/reserved {declared_rows}/{reserved}, expected {header_rows}/0"
1901 )));
1902 }
1903 let row_count = declared_rows as usize;
1904 let offset_bytes = row_count
1905 .checked_add(1)
1906 .and_then(|count| count.checked_mul(8))
1907 .ok_or_else(|| SegmentError::Geometry("stored-metadata offsets overflow".to_owned()))?;
1908 let offsets_end = 8_usize
1909 .checked_add(offset_bytes)
1910 .ok_or_else(|| SegmentError::Geometry("stored-metadata offset end overflow".to_owned()))?;
1911 let offsets = region.get(8..offsets_end).ok_or_else(|| {
1912 SegmentError::Geometry("stored-metadata offsets are truncated".to_owned())
1913 })?;
1914 let bytes = region
1915 .get(offsets_end..)
1916 .ok_or_else(|| SegmentError::Geometry("stored-metadata payload is truncated".to_owned()))?;
1917 Ok(StoredMetadataRows {
1918 offsets,
1919 bytes,
1920 row_count,
1921 })
1922}
1923
1924fn stored_text_view(region: &[u8], header_rows: u32) -> Result<StoredTextRows<'_>, SegmentError> {
1926 {
1927 let declared_rows = region
1928 .get(..4)
1929 .and_then(|value| value.try_into().ok())
1930 .map(u32::from_le_bytes)
1931 .ok_or_else(|| SegmentError::Geometry("stored-text header is truncated".to_owned()))?;
1932 let reserved = region
1933 .get(4..8)
1934 .and_then(|value| value.try_into().ok())
1935 .map(u32::from_le_bytes)
1936 .ok_or_else(|| {
1937 SegmentError::Geometry("stored-text reserved field is truncated".to_owned())
1938 })?;
1939 if declared_rows != header_rows || reserved != 0 {
1940 return Err(SegmentError::Geometry(format!(
1941 "stored-text rows/reserved {declared_rows}/{reserved}, expected {header_rows}/0"
1942 )));
1943 }
1944 let row_count = declared_rows as usize;
1945 let bitmap_len = row_count.div_ceil(8);
1946 let bitmap_end = 8_usize
1947 .checked_add(bitmap_len)
1948 .ok_or_else(|| SegmentError::Geometry("stored-text bitmap overflow".to_owned()))?;
1949 let offsets_len = row_count
1950 .checked_mul(8)
1951 .ok_or_else(|| SegmentError::Geometry("stored-text offsets overflow".to_owned()))?;
1952 let offsets_end = bitmap_end
1953 .checked_add(offsets_len)
1954 .ok_or_else(|| SegmentError::Geometry("stored-text offset end overflow".to_owned()))?;
1955 let present = region.get(8..bitmap_end).ok_or_else(|| {
1956 SegmentError::Geometry("stored-text presence bitmap is truncated".to_owned())
1957 })?;
1958 let offsets = region.get(bitmap_end..offsets_end).ok_or_else(|| {
1959 SegmentError::Geometry("stored-text offsets are truncated".to_owned())
1960 })?;
1961 let bytes = region
1962 .get(offsets_end..)
1963 .ok_or_else(|| SegmentError::Geometry("stored-text payload is truncated".to_owned()))?;
1964 if !row_count.is_multiple_of(8) {
1965 let used = row_count % 8;
1966 let padding_mask = !((1_u8 << used) - 1);
1967 if present.last().is_some_and(|byte| byte & padding_mask != 0) {
1968 return Err(SegmentError::Geometry(
1969 "stored-text presence padding bits are nonzero".to_owned(),
1970 ));
1971 }
1972 }
1973 Ok(StoredTextRows {
1974 present,
1975 offsets,
1976 bytes,
1977 row_count,
1978 })
1979 }
1980}
1981
1982impl SegmentReader {
1983 pub fn graph_node_blocks(&self) -> Result<GraphNodeBlocks<'_>, SegmentError> {
1985 let blocks = decode_node_blocks(self.region(RegionKind::GraphNodeBlocks)?)?;
1986 if blocks.layout().dims() != self.meta.dims {
1987 return Err(SegmentError::Geometry(format!(
1988 "graph dimensions {}, segment dimensions {}",
1989 blocks.layout().dims(),
1990 self.meta.dims
1991 )));
1992 }
1993 if blocks.node_count() != self.meta.row_count {
1994 return Err(SegmentError::Geometry(format!(
1995 "graph nodes {}, segment rows {}",
1996 blocks.node_count(),
1997 self.meta.row_count
1998 )));
1999 }
2000 Ok(blocks)
2001 }
2002
2003 pub(crate) fn bind_validated_graph_node_blocks(
2004 &self,
2005 descriptor: ValidatedGraphNodeBlocks,
2006 ) -> Result<GraphNodeBlocks<'_>, SegmentError> {
2007 let entry = self.entry(RegionKind::GraphNodeBlocks)?;
2008 let bytes = self.region_slice(entry)?;
2009 descriptor.bind(bytes).map_err(SegmentError::Graph)
2010 }
2011
2012 pub fn validate_all(&self) -> Result<(), SegmentError> {
2014 for entry in &self.entries {
2015 let bytes = self.region_slice(entry)?;
2016 let actual = xxh3_64(bytes);
2017 if actual != entry.checksum {
2018 return Err(FormatError::new(
2019 format!("segment:{}:region-{}", self.meta.id, entry.kind),
2020 FormatCheck::BlockChecksum,
2021 format!("expected {:#018x}, computed {actual:#018x}", entry.checksum),
2022 )
2023 .into());
2024 }
2025 }
2026 let bytes = self.mapping.as_bytes();
2027 let trailer_start = bytes.len().checked_sub(FILE_TRAILER_LEN).ok_or_else(|| {
2028 FormatError::new(
2029 format!("segment: {}", self.meta.id),
2030 FormatCheck::Length,
2031 "missing file trailer",
2032 )
2033 })?;
2034 let expected = read_u64("segment", bytes, trailer_start)?;
2035 let checksummed = bytes.get(..trailer_start).ok_or_else(|| {
2036 FormatError::new("segment", FormatCheck::Length, "invalid trailer position")
2037 })?;
2038 let actual = xxh3_64(checksummed);
2039 if actual != expected {
2040 return Err(FormatError::new(
2041 format!("segment: {}", self.meta.id),
2042 FormatCheck::FileChecksum,
2043 format!("expected {expected:#018x}, computed {actual:#018x}"),
2044 )
2045 .into());
2046 }
2047 Ok(())
2048 }
2049
2050 fn vector_payload(
2051 &self,
2052 kind: RegionKind,
2053 ) -> Result<(super::layout::VectorHeader, &[u8]), SegmentError> {
2054 let region = self.region(kind)?;
2055 let header_bytes = region.get(..VECTOR_HEADER_LEN).ok_or_else(|| {
2056 SegmentError::Geometry(format!("{kind:?} is shorter than vector header"))
2057 })?;
2058 let header = decode_vector_header(header_bytes)?;
2059 if header.dims != self.meta.dims
2060 || header.row_count != self.meta.row_count
2061 || (kind != RegionKind::VectorRescore && header.scheme != self.meta.scheme)
2062 {
2063 return Err(SegmentError::Geometry(format!(
2064 "{kind:?} header scheme/dims/rows {}/{}/{}, segment {}/{}/{}",
2065 header.scheme,
2066 header.dims,
2067 header.row_count,
2068 self.meta.scheme,
2069 self.meta.dims,
2070 self.meta.row_count
2071 )));
2072 }
2073 let payload = region
2074 .get(VECTOR_HEADER_LEN..)
2075 .ok_or_else(|| SegmentError::Geometry(format!("{kind:?} payload offset is invalid")))?;
2076 Ok((header, payload))
2077 }
2078
2079 fn vector_payload_unchecked(
2080 &self,
2081 kind: RegionKind,
2082 ) -> Result<(super::layout::VectorHeader, &[u8]), SegmentError> {
2083 let entry = self.entry(kind)?;
2084 let region = self.region_slice_unaccounted(entry)?;
2085 let header_bytes = region.get(..VECTOR_HEADER_LEN).ok_or_else(|| {
2086 SegmentError::Geometry(format!("{kind:?} is shorter than vector header"))
2087 })?;
2088 let header = decode_vector_header(header_bytes)?;
2089 if header.dims != self.meta.dims
2090 || header.row_count != self.meta.row_count
2091 || (kind != RegionKind::VectorRescore && header.scheme != self.meta.scheme)
2092 {
2093 return Err(SegmentError::Geometry(format!(
2094 "{kind:?} header scheme/dims/rows {}/{}/{}, segment {}/{}/{}",
2095 header.scheme,
2096 header.dims,
2097 header.row_count,
2098 self.meta.scheme,
2099 self.meta.dims,
2100 self.meta.row_count
2101 )));
2102 }
2103 let payload = region
2104 .get(VECTOR_HEADER_LEN..)
2105 .ok_or_else(|| SegmentError::Geometry(format!("{kind:?} payload offset is invalid")))?;
2106 Ok((header, payload))
2107 }
2108
2109 fn validate_rescore_byte_range(&self, start: usize, length: usize) -> Result<(), SegmentError> {
2110 let entry = self.entry(RegionKind::VectorRescore)?;
2111 let region_length = usize::try_from(entry.length).map_err(|_| {
2112 SegmentError::Geometry("rescore region length exceeds usize".to_owned())
2113 })?;
2114 let end = start
2115 .checked_add(length)
2116 .ok_or_else(|| SegmentError::Geometry("rescore byte range overflow".to_owned()))?;
2117 if end > region_length {
2118 return Err(SegmentError::Geometry(format!(
2119 "rescore byte range {start}..{end} exceeds {region_length}"
2120 )));
2121 }
2122 if length == 0 || !Self::query_checksums_enabled() {
2123 return Ok(());
2124 }
2125 let first_chunk = start / CHECKSUM_CHUNK_BYTES;
2126 let last_chunk = end.saturating_sub(1) / CHECKSUM_CHUNK_BYTES;
2127 let mut validated = self.rescore_valid_chunks.lock().map_err(|_| {
2128 SegmentError::Geometry("rescore validation state is poisoned".to_owned())
2129 })?;
2130 for chunk in first_chunk..=last_chunk {
2131 let word = chunk / u64::BITS as usize;
2132 let bit = chunk % u64::BITS as usize;
2133 let mask = 1_u64 << bit;
2134 let state = validated.get_mut(word).ok_or_else(|| {
2135 SegmentError::Geometry(format!("rescore checksum chunk {chunk} is untracked"))
2136 })?;
2137 if *state & mask != 0 {
2138 continue;
2139 }
2140 let chunk_index = u32::try_from(chunk).map_err(|_| {
2141 SegmentError::Geometry("rescore checksum chunk exceeds u32".to_owned())
2142 })?;
2143 self.region_chunk(RegionKind::VectorRescore, chunk_index)?;
2144 *state |= mask;
2145 }
2146 Ok(())
2147 }
2148
2149 fn account_query_cache(
2150 &self,
2151 bytes: usize,
2152 ) -> Result<Option<crate::lifecycle::stats::AccountedCounter>, crate::lifecycle::StoreError>
2153 {
2154 let Some(accounting) = &self.query_accounting else {
2155 return Ok(None);
2156 };
2157 let mut counter = crate::lifecycle::stats::AccountedCounter::new(
2158 accounting,
2159 crate::lifecycle::stats::AllocationComponent::Snapshot,
2160 )?;
2161 counter.set(bytes)?;
2162 Ok(Some(counter))
2163 }
2164
2165 fn entry(&self, kind: RegionKind) -> Result<&RegionEntry, SegmentError> {
2166 self.entries
2167 .iter()
2168 .find(|entry| entry.kind == kind.id())
2169 .ok_or(SegmentError::MissingRegion(kind))
2170 }
2171
2172 fn region_slice(&self, entry: &RegionEntry) -> Result<&[u8], SegmentError> {
2173 let bytes = self.region_slice_unaccounted(entry)?;
2174 account_data_read(bytes.len());
2175 Ok(bytes)
2176 }
2177
2178 fn region_slice_unaccounted(&self, entry: &RegionEntry) -> Result<&[u8], SegmentError> {
2179 let start = usize::try_from(entry.offset)
2180 .map_err(|_| SegmentError::Geometry("region offset exceeds usize".to_owned()))?;
2181 let length = usize::try_from(entry.length)
2182 .map_err(|_| SegmentError::Geometry("region length exceeds usize".to_owned()))?;
2183 let end = start
2184 .checked_add(length)
2185 .ok_or_else(|| SegmentError::Geometry("region end overflow".to_owned()))?;
2186 let bytes = self.mapping.as_bytes().get(start..end).ok_or_else(|| {
2187 SegmentError::Geometry(format!(
2188 "region {} range {start}..{end} exceeds file {}",
2189 entry.kind,
2190 self.mapping.length()
2191 ))
2192 })?;
2193 Ok(bytes)
2194 }
2195
2196 fn chunk_checksum(&self, kind: RegionKind, chunk_index: u32) -> Result<u64, SegmentError> {
2197 let table = self.region(RegionKind::ChecksumTable)?;
2198 let mut cursor = Cursor::new("checksum-table", table);
2199 let count = cursor.u32()?;
2200 let chunk_size = cursor.u32()?;
2201 if chunk_size as usize != CHECKSUM_CHUNK_BYTES {
2202 return Err(SegmentError::Geometry(format!(
2203 "checksum chunk size {chunk_size}, expected {CHECKSUM_CHUNK_BYTES}"
2204 )));
2205 }
2206 let mut found = None;
2207 for _ in 0..count {
2208 let entry_kind = cursor.u16()?;
2209 let reserved = cursor.u16()?;
2210 let entry_chunk = cursor.u32()?;
2211 let checksum = cursor.u64()?;
2212 if reserved != 0 {
2213 return Err(SegmentError::Geometry(
2214 "checksum-table reserved field is non-zero".to_owned(),
2215 ));
2216 }
2217 if entry_kind == kind.id() && entry_chunk == chunk_index {
2218 found = Some(checksum);
2219 }
2220 }
2221 cursor.finish().map_err(SegmentError::Geometry)?;
2222 found.ok_or_else(|| {
2223 SegmentError::Geometry(format!(
2224 "checksum table has no {kind:?} chunk {chunk_index}"
2225 ))
2226 })
2227 }
2228}
2229
2230const DOCUMENT_VERSION_BYTES: usize = 24;
2233
2234fn encode_document_version(version: DocumentVersion) -> [u8; DOCUMENT_VERSION_BYTES] {
2235 let doc_id = version.doc_id().get().to_le_bytes();
2236 let revision = version.revision().get().to_le_bytes();
2237 let mut bytes = [0_u8; DOCUMENT_VERSION_BYTES];
2238 for (slot, byte) in bytes.iter_mut().zip(doc_id.into_iter().chain(revision)) {
2239 *slot = byte;
2240 }
2241 bytes
2242}
2243
2244fn document_version_entry(bytes: &[u8], row: u32) -> &[u8] {
2248 (row as usize)
2249 .checked_mul(DOCUMENT_VERSION_BYTES)
2250 .and_then(|start| bytes.get(start..start.checked_add(DOCUMENT_VERSION_BYTES)?))
2251 .unwrap_or(&[])
2252}
2253
2254fn document_id_entry(bytes: &[u8], row: u32) -> &[u8] {
2255 document_version_entry(bytes, row)
2256 .get(..std::mem::size_of::<u128>())
2257 .unwrap_or(&[])
2258}
2259
2260fn arc_resident_bytes<T>(deep_bytes: usize) -> Option<usize> {
2261 deep_bytes
2262 .checked_add(std::mem::size_of::<T>())?
2263 .checked_add(2 * std::mem::size_of::<usize>())
2264}
2265
2266fn rescore_validation_words(entries: &[RegionEntry]) -> Result<usize, SegmentError> {
2267 let Some(entry) = entries
2268 .iter()
2269 .find(|entry| entry.kind == RegionKind::VectorRescore.id())
2270 else {
2271 return Ok(0);
2272 };
2273 let length = usize::try_from(entry.length)
2274 .map_err(|_| SegmentError::Geometry("rescore region length exceeds usize".to_owned()))?;
2275 let chunks = length.div_ceil(CHECKSUM_CHUNK_BYTES);
2276 Ok(chunks.div_ceil(u64::BITS as usize))
2277}
2278
2279pub fn validate_header_with_vfs(
2281 vfs: &dyn Vfs,
2282 path: &Path,
2283 expected: &SegmentMeta,
2284) -> Result<usize, SegmentError> {
2285 let actual_length = vfs
2286 .open(path)
2287 .map_err(|error| SegmentError::io(path, error))?;
2288 let mut header_bytes = vfs
2289 .read_range(path, 0, FILE_HEADER_LEN)
2290 .map_err(|error| SegmentError::io(path, error))?;
2291 let artifact = path.display().to_string();
2292 #[cfg(any(test, feature = "test-support"))]
2293 let actual_family = header_bytes
2294 .get(8..10)
2295 .and_then(|family| <[u8; 2]>::try_from(family).ok())
2296 .map(u16::from_le_bytes);
2297 let fixed =
2298 decode_header(&artifact, FormatFamily::Segment, &header_bytes).map_err(|error| {
2299 #[cfg(any(test, feature = "test-support"))]
2300 crate::lifecycle::record_storage_segment_format_fault(
2301 &error,
2302 actual_family,
2303 expected.id,
2304 );
2305 SegmentError::from(error)
2306 })?;
2307 let header_length = usize::try_from(fixed.header_length)
2308 .map_err(|_| SegmentError::Geometry("segment header length exceeds usize".to_owned()))?;
2309 validate_bounded_header_length(&artifact, header_length, actual_length)?;
2310 let remaining = header_length.saturating_sub(FILE_HEADER_LEN);
2311 if remaining > 0 {
2312 let tail = vfs
2313 .read_range(path, FILE_HEADER_LEN as u64, remaining)
2314 .map_err(|error| SegmentError::io(path, error))?;
2315 header_bytes.extend_from_slice(&tail);
2316 }
2317 let parsed = parse_segment_header(&artifact, &header_bytes, actual_length, expected.id)?;
2318 if !parsed.meta.same_segment_file(expected) {
2319 return Err(SegmentError::Geometry(format!(
2320 "manifest metadata {expected:?}, header metadata {:?}",
2321 parsed.meta
2322 )));
2323 }
2324 Ok(header_length)
2325}
2326
2327pub fn validate_segment_bytes(bytes: &[u8]) -> Result<SegmentMeta, SegmentError> {
2329 let id_bytes: [u8; 16] = bytes
2330 .get(FILE_HEADER_LEN..FILE_HEADER_LEN.saturating_add(16))
2331 .ok_or_else(|| {
2332 FormatError::new(
2333 "segment-bytes",
2334 FormatCheck::Length,
2335 format!("missing segment id in {} bytes", bytes.len()),
2336 )
2337 })?
2338 .try_into()
2339 .map_err(|_| {
2340 FormatError::new("segment-bytes", FormatCheck::Length, "invalid segment id")
2341 })?;
2342 let id = SegmentId::from_bytes(id_bytes);
2343 let actual_length = u64::try_from(bytes.len())
2344 .map_err(|_| SegmentError::Geometry("byte slice length exceeds u64".to_owned()))?;
2345 let parsed = parse_segment_header("segment-bytes", bytes, actual_length, id)?;
2346 for entry in &parsed.entries {
2347 let start = usize::try_from(entry.offset)
2348 .map_err(|_| SegmentError::Geometry("region offset exceeds usize".to_owned()))?;
2349 let length = usize::try_from(entry.length)
2350 .map_err(|_| SegmentError::Geometry("region length exceeds usize".to_owned()))?;
2351 let end = start
2352 .checked_add(length)
2353 .ok_or_else(|| SegmentError::Geometry("region end overflow".to_owned()))?;
2354 let region = bytes.get(start..end).ok_or_else(|| {
2355 FormatError::new(
2356 "segment-bytes",
2357 FormatCheck::BlockLength,
2358 format!("region {} range {start}..{end} is truncated", entry.kind),
2359 )
2360 })?;
2361 let actual = xxh3_64(region);
2362 if actual != entry.checksum {
2363 return Err(FormatError::new(
2364 format!("segment-bytes:region-{}", entry.kind),
2365 FormatCheck::BlockChecksum,
2366 format!("expected {:#018x}, computed {actual:#018x}", entry.checksum),
2367 )
2368 .into());
2369 }
2370 }
2371 let trailer = bytes.len().checked_sub(FILE_TRAILER_LEN).ok_or_else(|| {
2372 FormatError::new("segment-bytes", FormatCheck::Length, "missing file trailer")
2373 })?;
2374 let expected = read_u64("segment-bytes", bytes, trailer)?;
2375 let checksummed = bytes.get(..trailer).ok_or_else(|| {
2376 FormatError::new(
2377 "segment-bytes",
2378 FormatCheck::Length,
2379 "invalid trailer offset",
2380 )
2381 })?;
2382 let actual = xxh3_64(checksummed);
2383 if actual != expected {
2384 return Err(FormatError::new(
2385 "segment-bytes",
2386 FormatCheck::FileChecksum,
2387 format!("expected {expected:#018x}, computed {actual:#018x}"),
2388 )
2389 .into());
2390 }
2391 Ok(parsed.meta)
2392}
2393
2394fn preflight_region_count(
2395 artifact: &str,
2396 bytes: &[u8],
2397 actual_file_length: u64,
2398) -> Result<usize, SegmentError> {
2399 let fixed = decode_header(artifact, FormatFamily::Segment, bytes)?;
2400 if fixed.file_length != actual_file_length {
2401 return Err(FormatError::new(
2402 artifact,
2403 FormatCheck::FileLength,
2404 format!(
2405 "declared {}, actual {actual_file_length}",
2406 fixed.file_length
2407 ),
2408 )
2409 .into());
2410 }
2411 let header_length = usize::try_from(fixed.header_length)
2412 .map_err(|_| SegmentError::Geometry("header length exceeds usize".to_owned()))?;
2413 validate_bounded_header_length(artifact, header_length, actual_file_length)?;
2414 let header_bytes = bytes.get(..header_length).ok_or_else(|| {
2415 FormatError::new(
2416 artifact,
2417 FormatCheck::Length,
2418 format!("declared header {header_length}, available {}", bytes.len()),
2419 )
2420 })?;
2421 Ok(usize::from(read_u16(
2422 artifact,
2423 header_bytes,
2424 FILE_HEADER_LEN.saturating_add(20),
2425 )?))
2426}
2427
2428fn parse_segment_header(
2429 artifact: &str,
2430 bytes: &[u8],
2431 actual_file_length: u64,
2432 expected_id: SegmentId,
2433) -> Result<ParsedHeader, SegmentError> {
2434 #[cfg(any(test, feature = "test-support"))]
2435 let actual_family = bytes
2436 .get(8..10)
2437 .and_then(|family| <[u8; 2]>::try_from(family).ok())
2438 .map(u16::from_le_bytes);
2439 let fixed = decode_header(artifact, FormatFamily::Segment, bytes).map_err(|error| {
2440 #[cfg(any(test, feature = "test-support"))]
2441 crate::lifecycle::record_storage_segment_format_fault(&error, actual_family, expected_id);
2442 SegmentError::from(error)
2443 })?;
2444 if fixed.file_length != actual_file_length {
2445 return Err(FormatError::new(
2446 artifact,
2447 FormatCheck::FileLength,
2448 format!(
2449 "declared {}, actual {actual_file_length}",
2450 fixed.file_length
2451 ),
2452 )
2453 .into());
2454 }
2455 let header_length = usize::try_from(fixed.header_length)
2456 .map_err(|_| SegmentError::Geometry("header length exceeds usize".to_owned()))?;
2457 validate_bounded_header_length(artifact, header_length, actual_file_length)?;
2458 let header_bytes = bytes.get(..header_length).ok_or_else(|| {
2459 FormatError::new(
2460 artifact,
2461 FormatCheck::Length,
2462 format!("declared header {header_length}, available {}", bytes.len()),
2463 )
2464 })?;
2465 let id_bytes: [u8; 16] = header_bytes
2466 .get(FILE_HEADER_LEN..FILE_HEADER_LEN.saturating_add(16))
2467 .ok_or_else(|| FormatError::new(artifact, FormatCheck::Length, "missing segment id"))?
2468 .try_into()
2469 .map_err(|_| FormatError::new(artifact, FormatCheck::Length, "invalid segment id"))?;
2470 let actual_id = SegmentId::from_bytes(id_bytes);
2471 if actual_id != expected_id {
2472 #[cfg(any(test, feature = "test-support"))]
2473 crate::lifecycle::record_storage_segment_identity_fault(artifact, expected_id, actual_id);
2474 return Err(SegmentError::WrongObject {
2475 artifact: artifact.to_owned(),
2476 expected: expected_id,
2477 actual: actual_id,
2478 });
2479 }
2480 let row_count = read_u32(artifact, header_bytes, FILE_HEADER_LEN.saturating_add(16))?;
2481 let region_count = usize::from(read_u16(
2482 artifact,
2483 header_bytes,
2484 FILE_HEADER_LEN.saturating_add(20),
2485 )?);
2486 if read_u16(artifact, header_bytes, FILE_HEADER_LEN.saturating_add(22))? != 0 {
2487 return Err(SegmentError::Geometry(
2488 "segment prefix reserved field is non-zero".to_owned(),
2489 ));
2490 }
2491 let scheme = read_u16(artifact, header_bytes, FILE_HEADER_LEN.saturating_add(24))?;
2492 FormatRegistry::require_scheme(scheme)
2493 .map_err(|error| SegmentError::Geometry(error.to_string()))?;
2494 if read_u16(artifact, header_bytes, FILE_HEADER_LEN.saturating_add(26))? != 0 {
2495 return Err(SegmentError::Geometry(
2496 "segment scheme padding is non-zero".to_owned(),
2497 ));
2498 }
2499 let dims = read_u32(artifact, header_bytes, FILE_HEADER_LEN.saturating_add(28))?;
2500 let expected_header_length = FILE_HEADER_LEN
2501 .checked_add(SEGMENT_PREFIX_LEN)
2502 .and_then(|value| value.checked_add(region_count.saturating_mul(REGION_ENTRY_LEN)))
2503 .and_then(|value| value.checked_add(8))
2504 .ok_or_else(|| SegmentError::Geometry("header length overflow".to_owned()))?;
2505 if header_length != expected_header_length {
2506 return Err(FormatError::new(
2507 artifact,
2508 FormatCheck::HeaderLength,
2509 format!("declared {header_length}, directory implies {expected_header_length}"),
2510 )
2511 .into());
2512 }
2513 let checksum_offset = header_length.saturating_sub(8);
2514 let expected_checksum = read_u64(artifact, header_bytes, checksum_offset)?;
2515 let checksum_bytes = header_bytes.get(..checksum_offset).ok_or_else(|| {
2516 FormatError::new(
2517 artifact,
2518 FormatCheck::Length,
2519 "invalid header checksum offset",
2520 )
2521 })?;
2522 let actual_checksum = xxh3_64(checksum_bytes);
2523 if actual_checksum != expected_checksum {
2524 return Err(FormatError::new(
2525 artifact,
2526 FormatCheck::BlockChecksum,
2527 format!("header expected {expected_checksum:#018x}, computed {actual_checksum:#018x}"),
2528 )
2529 .into());
2530 }
2531
2532 let mut entries: Vec<RegionEntry> = Vec::with_capacity(region_count);
2533 let directory_start = FILE_HEADER_LEN.saturating_add(SEGMENT_PREFIX_LEN);
2534 for position in 0..region_count {
2535 let offset = directory_start
2536 .checked_add(position.saturating_mul(REGION_ENTRY_LEN))
2537 .ok_or_else(|| SegmentError::Geometry("directory offset overflow".to_owned()))?;
2538 let entry = RegionEntry {
2539 kind: read_u16(artifact, header_bytes, offset)?,
2540 version: read_u16(artifact, header_bytes, offset.saturating_add(2))?,
2541 reserved: read_u32(artifact, header_bytes, offset.saturating_add(4))?,
2542 offset: read_u64(artifact, header_bytes, offset.saturating_add(8))?,
2543 length: read_u64(artifact, header_bytes, offset.saturating_add(16))?,
2544 checksum: read_u64(artifact, header_bytes, offset.saturating_add(24))?,
2545 };
2546 if entry.reserved != 0 {
2547 return Err(SegmentError::Geometry(format!(
2548 "region {} reserved field is non-zero",
2549 entry.kind
2550 )));
2551 }
2552 if entries.iter().any(|existing| existing.kind == entry.kind) {
2553 return Err(SegmentError::Geometry(format!(
2554 "duplicate region kind {}",
2555 entry.kind
2556 )));
2557 }
2558 validate_entry(artifact, &entry, header_length, actual_file_length)?;
2559 if let Some(kind) = RegionKind::from_id(entry.kind)
2560 && let Some(family) = kind.family()
2561 {
2562 FormatRegistry::require(family.id(), entry.version).map_err(|error| {
2563 FormatError::new(artifact, FormatCheck::Version, error.to_string())
2564 })?;
2565 }
2566 entries.push(entry);
2567 }
2568 validate_non_overlapping(&entries)?;
2569 Ok(ParsedHeader {
2570 meta: SegmentMeta {
2571 id: actual_id,
2572 row_count,
2573 scheme,
2574 dims,
2575 file_size: actual_file_length,
2576 epoch_id: None,
2577 clustering_key_range: super::ClusteringKeyRange::Unstamped,
2578 },
2579 header_length,
2580 entries,
2581 })
2582}
2583
2584fn validate_bounded_header_length(
2585 artifact: &str,
2586 header_length: usize,
2587 actual_file_length: u64,
2588) -> Result<(), SegmentError> {
2589 if !(FILE_HEADER_LEN + SEGMENT_PREFIX_LEN + 8..=MAX_SEGMENT_HEADER_BYTES)
2590 .contains(&header_length)
2591 {
2592 return Err(FormatError::new(
2593 artifact,
2594 FormatCheck::HeaderLength,
2595 format!("header length {header_length} is outside bounded v1 range"),
2596 )
2597 .into());
2598 }
2599 let file_length = usize::try_from(actual_file_length)
2600 .map_err(|_| SegmentError::Geometry("file length exceeds usize".to_owned()))?;
2601 if header_length > file_length.saturating_sub(FILE_TRAILER_LEN) {
2602 return Err(FormatError::new(
2603 artifact,
2604 FormatCheck::HeaderLength,
2605 format!("header {header_length} exceeds file {file_length}"),
2606 )
2607 .into());
2608 }
2609 Ok(())
2610}
2611
2612fn validate_entry(
2613 artifact: &str,
2614 entry: &RegionEntry,
2615 header_length: usize,
2616 file_length: u64,
2617) -> Result<(), SegmentError> {
2618 let offset = usize::try_from(entry.offset)
2619 .map_err(|_| SegmentError::Geometry("region offset exceeds usize".to_owned()))?;
2620 let length = usize::try_from(entry.length)
2621 .map_err(|_| SegmentError::Geometry("region length exceeds usize".to_owned()))?;
2622 let minimum = align_up(header_length, REGION_ALIGNMENT)?;
2623 if offset < minimum || !offset.is_multiple_of(REGION_ALIGNMENT) {
2624 return Err(SegmentError::Geometry(format!(
2625 "region {} offset {offset} is below/aligned from {minimum}",
2626 entry.kind
2627 )));
2628 }
2629 let end = offset
2630 .checked_add(length)
2631 .ok_or_else(|| SegmentError::Geometry("region end overflow".to_owned()))?;
2632 let maximum = usize::try_from(file_length)
2633 .map_err(|_| SegmentError::Geometry("file length exceeds usize".to_owned()))?
2634 .checked_sub(FILE_TRAILER_LEN)
2635 .ok_or_else(|| SegmentError::Geometry("file is shorter than trailer".to_owned()))?;
2636 if end > maximum {
2637 return Err(FormatError::new(
2638 artifact,
2639 FormatCheck::BlockLength,
2640 format!("region {} ends at {end}, maximum {maximum}", entry.kind),
2641 )
2642 .into());
2643 }
2644 Ok(())
2645}
2646
2647fn validate_non_overlapping(entries: &[RegionEntry]) -> Result<(), SegmentError> {
2648 for (position, entry) in entries.iter().enumerate() {
2649 let entry_end = entry
2650 .offset
2651 .checked_add(entry.length)
2652 .ok_or_else(|| SegmentError::Geometry("region u64 end overflow".to_owned()))?;
2653 for other in entries.iter().skip(position.saturating_add(1)) {
2654 let other_end = other
2655 .offset
2656 .checked_add(other.length)
2657 .ok_or_else(|| SegmentError::Geometry("region u64 end overflow".to_owned()))?;
2658 if entry.offset < other_end && other.offset < entry_end {
2659 let (previous, current, previous_end) = if entry.offset <= other.offset {
2660 (entry, other, entry_end)
2661 } else {
2662 (other, entry, other_end)
2663 };
2664 return Err(SegmentError::Geometry(format!(
2665 "region {} begins {} before previous end {previous_end} for region {}",
2666 current.kind, current.offset, previous.kind
2667 )));
2668 }
2669 }
2670 }
2671 Ok(())
2672}
2673
2674fn cast_slice<'a, T>(
2675 bytes: &'a [u8],
2676 count: usize,
2677 artifact: &str,
2678) -> Result<&'a [T], SegmentError> {
2679 #[cfg(target_endian = "big")]
2680 {
2681 let _ = (bytes, count);
2682 return Err(SegmentError::Geometry(format!(
2683 "{artifact} zero-copy requires little-endian target"
2684 )));
2685 }
2686 #[cfg(target_endian = "little")]
2687 {
2688 let expected = count
2689 .checked_mul(std::mem::size_of::<T>())
2690 .ok_or_else(|| SegmentError::Geometry(format!("{artifact} byte length overflow")))?;
2691 if bytes.len() != expected {
2692 return Err(SegmentError::Geometry(format!(
2693 "{artifact} bytes {}, expected {expected}",
2694 bytes.len()
2695 )));
2696 }
2697 if !(bytes.as_ptr() as usize).is_multiple_of(std::mem::align_of::<T>()) {
2698 return Err(SegmentError::Geometry(format!(
2699 "{artifact} address is not {}-byte aligned",
2700 std::mem::align_of::<T>()
2701 )));
2702 }
2703 Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast::<T>(), count) })
2707 }
2708}