casc_storage/index/
group_index.rs

1//! Parser for .index group index files
2
3use crate::error::{CascError, Result};
4use crate::types::{ArchiveLocation, EKey, IndexEntry};
5use byteorder::{LittleEndian, ReadBytesExt};
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::{BufReader, Read};
9use std::path::Path;
10use tracing::{debug, trace};
11
12/// Header for group index files
13#[derive(Debug)]
14struct GroupIndexHeader {
15    version: u16,
16    bucket_index: u8,
17    #[allow(dead_code)]
18    extra_bytes: u8,
19    span_size_bytes: u8,
20    span_offset_bytes: u8,
21    ekey_bytes: u8,
22    archive_bytes: u8,
23    archive_total_size: u64,
24}
25
26/// Parser for .index group index files
27pub struct GroupIndex {
28    entries: HashMap<EKey, ArchiveLocation>,
29    header: GroupIndexHeader,
30}
31
32impl GroupIndex {
33    /// Parse a group index file from disk
34    pub fn parse_file(path: &Path) -> Result<Self> {
35        let file = File::open(path)?;
36        let mut reader = BufReader::new(file);
37        Self::parse(&mut reader)
38    }
39
40    /// Parse a group index file from a reader
41    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
42        // Read header
43        let header = Self::read_header(reader)?;
44
45        debug!(
46            "Parsing group index: version={}, bucket={:02x}, ekey_size={}",
47            header.version, header.bucket_index, header.ekey_bytes
48        );
49
50        // Calculate entry size
51        let entry_size = (header.ekey_bytes as usize)
52            + (header.archive_bytes as usize)
53            + (header.span_offset_bytes as usize)
54            + (header.span_size_bytes as usize);
55
56        // Read all remaining data
57        let mut data = Vec::new();
58        reader.read_to_end(&mut data)?;
59
60        // Parse entries
61        let entry_count = data.len() / entry_size;
62        debug!(
63            "Parsing {} entries of {} bytes each",
64            entry_count, entry_size
65        );
66
67        let mut entries = HashMap::new();
68        let mut offset = 0;
69
70        for i in 0..entry_count {
71            let entry = Self::parse_entry(&data[offset..], &header)?;
72            offset += entry_size;
73
74            if i < 5 {
75                trace!(
76                    "Entry {}: ekey={}, archive={}, offset={:x}, size={}",
77                    i,
78                    entry.ekey,
79                    entry.location.archive_id,
80                    entry.location.offset,
81                    entry.location.size
82                );
83            }
84
85            entries.insert(entry.ekey, entry.location);
86        }
87
88        debug!("Parsed {} entries", entries.len());
89
90        Ok(Self { entries, header })
91    }
92
93    fn read_header<R: Read>(reader: &mut R) -> Result<GroupIndexHeader> {
94        Ok(GroupIndexHeader {
95            version: reader.read_u16::<LittleEndian>()?,
96            bucket_index: reader.read_u8()?,
97            extra_bytes: reader.read_u8()?,
98            span_size_bytes: reader.read_u8()?,
99            span_offset_bytes: reader.read_u8()?,
100            ekey_bytes: reader.read_u8()?,
101            archive_bytes: reader.read_u8()?,
102            archive_total_size: reader.read_u64::<LittleEndian>()?,
103        })
104    }
105
106    fn parse_entry(data: &[u8], header: &GroupIndexHeader) -> Result<IndexEntry> {
107        let mut offset = 0;
108
109        // Read truncated EKey
110        let ekey = if header.ekey_bytes == 9 {
111            let mut full_key = [0u8; 16];
112            full_key[0..9].copy_from_slice(&data[offset..offset + 9]);
113            offset += 9;
114            EKey::new(full_key)
115        } else if header.ekey_bytes == 16 {
116            let key_bytes = &data[offset..offset + 16];
117            offset += 16;
118            EKey::from_slice(key_bytes)
119                .ok_or_else(|| CascError::InvalidIndexFormat("Invalid key size".into()))?
120        } else {
121            return Err(CascError::InvalidIndexFormat(format!(
122                "Unsupported ekey size: {}",
123                header.ekey_bytes
124            )));
125        };
126
127        // Read archive ID
128        let archive_id = match header.archive_bytes {
129            1 => {
130                let id = data[offset];
131                offset += 1;
132                id as u16
133            }
134            2 => {
135                let id = u16::from_le_bytes([data[offset], data[offset + 1]]);
136                offset += 2;
137                id
138            }
139            _ => {
140                return Err(CascError::InvalidIndexFormat(format!(
141                    "Unsupported archive bytes: {}",
142                    header.archive_bytes
143                )));
144            }
145        };
146
147        // Read offset
148        let file_offset = match header.span_offset_bytes {
149            4 => {
150                let bytes = [
151                    data[offset],
152                    data[offset + 1],
153                    data[offset + 2],
154                    data[offset + 3],
155                ];
156                offset += 4;
157                u32::from_le_bytes(bytes) as u64
158            }
159            5 => {
160                let mut bytes = [0u8; 8];
161                bytes[0..5].copy_from_slice(&data[offset..offset + 5]);
162                offset += 5;
163                u64::from_le_bytes(bytes)
164            }
165            6 => {
166                let mut bytes = [0u8; 8];
167                bytes[0..6].copy_from_slice(&data[offset..offset + 6]);
168                offset += 6;
169                u64::from_le_bytes(bytes)
170            }
171            _ => {
172                return Err(CascError::InvalidIndexFormat(format!(
173                    "Unsupported offset bytes: {}",
174                    header.span_offset_bytes
175                )));
176            }
177        };
178
179        // Read size
180        let size = match header.span_size_bytes {
181            4 => {
182                let bytes = [
183                    data[offset],
184                    data[offset + 1],
185                    data[offset + 2],
186                    data[offset + 3],
187                ];
188                u32::from_le_bytes(bytes)
189            }
190            3 => {
191                let mut bytes = [0u8; 4];
192                bytes[0..3].copy_from_slice(&data[offset..offset + 3]);
193                u32::from_le_bytes(bytes)
194            }
195            2 => {
196                let bytes = [data[offset], data[offset + 1]];
197                u16::from_le_bytes(bytes) as u32
198            }
199            _ => {
200                return Err(CascError::InvalidIndexFormat(format!(
201                    "Unsupported size bytes: {}",
202                    header.span_size_bytes
203                )));
204            }
205        };
206
207        Ok(IndexEntry {
208            ekey,
209            location: ArchiveLocation {
210                archive_id,
211                offset: file_offset,
212                size,
213            },
214        })
215    }
216
217    /// Get the bucket index for this group
218    pub fn bucket_index(&self) -> u8 {
219        self.header.bucket_index
220    }
221
222    /// Get the version of this index
223    pub fn version(&self) -> u16 {
224        self.header.version
225    }
226
227    /// Look up an entry by EKey
228    pub fn lookup(&self, ekey: &EKey) -> Option<&ArchiveLocation> {
229        self.entries.get(ekey)
230    }
231
232    /// Get the number of entries
233    pub fn len(&self) -> usize {
234        self.entries.len()
235    }
236
237    /// Check if the index is empty
238    pub fn is_empty(&self) -> bool {
239        self.entries.is_empty()
240    }
241
242    /// Iterate over all entries
243    pub fn entries(&self) -> impl Iterator<Item = (&EKey, &ArchiveLocation)> {
244        self.entries.iter()
245    }
246
247    /// Get total archive size
248    pub fn archive_total_size(&self) -> u64 {
249        self.header.archive_total_size
250    }
251}