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