vortex_file/footer/
deserializer.rs1use flatbuffers::root;
5use vortex_array::dtype::DType;
6use vortex_buffer::ByteBuffer;
7use vortex_buffer::ByteBufferMut;
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_bail;
11use vortex_error::vortex_err;
12use vortex_flatbuffers::FlatBuffer;
13use vortex_flatbuffers::ReadFlatBuffer;
14use vortex_session::VortexSession;
15
16use crate::EOF_SIZE;
17use crate::Footer;
18use crate::MAGIC_BYTES;
19use crate::VERSION;
20use crate::footer::FileStatistics;
21use crate::footer::postscript::Postscript;
22use crate::footer::postscript::PostscriptSegment;
23
24pub struct FooterDeserializer {
32 buffer: ByteBuffer,
36 session: VortexSession,
38 dtype: Option<DType>,
40
41 file_size: Option<u64>,
45 postscript: Option<Postscript>,
47}
48
49impl FooterDeserializer {
50 pub(super) fn new(initial_read: ByteBuffer, session: VortexSession) -> Self {
51 Self {
52 buffer: initial_read,
53 session,
54 dtype: None,
55 file_size: None,
56 postscript: None,
57 }
58 }
59
60 pub fn with_dtype(mut self, dtype: DType) -> Self {
64 self.dtype = Some(dtype);
65 self
66 }
67
68 pub fn with_some_dtype(mut self, dtype: Option<DType>) -> Self {
70 self.dtype = dtype;
71 self
72 }
73
74 pub fn with_size(mut self, file_size: u64) -> Self {
76 self.file_size = Some(file_size);
77 self
78 }
79
80 pub fn with_some_size(mut self, file_size: Option<u64>) -> Self {
82 self.file_size = file_size;
83 self
84 }
85
86 pub fn prefix_data(&mut self, more_data: ByteBuffer) {
88 let mut buffer = ByteBufferMut::with_capacity(self.buffer.len() + more_data.len());
89 buffer.extend_from_slice(&more_data);
90 buffer.extend_from_slice(&self.buffer);
91 self.buffer = buffer.freeze();
92 }
93
94 pub fn deserialize(&mut self) -> VortexResult<DeserializeStep> {
98 let postscript = if let Some(postscript) = &self.postscript {
99 postscript
100 } else {
101 self.postscript = Some(self.parse_postscript(&self.buffer)?);
102 self.postscript
103 .as_ref()
104 .vortex_expect("Just set postscript")
105 };
106
107 let dtype_segment = self
109 .dtype
110 .is_none()
111 .then(|| {
112 postscript.dtype.as_ref().ok_or_else(|| {
113 vortex_err!(
114 "Vortex file doesn't embed a DType and none provided to VortexOpenOptions"
115 )
116 })
117 })
118 .transpose()?;
119
120 let Some(file_size) = self.file_size else {
125 return Ok(DeserializeStep::NeedFileSize);
126 };
127 let initial_offset = file_size - (self.buffer.len() as u64);
128
129 let mut read_more_offset = initial_offset;
130 if let Some(dtype_segment) = &dtype_segment {
131 read_more_offset = read_more_offset.min(dtype_segment.offset);
132 }
133 if let Some(stats_segment) = &postscript.statistics {
134 read_more_offset = read_more_offset.min(stats_segment.offset);
135 }
136 read_more_offset = read_more_offset.min(postscript.layout.offset);
137 read_more_offset = read_more_offset.min(postscript.footer.offset);
138
139 if read_more_offset < initial_offset {
141 tracing::trace!(
142 "Initial read from {initial_offset} did not cover all footer segments, reading from {read_more_offset}"
143 );
144 return Ok(DeserializeStep::NeedMoreData {
145 offset: read_more_offset,
146 len: usize::try_from(initial_offset - read_more_offset)?,
147 });
148 }
149
150 let dtype = dtype_segment
152 .map(|segment| self.parse_dtype(initial_offset, &self.buffer, segment))
153 .transpose()?
154 .unwrap_or_else(|| self.dtype.clone().vortex_expect("DType was provided"));
155 let file_stats = postscript
156 .statistics
157 .as_ref()
158 .map(|segment| {
159 self.parse_file_statistics(
160 initial_offset,
161 &self.buffer,
162 segment,
163 &dtype,
164 &self.session,
165 )
166 })
167 .transpose()?;
168
169 Ok(DeserializeStep::Done(self.parse_footer(
170 initial_offset,
171 &self.buffer,
172 &postscript.footer,
173 &postscript.layout,
174 dtype,
175 file_stats,
176 )?))
177 }
178
179 pub fn buffer(&self) -> &ByteBuffer {
181 &self.buffer
182 }
183
184 fn parse_postscript(&self, initial_read: &[u8]) -> VortexResult<Postscript> {
186 if initial_read.len() < EOF_SIZE {
187 vortex_bail!(
188 "Initial read must be at least EOF_SIZE ({}) bytes",
189 EOF_SIZE
190 );
191 }
192 let eof_loc = initial_read.len() - EOF_SIZE;
193 let magic_bytes_loc = eof_loc + (EOF_SIZE - MAGIC_BYTES.len());
194
195 let magic_number = &initial_read[magic_bytes_loc..];
196 if magic_number != MAGIC_BYTES {
197 vortex_bail!("Malformed file, invalid magic bytes, got {magic_number:?}")
198 }
199
200 let version = u16::from_le_bytes(
201 initial_read[eof_loc..eof_loc + 2]
202 .try_into()
203 .map_err(|e| vortex_err!("Version was not a u16 {e}"))?,
204 );
205 if version != VERSION {
206 vortex_bail!("Malformed file, unsupported version {version}")
207 }
208
209 let ps_size = u16::from_le_bytes(
210 initial_read[eof_loc + 2..eof_loc + 4]
211 .try_into()
212 .map_err(|e| vortex_err!("Postscript size was not a u16 {e}"))?,
213 ) as usize;
214
215 if initial_read.len() < ps_size + EOF_SIZE {
216 vortex_bail!(
217 "Initial read must be at least {} bytes to include the Postscript",
218 ps_size + EOF_SIZE
219 );
220 }
221
222 Postscript::read_flatbuffer_bytes(&initial_read[eof_loc - ps_size..eof_loc])
223 }
224
225 fn parse_dtype(
227 &self,
228 initial_offset: u64,
229 initial_read: &[u8],
230 segment: &PostscriptSegment,
231 ) -> VortexResult<DType> {
232 let offset = usize::try_from(segment.offset - initial_offset)?;
233 let sliced_buffer =
234 FlatBuffer::copy_from(&initial_read[offset..offset + (segment.length as usize)]);
235 DType::from_flatbuffer(sliced_buffer, &self.session)
236 }
237
238 fn parse_file_statistics(
240 &self,
241 initial_offset: u64,
242 initial_read: &[u8],
243 segment: &PostscriptSegment,
244 dtype: &DType,
245 session: &VortexSession,
246 ) -> VortexResult<FileStatistics> {
247 let offset = usize::try_from(segment.offset - initial_offset)?;
248 let sliced_buffer =
249 FlatBuffer::copy_from(&initial_read[offset..offset + (segment.length as usize)]);
250
251 let fb = root::<vortex_flatbuffers::footer::FileStatistics>(&sliced_buffer)?;
252 FileStatistics::from_flatbuffer(&fb, dtype, session)
253 }
254
255 fn parse_footer(
257 &self,
258 initial_offset: u64,
259 initial_read: &[u8],
260 footer_segment: &PostscriptSegment,
261 layout_segment: &PostscriptSegment,
262 dtype: DType,
263 file_stats: Option<FileStatistics>,
264 ) -> VortexResult<Footer> {
265 let footer_offset = usize::try_from(footer_segment.offset - initial_offset)?;
266 let footer_bytes = FlatBuffer::copy_from(
267 &initial_read[footer_offset..footer_offset + (footer_segment.length as usize)],
268 );
269
270 let layout_offset = usize::try_from(layout_segment.offset - initial_offset)?;
271 let layout_bytes = FlatBuffer::copy_from(
272 &initial_read[layout_offset..layout_offset + (layout_segment.length as usize)],
273 );
274
275 Footer::from_flatbuffer(footer_bytes, layout_bytes, dtype, file_stats, &self.session)
276 }
277}
278
279#[derive(Debug)]
280pub enum DeserializeStep {
282 NeedMoreData {
284 offset: u64,
286 len: usize,
288 },
289 NeedFileSize,
291 Done(Footer),
293}