diff --git a/parquet/src/arrow/arrow_reader/mod.rs b/parquet/src/arrow/arrow_reader/mod.rs index 9a65587e2c45..a9190062b340 100644 --- a/parquet/src/arrow/arrow_reader/mod.rs +++ b/parquet/src/arrow/arrow_reader/mod.rs @@ -42,8 +42,6 @@ mod filter; mod selection; pub mod statistics; -use crate::file::footer; -use crate::file::page_index::index_reader; use crate::encryption::ciphers::FileDecryptionProperties; /// Builder for constructing parquet readers into arrow. @@ -373,35 +371,6 @@ pub struct ArrowReaderMetadata { } impl ArrowReaderMetadata { - /// Loads [`ArrowReaderMetadata`] from the provided [`ChunkReader`] - /// - /// See [`ParquetRecordBatchReaderBuilder::new_with_metadata`] for how this can be used - pub fn load2(reader: &T, options: ArrowReaderOptions) -> Result { - Self::load_with_decryption(reader, options, FileDecryptionProperties::builder().build()) - } - - pub fn load_with_decryption(reader: &T, options: ArrowReaderOptions, - file_decryption_properties: FileDecryptionProperties) -> Result { - let mut metadata = footer::parse_metadata_with_decryption(reader, file_decryption_properties)?; - if options.page_index { - let column_index = metadata - .row_groups() - .iter() - .map(|rg| index_reader::read_columns_indexes(reader, rg.columns())) - .collect::>>()?; - metadata.set_column_index(Some(column_index)); - - let offset_index = metadata - .row_groups() - .iter() - .map(|rg| index_reader::read_offset_indexes(reader, rg.columns())) - .collect::>>()?; - - metadata.set_offset_index(Some(offset_index)) - } - Self::try_new(Arc::new(metadata), options) - } - /// Loads [`ArrowReaderMetadata`] from the provided [`ChunkReader`], if necessary /// /// See [`ParquetRecordBatchReaderBuilder::new_with_metadata`] for an @@ -412,9 +381,14 @@ impl ArrowReaderMetadata { /// If `options` has [`ArrowReaderOptions::with_page_index`] true, but /// `Self::metadata` is missing the page index, this function will attempt /// to load the page index by making an object store request. - pub fn load(reader: &T, options: ArrowReaderOptions) -> Result { + pub fn load( + reader: &T, + options: ArrowReaderOptions, + file_decryption_properties: Option, + ) -> Result { let metadata = ParquetMetaDataReader::new() .with_page_indexes(options.page_index) + .with_encryption_properties(file_decryption_properties) .parse_and_finish(reader)?; Self::try_new(Arc::new(metadata), options) } @@ -561,12 +535,16 @@ impl ParquetRecordBatchReaderBuilder { /// Create a new [`ParquetRecordBatchReaderBuilder`] with [`ArrowReaderOptions`] pub fn try_new_with_options(reader: T, options: ArrowReaderOptions) -> Result { - let metadata = ArrowReaderMetadata::load(&reader, options)?; + let metadata = ArrowReaderMetadata::load(&reader, options, None)?; Ok(Self::new_with_metadata(reader, metadata)) } - pub fn try_new_with_decryption(reader: T, options: ArrowReaderOptions, file_decryption_properties: FileDecryptionProperties) -> Result { - let metadata = ArrowReaderMetadata::load_with_decryption(&reader, options, file_decryption_properties)?; + pub fn try_new_with_decryption( + reader: T, + options: ArrowReaderOptions, + file_decryption_properties: Option, + ) -> Result { + let metadata = ArrowReaderMetadata::load(&reader, options, file_decryption_properties)?; Ok(Self::new_with_metadata(reader, metadata)) } @@ -826,11 +804,18 @@ impl ParquetRecordBatchReader { .build() } - pub fn try_new_with_decryption(reader: T, batch_size: usize, - file_decryption_properties: FileDecryptionProperties) -> Result { - ParquetRecordBatchReaderBuilder::try_new_with_decryption(reader, Default::default(), file_decryption_properties)? - .with_batch_size(batch_size) - .build() + pub fn try_new_with_decryption( + reader: T, + batch_size: usize, + file_decryption_properties: Option, + ) -> Result { + ParquetRecordBatchReaderBuilder::try_new_with_decryption( + reader, + Default::default(), + file_decryption_properties, + )? + .with_batch_size(batch_size) + .build() } /// Create a new [`ParquetRecordBatchReader`] from the provided [`RowGroups`] @@ -1719,10 +1704,14 @@ mod tests { // todo let key_code: &[u8] = "0123456789012345".as_bytes(); // todo - let decryption_properties = ciphers::FileDecryptionProperties::builder() - .with_footer_key(key_code.to_vec()) - .build(); - let record_reader = ParquetRecordBatchReader::try_new_with_decryption(file, 128, decryption_properties).unwrap(); + let decryption_properties = Some( + ciphers::FileDecryptionProperties::builder() + .with_footer_key(key_code.to_vec()) + .build(), + ); + let record_reader = + ParquetRecordBatchReader::try_new_with_decryption(file, 128, decryption_properties) + .unwrap(); // todo check contents } diff --git a/parquet/src/arrow/async_reader/mod.rs b/parquet/src/arrow/async_reader/mod.rs index cee5f11dd598..c10443418ae4 100644 --- a/parquet/src/arrow/async_reader/mod.rs +++ b/parquet/src/arrow/async_reader/mod.rs @@ -205,7 +205,10 @@ impl AsyncFileReader for T { let mut buf = Vec::with_capacity(metadata_len); self.take(metadata_len as _).read_to_end(&mut buf).await?; - Ok(Arc::new(ParquetMetaDataReader::decode_metadata(&buf)?)) + // TODO: add self.file_decryption_properties + Ok(Arc::new(ParquetMetaDataReader::decode_metadata( + &buf, None, + )?)) } .boxed() } diff --git a/parquet/src/encryption/ciphers.rs b/parquet/src/encryption/ciphers.rs index db32146c6d5f..a067b56a4e6c 100644 --- a/parquet/src/encryption/ciphers.rs +++ b/parquet/src/encryption/ciphers.rs @@ -226,6 +226,7 @@ pub fn create_module_aad(file_aad: &[u8], module_type: ModuleType, row_group_ord Ok(aad) } +#[derive(Clone)] pub struct FileDecryptionProperties { footer_key: Option> } diff --git a/parquet/src/file/footer.rs b/parquet/src/file/footer.rs index c7829420b514..3192eac4cde0 100644 --- a/parquet/src/file/footer.rs +++ b/parquet/src/file/footer.rs @@ -17,20 +17,9 @@ //! Module for working with Parquet file footers. -use std::{io::Read, sync::Arc}; - -use crate::format::{ColumnOrder as TColumnOrder, FileMetaData as TFileMetaData, - FileCryptoMetaData as TFileCryptoMetaData, EncryptionAlgorithm}; -use crate::thrift::{TCompactSliceInputProtocol, TSerializable}; - -use crate::basic::ColumnOrder; -use crate::encryption::ciphers; -use crate::encryption::ciphers::{BlockDecryptor, FileDecryptionProperties, FileDecryptor}; -use crate::errors::{ParquetError, Result}; -use crate::file::{metadata::*, reader::ChunkReader, - FOOTER_SIZE, PARQUET_MAGIC, PARQUET_MAGIC_ENCR_FOOTER}; - -use crate::schema::types::{self, SchemaDescriptor}; +use crate::encryption::ciphers::FileDecryptionProperties; +use crate::errors::Result; +use crate::file::{metadata::*, reader::ChunkReader, FOOTER_SIZE}; /// Reads the [ParquetMetaData] from the footer of the parquet file. /// @@ -61,105 +50,19 @@ pub fn parse_metadata(chunk_reader: &R) -> Result(chunk_reader: &R) -> Result { - parse_metadata_with_decryption(chunk_reader, FileDecryptionProperties::builder().build()) -} - -pub fn parse_metadata_with_decryption(chunk_reader: &R, decr_props: FileDecryptionProperties) -> Result { - // check file is large enough to hold footer - let file_size = chunk_reader.len(); - if file_size < (FOOTER_SIZE as u64) { - return Err(general_err!( - "Invalid Parquet file. Size is smaller than footer" - )); - } - - let mut footer = [0_u8; 8]; - chunk_reader - .get_read(file_size - 8)? - .read_exact(&mut footer)?; - - let encrypted_footer; - // check this is indeed a parquet file - if footer[4..] == PARQUET_MAGIC { - encrypted_footer = false; - } else if footer[4..] == PARQUET_MAGIC_ENCR_FOOTER { - encrypted_footer = true; - //panic!() // todo rm - } else { - return Err(general_err!("Invalid Parquet file. Corrupt footer")); - } - - // get the metadata length from the footer - let metadata_len = u32::from_le_bytes(footer[..4].try_into().unwrap()) as usize; - - //let metadata_len = decode_footer(&footer)?; todo rm this function - let footer_metadata_len = FOOTER_SIZE + metadata_len; - - if footer_metadata_len > file_size as usize { - return Err(general_err!( - "Invalid Parquet file. Reported metadata length of {} + {} byte footer, but file is only {} bytes", - metadata_len, - FOOTER_SIZE, - file_size - )); - } - - let start = file_size - footer_metadata_len as u64; - - if encrypted_footer { - let file_decryptor = FileDecryptor::new(decr_props); - decode_encrypted_metadata(chunk_reader.get_bytes(start, metadata_len)?.as_ref(), file_decryptor) - } else { - decode_metadata(chunk_reader.get_bytes(start, metadata_len)?.as_ref()) - } -} - /// Decodes [`ParquetMetaData`] from the provided bytes. /// /// Typically this is used to decode the metadata from the end of a parquet -/// file. The format of `buf` is the Thift compact binary protocol, as specified +/// file. The format of `buf` is the Thrift compact binary protocol, as specified /// by the [Parquet Spec]. /// /// [Parquet Spec]: https://github.com/apache/parquet-format#metadata #[deprecated(since = "53.1.0", note = "Use ParquetMetaDataReader::decode_metadata")] -pub fn decode_metadata(buf: &[u8]) -> Result { - ParquetMetaDataReader::decode_metadata(buf) -} - -pub fn decode_metadata2(buf: &[u8]) -> Result { - decode_metadata_with_decryption(buf) -} - -/// Decodes [`ParquetMetaData`] from the provided bytes -// todo add file decryptor -pub fn decode_metadata_with_decryption(buf: &[u8]) -> Result { - // TODO: row group filtering - let mut prot = TCompactSliceInputProtocol::new(buf); - let t_file_metadata: TFileMetaData = TFileMetaData::read_from_in_protocol(&mut prot) - .map_err(|e| ParquetError::General(format!("Could not parse metadata: {e}")))?; - let schema = types::from_thrift(&t_file_metadata.schema)?; - let schema_descr = Arc::new(SchemaDescriptor::new(schema)); - let mut row_groups = Vec::new(); - for rg in t_file_metadata.row_groups { - row_groups.push(RowGroupMetaData::from_thrift(schema_descr.clone(), rg)?); - } - let column_orders = parse_column_orders(t_file_metadata.column_orders, &schema_descr); - - if t_file_metadata.encryption_algorithm.is_some() { - // todo get key_metadata etc. Set file decryptor in return value - // todo check signature - } - - let file_metadata = FileMetaData::new( - t_file_metadata.version, - t_file_metadata.num_rows, - t_file_metadata.created_by, - t_file_metadata.key_value_metadata, - schema_descr, - column_orders, - ); - Ok(ParquetMetaData::new(file_metadata, row_groups)) +pub fn decode_metadata( + buf: &[u8], + file_decryption_properties: Option, +) -> Result { + ParquetMetaDataReader::decode_metadata(buf, file_decryption_properties) } /// Decodes the Parquet footer returning the metadata length in bytes @@ -177,162 +80,3 @@ pub fn decode_metadata_with_decryption(buf: &[u8]) -> Result { pub fn decode_footer(slice: &[u8; FOOTER_SIZE]) -> Result { ParquetMetaDataReader::decode_footer(slice) } - -fn decode_encrypted_metadata(buf: &[u8], file_decryptor: FileDecryptor) -> Result { - // parse FileCryptoMetaData - let mut prot = TCompactSliceInputProtocol::new(buf.as_ref()); - let t_file_crypto_metadata: TFileCryptoMetaData = TFileCryptoMetaData::read_from_in_protocol(&mut prot) - .map_err(|e| ParquetError::General(format!("Could not parse crypto metadata: {e}")))?; - let algo = t_file_crypto_metadata.encryption_algorithm; - let aes_gcm_algo = if let EncryptionAlgorithm::AESGCMV1(a) = algo { a } - else { unreachable!() }; // todo decr: add support for GCMCTRV1 - - // todo decr: get key_metadata - - // remaining buffer contains encrypted FileMetaData - let decryptor = file_decryptor.get_footer_decryptor(); - // todo decr: get aad_prefix - // todo decr: set both aad_prefix and aad_file_unique in file_decryptor - let fmd_aad = ciphers::create_footer_aad(aes_gcm_algo.aad_file_unique.unwrap().as_ref()); - let decrypted_fmd_buf = decryptor.decrypt(prot.as_slice().as_ref(), fmd_aad.unwrap().as_ref()); - - // todo add file decryptor - decode_metadata_with_decryption(decrypted_fmd_buf.as_slice()) -} - -// todo decr: add encryption support -/// Decodes the footer returning the metadata length in bytes -pub fn decode_footer2(slice: &[u8; FOOTER_SIZE]) -> Result { - // check this is indeed a parquet file - if slice[4..] != PARQUET_MAGIC { - return Err(general_err!("Invalid Parquet file. Corrupt footer")); - } - - // get the metadata length from the footer - let metadata_len = u32::from_le_bytes(slice[..4].try_into().unwrap()); - // u32 won't be larger than usize in most cases - Ok(metadata_len as usize) -} - -/// Parses column orders from Thrift definition. -/// If no column orders are defined, returns `None`. -fn parse_column_orders( - t_column_orders: Option>, - schema_descr: &SchemaDescriptor, -) -> Option> { - match t_column_orders { - Some(orders) => { - // Should always be the case - assert_eq!( - orders.len(), - schema_descr.num_columns(), - "Column order length mismatch" - ); - let mut res = Vec::new(); - for (i, column) in schema_descr.columns().iter().enumerate() { - match orders[i] { - TColumnOrder::TYPEORDER(_) => { - let sort_order = ColumnOrder::get_sort_order( - column.logical_type(), - column.converted_type(), - column.physical_type(), - ); - res.push(ColumnOrder::TYPE_DEFINED_ORDER(sort_order)); - } - } - } - Some(res) - } - None => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bytes::Bytes; - - use crate::basic::SortOrder; - use crate::basic::Type; - use crate::format::TypeDefinedOrder; - use crate::schema::types::Type as SchemaType; - - #[test] - fn test_parse_metadata_size_smaller_than_footer() { - let test_file = tempfile::tempfile().unwrap(); - let reader_result = parse_metadata2(&test_file); - assert_eq!( - reader_result.unwrap_err().to_string(), - "Parquet error: Invalid Parquet file. Size is smaller than footer" - ); - } - - #[test] - fn test_parse_metadata_corrupt_footer() { - let data = Bytes::from(vec![1, 2, 3, 4, 5, 6, 7, 8]); - let reader_result = parse_metadata2(&data); - assert_eq!( - reader_result.unwrap_err().to_string(), - "Parquet error: Invalid Parquet file. Corrupt footer" - ); - } - - #[test] - fn test_parse_metadata_invalid_start() { - let test_file = Bytes::from(vec![255, 0, 0, 0, b'P', b'A', b'R', b'1']); - let reader_result = parse_metadata2(&test_file); - assert_eq!( - reader_result.unwrap_err().to_string(), - "Parquet error: Invalid Parquet file. Reported metadata length of 255 + 8 byte footer, but file is only 8 bytes" - ); - } - - #[test] - fn test_metadata_column_orders_parse() { - // Define simple schema, we do not need to provide logical types. - let fields = vec![ - Arc::new( - SchemaType::primitive_type_builder("col1", Type::INT32) - .build() - .unwrap(), - ), - Arc::new( - SchemaType::primitive_type_builder("col2", Type::FLOAT) - .build() - .unwrap(), - ), - ]; - let schema = SchemaType::group_type_builder("schema") - .with_fields(fields) - .build() - .unwrap(); - let schema_descr = SchemaDescriptor::new(Arc::new(schema)); - - let t_column_orders = Some(vec![ - TColumnOrder::TYPEORDER(TypeDefinedOrder::new()), - TColumnOrder::TYPEORDER(TypeDefinedOrder::new()), - ]); - - assert_eq!( - parse_column_orders(t_column_orders, &schema_descr), - Some(vec![ - ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED), - ColumnOrder::TYPE_DEFINED_ORDER(SortOrder::SIGNED) - ]) - ); - - // Test when no column orders are defined. - assert_eq!(parse_column_orders(None, &schema_descr), None); - } - - #[test] - #[should_panic(expected = "Column order length mismatch")] - fn test_metadata_column_orders_len_mismatch() { - let schema = SchemaType::group_type_builder("schema").build().unwrap(); - let schema_descr = SchemaDescriptor::new(Arc::new(schema)); - - let t_column_orders = Some(vec![TColumnOrder::TYPEORDER(TypeDefinedOrder::new())]); - - parse_column_orders(t_column_orders, &schema_descr); - } -} diff --git a/parquet/src/file/metadata/reader.rs b/parquet/src/file/metadata/reader.rs index 2a927f15fb64..96d19fbfb83d 100644 --- a/parquet/src/file/metadata/reader.rs +++ b/parquet/src/file/metadata/reader.rs @@ -20,13 +20,19 @@ use std::{io::Read, ops::Range, sync::Arc}; use bytes::Bytes; use crate::basic::ColumnOrder; +use crate::encryption::ciphers::{ + create_footer_aad, BlockDecryptor, FileDecryptionProperties, FileDecryptor, +}; use crate::errors::{ParquetError, Result}; use crate::file::metadata::{FileMetaData, ParquetMetaData, RowGroupMetaData}; use crate::file::page_index::index::Index; use crate::file::page_index::index_reader::{acc_range, decode_column_index, decode_offset_index}; use crate::file::reader::ChunkReader; -use crate::file::{FOOTER_SIZE, PARQUET_MAGIC}; -use crate::format::{ColumnOrder as TColumnOrder, FileMetaData as TFileMetaData}; +use crate::file::{FOOTER_SIZE, PARQUET_MAGIC, PARQUET_MAGIC_ENCR_FOOTER}; +use crate::format::{ + ColumnOrder as TColumnOrder, EncryptionAlgorithm, FileCryptoMetaData as TFileCryptoMetaData, + FileMetaData as TFileMetaData, +}; use crate::schema::types; use crate::schema::types::SchemaDescriptor; use crate::thrift::{TCompactSliceInputProtocol, TSerializable}; @@ -68,6 +74,7 @@ pub struct ParquetMetaDataReader { // Size of the serialized thrift metadata plus the 8 byte footer. Only set if // `self.parse_metadata` is called. metadata_size: Option, + file_decryption_properties: Option, } impl ParquetMetaDataReader { @@ -126,6 +133,17 @@ impl ParquetMetaDataReader { self } + /// Provide the [`FileDecryptionProperties`] to use when decrypting the file. + /// + /// This is only necessary when the file is encrypted. + pub fn with_encryption_properties( + mut self, + properties: Option, + ) -> Self { + self.file_decryption_properties = properties; + self + } + /// Indicates whether this reader has a [`ParquetMetaData`] internally. pub fn has_metadata(&self) -> bool { self.metadata.is_some() @@ -342,8 +360,13 @@ impl ParquetMetaDataReader { mut fetch: F, file_size: usize, ) -> Result<()> { - let (metadata, remainder) = - Self::load_metadata(&mut fetch, file_size, self.get_prefetch_size()).await?; + let (metadata, remainder) = Self::load_metadata( + &mut fetch, + file_size, + self.get_prefetch_size(), + self.file_decryption_properties.clone(), + ) + .await?; self.metadata = Some(metadata); @@ -507,7 +530,10 @@ impl ParquetMetaDataReader { } let start = file_size - footer_metadata_len as u64; - Self::decode_metadata(chunk_reader.get_bytes(start, metadata_len)?.as_ref()) + Self::decode_metadata( + chunk_reader.get_bytes(start, metadata_len)?.as_ref(), + self.file_decryption_properties.clone(), + ) } /// Return the number of bytes to read in the initial pass. If `prefetch_size` has @@ -528,6 +554,7 @@ impl ParquetMetaDataReader { fetch: &mut F, file_size: usize, prefetch: usize, + file_decryption_properties: Option, ) -> Result<(ParquetMetaData, Option<(usize, Bytes)>)> { if file_size < FOOTER_SIZE { return Err(eof_err!("file size of {} is less than footer", file_size)); @@ -566,12 +593,15 @@ impl ParquetMetaDataReader { if length > suffix_len - FOOTER_SIZE { let metadata_start = file_size - length - FOOTER_SIZE; let meta = fetch.fetch(metadata_start..file_size - FOOTER_SIZE).await?; - Ok((Self::decode_metadata(&meta)?, None)) + Ok(( + Self::decode_metadata(&meta, file_decryption_properties)?, + None, + )) } else { let metadata_start = file_size - length - FOOTER_SIZE - footer_start; let slice = &suffix[metadata_start..suffix_len - FOOTER_SIZE]; Ok(( - Self::decode_metadata(slice)?, + Self::decode_metadata(slice, file_decryption_properties)?, Some((footer_start, suffix.slice(..metadata_start))), )) } @@ -581,16 +611,16 @@ impl ParquetMetaDataReader { /// /// A parquet footer is 8 bytes long and has the following layout: /// * 4 bytes for the metadata length - /// * 4 bytes for the magic bytes 'PAR1' + /// * 4 bytes for the magic bytes 'PAR1' or 'PARE' (encrypted footer) /// /// ```text - /// +-----+--------+ - /// | len | 'PAR1' | - /// +-----+--------+ + /// +-----+------------------+ + /// | len | 'PAR1' or 'PARE' | + /// +-----+------------------+ /// ``` pub fn decode_footer(slice: &[u8; FOOTER_SIZE]) -> Result { // check this is indeed a parquet file - if slice[4..] != PARQUET_MAGIC { + if slice[4..] != PARQUET_MAGIC && slice[4..] != PARQUET_MAGIC_ENCR_FOOTER { return Err(general_err!("Invalid Parquet file. Corrupt footer")); } @@ -603,22 +633,58 @@ impl ParquetMetaDataReader { /// Decodes [`ParquetMetaData`] from the provided bytes. /// /// Typically this is used to decode the metadata from the end of a parquet - /// file. The format of `buf` is the Thift compact binary protocol, as specified + /// file. The format of `buf` is the Thrift compact binary protocol, as specified /// by the [Parquet Spec]. /// /// [Parquet Spec]: https://github.com/apache/parquet-format#metadata - pub fn decode_metadata(buf: &[u8]) -> Result { + pub fn decode_metadata( + buf: &[u8], + file_decryption_properties: Option, + ) -> Result { let mut prot = TCompactSliceInputProtocol::new(buf); + + let decrypted_fmd_buf; + if let Some(file_decryption_properties) = file_decryption_properties { + let t_file_crypto_metadata: TFileCryptoMetaData = + TFileCryptoMetaData::read_from_in_protocol(&mut prot) + .map_err(|e| general_err!("Could not parse crypto metadata: {}", e))?; + let algo = t_file_crypto_metadata.encryption_algorithm; + let aes_gcm_algo = if let EncryptionAlgorithm::AESGCMV1(a) = algo { + a + } else { + unreachable!() + }; // todo decr: add support for GCMCTRV1 + + // todo decr: get key_metadata + + // remaining buffer contains encrypted FileMetaData + let file_decryptor = FileDecryptor::new(file_decryption_properties); + let decryptor = file_decryptor.get_footer_decryptor(); + // todo decr: get aad_prefix + // todo decr: set both aad_prefix and aad_file_unique in file_decryptor + let fmd_aad = create_footer_aad(aes_gcm_algo.aad_file_unique.unwrap().as_ref()); + decrypted_fmd_buf = + decryptor.decrypt(prot.as_slice().as_ref(), fmd_aad.unwrap().as_ref()); + prot = TCompactSliceInputProtocol::new(decrypted_fmd_buf.as_ref()); + } + let t_file_metadata: TFileMetaData = TFileMetaData::read_from_in_protocol(&mut prot) .map_err(|e| general_err!("Could not parse metadata: {}", e))?; let schema = types::from_thrift(&t_file_metadata.schema)?; let schema_descr = Arc::new(SchemaDescriptor::new(schema)); let mut row_groups = Vec::new(); + // TODO: row group filtering for rg in t_file_metadata.row_groups { row_groups.push(RowGroupMetaData::from_thrift(schema_descr.clone(), rg)?); } let column_orders = Self::parse_column_orders(t_file_metadata.column_orders, &schema_descr); + // todo add file decryptor + if t_file_metadata.encryption_algorithm.is_some() { + // todo get key_metadata etc. Set file decryptor in return value + // todo check signature + } + let file_metadata = FileMetaData::new( t_file_metadata.version, t_file_metadata.num_rows,