git_simple_encrypt/crypt/
header.rs1use rand::Rng;
14
15pub const MAGIC: &[u8; 5] = b"GITSE";
16pub const VERSION: u8 = 3;
17pub(super) const FLAG_COMPRESSED: u8 = 1 << 0;
18pub(super) const ENC_ALGO: u8 = 1;
19
20pub const SALT_LEN: usize = 16;
21pub const FILE_ID_LEN: usize = 16;
22pub const NONCE_LEN: usize = 24;
23pub const HEADER_LEN: usize = 64;
24pub(super) const RESERVED_LEN: usize =
25 HEADER_LEN - (MAGIC.len() + 1 + 1 + 1 + SALT_LEN + FILE_ID_LEN);
26
27pub const CHUNK_SIZE: usize = 65536;
28
29#[inline]
30#[must_use]
31pub const fn is_encrypted_version(v: u8) -> bool {
32 v == VERSION
33}
34
35#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct FileHeader {
38 pub magic: [u8; 5],
39 pub version: u8,
40 pub flags: u8,
41 pub enc_algo: u8,
42 pub salt: [u8; SALT_LEN],
43 pub file_id: [u8; FILE_ID_LEN],
44 pub reserved: [u8; RESERVED_LEN],
45}
46
47const _: () = assert!(std::mem::size_of::<FileHeader>() == HEADER_LEN);
48const _: () = assert!(std::mem::align_of::<FileHeader>() == 1);
49
50impl FileHeader {
51 #[must_use]
52 pub const fn new(compressed: bool, salt: [u8; SALT_LEN], file_id: [u8; FILE_ID_LEN]) -> Self {
53 let mut flags = 0u8;
54 if compressed {
55 flags |= FLAG_COMPRESSED;
56 }
57 Self {
58 magic: *MAGIC,
59 version: VERSION,
60 flags,
61 enc_algo: ENC_ALGO,
62 salt,
63 file_id,
64 reserved: [0u8; RESERVED_LEN],
65 }
66 }
67
68 #[must_use]
69 pub fn generate_file_id() -> [u8; FILE_ID_LEN] {
70 let mut rng = rand::rng();
71 let mut id = [0u8; FILE_ID_LEN];
72 rng.fill_bytes(&mut id);
73 id
74 }
75
76 pub fn from_bytes(bytes: &[u8; HEADER_LEN]) -> crate::error::Result<&Self> {
77 use crate::error::Error;
78
79 let header: &Self = unsafe { &*(bytes.as_ptr().cast()) };
80
81 if &header.magic != MAGIC {
82 return Err(Error::InvalidMagic);
83 }
84 if !is_encrypted_version(header.version) {
85 return Err(Error::UnsupportedVersion(header.version));
86 }
87 if header.enc_algo != ENC_ALGO {
88 return Err(Error::UnsupportedAlgo(header.enc_algo));
89 }
90
91 Ok(header)
92 }
93
94 pub fn read_from<R: std::io::Read>(reader: &mut R) -> crate::error::Result<Self> {
95 let mut buf = [0u8; HEADER_LEN];
96 reader.read_exact(&mut buf)?;
97 Ok(*Self::from_bytes(&buf)?)
98 }
99
100 pub fn write_to<W: std::io::Write>(&self, writer: &mut W) -> crate::error::Result<()> {
101 writer.write_all(self.as_bytes())?;
102 Ok(())
103 }
104
105 #[must_use]
106 pub const fn as_bytes(&self) -> &[u8; HEADER_LEN] {
107 unsafe { &*std::ptr::from_ref::<Self>(self).cast() }
108 }
109
110 #[must_use]
111 pub const fn is_compressed(&self) -> bool {
112 (self.flags & FLAG_COMPRESSED) != 0
113 }
114}