Skip to main content

egs_api/api/types/
chunk.rs

1use flate2::read::ZlibDecoder;
2use log::{debug, error};
3use std::io::Read;
4
5/// Struct holding data for downloaded chunks
6#[derive(Default, Debug, Clone, PartialEq)]
7pub struct Chunk {
8    header_version: u32,
9    header_size: u32,
10    compressed_size: u32,
11    /// Guid of the chunk
12    pub guid: String,
13    /// Chunk Hash
14    pub hash: u64,
15    compressed: bool,
16    /// Chunk sha hash
17    pub sha_hash: Option<Vec<u8>>,
18    /// 1 = rolling hash, 2 = sha hash, 3 = both
19    pub hash_type: Option<u8>,
20    /// Total chunk size
21    pub uncompressed_size: Option<u32>,
22    /// Chunk data
23    pub data: Vec<u8>,
24}
25
26impl Chunk {
27    /// Parse chunk from binary vector
28    pub fn from_vec(buffer: Vec<u8>) -> Option<Chunk> {
29        let mut position: usize = 0;
30        let magic = crate::api::utils::read_le(&buffer, &mut position);
31        if magic != 2986228386 {
32            error!("No header magic");
33            return None;
34        }
35        let mut res = Chunk {
36            header_version: crate::api::utils::read_le(&buffer, &mut position),
37            header_size: crate::api::utils::read_le(&buffer, &mut position),
38            compressed_size: crate::api::utils::read_le(&buffer, &mut position),
39            guid: format!(
40                "{:08x}{:08x}{:08x}{:08x}",
41                crate::api::utils::read_le(&buffer, &mut position),
42                crate::api::utils::read_le(&buffer, &mut position),
43                crate::api::utils::read_le(&buffer, &mut position),
44                crate::api::utils::read_le(&buffer, &mut position)
45            ),
46            hash: crate::api::utils::read_le_64(&buffer, &mut position),
47            compressed: !matches!(buffer[position], 0),
48            sha_hash: None,
49            hash_type: None,
50            uncompressed_size: None,
51            data: vec![],
52        };
53        position += 1;
54
55        if res.header_version >= 2 {
56            position += 20;
57            res.sha_hash = Some(buffer[position - 20..position].into());
58            res.hash_type = Some(buffer[position]);
59            position += 1;
60        }
61        if res.header_version >= 3 {
62            res.uncompressed_size = Some(crate::api::utils::read_le(&buffer, &mut position));
63        }
64        debug!("Got chunk: {:?}", res);
65        res.data = if res.compressed {
66            let mut z = ZlibDecoder::new(&buffer[position..]);
67            let mut data: Vec<u8> = Vec::new();
68            if z.read_to_end(&mut data).is_err() {
69                error!("Failed to decompress chunk data");
70                return None;
71            }
72            data
73        } else {
74            buffer[position..].to_vec()
75        };
76        Some(res)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use flate2::Compression;
84    use flate2::write::ZlibEncoder;
85    use std::io::Write;
86
87    #[test]
88    fn from_vec_wrong_magic() {
89        let buffer = vec![0u8; 45];
90        assert_eq!(Chunk::from_vec(buffer), None);
91    }
92
93    #[test]
94    fn from_vec_valid_uncompressed() {
95        let mut buffer: Vec<u8> = Vec::new();
96        buffer.extend_from_slice(&2986228386u32.to_le_bytes());
97        buffer.extend_from_slice(&1u32.to_le_bytes());
98        buffer.extend_from_slice(&40u32.to_le_bytes());
99        buffer.extend_from_slice(&5u32.to_le_bytes());
100        buffer.extend_from_slice(&0u32.to_le_bytes());
101        buffer.extend_from_slice(&0u32.to_le_bytes());
102        buffer.extend_from_slice(&0u32.to_le_bytes());
103        buffer.extend_from_slice(&0u32.to_le_bytes());
104        buffer.extend_from_slice(&42u64.to_le_bytes());
105        buffer.push(0u8);
106        buffer.extend_from_slice(&[1, 2, 3, 4, 5]);
107
108        let chunk = Chunk::from_vec(buffer).unwrap();
109        assert!(chunk.guid.starts_with("00000000"));
110        assert_eq!(chunk.hash, 42);
111        assert_eq!(chunk.data, vec![1, 2, 3, 4, 5]);
112        assert!(!chunk.compressed);
113    }
114
115    #[test]
116    fn from_vec_valid_compressed() {
117        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
118        encoder.write_all(&[10, 20, 30]).unwrap();
119        let compressed = encoder.finish().unwrap();
120
121        let mut buffer: Vec<u8> = Vec::new();
122        buffer.extend_from_slice(&2986228386u32.to_le_bytes());
123        buffer.extend_from_slice(&1u32.to_le_bytes());
124        buffer.extend_from_slice(&40u32.to_le_bytes());
125        buffer.extend_from_slice(&(compressed.len() as u32).to_le_bytes());
126        buffer.extend_from_slice(&0u32.to_le_bytes());
127        buffer.extend_from_slice(&0u32.to_le_bytes());
128        buffer.extend_from_slice(&0u32.to_le_bytes());
129        buffer.extend_from_slice(&0u32.to_le_bytes());
130        buffer.extend_from_slice(&42u64.to_le_bytes());
131        buffer.push(1u8);
132        buffer.extend_from_slice(&compressed);
133
134        let chunk = Chunk::from_vec(buffer).unwrap();
135        assert_eq!(chunk.data, vec![10, 20, 30]);
136    }
137
138    #[test]
139    fn from_vec_version2_has_sha() {
140        let mut buffer: Vec<u8> = Vec::new();
141        buffer.extend_from_slice(&2986228386u32.to_le_bytes());
142        buffer.extend_from_slice(&2u32.to_le_bytes());
143        buffer.extend_from_slice(&40u32.to_le_bytes());
144        buffer.extend_from_slice(&5u32.to_le_bytes());
145        buffer.extend_from_slice(&0u32.to_le_bytes());
146        buffer.extend_from_slice(&0u32.to_le_bytes());
147        buffer.extend_from_slice(&0u32.to_le_bytes());
148        buffer.extend_from_slice(&0u32.to_le_bytes());
149        buffer.extend_from_slice(&42u64.to_le_bytes());
150        buffer.push(0u8);
151        buffer.extend_from_slice(&[0xAB; 20]);
152        buffer.push(2u8);
153        buffer.extend_from_slice(&[1, 2, 3, 4, 5]);
154
155        let chunk = Chunk::from_vec(buffer).unwrap();
156        assert_eq!(chunk.sha_hash, Some(vec![0xAB; 20]));
157        assert_eq!(chunk.hash_type, Some(2));
158    }
159}