1use std::sync::Arc;
5
6use flatbuffers::root;
7use vortex_array::dtype::DType;
8use vortex_buffer::ByteBuffer;
9use vortex_buffer::ByteBufferMut;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_error::vortex_err;
14use vortex_flatbuffers::FlatBuffer;
15use vortex_flatbuffers::ReadFlatBuffer;
16use vortex_session::VortexSession;
17
18use crate::EOF_SIZE;
19use crate::Footer;
20use crate::MAGIC_BYTES;
21use crate::VERSION;
22use crate::footer::FileStatistics;
23use crate::footer::SegmentSpec;
24use crate::footer::postscript::Postscript;
25use crate::footer::postscript::PostscriptSegment;
26
27pub struct FooterDeserializer {
35 buffer: ByteBuffer,
39 session: VortexSession,
41 dtype: Option<DType>,
43
44 file_size: Option<u64>,
49 postscript: Option<Postscript>,
51}
52
53impl FooterDeserializer {
54 pub(super) fn new(initial_read: ByteBuffer, session: VortexSession) -> Self {
55 Self {
56 buffer: initial_read,
57 session,
58 dtype: None,
59 file_size: None,
60 postscript: None,
61 }
62 }
63
64 pub fn with_dtype(mut self, dtype: DType) -> Self {
68 self.dtype = Some(dtype);
69 self
70 }
71
72 pub fn with_some_dtype(mut self, dtype: Option<DType>) -> Self {
74 self.dtype = dtype;
75 self
76 }
77
78 pub fn with_size(mut self, file_size: u64) -> Self {
84 self.file_size = Some(file_size);
85 self
86 }
87
88 pub fn with_some_size(mut self, file_size: Option<u64>) -> Self {
90 self.file_size = file_size;
91 self
92 }
93
94 pub fn prefix_data(&mut self, more_data: ByteBuffer) {
96 let mut buffer = ByteBufferMut::with_capacity(self.buffer.len() + more_data.len());
97 buffer.extend_from_slice(&more_data);
98 buffer.extend_from_slice(&self.buffer);
99 self.buffer = buffer.freeze();
100 }
101
102 pub fn deserialize(&mut self) -> VortexResult<DeserializeStep> {
106 let postscript = if let Some(postscript) = &self.postscript {
107 postscript
108 } else {
109 self.postscript = Some(self.parse_postscript(&self.buffer)?);
110 self.postscript
111 .as_ref()
112 .vortex_expect("Just set postscript")
113 };
114
115 let dtype_segment = self
117 .dtype
118 .is_none()
119 .then(|| {
120 postscript.dtype.as_ref().ok_or_else(|| {
121 vortex_err!(
122 "Vortex file doesn't embed a DType and none provided to VortexOpenOptions"
123 )
124 })
125 })
126 .transpose()?;
127
128 let Some(file_size) = self.file_size else {
133 return Ok(DeserializeStep::NeedFileSize);
134 };
135 let initial_offset = file_size
136 .checked_sub(self.buffer.len() as u64)
137 .ok_or_else(|| {
138 vortex_err!(
139 "Footer buffer length {} exceeds declared file size {file_size}",
140 self.buffer.len()
141 )
142 })?;
143
144 let mut read_more_offset = initial_offset;
145 if let Some(dtype_segment) = &dtype_segment {
146 read_more_offset = read_more_offset.min(dtype_segment.offset);
147 }
148 if let Some(stats_segment) = &postscript.statistics {
149 read_more_offset = read_more_offset.min(stats_segment.offset);
150 }
151 read_more_offset = read_more_offset.min(postscript.layout.offset);
152 read_more_offset = read_more_offset.min(postscript.footer.offset);
153
154 if read_more_offset < initial_offset {
156 tracing::trace!(
157 "Initial read from {initial_offset} did not cover all footer segments, reading from {read_more_offset}"
158 );
159 return Ok(DeserializeStep::NeedMoreData {
160 offset: read_more_offset,
161 len: usize::try_from(initial_offset - read_more_offset)?,
162 });
163 }
164
165 let dtype = dtype_segment
167 .map(|segment| self.parse_dtype(initial_offset, &self.buffer, segment))
168 .transpose()?
169 .unwrap_or_else(|| self.dtype.clone().vortex_expect("DType was provided"));
170 let file_stats = postscript
171 .statistics
172 .as_ref()
173 .map(|segment| {
174 self.parse_file_statistics(
175 initial_offset,
176 &self.buffer,
177 segment,
178 &dtype,
179 &self.session,
180 )
181 })
182 .transpose()?;
183 let metadata = postscript
184 .metadata
185 .iter()
186 .map(|metadata| {
187 let segment = SegmentSpec {
188 offset: metadata.segment.offset,
189 length: metadata.segment.length,
190 alignment: metadata.segment.alignment,
191 };
192 let end = segment
193 .offset
194 .checked_add(u64::from(segment.length))
195 .ok_or_else(|| {
196 vortex_err!("Metadata segment {} range overflowed u64", metadata.key)
197 })?;
198 if end > file_size {
199 vortex_bail!(
200 "Metadata segment {} range {}..{} exceeds file size {}",
201 metadata.key,
202 segment.offset,
203 end,
204 file_size
205 );
206 }
207 let offset = usize::try_from(segment.offset)?;
208 if !segment.alignment.is_offset_aligned(offset) {
209 vortex_bail!(
210 "Metadata segment {} offset {} is not aligned to {}",
211 metadata.key,
212 segment.offset,
213 segment.alignment
214 );
215 }
216 Ok((metadata.key.clone(), segment))
217 })
218 .collect::<VortexResult<Arc<[_]>>>()?;
219
220 Ok(DeserializeStep::Done(self.parse_footer(
221 initial_offset,
222 &self.buffer,
223 postscript,
224 dtype,
225 file_stats,
226 metadata,
227 )?))
228 }
229
230 pub fn buffer(&self) -> &ByteBuffer {
232 &self.buffer
233 }
234
235 fn parse_postscript(&self, initial_read: &[u8]) -> VortexResult<Postscript> {
237 if initial_read.len() < EOF_SIZE {
238 vortex_bail!(
239 "Initial read must be at least EOF_SIZE ({}) bytes",
240 EOF_SIZE
241 );
242 }
243 let eof_loc = initial_read.len() - EOF_SIZE;
244 let magic_bytes_loc = eof_loc + (EOF_SIZE - MAGIC_BYTES.len());
245
246 let magic_number = &initial_read[magic_bytes_loc..];
247 if magic_number != MAGIC_BYTES {
248 vortex_bail!("Malformed file, invalid magic bytes, got {magic_number:?}")
249 }
250
251 let version = u16::from_le_bytes(
252 initial_read[eof_loc..eof_loc + 2]
253 .try_into()
254 .map_err(|e| vortex_err!("Version was not a u16 {e}"))?,
255 );
256 if version != VERSION {
257 vortex_bail!("Malformed file, unsupported version {version}")
258 }
259
260 let ps_size = u16::from_le_bytes(
261 initial_read[eof_loc + 2..eof_loc + 4]
262 .try_into()
263 .map_err(|e| vortex_err!("Postscript size was not a u16 {e}"))?,
264 ) as usize;
265
266 if initial_read.len() < ps_size + EOF_SIZE {
267 vortex_bail!(
268 "Initial read must be at least {} bytes to include the Postscript",
269 ps_size + EOF_SIZE
270 );
271 }
272
273 Postscript::read_flatbuffer_bytes(&initial_read[eof_loc - ps_size..eof_loc])
274 }
275
276 fn parse_dtype(
278 &self,
279 initial_offset: u64,
280 initial_read: &[u8],
281 segment: &PostscriptSegment,
282 ) -> VortexResult<DType> {
283 let sliced_buffer = FlatBuffer::copy_from(checked_segment_slice(
284 initial_read,
285 initial_offset,
286 segment,
287 )?);
288 DType::from_flatbuffer(sliced_buffer, &self.session)
289 }
290
291 fn parse_file_statistics(
293 &self,
294 initial_offset: u64,
295 initial_read: &[u8],
296 segment: &PostscriptSegment,
297 dtype: &DType,
298 session: &VortexSession,
299 ) -> VortexResult<FileStatistics> {
300 let sliced_buffer = checked_segment_slice(initial_read, initial_offset, segment)?;
301
302 let fb = root::<vortex_flatbuffers::footer::FileStatistics>(sliced_buffer)?;
303 FileStatistics::from_flatbuffer(&fb, dtype, session)
304 }
305
306 fn parse_footer(
308 &self,
309 initial_offset: u64,
310 initial_read: &[u8],
311 postscript: &Postscript,
312 dtype: DType,
313 file_stats: Option<FileStatistics>,
314 metadata: Arc<[(String, SegmentSpec)]>,
315 ) -> VortexResult<Footer> {
316 let footer_segment = &postscript.footer;
317 let footer_bytes = checked_segment_slice(initial_read, initial_offset, footer_segment)?;
318
319 let layout_segment = &postscript.layout;
320 let layout_bytes = FlatBuffer::copy_from(checked_segment_slice(
321 initial_read,
322 initial_offset,
323 layout_segment,
324 )?);
325
326 Footer::from_flatbuffer(
327 footer_bytes,
328 layout_bytes,
329 dtype,
330 file_stats,
331 metadata,
332 &self.session,
333 )
334 }
335}
336
337fn checked_segment_slice<'a>(
338 read: &'a [u8],
339 read_offset: u64,
340 segment: &PostscriptSegment,
341) -> VortexResult<&'a [u8]> {
342 let offset = usize::try_from(segment.offset.checked_sub(read_offset).ok_or_else(|| {
343 vortex_err!(
344 "Segment offset {} is smaller than file read offset {read_offset}",
345 segment.offset
346 )
347 })?)?;
348 offset
349 .checked_add(segment.length as usize)
350 .and_then(|end| read.get(offset..end))
351 .ok_or_else(|| {
352 vortex_err!(
353 "Segment length {} (at offset {}) out of bounds of slice of length {}",
354 segment.length,
355 offset,
356 read.len()
357 )
358 })
359}
360
361#[cfg(test)]
362mod tests {
363 use rstest::rstest;
364 use vortex_array::array_session;
365 use vortex_array::dtype::Nullability;
366 use vortex_array::dtype::PType;
367 use vortex_flatbuffers::WriteFlatBufferExt;
368
369 use super::*;
370
371 fn segment(offset: u64, length: u32) -> PostscriptSegment {
372 PostscriptSegment {
373 offset,
374 length,
375 alignment: FlatBuffer::alignment(),
376 }
377 }
378
379 #[test]
380 fn in_bounds_segment_slice() -> VortexResult<()> {
381 let read: Vec<u8> = (0u8..10).collect();
382 let sliced = checked_segment_slice(&read, 100, &segment(104, 4))?;
383 assert_eq!(sliced, &read[4..8]);
384 Ok(())
385 }
386
387 #[rstest]
388 #[case::offset_before_read_start(100, 99, 4, "smaller than file read offset")]
389 #[case::end_past_buffer(100, 105, 6, "out of bounds")]
390 #[case::offset_past_buffer(100, 120, 1, "out of bounds")]
391 #[case::end_overflows_usize(0, u64::MAX, u32::MAX, "out of bounds")]
392 fn out_of_bounds_segment_slice(
393 #[case] read_offset: u64,
394 #[case] segment_offset: u64,
395 #[case] segment_length: u32,
396 #[case] expected: &str,
397 ) {
398 let read = [0u8; 10];
399 let err =
400 checked_segment_slice(&read, read_offset, &segment(segment_offset, segment_length))
401 .unwrap_err();
402 assert!(err.to_string().contains(expected), "{err}");
403 }
404
405 fn eof_buffer(postscript: &Postscript) -> VortexResult<ByteBuffer> {
406 let postscript_bytes = postscript.write_flatbuffer_bytes()?;
407 let mut buffer = ByteBufferMut::with_capacity(postscript_bytes.len() + EOF_SIZE);
408 buffer.extend_from_slice(&postscript_bytes);
409 buffer.extend_from_slice(&VERSION.to_le_bytes());
410 buffer.extend_from_slice(&u16::try_from(postscript_bytes.len())?.to_le_bytes());
411 buffer.extend_from_slice(&MAGIC_BYTES);
412 Ok(buffer.freeze())
413 }
414
415 #[rstest]
416 #[case::length_past_eof(segment(0, u32::MAX))]
417 #[case::offset_overflow(segment(u64::MAX, u32::MAX))]
418 fn deserialize_rejects_out_of_bounds_footer_segment(
419 #[case] footer_segment: PostscriptSegment,
420 ) -> VortexResult<()> {
421 let postscript = Postscript {
422 dtype: None,
423 layout: segment(0, 1),
424 statistics: None,
425 footer: footer_segment,
426 metadata: Vec::new(),
427 };
428 let buffer = eof_buffer(&postscript)?;
429 let file_size = buffer.len() as u64;
430
431 let mut deserializer = FooterDeserializer::new(buffer, array_session())
432 .with_dtype(DType::Primitive(PType::I32, Nullability::NonNullable))
433 .with_size(file_size);
434 let err = deserializer.deserialize().unwrap_err();
435 assert!(err.to_string().contains("out of bounds"), "{err}");
436 Ok(())
437 }
438
439 #[test]
440 fn deserialize_rejects_buffer_larger_than_declared_size() -> VortexResult<()> {
441 let postscript = Postscript {
442 dtype: None,
443 layout: segment(0, 1),
444 statistics: None,
445 footer: segment(1, 1),
446 metadata: Vec::new(),
447 };
448 let buffer = eof_buffer(&postscript)?;
449 let declared_size = buffer.len() as u64 - 1;
450
451 let mut deserializer = FooterDeserializer::new(buffer, array_session())
452 .with_dtype(DType::Primitive(PType::I32, Nullability::NonNullable))
453 .with_size(declared_size);
454 let err = deserializer.deserialize().unwrap_err();
455 assert!(err.to_string().contains("exceeds declared"), "{err}");
456 Ok(())
457 }
458}
459
460#[derive(Debug)]
461pub enum DeserializeStep {
463 NeedMoreData {
465 offset: u64,
467 len: usize,
469 },
470 NeedFileSize,
472 Done(Footer),
474}