Skip to main content

parquet/
parquet_thrift.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Structs used for encoding and decoding Parquet Thrift objects.
19//!
20//! These include:
21//! * [`ThriftCompactInputProtocol`]: Trait implemented by Thrift decoders.
22//!     * [`ThriftSliceInputProtocol`]: Thrift decoder that takes a slice of bytes as input.
23//!     * [`ThriftReadInputProtocol`]: Thrift decoder that takes a [`Read`] as input.
24//! * [`ReadThrift`]: Trait implemented by serializable objects.
25//! * [`ThriftCompactOutputProtocol`]: Thrift encoder.
26//! * [`WriteThrift`]: Trait implemented by serializable objects.
27//! * [`WriteThriftField`]: Trait implemented by serializable objects that are fields in Thrift structs.
28
29use std::{
30    cmp::Ordering,
31    io::{Read, Write},
32};
33
34use crate::{
35    errors::{ParquetError, Result},
36    write_thrift_field,
37};
38use std::io::Error;
39use std::num::TryFromIntError;
40use std::str::Utf8Error;
41
42#[derive(Debug)]
43pub(crate) enum ThriftProtocolError {
44    Eof,
45    IO(Error),
46    InvalidFieldType(u8),
47    InvalidElementType(u8),
48    FieldDeltaOverflow { field_delta: u8, last_field_id: i16 },
49    InvalidBoolean(u8),
50    IntegerOverflow,
51    Utf8Error,
52    SkipDepth(FieldType),
53    SkipUnsupportedType(FieldType),
54}
55
56impl From<ThriftProtocolError> for ParquetError {
57    #[inline(never)]
58    fn from(e: ThriftProtocolError) -> Self {
59        match e {
60            ThriftProtocolError::Eof => eof_err!("Unexpected EOF"),
61            ThriftProtocolError::IO(e) => e.into(),
62            ThriftProtocolError::InvalidFieldType(value) => match FieldType::try_from(value) {
63                Ok(fld_type) => general_err!("Unexpected struct field type {:?}", fld_type),
64                Err(_) => general_err!("Unexpected struct field type {}", value),
65            },
66            ThriftProtocolError::InvalidElementType(value) => {
67                general_err!("Unexpected list/set element type {}", value)
68            }
69            ThriftProtocolError::FieldDeltaOverflow {
70                field_delta,
71                last_field_id,
72            } => general_err!("cannot add {} to {}", field_delta, last_field_id),
73            ThriftProtocolError::InvalidBoolean(value) => {
74                general_err!("cannot convert {} into bool", value)
75            }
76            ThriftProtocolError::IntegerOverflow => {
77                general_err!("integer overflow decoding thrift value")
78            }
79            ThriftProtocolError::Utf8Error => general_err!("invalid utf8"),
80            ThriftProtocolError::SkipDepth(field_type) => {
81                general_err!("cannot parse past {:?}", field_type)
82            }
83            ThriftProtocolError::SkipUnsupportedType(field_type) => {
84                general_err!("cannot skip field type {:?}", field_type)
85            }
86        }
87    }
88}
89
90impl From<Utf8Error> for ThriftProtocolError {
91    fn from(_: Utf8Error) -> Self {
92        // ignore error payload to reduce the size of ThriftProtocolError
93        Self::Utf8Error
94    }
95}
96
97impl From<Error> for ThriftProtocolError {
98    fn from(e: Error) -> Self {
99        Self::IO(e)
100    }
101}
102
103impl From<TryFromIntError> for ThriftProtocolError {
104    fn from(_: TryFromIntError) -> Self {
105        // ignore error payload to reduce the size of ThriftProtocolError
106        Self::IntegerOverflow
107    }
108}
109
110pub type ThriftProtocolResult<T> = Result<T, ThriftProtocolError>;
111
112/// Wrapper for thrift `double` fields. This is used to provide
113/// an implementation of `Eq` for floats. This implementation
114/// uses IEEE 754 total order.
115#[derive(Debug, Clone, Copy, PartialEq)]
116pub struct OrderedF64(f64);
117
118impl From<f64> for OrderedF64 {
119    fn from(value: f64) -> Self {
120        Self(value)
121    }
122}
123
124impl From<OrderedF64> for f64 {
125    fn from(value: OrderedF64) -> Self {
126        value.0
127    }
128}
129
130impl Eq for OrderedF64 {} // Marker trait, requires PartialEq
131
132impl Ord for OrderedF64 {
133    fn cmp(&self, other: &Self) -> Ordering {
134        self.0.total_cmp(&other.0)
135    }
136}
137
138impl PartialOrd for OrderedF64 {
139    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
140        Some(self.cmp(other))
141    }
142}
143
144// Thrift compact protocol types for struct fields.
145#[derive(Clone, Copy, Debug, Eq, PartialEq)]
146pub(crate) enum FieldType {
147    Stop = 0,
148    BooleanTrue = 1,
149    BooleanFalse = 2,
150    Byte = 3,
151    I16 = 4,
152    I32 = 5,
153    I64 = 6,
154    Double = 7,
155    Binary = 8,
156    List = 9,
157    Set = 10,
158    Map = 11,
159    Struct = 12,
160    Uuid = 13,
161}
162
163impl TryFrom<u8> for FieldType {
164    type Error = ThriftProtocolError;
165    fn try_from(value: u8) -> ThriftProtocolResult<Self> {
166        match value {
167            0 => Ok(Self::Stop),
168            1 => Ok(Self::BooleanTrue),
169            2 => Ok(Self::BooleanFalse),
170            3 => Ok(Self::Byte),
171            4 => Ok(Self::I16),
172            5 => Ok(Self::I32),
173            6 => Ok(Self::I64),
174            7 => Ok(Self::Double),
175            8 => Ok(Self::Binary),
176            9 => Ok(Self::List),
177            10 => Ok(Self::Set),
178            11 => Ok(Self::Map),
179            12 => Ok(Self::Struct),
180            13 => Ok(Self::Uuid),
181            _ => Err(ThriftProtocolError::InvalidFieldType(value)),
182        }
183    }
184}
185
186impl From<ElementType> for FieldType {
187    fn from(value: ElementType) -> Self {
188        match value {
189            ElementType::Bool => Self::BooleanTrue,
190            ElementType::Byte => Self::Byte,
191            ElementType::I16 => Self::I16,
192            ElementType::I32 => Self::I32,
193            ElementType::I64 => Self::I64,
194            ElementType::Double => Self::Double,
195            ElementType::Binary => Self::Binary,
196            ElementType::List => Self::List,
197            ElementType::Set => Self::Set,
198            ElementType::Map => Self::Map,
199            ElementType::Struct => Self::Struct,
200            ElementType::Uuid => Self::Uuid,
201        }
202    }
203}
204
205// Thrift compact protocol types for list elements
206#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub(crate) enum ElementType {
208    Bool = 2,
209    Byte = 3,
210    I16 = 4,
211    I32 = 5,
212    I64 = 6,
213    Double = 7,
214    Binary = 8,
215    List = 9,
216    Set = 10,
217    Map = 11,
218    Struct = 12,
219    Uuid = 13,
220}
221
222impl TryFrom<u8> for ElementType {
223    type Error = ThriftProtocolError;
224    fn try_from(value: u8) -> ThriftProtocolResult<Self> {
225        match value {
226            // For historical and compatibility reasons, a reader should be capable to deal with both cases.
227            // The only valid value in the original spec was 2, but due to an widespread implementation bug
228            // the defacto standard across large parts of the library became 1 instead.
229            // As a result, both values are now allowed.
230            // https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#list-and-set
231            1 | 2 => Ok(Self::Bool),
232            3 => Ok(Self::Byte),
233            4 => Ok(Self::I16),
234            5 => Ok(Self::I32),
235            6 => Ok(Self::I64),
236            7 => Ok(Self::Double),
237            8 => Ok(Self::Binary),
238            9 => Ok(Self::List),
239            10 => Ok(Self::Set),
240            11 => Ok(Self::Map),
241            12 => Ok(Self::Struct),
242            13 => Ok(Self::Uuid),
243            _ => Err(ThriftProtocolError::InvalidElementType(value)),
244        }
245    }
246}
247
248/// Struct used to describe a [thrift struct] field during decoding.
249///
250/// [thrift struct]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#struct-encoding
251pub(crate) struct FieldIdentifier {
252    /// The type for the field.
253    pub(crate) field_type: FieldType,
254    /// The field's `id`. May be computed from delta or directly decoded.
255    pub(crate) id: i16,
256}
257
258impl FieldIdentifier {
259    pub(crate) fn bool_val(&self) -> ThriftProtocolResult<bool> {
260        match self.field_type {
261            FieldType::BooleanTrue => Ok(true),
262            FieldType::BooleanFalse => Ok(false),
263            _ => Err(ThriftProtocolError::InvalidFieldType(self.field_type as u8)),
264        }
265    }
266}
267
268/// Struct used to describe a [thrift list].
269///
270/// [thrift list]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#list-and-set
271#[derive(Clone, Debug, Eq, PartialEq)]
272pub(crate) struct ListIdentifier {
273    /// The type for each element in the list.
274    pub(crate) element_type: ElementType,
275    /// Number of elements contained in the list.
276    pub(crate) size: i32,
277}
278
279/// Low-level object used to deserialize structs encoded with the Thrift [compact] protocol.
280///
281/// Implementation of this trait must provide the low-level functions `read_byte`, `read_bytes`,
282/// `skip_bytes`, and `read_double`. These primitives are used by the default functions provided
283/// here to perform deserialization.
284///
285/// [compact]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md
286pub(crate) trait ThriftCompactInputProtocol<'a> {
287    /// Read a single byte from the input.
288    fn read_byte(&mut self) -> ThriftProtocolResult<u8>;
289
290    /// Read a Thrift encoded [binary] from the input.
291    ///
292    /// [binary]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#binary-encoding
293    fn read_bytes(&mut self) -> ThriftProtocolResult<&'a [u8]>;
294
295    fn read_bytes_owned(&mut self) -> ThriftProtocolResult<Vec<u8>>;
296
297    /// Skip the next `n` bytes of input.
298    fn skip_bytes(&mut self, n: usize) -> ThriftProtocolResult<()>;
299
300    /// Remaining unread bytes, if this protocol is backed by a finite buffer.
301    ///
302    /// Used to reject Thrift collection sizes that cannot fit in the remaining
303    /// input before allocating.
304    fn remaining_bytes(&self) -> Option<usize> {
305        None
306    }
307
308    /// Read a ULEB128 encoded unsigned varint from the input.
309    fn read_vlq(&mut self) -> ThriftProtocolResult<u64> {
310        // try the happy path first
311        let byte = self.read_byte()?;
312        if byte & 0x80 == 0 {
313            return Ok(byte as u64);
314        }
315        let mut in_progress = (byte & 0x7f) as u64;
316        let mut shift = 7;
317        loop {
318            let byte = self.read_byte()?;
319            in_progress |= ((byte & 0x7F) as u64).wrapping_shl(shift);
320            if byte & 0x80 == 0 {
321                return Ok(in_progress);
322            }
323            shift += 7;
324        }
325    }
326
327    /// Read a zig-zag encoded signed varint from the input.
328    fn read_zig_zag(&mut self) -> ThriftProtocolResult<i64> {
329        let val = self.read_vlq()?;
330        Ok((val >> 1) as i64 ^ -((val & 1) as i64))
331    }
332
333    /// Read the [`ListIdentifier`] for a Thrift encoded list.
334    fn read_list_begin(&mut self) -> ThriftProtocolResult<ListIdentifier> {
335        let header = self.read_byte()?;
336        // some parquet writers will have an element_type of 0 for an empty list.
337        // account for that and return a bogus but valid element_type.
338        if header == 0 {
339            return Ok(ListIdentifier {
340                element_type: ElementType::Byte,
341                size: 0,
342            });
343        }
344        let element_type = ElementType::try_from(header & 0x0f)?;
345
346        let possible_element_count = (header & 0xF0) >> 4;
347        let element_count = if possible_element_count != 15 {
348            // high bits set high if count and type encoded separately
349            possible_element_count as i32
350        } else {
351            // The list size on the wire is an unsigned varint, but we represent
352            // it as `i32` (matching Java's `int` and the Thrift schema).
353            // A varint that decodes above `i32::MAX` is malformed input — reject
354            // it here at the protocol layer rather than letting the cast wrap
355            // into a negative size that downstream allocation code has to
356            // re-validate.
357            i32::try_from(self.read_vlq()?)?
358        };
359
360        Ok(ListIdentifier {
361            element_type,
362            size: element_count,
363        })
364    }
365
366    // Full field ids are uncommon.
367    // Not inlining this method reduces the code size of `read_field_begin`, which then ideally gets
368    // inlined everywhere.
369    #[cold]
370    fn read_full_field_id(&mut self) -> ThriftProtocolResult<i16> {
371        self.read_i16()
372    }
373
374    /// Read the [`FieldIdentifier`] for a field in a Thrift encoded struct.
375    fn read_field_begin(&mut self, last_field_id: i16) -> ThriftProtocolResult<FieldIdentifier> {
376        // we can read at least one byte, which is:
377        // - the type
378        // - the field delta and the type
379        let field_type = self.read_byte()?;
380        if field_type & 0xf == 0 {
381            return Ok(FieldIdentifier {
382                field_type: FieldType::Stop,
383                id: 0,
384            });
385        }
386
387        let field_delta = (field_type & 0xf0) >> 4;
388        let field_type = FieldType::try_from(field_type & 0xf)?;
389
390        let id = if field_delta != 0 {
391            last_field_id.checked_add(field_delta as i16).ok_or(
392                ThriftProtocolError::FieldDeltaOverflow {
393                    field_delta,
394                    last_field_id,
395                },
396            )?
397        } else {
398            self.read_full_field_id()?
399        };
400
401        Ok(FieldIdentifier { field_type, id })
402    }
403
404    /// This is a specialized version of [`Self::read_field_begin`], solely for use in parsing
405    /// simple structs. This function assumes that the delta field will always be less than 0xf,
406    /// fields will be in order, and no boolean fields will be read.
407    /// This also skips validation of the field type.
408    ///
409    /// Returns a tuple of `(field_type, field_delta)`.
410    fn read_field_header(&mut self) -> ThriftProtocolResult<(u8, u8)> {
411        let field_type = self.read_byte()?;
412        let field_delta = (field_type & 0xf0) >> 4;
413        let field_type = field_type & 0xf;
414        Ok((field_type, field_delta))
415    }
416
417    /// Read a boolean list element. This should not be used for struct fields. For the latter,
418    /// use the [`FieldIdentifier::bool_val`] field.
419    fn read_bool(&mut self) -> ThriftProtocolResult<bool> {
420        let b = self.read_byte()?;
421        // Previous versions of the thrift specification said to use 0 and 1 inside collections,
422        // but that differed from existing implementations.
423        // The specification was updated in https://github.com/apache/thrift/commit/2c29c5665bc442e703480bb0ee60fe925ffe02e8.
424        // At least the go implementation seems to have followed the previously documented values.
425        match b {
426            0x01 => Ok(true),
427            0x00 | 0x02 => Ok(false),
428            _ => Err(ThriftProtocolError::InvalidBoolean(b)),
429        }
430    }
431
432    /// Read a Thrift [binary] as a UTF-8 encoded string.
433    ///
434    /// [binary]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#binary-encoding
435    fn read_string(&mut self) -> ThriftProtocolResult<&'a str> {
436        let slice = self.read_bytes()?;
437        Ok(std::str::from_utf8(slice)?)
438    }
439
440    /// Read an `i8`.
441    fn read_i8(&mut self) -> ThriftProtocolResult<i8> {
442        Ok(self.read_byte()? as _)
443    }
444
445    /// Read an `i16`.
446    fn read_i16(&mut self) -> ThriftProtocolResult<i16> {
447        Ok(self.read_zig_zag()? as _)
448    }
449
450    /// Read an `i32`.
451    fn read_i32(&mut self) -> ThriftProtocolResult<i32> {
452        Ok(self.read_zig_zag()? as _)
453    }
454
455    /// Read an `i64`.
456    fn read_i64(&mut self) -> ThriftProtocolResult<i64> {
457        self.read_zig_zag()
458    }
459
460    /// Read a Thrift `double` as `f64`.
461    fn read_double(&mut self) -> ThriftProtocolResult<f64>;
462
463    /// Skip a ULEB128 encoded varint.
464    fn skip_vlq(&mut self) -> ThriftProtocolResult<()> {
465        loop {
466            let byte = self.read_byte()?;
467            if byte & 0x80 == 0 {
468                return Ok(());
469            }
470        }
471    }
472
473    /// Skip a thrift [binary].
474    ///
475    /// [binary]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#binary-encoding
476    fn skip_binary(&mut self) -> ThriftProtocolResult<()> {
477        let len = usize::try_from(self.read_vlq()?)?;
478        self.skip_bytes(len)
479    }
480
481    /// Skip a field with type `field_type` recursively until the default
482    /// maximum skip depth (currently 64) is reached.
483    fn skip(&mut self, field_type: FieldType) -> ThriftProtocolResult<()> {
484        const DEFAULT_SKIP_DEPTH: i8 = 64;
485        self.skip_till_depth(field_type, DEFAULT_SKIP_DEPTH)
486    }
487
488    /// Empty structs in unions consist of a single byte of 0 for the field stop record.
489    /// This skips that byte without encuring the cost of processing the [`FieldIdentifier`].
490    /// Will return an error if the struct is not actually empty.
491    fn skip_empty_struct(&mut self) -> Result<()> {
492        let b = self.read_byte()?;
493        if b != 0 {
494            Err(general_err!("Empty struct has fields"))
495        } else {
496            Ok(())
497        }
498    }
499
500    /// Skip a field with type `field_type` recursively up to `depth` levels.
501    fn skip_till_depth(&mut self, field_type: FieldType, depth: i8) -> ThriftProtocolResult<()> {
502        if depth == 0 {
503            return Err(ThriftProtocolError::SkipDepth(field_type));
504        }
505
506        match field_type {
507            // boolean field has no data
508            FieldType::BooleanFalse | FieldType::BooleanTrue => Ok(()),
509            FieldType::Byte => self.read_i8().map(|_| ()),
510            FieldType::I16 => self.skip_vlq(),
511            FieldType::I32 => self.skip_vlq(),
512            FieldType::I64 => self.skip_vlq(),
513            FieldType::Double => self.skip_bytes(8),
514            FieldType::Binary => self.skip_binary(),
515            // see https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#struct
516            FieldType::Struct => {
517                loop {
518                    // we don't need field id for skipping, so always pass 0 for last id
519                    let field_ident = self.read_field_begin(0)?;
520                    if field_ident.field_type == FieldType::Stop {
521                        break;
522                    }
523                    self.skip_till_depth(field_ident.field_type, depth - 1)?;
524                }
525                Ok(())
526            }
527            // lists and sets are encoded the same
528            // see https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#list-and-set
529            FieldType::List | FieldType::Set => {
530                let list_ident = self.read_list_begin()?;
531                if list_ident.element_type == ElementType::Bool {
532                    self.skip_bool_list(list_ident.size, depth - 1)
533                } else {
534                    let field_type = list_ident.element_type.into();
535                    for _ in 0..list_ident.size {
536                        self.skip_till_depth(field_type, depth - 1)?;
537                    }
538                    Ok(())
539                }
540            }
541            // see https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#map
542            FieldType::Map => self.skip_map(depth),
543            // see https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#universal-unique-identifier-encoding
544            FieldType::Uuid => self.skip_bytes(16),
545            FieldType::Stop => Err(ThriftProtocolError::SkipUnsupportedType(field_type)),
546        }
547    }
548
549    /// Skip boolean list or set payloads after reading their header.
550    #[cold]
551    fn skip_bool_list(&mut self, size: i32, depth: i8) -> ThriftProtocolResult<()> {
552        if size > 0 && depth == 0 {
553            return Err(ThriftProtocolError::SkipDepth(FieldType::BooleanTrue));
554        }
555        self.skip_bytes(size as usize)
556    }
557
558    /// Skip maps, which are not used by the current Parquet metadata schema.
559    #[cold]
560    fn skip_map(&mut self, depth: i8) -> ThriftProtocolResult<()> {
561        let size = i32::try_from(self.read_vlq()?)?;
562        if size > 0 {
563            let kv = self.read_byte()?;
564            let key_type = ElementType::try_from(kv >> 4)?;
565            let val_type = ElementType::try_from(kv & 0xf)?;
566            for _ in 0..size {
567                self.skip_element_till_depth(key_type, depth - 1)?;
568                self.skip_element_till_depth(val_type, depth - 1)?;
569            }
570        }
571        Ok(())
572    }
573
574    /// Collection booleans have a payload byte, unlike inline struct fields.
575    fn skip_element_till_depth(
576        &mut self,
577        element_type: ElementType,
578        depth: i8,
579    ) -> ThriftProtocolResult<()> {
580        if depth == 0 {
581            return Err(ThriftProtocolError::SkipDepth(element_type.into()));
582        }
583        match element_type {
584            ElementType::Bool => self.skip_bytes(1),
585            other => self.skip_till_depth(other.into(), depth),
586        }
587    }
588}
589
590/// A high performance Thrift reader that reads from a slice of bytes.
591pub(crate) struct ThriftSliceInputProtocol<'a> {
592    buf: &'a [u8],
593}
594
595impl<'a> ThriftSliceInputProtocol<'a> {
596    /// Create a new `ThriftSliceInputProtocol` using the bytes in `buf`.
597    pub fn new(buf: &'a [u8]) -> Self {
598        Self { buf }
599    }
600
601    /// Return the current buffer as a slice.
602    pub fn as_slice(&self) -> &'a [u8] {
603        self.buf
604    }
605}
606
607impl<'b, 'a: 'b> ThriftCompactInputProtocol<'b> for ThriftSliceInputProtocol<'a> {
608    #[inline]
609    fn read_byte(&mut self) -> ThriftProtocolResult<u8> {
610        let ret = *self.buf.first().ok_or(ThriftProtocolError::Eof)?;
611        self.buf = &self.buf[1..];
612        Ok(ret)
613    }
614
615    fn read_bytes(&mut self) -> ThriftProtocolResult<&'b [u8]> {
616        let len = self.read_vlq()? as usize;
617        let ret = self.buf.get(..len).ok_or(ThriftProtocolError::Eof)?;
618        self.buf = &self.buf[len..];
619        Ok(ret)
620    }
621
622    fn read_bytes_owned(&mut self) -> ThriftProtocolResult<Vec<u8>> {
623        Ok(self.read_bytes()?.to_vec())
624    }
625
626    #[inline]
627    fn skip_bytes(&mut self, n: usize) -> ThriftProtocolResult<()> {
628        self.buf.get(..n).ok_or(ThriftProtocolError::Eof)?;
629        self.buf = &self.buf[n..];
630        Ok(())
631    }
632
633    fn read_double(&mut self) -> ThriftProtocolResult<f64> {
634        let slice = self.buf.get(..8).ok_or(ThriftProtocolError::Eof)?;
635        self.buf = &self.buf[8..];
636        match slice.try_into() {
637            Ok(slice) => Ok(f64::from_le_bytes(slice)),
638            Err(_) => unreachable!(),
639        }
640    }
641
642    fn remaining_bytes(&self) -> Option<usize> {
643        Some(self.buf.len())
644    }
645}
646
647/// A Thrift input protocol that wraps a [`Read`] object.
648///
649/// Note that this is only intended for use in reading Parquet page headers. This will panic
650/// if Thrift `binary` data is encountered because a slice of that data cannot be returned.
651pub(crate) struct ThriftReadInputProtocol<R: Read> {
652    reader: R,
653}
654
655impl<R: Read> ThriftReadInputProtocol<R> {
656    pub(crate) fn new(reader: R) -> Self {
657        Self { reader }
658    }
659}
660
661impl<'a, R: Read> ThriftCompactInputProtocol<'a> for ThriftReadInputProtocol<R> {
662    #[inline]
663    fn read_byte(&mut self) -> ThriftProtocolResult<u8> {
664        let mut buf = [0_u8; 1];
665        self.reader.read_exact(&mut buf)?;
666        Ok(buf[0])
667    }
668
669    fn read_bytes(&mut self) -> ThriftProtocolResult<&'a [u8]> {
670        unimplemented!()
671    }
672
673    fn read_bytes_owned(&mut self) -> ThriftProtocolResult<Vec<u8>> {
674        let len = self.read_vlq()? as usize;
675        let mut v = Vec::with_capacity(len);
676        std::io::copy(&mut self.reader.by_ref().take(len as u64), &mut v)?;
677        Ok(v)
678    }
679
680    fn skip_bytes(&mut self, n: usize) -> ThriftProtocolResult<()> {
681        let skipped = std::io::copy(
682            &mut self.reader.by_ref().take(n as u64),
683            &mut std::io::sink(),
684        )?;
685        if skipped != n as u64 {
686            return Err(ThriftProtocolError::Eof);
687        }
688        Ok(())
689    }
690
691    fn read_double(&mut self) -> ThriftProtocolResult<f64> {
692        let mut buf = [0_u8; 8];
693        self.reader.read_exact(&mut buf)?;
694        Ok(f64::from_le_bytes(buf))
695    }
696}
697
698/// Trait implemented for objects that can be deserialized from a Thrift input stream.
699/// Implementations are provided for Thrift primitive types.
700pub(crate) trait ReadThrift<'a, R: ThriftCompactInputProtocol<'a>> {
701    /// Read an object of type `Self` from the input protocol object.
702    fn read_thrift(prot: &mut R) -> Result<Self>
703    where
704        Self: Sized;
705}
706
707impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for bool {
708    fn read_thrift(prot: &mut R) -> Result<Self> {
709        Ok(prot.read_bool()?)
710    }
711}
712
713impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for i8 {
714    fn read_thrift(prot: &mut R) -> Result<Self> {
715        Ok(prot.read_i8()?)
716    }
717}
718
719impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for i16 {
720    fn read_thrift(prot: &mut R) -> Result<Self> {
721        Ok(prot.read_i16()?)
722    }
723}
724
725impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for i32 {
726    fn read_thrift(prot: &mut R) -> Result<Self> {
727        Ok(prot.read_i32()?)
728    }
729}
730
731impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for i64 {
732    fn read_thrift(prot: &mut R) -> Result<Self> {
733        Ok(prot.read_i64()?)
734    }
735}
736
737impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for OrderedF64 {
738    fn read_thrift(prot: &mut R) -> Result<Self> {
739        Ok(OrderedF64(prot.read_double()?))
740    }
741}
742
743impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for &'a str {
744    fn read_thrift(prot: &mut R) -> Result<Self> {
745        Ok(prot.read_string()?)
746    }
747}
748
749impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for String {
750    fn read_thrift(prot: &mut R) -> Result<Self> {
751        Ok(String::from_utf8(prot.read_bytes_owned()?)?)
752    }
753}
754
755impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for &'a [u8] {
756    fn read_thrift(prot: &mut R) -> Result<Self> {
757        Ok(prot.read_bytes()?)
758    }
759}
760
761/// Read a Thrift encoded [list] from the input protocol object.
762///
763/// [list]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#list-and-set
764pub(crate) fn read_thrift_vec<'a, T, R>(prot: &mut R) -> Result<Vec<T>>
765where
766    R: ThriftCompactInputProtocol<'a>,
767    T: ReadThrift<'a, R> + WriteThrift,
768{
769    let list_ident = prot.read_list_begin()?;
770    validate_list_type(T::ELEMENT_TYPE, &list_ident)?;
771    let size = list_ident.size as usize;
772    check_list_size(prot.remaining_bytes(), size)?;
773    let mut res = Vec::with_capacity(size);
774    for _ in 0..list_ident.size {
775        let val = T::read_thrift(prot)?;
776        res.push(val);
777    }
778    Ok(res)
779}
780
781pub(crate) fn validate_list_type(expected: ElementType, got: &ListIdentifier) -> Result<()> {
782    if got.element_type != expected {
783        return Err(general_err!(
784            "Expected list element type of {:?} but got {:?}",
785            expected,
786            got.element_type
787        ));
788    }
789    Ok(())
790}
791
792/// Reject a declared list size that cannot fit the remaining input. Each list
793/// element is at least one byte on the wire, so a size past the remaining
794/// bytes is a malformed header; bounding it before reserving stops a huge
795/// allocation. `remaining` is `None` when the protocol is not backed by a
796/// finite buffer, in which case there is nothing to check against.
797pub(crate) fn check_list_size(remaining: Option<usize>, size: usize) -> Result<()> {
798    if let Some(remaining) = remaining
799        && size > remaining
800    {
801        return Err(general_err!(
802            "Thrift list size {size} exceeds remaining input length {remaining}"
803        ));
804    }
805    Ok(())
806}
807
808/////////////////////////
809// thrift compact output
810
811/// Low-level object used to serialize structs to the Thrift [compact output] protocol.
812///
813/// This struct serves as a wrapper around a [`Write`] object, to which thrift encoded data
814/// will written. The implementation provides functions to write Thrift primitive types, as well
815/// as functions used in the encoding of lists and structs. This should rarely be used directly,
816/// but is instead intended for use by implementers of [`WriteThrift`] and [`WriteThriftField`].
817///
818/// [compact output]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md
819pub(crate) struct ThriftCompactOutputProtocol<W: Write> {
820    writer: W,
821    write_path_in_schema: bool,
822    write_rg_ordinal: bool,
823}
824
825impl<W: Write> ThriftCompactOutputProtocol<W> {
826    /// Create a new `ThriftCompactOutputProtocol` wrapping the byte sink `writer`.
827    pub(crate) fn new(writer: W) -> Self {
828        Self {
829            writer,
830            write_path_in_schema: true,
831            write_rg_ordinal: true,
832        }
833    }
834
835    // TODO(ets): at some point there should probably be a properties object
836    // to control aspects of thrift output. But since this is the only option to date
837    // I'm choosing a simpler API.
838    /// Control the writing of the `path_in_schema` element of the `ColumnMetaData`
839    pub(crate) fn set_write_path_in_schema(&mut self, val: bool) {
840        self.write_path_in_schema = val;
841    }
842
843    /// Indicate whether or not to emit `path_in_schema`.
844    pub(crate) fn write_path_in_schema(&self) -> bool {
845        self.write_path_in_schema
846    }
847
848    /// Control the writing of the `ordinal` element of the `RowGroup` struct.
849    ///
850    /// The Thrift `ordinal` field on the `RowGroup` struct is `i16`, but the
851    /// Thrift compact protocol allows for up to 2^31 elements in a list. If
852    /// more than 2^15 row groups are to be written, this can be set to `false`
853    /// to prevent writing the ordinal for some row groups but not others.
854    pub(crate) fn set_write_row_group_ordinal(&mut self, val: bool) {
855        self.write_rg_ordinal = val;
856    }
857
858    /// Indicate whether or not to emit `ordinal`.
859    pub(crate) fn write_row_group_ordinal(&self) -> bool {
860        self.write_rg_ordinal
861    }
862
863    /// Write a single byte to the output stream.
864    fn write_byte(&mut self, b: u8) -> Result<()> {
865        self.writer.write_all(&[b])?;
866        Ok(())
867    }
868
869    /// Write the given `u64` as a ULEB128 encoded varint.
870    fn write_vlq(&mut self, val: u64) -> Result<()> {
871        let mut v = val;
872        while v > 0x7f {
873            self.write_byte(v as u8 | 0x80)?;
874            v >>= 7;
875        }
876        self.write_byte(v as u8)
877    }
878
879    /// Write the given `i64` as a zig-zag encoded varint.
880    fn write_zig_zag(&mut self, val: i64) -> Result<()> {
881        let s = (val < 0) as i64;
882        self.write_vlq((((val ^ -s) << 1) + s) as u64)
883    }
884
885    /// Used to mark the start of a Thrift struct field of type `field_type`. `last_field_id`
886    /// is used to compute a delta to the given `field_id` per the compact protocol [spec].
887    ///
888    /// [spec]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#struct-encoding
889    pub(crate) fn write_field_begin(
890        &mut self,
891        field_type: FieldType,
892        field_id: i16,
893        last_field_id: i16,
894    ) -> Result<()> {
895        let delta = field_id.wrapping_sub(last_field_id);
896        if delta > 0 && delta <= 0xf {
897            self.write_byte(((delta as u8) << 4) | field_type as u8)
898        } else {
899            self.write_byte(field_type as u8)?;
900            self.write_i16(field_id)
901        }
902    }
903
904    /// Used to indicate the start of a list of `element_type` elements.
905    pub(crate) fn write_list_begin(&mut self, element_type: ElementType, len: usize) -> Result<()> {
906        if len < 15 {
907            self.write_byte(((len as u8) << 4) | element_type as u8)
908        } else {
909            self.write_byte(0xf0u8 | element_type as u8)?;
910            self.write_vlq(len as _)
911        }
912    }
913
914    /// Used to mark the end of a struct. This must be called after all fields of the struct have
915    /// been written.
916    pub(crate) fn write_struct_end(&mut self) -> Result<()> {
917        self.write_byte(0)
918    }
919
920    /// Serialize a slice of `u8`s. This will encode a length, and then write the bytes without
921    /// further encoding.
922    pub(crate) fn write_bytes(&mut self, val: &[u8]) -> Result<()> {
923        self.write_vlq(val.len() as u64)?;
924        self.writer.write_all(val)?;
925        Ok(())
926    }
927
928    /// Short-cut method used to encode structs that have no fields (often used in Thrift unions).
929    /// This simply encodes the field id and then immediately writes the end-of-struct marker.
930    pub(crate) fn write_empty_struct(&mut self, field_id: i16, last_field_id: i16) -> Result<i16> {
931        self.write_field_begin(FieldType::Struct, field_id, last_field_id)?;
932        self.write_struct_end()?;
933        Ok(last_field_id)
934    }
935
936    /// Write a boolean value.
937    pub(crate) fn write_bool(&mut self, val: bool) -> Result<()> {
938        match val {
939            true => self.write_byte(1),
940            false => self.write_byte(2),
941        }
942    }
943
944    /// Write a zig-zag encoded `i8` value.
945    pub(crate) fn write_i8(&mut self, val: i8) -> Result<()> {
946        self.write_byte(val as u8)
947    }
948
949    /// Write a zig-zag encoded `i16` value.
950    pub(crate) fn write_i16(&mut self, val: i16) -> Result<()> {
951        self.write_zig_zag(val as _)
952    }
953
954    /// Write a zig-zag encoded `i32` value.
955    pub(crate) fn write_i32(&mut self, val: i32) -> Result<()> {
956        self.write_zig_zag(val as _)
957    }
958
959    /// Write a zig-zag encoded `i64` value.
960    pub(crate) fn write_i64(&mut self, val: i64) -> Result<()> {
961        self.write_zig_zag(val)
962    }
963
964    /// Write a double value.
965    pub(crate) fn write_double(&mut self, val: f64) -> Result<()> {
966        self.writer.write_all(&val.to_le_bytes())?;
967        Ok(())
968    }
969}
970
971/// Trait implemented by objects that are to be serialized to a Thrift [compact output] protocol
972/// stream. Implementations are also provided for primitive Thrift types.
973///
974/// [compact output]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md
975pub(crate) trait WriteThrift {
976    /// The [`ElementType`] to use when a list of this object is written.
977    const ELEMENT_TYPE: ElementType;
978
979    /// Serialize this object to the given `writer`.
980    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()>;
981}
982
983/// Implementation for a vector of thrift serializable objects that implement [`WriteThrift`].
984/// This will write the necessary list header and then serialize the elements one-at-a-time.
985impl<T> WriteThrift for Vec<T>
986where
987    T: WriteThrift,
988{
989    const ELEMENT_TYPE: ElementType = ElementType::List;
990
991    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
992        writer.write_list_begin(T::ELEMENT_TYPE, self.len())?;
993        for item in self {
994            item.write_thrift(writer)?;
995        }
996        Ok(())
997    }
998}
999
1000impl WriteThrift for bool {
1001    const ELEMENT_TYPE: ElementType = ElementType::Bool;
1002
1003    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1004        writer.write_bool(*self)
1005    }
1006}
1007
1008impl WriteThrift for i8 {
1009    const ELEMENT_TYPE: ElementType = ElementType::Byte;
1010
1011    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1012        writer.write_i8(*self)
1013    }
1014}
1015
1016impl WriteThrift for i16 {
1017    const ELEMENT_TYPE: ElementType = ElementType::I16;
1018
1019    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1020        writer.write_i16(*self)
1021    }
1022}
1023
1024impl WriteThrift for i32 {
1025    const ELEMENT_TYPE: ElementType = ElementType::I32;
1026
1027    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1028        writer.write_i32(*self)
1029    }
1030}
1031
1032impl WriteThrift for i64 {
1033    const ELEMENT_TYPE: ElementType = ElementType::I64;
1034
1035    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1036        writer.write_i64(*self)
1037    }
1038}
1039
1040impl WriteThrift for OrderedF64 {
1041    const ELEMENT_TYPE: ElementType = ElementType::Double;
1042
1043    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1044        writer.write_double(self.0)
1045    }
1046}
1047
1048impl WriteThrift for f64 {
1049    const ELEMENT_TYPE: ElementType = ElementType::Double;
1050
1051    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1052        writer.write_double(*self)
1053    }
1054}
1055
1056impl WriteThrift for &[u8] {
1057    const ELEMENT_TYPE: ElementType = ElementType::Binary;
1058
1059    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1060        writer.write_bytes(self)
1061    }
1062}
1063
1064impl WriteThrift for &str {
1065    const ELEMENT_TYPE: ElementType = ElementType::Binary;
1066
1067    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1068        writer.write_bytes(self.as_bytes())
1069    }
1070}
1071
1072impl WriteThrift for String {
1073    const ELEMENT_TYPE: ElementType = ElementType::Binary;
1074
1075    fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1076        writer.write_bytes(self.as_bytes())
1077    }
1078}
1079
1080/// Trait implemented by objects that are fields of Thrift structs.
1081///
1082/// For example, given the Thrift struct definition
1083/// ```ignore
1084/// struct MyStruct {
1085///   1: required i32 field1
1086///   2: optional bool field2
1087///   3: optional OtherStruct field3
1088/// }
1089/// ```
1090///
1091/// which becomes in Rust
1092/// ```ignore
1093/// # struct OtherStruct {}
1094/// struct MyStruct {
1095///   field1: i32,
1096///   field2: Option<bool>,
1097///   field3: Option<OtherStruct>,
1098/// }
1099/// ```
1100/// the impl of `WriteThrift` for `MyStruct` will use the `WriteThriftField` impls for `i32`,
1101/// `bool`, and `OtherStruct`.
1102///
1103/// ```ignore
1104/// impl WriteThrift for MyStruct {
1105///   fn write_thrift<W: Write>(&self, writer: &mut ThriftCompactOutputProtocol<W>) -> Result<()> {
1106///     let mut last_field_id = 0i16;
1107///     last_field_id = self.field1.write_thrift_field(writer, 1, last_field_id)?;
1108///     if self.field2.is_some() {
1109///       // if field2 is `None` then this assignment won't happen and last_field_id will remain
1110///       // `1` when writing `field3`
1111///       last_field_id = self.field2.write_thrift_field(writer, 2, last_field_id)?;
1112///     }
1113///     if self.field3.is_some() {
1114///       // no need to assign last_field_id since this is the final field.
1115///       self.field3.write_thrift_field(writer, 3, last_field_id)?;
1116///     }
1117///     writer.write_struct_end()
1118///   }
1119/// }
1120/// ```
1121///
1122pub(crate) trait WriteThriftField {
1123    /// Used to write struct fields (which may be primitive or IDL defined types). This will
1124    /// write the field marker for the given `field_id`, using `last_field_id` to compute the
1125    /// field delta used by the Thrift [compact protocol]. On success this will return `field_id`
1126    /// to be used in chaining.
1127    ///
1128    /// [compact protocol]: https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md#struct-encoding
1129    fn write_thrift_field<W: Write>(
1130        &self,
1131        writer: &mut ThriftCompactOutputProtocol<W>,
1132        field_id: i16,
1133        last_field_id: i16,
1134    ) -> Result<i16>;
1135}
1136
1137// bool struct fields are written differently to bool values
1138impl WriteThriftField for bool {
1139    fn write_thrift_field<W: Write>(
1140        &self,
1141        writer: &mut ThriftCompactOutputProtocol<W>,
1142        field_id: i16,
1143        last_field_id: i16,
1144    ) -> Result<i16> {
1145        // boolean only writes the field header
1146        match *self {
1147            true => writer.write_field_begin(FieldType::BooleanTrue, field_id, last_field_id)?,
1148            false => writer.write_field_begin(FieldType::BooleanFalse, field_id, last_field_id)?,
1149        }
1150        Ok(field_id)
1151    }
1152}
1153
1154write_thrift_field!(i8, FieldType::Byte);
1155write_thrift_field!(i16, FieldType::I16);
1156write_thrift_field!(i32, FieldType::I32);
1157write_thrift_field!(i64, FieldType::I64);
1158write_thrift_field!(OrderedF64, FieldType::Double);
1159write_thrift_field!(f64, FieldType::Double);
1160write_thrift_field!(String, FieldType::Binary);
1161
1162impl WriteThriftField for &[u8] {
1163    fn write_thrift_field<W: Write>(
1164        &self,
1165        writer: &mut ThriftCompactOutputProtocol<W>,
1166        field_id: i16,
1167        last_field_id: i16,
1168    ) -> Result<i16> {
1169        writer.write_field_begin(FieldType::Binary, field_id, last_field_id)?;
1170        writer.write_bytes(self)?;
1171        Ok(field_id)
1172    }
1173}
1174
1175impl WriteThriftField for &str {
1176    fn write_thrift_field<W: Write>(
1177        &self,
1178        writer: &mut ThriftCompactOutputProtocol<W>,
1179        field_id: i16,
1180        last_field_id: i16,
1181    ) -> Result<i16> {
1182        writer.write_field_begin(FieldType::Binary, field_id, last_field_id)?;
1183        writer.write_bytes(self.as_bytes())?;
1184        Ok(field_id)
1185    }
1186}
1187
1188impl<T> WriteThriftField for Vec<T>
1189where
1190    T: WriteThrift,
1191{
1192    fn write_thrift_field<W: Write>(
1193        &self,
1194        writer: &mut ThriftCompactOutputProtocol<W>,
1195        field_id: i16,
1196        last_field_id: i16,
1197    ) -> Result<i16> {
1198        writer.write_field_begin(FieldType::List, field_id, last_field_id)?;
1199        self.write_thrift(writer)?;
1200        Ok(field_id)
1201    }
1202}
1203
1204#[cfg(test)]
1205pub(crate) mod tests {
1206    use crate::basic::{TimeUnit, Type};
1207
1208    use super::*;
1209    use std::fmt::Debug;
1210
1211    pub(crate) fn test_roundtrip<T>(val: T)
1212    where
1213        T: for<'a> ReadThrift<'a, ThriftSliceInputProtocol<'a>> + WriteThrift + PartialEq + Debug,
1214    {
1215        let mut buf = Vec::<u8>::new();
1216        {
1217            let mut writer = ThriftCompactOutputProtocol::new(&mut buf);
1218            val.write_thrift(&mut writer).unwrap();
1219        }
1220
1221        let mut prot = ThriftSliceInputProtocol::new(&buf);
1222        let read_val = T::read_thrift(&mut prot).unwrap();
1223        assert_eq!(val, read_val);
1224    }
1225
1226    #[test]
1227    fn test_enum_roundtrip() {
1228        test_roundtrip(Type::BOOLEAN);
1229        test_roundtrip(Type::INT32);
1230        test_roundtrip(Type::INT64);
1231        test_roundtrip(Type::INT96);
1232        test_roundtrip(Type::FLOAT);
1233        test_roundtrip(Type::DOUBLE);
1234        test_roundtrip(Type::BYTE_ARRAY);
1235        test_roundtrip(Type::FIXED_LEN_BYTE_ARRAY);
1236    }
1237
1238    #[test]
1239    fn test_union_all_empty_roundtrip() {
1240        test_roundtrip(TimeUnit::MILLIS);
1241        test_roundtrip(TimeUnit::MICROS);
1242        test_roundtrip(TimeUnit::NANOS);
1243    }
1244
1245    #[test]
1246    fn test_decode_empty_list() {
1247        let data = vec![0u8; 1];
1248        let mut prot = ThriftSliceInputProtocol::new(&data);
1249        let header = prot.read_list_begin().expect("error reading list header");
1250        assert_eq!(header.size, 0);
1251        assert_eq!(header.element_type, ElementType::Byte);
1252    }
1253
1254    /// A Thrift list header whose `size` varint decodes above `i32::MAX`
1255    /// must be rejected at the protocol layer rather than wrapping into a
1256    /// negative `i32` and being smuggled into downstream allocation code.
1257    #[test]
1258    fn test_read_list_begin_size_above_i32_max_returns_err() {
1259        // List header: element_type=8 (Binary), 0xF=follow-up varint.
1260        // Varint 80 80 80 80 08 decodes to 0x8000_0000 = i32::MAX + 1.
1261        let mut data: Vec<u8> = vec![0xF8];
1262        data.extend_from_slice(&[0x80, 0x80, 0x80, 0x80, 0x08]);
1263        let mut prot = ThriftSliceInputProtocol::new(&data);
1264        let result = prot.read_list_begin();
1265        assert!(result.is_err(), "expected error, got {result:?}");
1266    }
1267
1268    // Unknown fields must leave the following known field aligned.
1269    crate::thrift_struct! {
1270        struct KnownField {
1271            2: required i32 value;
1272        }
1273    }
1274
1275    #[test]
1276    fn test_skip_boolean_collections_before_known_field() {
1277        for collection_type in [FieldType::List, FieldType::Set] {
1278            // False then true must not be mistaken for inline field headers.
1279            // Known field 2 follows with i32(42).
1280            let data = [0x10 | collection_type as u8, 0x22, 2, 1, 0x15, 84, 0];
1281            let mut protocol = ThriftSliceInputProtocol::new(&data);
1282            assert_eq!(KnownField::read_thrift(&mut protocol).unwrap().value, 42);
1283            assert!(protocol.as_slice().is_empty());
1284        }
1285    }
1286
1287    #[test]
1288    fn test_skip_map_before_known_field() {
1289        // Unknown field 1: map<bool, i32> with two entries. Known field 2: i32(42).
1290        let data = [0x1b, 2, 0x25, 1, 14, 2, 16, 0x15, 84, 0];
1291        let mut protocol = ThriftSliceInputProtocol::new(&data);
1292        assert_eq!(KnownField::read_thrift(&mut protocol).unwrap().value, 42);
1293        assert!(protocol.as_slice().is_empty());
1294    }
1295
1296    #[test]
1297    fn test_read_list_wrong_type() {
1298        // list header: 4 elements of `Boolean`
1299        let data = [0x42, 0x01];
1300        let mut prot = ThriftSliceInputProtocol::new(&data);
1301        // try to read as list<i32>
1302        let result = read_thrift_vec::<i32, ThriftSliceInputProtocol>(&mut prot);
1303        println!("{result:?}");
1304        assert!(
1305            result
1306                .unwrap_err()
1307                .to_string()
1308                .contains("Expected list element type of I32 but got Bool")
1309        );
1310    }
1311
1312    #[test]
1313    fn test_read_thrift_vec_roundtrip_i32() {
1314        // 2-element list of i32: header 0x25 (count=2, type=I32), two zigzag zeros.
1315        let data = [0x25, 0x00, 0x00];
1316        let mut prot = ThriftSliceInputProtocol::new(&data);
1317        let result = read_thrift_vec::<i32, ThriftSliceInputProtocol>(&mut prot).unwrap();
1318        assert_eq!(result, vec![0, 0]);
1319    }
1320
1321    #[test]
1322    fn test_read_thrift_vec_size_exceeds_remaining_returns_err() {
1323        // Header 0xE5: 14 i32 elements, no payload. After reading the header
1324        // zero bytes remain, so this must error instead of reserving 14 slots
1325        // (and, for a larger declared size, gigabytes).
1326        let data = [0xE5];
1327        let mut prot = ThriftSliceInputProtocol::new(&data);
1328        let result = read_thrift_vec::<i32, ThriftSliceInputProtocol>(&mut prot);
1329        assert!(result.is_err(), "expected error, got {result:?}");
1330        assert!(
1331            result
1332                .unwrap_err()
1333                .to_string()
1334                .contains("Thrift list size 14 exceeds remaining input length 0")
1335        );
1336    }
1337
1338    #[test]
1339    fn test_read_thrift_vec_huge_declared_size_returns_err() {
1340        // Compact list header: 0xF5 = I32 elements, size follows as a varint.
1341        // 0xfc 0xfc 0xfc 0x33 decodes to 109_002_364 — the schema-list case
1342        // from #10920 — with only two leftover bytes.
1343        let data = [0xF5, 0xfc, 0xfc, 0xfc, 0x33, 0x00, 0x00];
1344        let mut prot = ThriftSliceInputProtocol::new(&data);
1345        let result = read_thrift_vec::<i32, ThriftSliceInputProtocol>(&mut prot);
1346        assert!(result.is_err(), "expected error, got {result:?}");
1347        let err = result.unwrap_err().to_string();
1348        assert!(
1349            err.contains("Thrift list size 109002364 exceeds remaining input length 2"),
1350            "{err}"
1351        );
1352    }
1353}