Skip to main content

shadow_crypt_core/
version.rs

1use crate::errors::HeaderError;
2
3/// Magic bytes shared by every shadow file format version.
4pub const MAGIC: [u8; 6] = *b"SHADOW";
5
6/// Length of the magic-and-version preamble shared by every format version.
7pub const PREAMBLE_LENGTH: usize = MAGIC.len() + 1;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Version {
11    V1,
12    V2,
13    V3,
14}
15
16impl Version {
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            Version::V1 => "v1",
20            Version::V2 => "v2",
21            Version::V3 => "v3",
22        }
23    }
24
25    pub fn as_u8(&self) -> u8 {
26        match self {
27            Version::V1 => 1,
28            Version::V2 => 2,
29            Version::V3 => 3,
30        }
31    }
32}
33
34impl TryFrom<u8> for Version {
35    type Error = ();
36
37    fn try_from(value: u8) -> Result<Self, Self::Error> {
38        match value {
39            1 => Ok(Version::V1),
40            2 => Ok(Version::V2),
41            3 => Ok(Version::V3),
42            _ => Err(()),
43        }
44    }
45}
46
47/// Reads the magic-and-version preamble shared by all format versions.
48///
49/// Every shadow file starts with the 6 magic bytes followed by one version
50/// byte; this lets callers pick the right version module without depending
51/// on any of them.
52pub fn read_file_version(bytes: &[u8]) -> Result<Version, HeaderError> {
53    if bytes.len() < PREAMBLE_LENGTH {
54        return Err(HeaderError::InsufficientBytes);
55    }
56    if bytes[..MAGIC.len()] != MAGIC {
57        return Err(HeaderError::InvalidData);
58    }
59    Version::try_from(bytes[MAGIC.len()]).map_err(|_| HeaderError::InvalidData)
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_version_as_str() {
68        assert_eq!(Version::V1.as_str(), "v1");
69        assert_eq!(Version::V2.as_str(), "v2");
70        assert_eq!(Version::V3.as_str(), "v3");
71    }
72
73    #[test]
74    fn test_version_as_u8() {
75        assert_eq!(Version::V1.as_u8(), 1);
76        assert_eq!(Version::V2.as_u8(), 2);
77        assert_eq!(Version::V3.as_u8(), 3);
78    }
79
80    #[test]
81    fn test_try_from_u8() {
82        assert_eq!(Version::try_from(1), Ok(Version::V1));
83        assert_eq!(Version::try_from(2), Ok(Version::V2));
84        assert_eq!(Version::try_from(3), Ok(Version::V3));
85        assert!(Version::try_from(0).is_err());
86        assert!(Version::try_from(4).is_err());
87    }
88
89    #[test]
90    fn test_read_file_version() {
91        let mut bytes = b"SHADOW".to_vec();
92        bytes.push(2);
93        assert_eq!(read_file_version(&bytes).unwrap(), Version::V2);
94
95        bytes[6] = 1;
96        assert_eq!(read_file_version(&bytes).unwrap(), Version::V1);
97
98        bytes[6] = 99;
99        assert!(read_file_version(&bytes).is_err());
100
101        assert!(read_file_version(b"SHADO").is_err());
102        assert!(read_file_version(b"NOTSHD\x01").is_err());
103    }
104}