morpheus_core/fs/fat32_ops/
filename.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! FAT32 8.3 Filename Utilities
//!
//! Utilities for generating 8.3 compatible filenames from long names.

extern crate alloc;
use alloc::format;
use alloc::string::String;

/// Generate 8.3 compatible manifest filename from ISO name using CRC32 hash.
///
/// FAT32 8.3 format limits names to 8 characters + 3 character extension.
/// To avoid truncation collisions, we use CRC32 hash of the full ISO name.
///
/// # Arguments
/// * `iso_name` - Full ISO filename (e.g., "tails-6.10.iso", "ubuntu-24.04-desktop.iso")
///
/// # Returns
/// * 8.3 compatible filename (e.g., "4B2A7C3D.MFS")
///
/// # Examples
/// ```
/// let filename = generate_8_3_manifest_name("tails-6.10.iso");
/// assert_eq!(filename.len(), 12); // "XXXXXXXX.MFS"
/// assert!(filename.ends_with(".MFS"));
/// ```
pub fn generate_8_3_manifest_name(iso_name: &str) -> String {
    let hash = crc32(iso_name.as_bytes());
    format!("{:08X}.MFS", hash)
}

/// Calculate CRC32 checksum of data.
///
/// Uses standard CRC32 algorithm with polynomial 0xEDB88320.
///
/// # Arguments
/// * `data` - Input bytes to hash
///
/// # Returns
/// * CRC32 checksum as u32
fn crc32(data: &[u8]) -> u32 {
    const POLYNOMIAL: u32 = 0xEDB88320;

    let mut crc: u32 = 0xFFFFFFFF;

    for &byte in data {
        crc ^= byte as u32;
        for _ in 0..8 {
            if crc & 1 != 0 {
                crc = (crc >> 1) ^ POLYNOMIAL;
            } else {
                crc >>= 1;
            }
        }
    }

    !crc
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::vec::Vec;

    #[test]
    fn test_crc32_known_values() {
        // Test against known CRC32 values
        assert_eq!(crc32(b""), 0x00000000);
        assert_eq!(crc32(b"123456789"), 0xCBF43926);
        assert_eq!(
            crc32(b"The quick brown fox jumps over the lazy dog"),
            0x414FA339
        );
    }

    #[test]
    fn test_generate_8_3_manifest_name_format() {
        let result = generate_8_3_manifest_name("tails-6.10.iso");

        // Should be exactly 12 characters (8 + dot + 3)
        assert_eq!(result.len(), 12, "Expected length 12, got: {}", result);

        // Should end with .MFS
        assert!(
            result.ends_with(".MFS"),
            "Expected .MFS suffix, got: {}",
            result
        );

        // Should be all uppercase hex + .MFS
        // Note: hex digits 0-9 and A-F are expected
        let name_part = &result[..8];
        for c in name_part.chars() {
            assert!(
                matches!(c, '0'..='9' | 'A'..='F'),
                "Expected uppercase hex digit, got '{}' in '{}'",
                c,
                result
            );
        }
    }

    #[test]
    fn test_different_names_different_hashes() {
        let hash1 = generate_8_3_manifest_name("tails-6.10.iso");
        let hash2 = generate_8_3_manifest_name("ubuntu-24.04-desktop.iso");
        let hash3 = generate_8_3_manifest_name("kali-2024.4.iso");

        // All should be different
        assert_ne!(hash1, hash2);
        assert_ne!(hash2, hash3);
        assert_ne!(hash1, hash3);
    }

    #[test]
    fn test_same_name_same_hash() {
        let hash1 = generate_8_3_manifest_name("test.iso");
        let hash2 = generate_8_3_manifest_name("test.iso");

        // Same input should produce same output
        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_collision_resistance() {
        // Test common distro names don't collide
        let distros = [
            "tails-6.10.iso",
            "ubuntu-24.04-desktop.iso",
            "ubuntu-24.04-server.iso",
            "fedora-41-workstation.iso",
            "kali-2024.4.iso",
            "debian-12.8-netinst.iso",
            "arch-linux-latest.iso",
            "linuxmint-22.iso",
        ];

        let mut hashes = Vec::new();
        for distro in &distros {
            let hash = generate_8_3_manifest_name(distro);
            assert!(!hashes.contains(&hash), "Collision detected for {}", distro);
            hashes.push(hash);
        }
    }
}