@@ -40,8 +40,8 @@ pub struct FooterDeserializer {
4040
4141 // Internal state that we accumulate
4242
43- // The file size, possibly provided externally .
44- file_size : Option < u64 > ,
43+ // The size of the byte stream containing the serialized footer .
44+ stream_size : Option < u64 > ,
4545 // The postscript, once we've parsed it.
4646 postscript : Option < Postscript > ,
4747}
@@ -52,7 +52,7 @@ impl FooterDeserializer {
5252 buffer : initial_read,
5353 session,
5454 dtype : None ,
55- file_size : None ,
55+ stream_size : None ,
5656 postscript : None ,
5757 }
5858 }
@@ -71,15 +71,19 @@ impl FooterDeserializer {
7171 self
7272 }
7373
74- /// Provide the total file size.
75- pub fn with_size ( mut self , file_size : u64 ) -> Self {
76- self . file_size = Some ( file_size) ;
74+ /// Provide the total size of the byte stream containing this serialized footer.
75+ ///
76+ /// For a footer read from the end of a Vortex file, this is the file size. For a standalone
77+ /// blob created by [`crate::footer::FooterSerializer`] with its default offset, this is the
78+ /// size of that blob, not the size of the data file described by the footer.
79+ pub fn with_size ( mut self , stream_size : u64 ) -> Self {
80+ self . stream_size = Some ( stream_size) ;
7781 self
7882 }
7983
80- /// Provide or clear the total file size.
81- pub fn with_some_size ( mut self , file_size : Option < u64 > ) -> Self {
82- self . file_size = file_size ;
84+ /// Provide or clear the total size of the byte stream containing this serialized footer .
85+ pub fn with_some_size ( mut self , stream_size : Option < u64 > ) -> Self {
86+ self . stream_size = stream_size ;
8387 self
8488 }
8589
@@ -120,11 +124,18 @@ impl FooterDeserializer {
120124 // The other postscript segments are required, so now we figure out our the offset that
121125 // contains all the required segments.
122126
123- // The initial offset is the file size - the size of our initial read.
124- let Some ( file_size ) = self . file_size else {
127+ // The initial offset is the byte stream size minus the size of our initial read.
128+ let Some ( stream_size ) = self . stream_size else {
125129 return Ok ( DeserializeStep :: NeedFileSize ) ;
126130 } ;
127- let initial_offset = file_size - ( self . buffer . len ( ) as u64 ) ;
131+ let initial_offset = stream_size
132+ . checked_sub ( self . buffer . len ( ) as u64 )
133+ . ok_or_else ( || {
134+ vortex_err ! (
135+ "Footer buffer length {} exceeds declared byte stream size {stream_size}" ,
136+ self . buffer. len( )
137+ )
138+ } ) ?;
128139
129140 let mut read_more_offset = initial_offset;
130141 if let Some ( dtype_segment) = & dtype_segment {
@@ -269,20 +280,7 @@ impl FooterDeserializer {
269280 layout_segment,
270281 ) ?) ;
271282
272- // The file size is always known by the time we parse the footer, since `deserialize`
273- // returns `NeedFileSize` before reaching this point when it is missing.
274- let file_size = self
275- . file_size
276- . vortex_expect ( "file size is required before parsing the footer" ) ;
277-
278- Footer :: from_flatbuffer (
279- footer_bytes,
280- layout_bytes,
281- dtype,
282- file_stats,
283- file_size,
284- & self . session ,
285- )
283+ Footer :: from_flatbuffer ( footer_bytes, layout_bytes, dtype, file_stats, & self . session )
286284 }
287285}
288286
@@ -386,6 +384,25 @@ mod tests {
386384 assert ! ( err. to_string( ) . contains( "out of bounds" ) , "{err}" ) ;
387385 Ok ( ( ) )
388386 }
387+
388+ #[ test]
389+ fn deserialize_rejects_buffer_larger_than_declared_size ( ) -> VortexResult < ( ) > {
390+ let postscript = Postscript {
391+ dtype : None ,
392+ layout : segment ( 0 , 1 ) ,
393+ statistics : None ,
394+ footer : segment ( 1 , 1 ) ,
395+ } ;
396+ let buffer = eof_buffer ( & postscript) ?;
397+ let declared_size = buffer. len ( ) as u64 - 1 ;
398+
399+ let mut deserializer = FooterDeserializer :: new ( buffer, array_session ( ) )
400+ . with_dtype ( DType :: Primitive ( PType :: I32 , Nullability :: NonNullable ) )
401+ . with_size ( declared_size) ;
402+ let err = deserializer. deserialize ( ) . unwrap_err ( ) ;
403+ assert ! ( err. to_string( ) . contains( "exceeds declared" ) , "{err}" ) ;
404+ Ok ( ( ) )
405+ }
389406}
390407
391408#[ derive( Debug ) ]
@@ -398,7 +415,7 @@ pub enum DeserializeStep {
398415 /// Number of bytes to read and prefix into the deserializer.
399416 len : usize ,
400417 } ,
401- /// The total file size is required before offsets can be resolved.
418+ /// The total size of the byte stream is required before offsets can be resolved.
402419 NeedFileSize ,
403420 /// Footer deserialization is complete.
404421 Done ( Footer ) ,
0 commit comments