alopex_core/storage/format/
section.rs1use std::convert::TryInto;
4use std::mem;
5
6use crate::storage::compression::CompressionAlgorithm;
7use crate::storage::format::{FormatError, SECTION_ENTRY_SIZE};
8
9#[repr(u8)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SectionType {
13 Metadata = 0x00,
15 SSTable = 0x01,
17 VectorIndex = 0x02,
19 ColumnarSegment = 0x03,
21 LargeValue = 0x04,
23 Intent = 0x05,
25 Lock = 0x06,
27 RaftLog = 0x07,
29}
30
31impl TryFrom<u8> for SectionType {
32 type Error = FormatError;
33
34 fn try_from(value: u8) -> Result<Self, Self::Error> {
35 match value {
36 0x00 => Ok(Self::Metadata),
37 0x01 => Ok(Self::SSTable),
38 0x02 => Ok(Self::VectorIndex),
39 0x03 => Ok(Self::ColumnarSegment),
40 0x04 => Ok(Self::LargeValue),
41 0x05 => Ok(Self::Intent),
42 0x06 => Ok(Self::Lock),
43 0x07 => Ok(Self::RaftLog),
44 _ => Err(FormatError::IncompleteWrite),
45 }
46 }
47}
48
49#[repr(C)]
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct SectionEntry {
53 pub section_type: SectionType,
55 pub compression: CompressionAlgorithm,
57 pub _padding: [u8; 2],
59 pub section_id: u32,
61 pub offset: u64,
63 pub compressed_length: u64,
65 pub uncompressed_length: u64,
67 pub checksum: u32,
69 pub _padding2: [u8; 4],
71}
72
73const _: () = assert!(mem::size_of::<SectionEntry>() == SECTION_ENTRY_SIZE);
74
75impl SectionEntry {
76 pub const SIZE: usize = SECTION_ENTRY_SIZE;
78
79 #[allow(clippy::too_many_arguments)]
81 pub fn new(
82 section_type: SectionType,
83 compression: CompressionAlgorithm,
84 section_id: u32,
85 offset: u64,
86 compressed_length: u64,
87 uncompressed_length: u64,
88 checksum: u32,
89 ) -> Self {
90 Self {
91 section_type,
92 compression,
93 _padding: [0u8; 2],
94 section_id,
95 offset,
96 compressed_length,
97 uncompressed_length,
98 checksum,
99 _padding2: [0u8; 4],
100 }
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct SectionIndex {
107 pub count: u32,
109 pub entries: Vec<SectionEntry>,
111}
112
113impl Default for SectionIndex {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl SectionIndex {
120 pub const ENTRY_SIZE: usize = SECTION_ENTRY_SIZE;
122
123 pub fn new() -> Self {
125 Self {
126 count: 0,
127 entries: Vec::new(),
128 }
129 }
130
131 pub fn add_entry(&mut self, entry: SectionEntry) {
133 self.entries.push(entry);
134 self.count = self.entries.len() as u32;
135 }
136
137 pub fn filter_by_type(&self, section_type: SectionType) -> Vec<&SectionEntry> {
139 self.entries
140 .iter()
141 .filter(|entry| entry.section_type == section_type)
142 .collect()
143 }
144
145 pub fn find_by_id(&self, section_id: u32) -> Option<&SectionEntry> {
147 self.entries
148 .iter()
149 .find(|entry| entry.section_id == section_id)
150 }
151
152 pub fn from_bytes(bytes: &[u8]) -> Result<Self, FormatError> {
154 if bytes.len() < 4 {
155 return Err(FormatError::IncompleteWrite);
156 }
157
158 let count = u32::from_le_bytes(
159 bytes[0..4]
160 .try_into()
161 .expect("fixed slice length for count"),
162 );
163 let expected_size = 4 + count as usize * SECTION_ENTRY_SIZE;
164 if bytes.len() < expected_size {
165 return Err(FormatError::IncompleteWrite);
166 }
167
168 let mut entries = Vec::with_capacity(count as usize);
169 let mut cursor = 4;
170 for _ in 0..count {
171 let section_type = SectionType::try_from(bytes[cursor])?;
172 let compression = parse_compression(bytes[cursor + 1])?;
173
174 let padding = [bytes[cursor + 2], bytes[cursor + 3]];
175 if padding != [0, 0] {
176 return Err(FormatError::IncompleteWrite);
177 }
178
179 let section_id = u32::from_le_bytes(
180 bytes[cursor + 4..cursor + 8]
181 .try_into()
182 .expect("fixed slice length for section_id"),
183 );
184 let offset = u64::from_le_bytes(
185 bytes[cursor + 8..cursor + 16]
186 .try_into()
187 .expect("fixed slice length for offset"),
188 );
189 let compressed_length = u64::from_le_bytes(
190 bytes[cursor + 16..cursor + 24]
191 .try_into()
192 .expect("fixed slice length for compressed_length"),
193 );
194 let uncompressed_length = u64::from_le_bytes(
195 bytes[cursor + 24..cursor + 32]
196 .try_into()
197 .expect("fixed slice length for uncompressed_length"),
198 );
199 let checksum = u32::from_le_bytes(
200 bytes[cursor + 32..cursor + 36]
201 .try_into()
202 .expect("fixed slice length for checksum"),
203 );
204 let padding2: [u8; 4] = bytes[cursor + 36..cursor + 40]
205 .try_into()
206 .expect("fixed slice length for padding2");
207 if padding2 != [0; 4] {
208 return Err(FormatError::IncompleteWrite);
209 }
210
211 entries.push(SectionEntry {
212 section_type,
213 compression,
214 _padding: [0u8; 2],
215 section_id,
216 offset,
217 compressed_length,
218 uncompressed_length,
219 checksum,
220 _padding2: [0u8; 4],
221 });
222 cursor += SECTION_ENTRY_SIZE;
223 }
224
225 Ok(Self { count, entries })
226 }
227
228 pub fn to_bytes(&self) -> Vec<u8> {
230 let count = self.entries.len() as u32;
231 let mut bytes = Vec::with_capacity(self.serialized_size());
232 bytes.extend_from_slice(&count.to_le_bytes());
233
234 for entry in &self.entries {
235 bytes.push(entry.section_type as u8);
236 bytes.push(entry.compression as u8);
237 bytes.extend_from_slice(&[0u8; 2]);
238 bytes.extend_from_slice(&entry.section_id.to_le_bytes());
239 bytes.extend_from_slice(&entry.offset.to_le_bytes());
240 bytes.extend_from_slice(&entry.compressed_length.to_le_bytes());
241 bytes.extend_from_slice(&entry.uncompressed_length.to_le_bytes());
242 bytes.extend_from_slice(&entry.checksum.to_le_bytes());
243 bytes.extend_from_slice(&[0u8; 4]);
244 }
245
246 bytes
247 }
248
249 pub fn serialized_size(&self) -> usize {
251 4 + self.entries.len() * SECTION_ENTRY_SIZE
252 }
253}
254
255fn parse_compression(byte: u8) -> Result<CompressionAlgorithm, FormatError> {
256 match byte {
257 0 => Ok(CompressionAlgorithm::None),
258 1 => Ok(CompressionAlgorithm::Snappy),
259 2 => Ok(CompressionAlgorithm::Zstd),
260 3 => Ok(CompressionAlgorithm::Lz4),
261 other => Err(FormatError::UnsupportedCompression { algorithm: other }),
262 }
263}