Skip to main content

idb/innodb/
encryption.rs

1//! Tablespace encryption detection.
2//!
3//! Detects whether a tablespace is encrypted by inspecting FSP flags.
4//! MySQL uses bit 13 for tablespace-level encryption (AES). MariaDB does
5//! not have a tablespace-level encryption flag; encryption is per-page
6//! (page type 37401).
7
8use crate::innodb::vendor::VendorInfo;
9use byteorder::{BigEndian, ByteOrder};
10
11/// Encryption algorithm detected from FSP flags.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum EncryptionAlgorithm {
14    None,
15    Aes,
16}
17
18/// Detect encryption from FSP space flags.
19///
20/// When `vendor_info` indicates MariaDB, returns `None` because MariaDB
21/// does not use a tablespace-level encryption flag. For MySQL/Percona,
22/// checks bit 13 of FSP flags.
23pub fn detect_encryption(
24    fsp_flags: u32,
25    vendor_info: Option<&VendorInfo>,
26) -> EncryptionAlgorithm {
27    // MariaDB: no tablespace-level encryption flag
28    if vendor_info.is_some_and(|v| v.vendor == crate::innodb::vendor::InnoDbVendor::MariaDB) {
29        return EncryptionAlgorithm::None;
30    }
31
32    if (fsp_flags >> 13) & 0x01 != 0 {
33        EncryptionAlgorithm::Aes
34    } else {
35        EncryptionAlgorithm::None
36    }
37}
38
39/// Check if a tablespace is encrypted based on its FSP flags.
40pub fn is_encrypted(fsp_flags: u32) -> bool {
41    detect_encryption(fsp_flags, None) != EncryptionAlgorithm::None
42}
43
44/// Check if a page type indicates MariaDB page-level encryption.
45pub fn is_mariadb_encrypted_page(page_type: u16) -> bool {
46    page_type == crate::innodb::constants::FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED
47}
48
49/// Read the encryption key version from a MariaDB encrypted page.
50///
51/// For page type 37401 (PAGE_COMPRESSED_ENCRYPTED), the key version
52/// is stored as a u32 at byte offset 26.
53pub fn mariadb_encryption_key_version(page_data: &[u8]) -> Option<u32> {
54    if page_data.len() < 30 {
55        return None;
56    }
57    Some(BigEndian::read_u32(&page_data[26..]))
58}
59
60impl std::fmt::Display for EncryptionAlgorithm {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            EncryptionAlgorithm::None => write!(f, "None"),
64            EncryptionAlgorithm::Aes => write!(f, "AES"),
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::innodb::vendor::MariaDbFormat;
73
74    #[test]
75    fn test_detect_encryption_mysql() {
76        assert_eq!(detect_encryption(0, None), EncryptionAlgorithm::None);
77        assert_eq!(detect_encryption(1 << 13, None), EncryptionAlgorithm::Aes);
78        assert_eq!(detect_encryption(0xFF, None), EncryptionAlgorithm::None);
79        assert_eq!(
80            detect_encryption(0xFF | (1 << 13), None),
81            EncryptionAlgorithm::Aes
82        );
83    }
84
85    #[test]
86    fn test_detect_encryption_mariadb_returns_none() {
87        let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
88        // Even with bit 13 set, MariaDB returns None (no TS-level encryption)
89        assert_eq!(
90            detect_encryption(1 << 13, Some(&vendor)),
91            EncryptionAlgorithm::None
92        );
93    }
94
95    #[test]
96    fn test_is_encrypted() {
97        assert!(!is_encrypted(0));
98        assert!(is_encrypted(1 << 13));
99    }
100
101    #[test]
102    fn test_is_mariadb_encrypted_page() {
103        assert!(is_mariadb_encrypted_page(37401));
104        assert!(!is_mariadb_encrypted_page(17855));
105    }
106
107    #[test]
108    fn test_mariadb_encryption_key_version() {
109        let mut page = vec![0u8; 38];
110        BigEndian::write_u32(&mut page[26..], 42);
111        assert_eq!(mariadb_encryption_key_version(&page), Some(42));
112    }
113}