Skip to main content

zrip_core/frame/
header.rs

1#![forbid(unsafe_code)]
2
3use crate::error::DecompressError;
4use crate::frame::ZSTD_MAGIC;
5
6#[derive(Debug, Clone)]
7pub struct FrameHeader {
8    pub window_size: u64,
9    pub frame_content_size: Option<u64>,
10    pub dict_id: Option<u32>,
11    pub content_checksum: bool,
12    #[allow(dead_code)]
13    pub single_segment: bool,
14    pub header_size: usize,
15}
16
17pub fn parse_frame_header(data: &[u8]) -> Result<FrameHeader, DecompressError> {
18    if data.len() < 4 {
19        return Err(DecompressError::InputExhausted);
20    }
21
22    let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
23    if magic != ZSTD_MAGIC {
24        return Err(DecompressError::BadMagic);
25    }
26
27    if data.len() < 5 {
28        return Err(DecompressError::BadFrameHeader);
29    }
30
31    let descriptor = data[4];
32    let dict_id_flag = descriptor & 0x03;
33    let content_checksum = (descriptor & 0x04) != 0;
34    let single_segment = (descriptor & 0x20) != 0;
35    let fcs_field_size_flag = (descriptor >> 6) & 0x03;
36
37    let reserved = (descriptor & 0x08) != 0;
38    if reserved {
39        return Err(DecompressError::BadFrameHeader);
40    }
41    let unused = (descriptor & 0x10) != 0;
42    if unused {
43        return Err(DecompressError::BadFrameHeader);
44    }
45
46    let mut offset = 5;
47
48    let window_size = if single_segment {
49        0
50    } else {
51        if data.len() <= offset {
52            return Err(DecompressError::BadFrameHeader);
53        }
54        let window_desc = data[offset];
55        offset += 1;
56        let exponent = (window_desc >> 3) as u64;
57        let mantissa = (window_desc & 0x07) as u64;
58        let window_base = 1u64 << (10 + exponent);
59        let window_add = (window_base >> 3) * mantissa;
60        window_base + window_add
61    };
62
63    let dict_id_size = match dict_id_flag {
64        0 => 0,
65        1 => 1,
66        2 => 2,
67        3 => 4,
68        _ => unreachable!(),
69    };
70
71    let dict_id = if dict_id_size > 0 {
72        if data.len() < offset + dict_id_size {
73            return Err(DecompressError::BadFrameHeader);
74        }
75        let id = match dict_id_size {
76            1 => data[offset] as u32,
77            2 => u16::from_le_bytes([data[offset], data[offset + 1]]) as u32,
78            4 => u32::from_le_bytes([
79                data[offset],
80                data[offset + 1],
81                data[offset + 2],
82                data[offset + 3],
83            ]),
84            _ => unreachable!(),
85        };
86        offset += dict_id_size;
87        Some(id)
88    } else {
89        None
90    };
91
92    let fcs_field_size = match fcs_field_size_flag {
93        0 => {
94            if single_segment {
95                1
96            } else {
97                0
98            }
99        }
100        1 => 2,
101        2 => 4,
102        3 => 8,
103        _ => unreachable!(),
104    };
105
106    let frame_content_size = if fcs_field_size > 0 {
107        if data.len() < offset + fcs_field_size {
108            return Err(DecompressError::BadFrameHeader);
109        }
110        let fcs = match fcs_field_size {
111            1 => data[offset] as u64,
112            2 => u16::from_le_bytes([data[offset], data[offset + 1]]) as u64 + 256,
113            4 => u32::from_le_bytes([
114                data[offset],
115                data[offset + 1],
116                data[offset + 2],
117                data[offset + 3],
118            ]) as u64,
119            8 => u64::from_le_bytes([
120                data[offset],
121                data[offset + 1],
122                data[offset + 2],
123                data[offset + 3],
124                data[offset + 4],
125                data[offset + 5],
126                data[offset + 6],
127                data[offset + 7],
128            ]),
129            _ => unreachable!(),
130        };
131        offset += fcs_field_size;
132        Some(fcs)
133    } else {
134        None
135    };
136
137    let final_window_size = if single_segment {
138        frame_content_size.unwrap_or(0)
139    } else {
140        window_size
141    };
142
143    Ok(FrameHeader {
144        window_size: final_window_size,
145        frame_content_size,
146        dict_id,
147        content_checksum,
148        single_segment,
149        header_size: offset,
150    })
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn parse_minimal_header() {
159        let data = [0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x00];
160        let hdr = parse_frame_header(&data).unwrap();
161        assert!(hdr.single_segment);
162        assert!(!hdr.content_checksum);
163        assert_eq!(hdr.dict_id, None);
164        assert_eq!(hdr.frame_content_size, Some(0));
165    }
166
167    #[test]
168    fn bad_magic() {
169        let data = [0x00, 0x00, 0x00, 0x00, 0x00];
170        assert!(matches!(
171            parse_frame_header(&data),
172            Err(DecompressError::BadMagic)
173        ));
174    }
175}