@@ -757,6 +757,104 @@ pub fn serialize_binary_array_long(values: &[Option<i64>]) -> Vec<u8> {
757757 data
758758}
759759
760+ /// Reverse of [`serialize_binary_array_str`].
761+ pub fn deserialize_binary_array_str ( data : & [ u8 ] ) -> crate :: Result < Vec < String > > {
762+ let n = read_binary_array_len ( data) ?;
763+ let header = binary_array_header ( n) ;
764+ // The fixed element region is `n * 8` bytes after the header; reject any
765+ // count whose slots cannot fit in the buffer up front. This bounds both the
766+ // reservation and the loop, so a forged large count (with or without
767+ // element slots) cannot amplify memory before per-element validation.
768+ check_binary_array_fits ( n, header, data. len ( ) ) ?;
769+ let mut out = Vec :: with_capacity ( n) ;
770+ for k in 0 ..n {
771+ let eo = header + k * 8 ;
772+ let slot = data
773+ . get ( eo..eo + 8 )
774+ . ok_or_else ( || bin_arr_err ( "string element slot out of range" ) ) ?;
775+ let marker = slot[ 7 ] ;
776+ let bytes = if marker & 0x80 != 0 {
777+ let len = ( marker & 0x7F ) as usize ;
778+ slot. get ( ..len)
779+ . ok_or_else ( || bin_arr_err ( "inline string length out of range" ) ) ?
780+ } else {
781+ let encoded = u64:: from_le_bytes ( slot. try_into ( ) . unwrap ( ) ) ;
782+ let var_off = ( encoded >> 32 ) as usize ;
783+ let len = ( encoded & 0xFFFF_FFFF ) as usize ;
784+ let end = var_off
785+ . checked_add ( len)
786+ . ok_or_else ( || bin_arr_err ( "variable string bytes out of range" ) ) ?;
787+ data. get ( var_off..end)
788+ . ok_or_else ( || bin_arr_err ( "variable string bytes out of range" ) ) ?
789+ } ;
790+ out. push (
791+ std:: str:: from_utf8 ( bytes)
792+ . map_err ( |_| bin_arr_err ( "string element is not valid UTF-8" ) ) ?
793+ . to_string ( ) ,
794+ ) ;
795+ }
796+ Ok ( out)
797+ }
798+
799+ /// Reverse of [`serialize_binary_array_long`].
800+ pub fn deserialize_binary_array_long ( data : & [ u8 ] ) -> crate :: Result < Vec < Option < i64 > > > {
801+ let n = read_binary_array_len ( data) ?;
802+ let header = binary_array_header ( n) ;
803+ // See `deserialize_binary_array_str`: reject a count whose fixed element
804+ // region overflows the buffer before allocating. Null elements skip the
805+ // per-slot read, so this up-front check is what prevents a forged
806+ // "large count + all-null bitmap + no slots" input from amplifying memory.
807+ check_binary_array_fits ( n, header, data. len ( ) ) ?;
808+ let mut out = Vec :: with_capacity ( n) ;
809+ for k in 0 ..n {
810+ let null = data
811+ . get ( 4 + k / 8 )
812+ . map ( |b| b & ( 1 << ( k % 8 ) ) != 0 )
813+ . unwrap_or ( false ) ;
814+ if null {
815+ out. push ( None ) ;
816+ } else {
817+ let eo = header + k * 8 ;
818+ let slot = data
819+ . get ( eo..eo + 8 )
820+ . ok_or_else ( || bin_arr_err ( "long element slot out of range" ) ) ?;
821+ out. push ( Some ( i64:: from_le_bytes ( slot. try_into ( ) . unwrap ( ) ) ) ) ;
822+ }
823+ }
824+ Ok ( out)
825+ }
826+
827+ fn read_binary_array_len ( data : & [ u8 ] ) -> crate :: Result < usize > {
828+ let raw = data
829+ . get ( 0 ..4 )
830+ . ok_or_else ( || bin_arr_err ( "binary array too short for length prefix" ) ) ?;
831+ let n = i32:: from_le_bytes ( raw. try_into ( ) . unwrap ( ) ) ;
832+ if n < 0 {
833+ return Err ( bin_arr_err ( "binary array has negative length" ) ) ;
834+ }
835+ Ok ( n as usize )
836+ }
837+
838+ /// Reject a binary array whose `n` fixed 8-byte element slots cannot fit in the
839+ /// buffer after its `header`. Computed without overflow so a forged count
840+ /// cannot wrap; guards allocation and iteration for both decoders (`None`
841+ /// elements otherwise skip the per-slot bounds check).
842+ fn check_binary_array_fits ( n : usize , header : usize , data_len : usize ) -> crate :: Result < ( ) > {
843+ if n > data_len. saturating_sub ( header) / 8 {
844+ return Err ( bin_arr_err (
845+ "binary array element region exceeds buffer length" ,
846+ ) ) ;
847+ }
848+ Ok ( ( ) )
849+ }
850+
851+ fn bin_arr_err ( msg : & str ) -> crate :: Error {
852+ crate :: Error :: DataInvalid {
853+ message : msg. to_string ( ) ,
854+ source : None ,
855+ }
856+ }
857+
760858/// Extract a Datum from an Arrow RecordBatch column at the given row index.
761859pub fn extract_datum_from_arrow (
762860 batch : & RecordBatch ,
@@ -1917,4 +2015,59 @@ mod tests {
19172015 "binary-row write path must store euclidean timestamp parts"
19182016 ) ;
19192017 }
2018+
2019+ #[ test]
2020+ fn binary_array_str_round_trips ( ) {
2021+ for v in [
2022+ vec ! [ ] ,
2023+ vec ! [ "a" . to_string( ) ] ,
2024+ vec ! [ "" . to_string( ) , "short" . to_string( ) , "x" . repeat( 20 ) ] ,
2025+ vec ! [ "1234567" . to_string( ) , "12345678" . to_string( ) ] , // 7-byte inline vs 8-byte pointer
2026+ ] {
2027+ let bytes = serialize_binary_array_str ( & v) ;
2028+ assert_eq ! ( deserialize_binary_array_str( & bytes) . unwrap( ) , v) ;
2029+ }
2030+ }
2031+
2032+ #[ test]
2033+ fn binary_array_long_round_trips ( ) {
2034+ for v in [
2035+ vec ! [ ] ,
2036+ vec ! [ Some ( 1i64 ) , None , Some ( -5 ) , Some ( i64 :: MAX ) ] ,
2037+ vec ! [ None , None ] ,
2038+ ] {
2039+ let bytes = serialize_binary_array_long ( & v) ;
2040+ assert_eq ! ( deserialize_binary_array_long( & bytes) . unwrap( ) , v) ;
2041+ }
2042+ }
2043+
2044+ #[ test]
2045+ fn binary_array_str_rejects_truncated ( ) {
2046+ assert ! ( deserialize_binary_array_str( & [ 1 , 0 ] ) . is_err( ) ) ; // < 4 header bytes
2047+ }
2048+
2049+ #[ test]
2050+ fn binary_array_rejects_huge_length_prefix ( ) {
2051+ // A 4-byte buffer whose length prefix decodes to i32::MAX must return an
2052+ // Err rather than eagerly reserving a huge Vec (capacity-overflow / OOM).
2053+ let huge = [ 0xFF , 0xFF , 0xFF , 0x7F ] ;
2054+ assert ! ( deserialize_binary_array_str( & huge) . is_err( ) ) ;
2055+ assert ! ( deserialize_binary_array_long( & huge) . is_err( ) ) ;
2056+ }
2057+
2058+ #[ test]
2059+ fn binary_array_long_rejects_all_null_amplification ( ) {
2060+ // Forged input: a large element count with an all-ones null bitmap and
2061+ // NO element slots. Null elements skip the per-slot bounds check, so
2062+ // without an up-front `count * 8 <= remaining` guard the loop would
2063+ // push `count` `None`s from a tiny buffer (~128x memory amplification),
2064+ // reachable through the C entry point (OOM risk). Must error, not
2065+ // allocate.
2066+ let count: i32 = 8000 ;
2067+ let bitmap_len = ( count as usize ) . div_ceil ( 8 ) ; // 1000 bytes
2068+ let mut buf = Vec :: with_capacity ( 4 + bitmap_len) ;
2069+ buf. extend_from_slice ( & count. to_le_bytes ( ) ) ;
2070+ buf. extend ( std:: iter:: repeat_n ( 0xFFu8 , bitmap_len) ) ; // every element null
2071+ assert ! ( deserialize_binary_array_long( & buf) . is_err( ) ) ;
2072+ }
19202073}
0 commit comments