parquet/file/metadata/footer_tail.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
18use crate::errors::{ParquetError, Result};
19use crate::file::{FOOTER_SIZE, PARQUET_MAGIC, PARQUET_MAGIC_ENCR_FOOTER};
20
21/// Parsed Parquet footer tail (last 8 bytes of a Parquet file)
22///
23/// There are 8 bytes at the end of the Parquet footer with the following layout:
24/// * 4 bytes for the metadata length
25/// * 4 bytes for the magic bytes 'PAR1' or 'PARE' (encrypted footer)
26///
27/// ```text
28/// +-----+------------------+
29/// | len | 'PAR1' or 'PARE' |
30/// +-----+------------------+
31/// ```
32///
33/// # Examples
34/// ```
35/// # use parquet::file::metadata::FooterTail;
36/// // a non encrypted footer with 28 bytes of metadata
37/// let last_8_bytes: [u8; 8] = [0x1C, 0x00, 0x00, 0x00, b'P', b'A', b'R', b'1'];
38/// let footer_tail = FooterTail::try_from(last_8_bytes).unwrap();
39/// assert_eq!(footer_tail.metadata_length(), 28);
40/// assert_eq!(footer_tail.is_encrypted_footer(), false);
41/// ```
42///
43/// ```
44/// # use parquet::file::metadata::FooterTail;
45/// // an encrypted footer with 512 bytes of metadata
46/// let last_8_bytes = vec![0x00, 0x02, 0x00, 0x00, b'P', b'A', b'R', b'E'];
47/// let footer_tail = FooterTail::try_from(&last_8_bytes[..]).unwrap();
48/// assert_eq!(footer_tail.metadata_length(), 512);
49/// assert_eq!(footer_tail.is_encrypted_footer(), true);
50/// ```
51///
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct FooterTail {
54 metadata_length: usize,
55 encrypted_footer: bool,
56}
57
58impl FooterTail {
59 /// Try to decode the footer tail from the given 8 bytes
60 pub fn try_new(slice: &[u8; FOOTER_SIZE]) -> Result<FooterTail> {
61 let magic = &slice[4..];
62 let encrypted_footer = if magic == PARQUET_MAGIC_ENCR_FOOTER {
63 true
64 } else if magic == PARQUET_MAGIC {
65 false
66 } else {
67 return Err(general_err!("Invalid Parquet file. Corrupt footer"));
68 };
69 // get the metadata length from the footer
70 let metadata_len = u32::from_le_bytes(slice[..4].try_into().unwrap());
71
72 Ok(FooterTail {
73 // u32 won't be larger than usize in most cases
74 metadata_length: metadata_len.try_into()?,
75 encrypted_footer,
76 })
77 }
78
79 /// The length of the footer metadata in bytes
80 pub fn metadata_length(&self) -> usize {
81 self.metadata_length
82 }
83
84 /// Whether the footer metadata is encrypted
85 pub fn is_encrypted_footer(&self) -> bool {
86 self.encrypted_footer
87 }
88}
89
90impl TryFrom<[u8; FOOTER_SIZE]> for FooterTail {
91 type Error = ParquetError;
92
93 fn try_from(value: [u8; FOOTER_SIZE]) -> Result<Self> {
94 Self::try_new(&value)
95 }
96}
97
98impl TryFrom<&[u8]> for FooterTail {
99 type Error = ParquetError;
100
101 fn try_from(value: &[u8]) -> Result<Self> {
102 if value.len() != FOOTER_SIZE {
103 return Err(general_err!(
104 "Invalid footer length {}, expected {FOOTER_SIZE}",
105 value.len()
106 ));
107 }
108 let slice: &[u8; FOOTER_SIZE] = value.try_into().unwrap();
109 Self::try_new(slice)
110 }
111}