1use crate::error::AescryptError;
5use std::io::Read;
6
7pub fn read_version<R: Read>(mut reader: R) -> Result<u8, AescryptError> {
14 let mut magic = [0u8; 3];
15 reader.read_exact(&mut magic).map_err(AescryptError::Io)?;
16
17 if magic != [b'A', b'E', b'S'] {
18 return Err(AescryptError::Header(
19 "Not an AES Crypt file: invalid magic".into(),
20 ));
21 }
22
23 let mut buf = [0u8; 2];
24 match reader.read(&mut buf) {
25 Ok(2) => {
26 let version = buf[0];
27 let reserved = buf[1];
28
29 if version > 3 {
30 return Err(AescryptError::Header(format!(
31 "Unsupported version: {version}"
32 )));
33 }
34 if version >= 1 && reserved != 0x00 {
35 return Err(AescryptError::Header(
36 "Invalid header: reserved byte != 0x00".into(),
37 ));
38 }
39 Ok(version)
40 }
41 Ok(1) => {
42 if buf[0] == 0 {
44 Ok(0)
45 } else {
46 Err(AescryptError::Header(
47 "Invalid v0 header: version byte not zero".into(),
48 ))
49 }
50 }
51 Ok(0) | Err(_) => {
52 Ok(0)
54 }
55 _ => unreachable!("read() only returns 0–2 for a 2-byte buffer"),
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::io::Cursor;
63
64 #[test]
65 fn real_world_vectors() {
66 let cases = &[
67 ("41455300", 0u8), ("4145530000", 0u8), ("41455300ff", 0u8), ("4145530100", 1u8),
71 ("4145530200", 2u8),
72 ("4145530300", 3u8),
73 ];
74
75 for &(hex, expected) in cases {
76 let bytes = hex::decode(hex).unwrap();
77 assert_eq!(read_version(Cursor::new(&bytes)).unwrap(), expected);
78 }
79 }
80
81 #[test]
82 fn short_file_only_aes() {
83 assert_eq!(read_version(Cursor::new(b"AES")).unwrap(), 0);
84 }
85
86 #[test]
87 fn invalid_magic() {
88 let err = read_version(Cursor::new(b"XYZ\x03\x00")).unwrap_err();
89 assert_eq!(
90 err.to_string(),
91 "Header error: Not an AES Crypt file: invalid magic"
92 );
93 }
94}