Skip to main content

parquet/file/metadata/
footer_tail.rs

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