prefix_file_tree/scheme/
encoding.rs

1use crate::scheme::{Case, Error, Scheme};
2use data_encoding::BASE32;
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::ffi::OsStr;
6
7/// Fixed-length Base32 name encoding scheme.
8///
9/// Note that padding is not handled, and that `N` must be a multiple of 5.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub struct Base32<const N: usize> {
12    pub case: Case,
13}
14
15impl<const N: usize> Base32<N> {
16    const VALID: () = assert!(N.is_multiple_of(5), "N must be a multiple of 5 for Base32 encoding");
17
18    #[must_use]
19    pub const fn new(case: Case) -> Self {
20        let () = Self::VALID;
21        Self { case }
22    }
23}
24
25impl<const N: usize> Scheme for Base32<N> {
26    type Name = [u8; N];
27    type NameRef<'a> = [u8; N];
28
29    fn fixed_length() -> Option<usize> {
30        Some(N / 5 * 8)
31    }
32
33    fn name_to_string<'a>(&self, name: Self::NameRef<'a>) -> Cow<'a, str> {
34        BASE32.encode(&name).into()
35    }
36
37    fn cmp_prefix_part(&self, a: &OsStr, b: &OsStr) -> Result<Ordering, Error> {
38        let a_chars = a
39            .as_encoded_bytes()
40            .iter()
41            .map(|byte| Base32Char::try_from(*byte))
42            .collect::<Result<Vec<_>, _>>()?;
43
44        let b_chars = b
45            .as_encoded_bytes()
46            .iter()
47            .map(|byte| Base32Char::try_from(*byte))
48            .collect::<Result<Vec<_>, _>>()?;
49
50        Ok(a_chars.cmp(&b_chars))
51    }
52
53    fn name_from_file_stem(&self, file_stem: &OsStr) -> Result<Self::Name, Error> {
54        let () = Self::VALID;
55        let as_bytes = file_stem.as_encoded_bytes();
56
57        if as_bytes.len() == N / 5 * 8 {
58            let decoded = BASE32
59                .decode(as_bytes)
60                .map_err(|error| Error::InvalidByte(as_bytes[error.position]))?;
61
62            Ok(decoded.try_into().expect("Invalid decoded bytes length"))
63        } else {
64            Err(Error::InvalidLength(as_bytes.len()))
65        }
66    }
67}
68
69#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
70enum Base32Char {
71    Alphabetic(u8),
72    Numeric(u8),
73}
74
75impl TryFrom<u8> for Base32Char {
76    type Error = Error;
77
78    fn try_from(value: u8) -> Result<Self, Self::Error> {
79        if value.is_ascii_uppercase() {
80            Ok(Self::Alphabetic(value))
81        } else if (b'2'..=b'7').contains(&value) {
82            Ok(Self::Numeric(value))
83        } else {
84            Err(Error::InvalidByte(value))
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::Tree;
92    use std::io::Write;
93
94    #[test]
95    fn test_base32() -> Result<(), Box<dyn std::error::Error>> {
96        let base = tempfile::tempdir()?;
97        let prefix_part_lengths = vec![3, 2];
98
99        let name_1 = b"abcd_abcd_abcd_abcd_";
100        //let name_2 = b"abcd_abcd_abcd_efgh_";
101        let name_2 = &[255u8; 20];
102        let name_3 = b"abcd_abcd_abcd_efgh_";
103
104        let tree = Tree::builder(base)
105            .with_scheme(crate::scheme::encoding::Base32::<20>::new(
106                crate::scheme::Case::Lower,
107            ))
108            .with_prefix_part_lengths(prefix_part_lengths)
109            .build()?;
110
111        let mut file = tree.create_file(*name_1)?.expect("Unexpected file");
112
113        file.write_all(b"foo")?;
114
115        let file = tree.create_file(*name_1)?;
116
117        assert!(file.is_none());
118
119        let mut file = tree.create_file(*name_2)?.expect("Unexpected file");
120
121        file.write_all(b"bar")?;
122
123        let mut file = tree.create_file(*name_3)?.expect("Unexpected file");
124
125        file.write_all(b"qux")?;
126
127        let entries = tree.entries().collect::<Result<Vec<_>, _>>()?;
128
129        assert!(
130            entries[0]
131                .path
132                .to_string_lossy()
133                .ends_with("/MFR/GG/MFRGGZC7MFRGGZC7MFRGGZC7MFRGGZC7")
134        );
135
136        assert_eq!(
137            entries
138                .into_iter()
139                .map(|entry| entry.name)
140                .collect::<Vec<_>>(),
141            vec![*name_1, *name_3, *name_2]
142        );
143
144        Ok(())
145    }
146}