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