Skip to main content

zstd_rs/
frame.rs

1//! Frame and block headers (RFC 8878 §3.1.1), shared by encoder and decoder.
2use crate::error::{Error, Result};
3use alloc::vec::Vec;
4
5pub const MAGIC: u32 = 0xFD2F_B528;
6pub const SKIPPABLE_MASK: u32 = 0xFFFF_FFF0;
7pub const SKIPPABLE_BASE: u32 = 0x184D_2A50;
8/// Largest block content (and regenerated size) the format allows.
9pub const BLOCK_MAX: usize = 128 * 1024;
10pub const WINDOW_LOG_MIN: u32 = 10;
11pub const WINDOW_LOG_MAX: u32 = 31;
12
13/// Frame layout: standard (4-byte magic number first) or the reference's
14/// "magicless" variant (`ZSTD_f_zstd1_magicless`), which omits the magic number.
15/// Magicless frames save 4 bytes per frame when both ends agree on the format out
16/// of band; they cannot be mixed with skippable frames.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum FrameFormat {
19    /// RFC 8878 frames, starting with the magic number.
20    Standard,
21    /// Frames without the magic number.
22    Magicless,
23}
24
25/// A parsed frame header.
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct FrameHeader {
28    /// Window size in bytes (equal to the content size for single-segment frames).
29    pub window_size: u64,
30    /// Declared decompressed size, if recorded.
31    pub content_size: Option<u64>,
32    /// 0 when the frame names no dictionary.
33    pub dict_id: u32,
34    /// A 4-byte XXH64-based checksum follows the last block.
35    pub checksum: bool,
36    /// No window descriptor: the window is the whole content.
37    pub single_segment: bool,
38    /// Bytes from the magic number through the end of the header.
39    pub header_len: usize,
40}
41
42fn le(src: &[u8], at: usize, n: usize) -> Result<u64> {
43    let b = src.get(at..at + n).ok_or(Error::Truncated)?;
44    Ok(b.iter().rev().fold(0u64, |acc, &x| (acc << 8) | x as u64))
45}
46
47impl FrameHeader {
48    /// Parses the header of a zstd frame starting at `src[0]` (the magic number).
49    pub fn parse(src: &[u8]) -> Result<FrameHeader> {
50        Self::parse_format(src, FrameFormat::Standard)
51    }
52
53    /// Parses a frame header in the given format.
54    pub fn parse_format(src: &[u8], format: FrameFormat) -> Result<FrameHeader> {
55        let start = match format {
56            FrameFormat::Standard => {
57                if le(src, 0, 4)? as u32 != MAGIC {
58                    return Err(Error::BadMagic);
59                }
60                4
61            }
62            FrameFormat::Magicless => 0,
63        };
64        let fhd = *src.get(start).ok_or(Error::Truncated)?;
65        let fcs_flag = fhd >> 6;
66        let single_segment = fhd & 0x20 != 0;
67        if fhd & 0x08 != 0 {
68            return Err(Error::Reserved);
69        }
70        let checksum = fhd & 0x04 != 0;
71        let did_len = [0usize, 1, 2, 4][(fhd & 3) as usize];
72        let fcs_len = match fcs_flag {
73            0 if single_segment => 1,
74            0 => 0,
75            1 => 2,
76            2 => 4,
77            _ => 8,
78        };
79        let mut at = start + 1;
80        let mut window_size = 0u64;
81        if !single_segment {
82            let wd = *src.get(at).ok_or(Error::Truncated)?;
83            let log = WINDOW_LOG_MIN + (wd >> 3) as u32;
84            let base = 1u64 << log;
85            window_size = base + (base / 8) * (wd & 7) as u64;
86            at += 1;
87        }
88        let dict_id = le(src, at, did_len)? as u32;
89        at += did_len;
90        let content_size = match fcs_len {
91            0 => None,
92            2 => Some(le(src, at, 2)? + 256),
93            n => Some(le(src, at, n)?),
94        };
95        at += fcs_len;
96        if single_segment {
97            window_size = content_size.ok_or(Error::Corrupt("single segment without size"))?;
98        }
99        Ok(FrameHeader { window_size, content_size, dict_id, checksum, single_segment, header_len: at })
100    }
101
102    /// Appends this header (magic included). `window_log` is used when not single-segment.
103    pub fn write(&self, window_log: u32, out: &mut Vec<u8>) {
104        self.write_format(FrameFormat::Standard, window_log, out)
105    }
106
107    /// Appends this header in the given format.
108    pub fn write_format(&self, format: FrameFormat, window_log: u32, out: &mut Vec<u8>) {
109        if format == FrameFormat::Standard {
110            out.extend_from_slice(&MAGIC.to_le_bytes());
111        }
112        let (fcs_flag, fcs_len) = match self.content_size {
113            None => (0u8, 0usize),
114            Some(n) if self.single_segment && n < 256 => (0, 1),
115            Some(n) if (256..65536 + 256).contains(&n) => (1, 2),
116            Some(n) if n <= u32::MAX as u64 => (2, 4),
117            Some(_) => (3, 8),
118        };
119        let (did_flag, did_len) = match self.dict_id {
120            0 => (0u8, 0usize),
121            1..=255 => (1, 1),
122            256..=65535 => (2, 2),
123            _ => (3, 4),
124        };
125        let fhd = (fcs_flag << 6) | ((self.single_segment as u8) << 5) | ((self.checksum as u8) << 2) | did_flag;
126        out.push(fhd);
127        if !self.single_segment {
128            out.push(((window_log - WINDOW_LOG_MIN) << 3) as u8);
129        }
130        out.extend_from_slice(&self.dict_id.to_le_bytes()[..did_len]);
131        if let Some(n) = self.content_size {
132            let v = if fcs_len == 2 { n - 256 } else { n };
133            out.extend_from_slice(&v.to_le_bytes()[..fcs_len]);
134        }
135    }
136}
137
138/// Block types (RFC 8878 §3.1.1.2).
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum BlockType {
141    Raw,
142    Rle,
143    Compressed,
144}
145
146/// Parses a 3-byte block header: (last, type, size).
147pub fn parse_block_header(src: &[u8]) -> Result<(bool, BlockType, usize)> {
148    let v = le(src, 0, 3)? as u32;
149    let ty = match (v >> 1) & 3 {
150        0 => BlockType::Raw,
151        1 => BlockType::Rle,
152        2 => BlockType::Compressed,
153        _ => return Err(Error::Reserved),
154    };
155    Ok((v & 1 == 1, ty, (v >> 3) as usize))
156}
157
158pub fn write_block_header(last: bool, ty: BlockType, size: usize, out: &mut Vec<u8>) {
159    let t = match ty {
160        BlockType::Raw => 0,
161        BlockType::Rle => 1,
162        BlockType::Compressed => 2,
163    };
164    let v = (last as u32) | (t << 1) | ((size as u32) << 3);
165    out.extend_from_slice(&v.to_le_bytes()[..3]);
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn header_roundtrip() {
174        for (cs, ss, dict, ck) in
175            [(Some(0u64), true, 0u32, false), (Some(300), true, 7, true), (Some(70000), false, 70000, false), (None, false, 1 << 30, true)]
176        {
177            let h = FrameHeader { window_size: 0, content_size: cs, dict_id: dict, checksum: ck, single_segment: ss, header_len: 0 };
178            let mut out = Vec::new();
179            h.write(20, &mut out);
180            let p = FrameHeader::parse(&out).unwrap();
181            assert_eq!((p.content_size, p.dict_id, p.checksum, p.single_segment, p.header_len), (cs, dict, ck, ss, out.len()));
182            if !ss {
183                assert_eq!(p.window_size, 1 << 20);
184            }
185        }
186    }
187}