zrip_core/frame/
header.rs1#![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}