objects/store/pack/
pack_index.rs1use crate::store::{Result, pack::PackObjectId};
5
6pub(super) const INDEX_MAGIC: &[u8] = b"LMI\0";
7pub(super) const INDEX_VERSION: u32 = 2;
8const MIN_INDEX_ENTRY_LEN: usize = 17 + 8;
9
10#[derive(Debug, Clone, Copy)]
12pub struct IndexEntry {
13 pub id: PackObjectId,
14 pub offset: u64,
15}
16
17#[derive(Debug)]
19pub struct PackIndex {
20 entries: Vec<IndexEntry>,
21}
22
23impl PackIndex {
24 pub fn new() -> Self {
26 Self {
27 entries: Vec::new(),
28 }
29 }
30
31 pub fn add(&mut self, id: PackObjectId, offset: u64) {
33 self.entries.push(IndexEntry { id, offset });
34 }
35
36 pub fn sort(&mut self) {
38 self.entries.sort_by_key(|e| e.id);
39 }
40
41 pub fn find(&self, id: &PackObjectId) -> Option<u64> {
43 self.entries
44 .binary_search_by_key(id, |e| e.id)
45 .ok()
46 .map(|idx| self.entries[idx].offset)
47 }
48
49 pub fn to_bytes(&self) -> Vec<u8> {
51 let mut result = Vec::new();
52 result.extend_from_slice(INDEX_MAGIC);
53 result.extend_from_slice(&INDEX_VERSION.to_be_bytes());
54 result.extend_from_slice(&(self.entries.len() as u64).to_be_bytes());
55 for entry in &self.entries {
56 entry.id.encode_tagged(&mut result);
57 result.extend_from_slice(&entry.offset.to_be_bytes());
58 }
59 result
60 }
61
62 pub fn from_bytes(data: &[u8]) -> Result<Self> {
64 if data.len() < 16 {
65 return Err(crate::store::StoreError::InvalidObject(
66 "Index too short".to_string(),
67 ));
68 }
69 if &data[0..4] != INDEX_MAGIC {
70 return Err(crate::store::StoreError::InvalidObject(
71 "Invalid index magic".to_string(),
72 ));
73 }
74 let version = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
75 if version != INDEX_VERSION {
76 return Err(crate::store::StoreError::InvalidObject(format!(
77 "Unsupported index version: {}",
78 version
79 )));
80 }
81 let count = u64::from_be_bytes([
82 data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
83 ]);
84 let max_entries = ((data.len() - 16) / MIN_INDEX_ENTRY_LEN) as u64;
85 if count > max_entries {
86 return Err(crate::store::StoreError::InvalidObject(format!(
87 "Index entry count {} exceeds available data capacity {}",
88 count, max_entries
89 )));
90 }
91 let count = usize::try_from(count).map_err(|_| {
92 crate::store::StoreError::InvalidObject(
93 "Index entry count exceeds platform limits".to_string(),
94 )
95 })?;
96 let mut entries = Vec::with_capacity(count);
97 let mut pos = 16;
98 for _ in 0..count {
99 let (id, id_len) = PackObjectId::decode_tagged(&data[pos..])?;
100 pos += id_len;
101 if pos + 8 > data.len() {
102 return Err(crate::store::StoreError::InvalidObject(
103 "Index data truncated".to_string(),
104 ));
105 }
106 let offset = u64::from_be_bytes(data[pos..pos + 8].try_into().map_err(|_| {
107 crate::store::StoreError::InvalidObject("Invalid offset length".to_string())
108 })?);
109 entries.push(IndexEntry { id, offset });
110 pos += 8;
111 }
112 Ok(Self { entries })
113 }
114}
115
116impl PackIndex {
117 pub fn ids(&self) -> Vec<PackObjectId> {
119 self.entries.iter().map(|e| e.id).collect()
120 }
121}
122
123impl Default for PackIndex {
124 fn default() -> Self {
125 Self::new()
126 }
127}