snap/
frame.rs

1use crate::bytes;
2use crate::compress::{max_compress_len, Encoder};
3use crate::crc32::CheckSummer;
4use crate::error::Error;
5use crate::MAX_BLOCK_SIZE;
6
7/// The maximum chunk of compressed bytes that can be processed at one time.
8///
9/// This is computed via `max_compress_len(MAX_BLOCK_SIZE)`.
10///
11/// TODO(ag): Replace with const fn once they support nominal branching.
12pub const MAX_COMPRESS_BLOCK_SIZE: usize = 76490;
13
14/// The special magic string that starts any stream.
15///
16/// This may appear more than once in a stream in order to support easy
17/// concatenation of files compressed in the Snappy frame format.
18pub const STREAM_IDENTIFIER: &'static [u8] = b"\xFF\x06\x00\x00sNaPpY";
19
20/// The body of the special stream identifier.
21pub const STREAM_BODY: &'static [u8] = b"sNaPpY";
22
23/// The length of a snappy chunk type (1 byte), packet length (3 bytes)
24/// and CRC field (4 bytes). This is technically the chunk header _plus_
25/// the CRC present in most chunks.
26pub const CHUNK_HEADER_AND_CRC_SIZE: usize = 8;
27
28/// An enumeration describing each of the 4 main chunk types.
29#[derive(Copy, Clone, Debug, Eq, PartialEq)]
30pub enum ChunkType {
31    Stream = 0xFF,
32    Compressed = 0x00,
33    Uncompressed = 0x01,
34    Padding = 0xFE,
35}
36
37impl ChunkType {
38    /// Converts a byte to one of the four defined chunk types represented by
39    /// a single byte. If the chunk type is reserved, then it is returned as
40    /// an Err.
41    pub fn from_u8(b: u8) -> Result<ChunkType, u8> {
42        match b {
43            0xFF => Ok(ChunkType::Stream),
44            0x00 => Ok(ChunkType::Compressed),
45            0x01 => Ok(ChunkType::Uncompressed),
46            0xFE => Ok(ChunkType::Padding),
47            b => Err(b),
48        }
49    }
50}
51
52/// Compress a single frame (or decide to pass it through uncompressed). This
53/// will output a frame header in `dst_chunk_header`, and it will return a slice
54/// pointing to the data to use in the frame. The `dst_chunk_header` array must
55/// always have a size of 8 bytes.
56///
57/// If `always_use_dst` is set to false, the return value may point into either
58/// `src` (for data we couldn't compress) or into `dst` (for data we could
59/// compress). If `always_use_dst` is true, the data will always be in `dst`.
60/// This is a bit weird, but because of Rust's ownership rules, it's easiest
61/// for a single function to always be in charge of writing to `dst`.
62pub fn compress_frame<'a>(
63    enc: &mut Encoder,
64    checksummer: CheckSummer,
65    src: &'a [u8],
66    dst_chunk_header: &mut [u8],
67    dst: &'a mut [u8],
68    always_use_dst: bool,
69) -> Result<&'a [u8], Error> {
70    // This is a purely internal function, with a bunch of preconditions.
71    assert!(src.len() <= MAX_BLOCK_SIZE);
72    assert!(dst.len() >= max_compress_len(MAX_BLOCK_SIZE));
73    assert_eq!(dst_chunk_header.len(), CHUNK_HEADER_AND_CRC_SIZE);
74
75    // Build a checksum of our _uncompressed_ data.
76    let checksum = checksummer.crc32c_masked(src);
77
78    // Compress the buffer. If compression sucked, throw it out and
79    // write uncompressed bytes instead. Since our buffer is at most
80    // MAX_BLOCK_SIZE and our dst buffer has size
81    // max_compress_len(MAX_BLOCK_SIZE), we have enough space.
82    let compress_len = enc.compress(src, dst)?;
83    let (chunk_type, chunk_len) =
84        // We add 4 to the chunk_len because of the checksum.
85        if compress_len >= src.len() - (src.len() / 8) {
86            (ChunkType::Uncompressed, 4 + src.len())
87        } else {
88            (ChunkType::Compressed, 4 + compress_len)
89        };
90
91    dst_chunk_header[0] = chunk_type as u8;
92    bytes::write_u24_le(chunk_len as u32, &mut dst_chunk_header[1..]);
93    bytes::write_u32_le(checksum, &mut dst_chunk_header[4..]);
94
95    // Return the data to put in our frame.
96    if chunk_type == ChunkType::Compressed {
97        Ok(&dst[0..compress_len])
98    } else if always_use_dst {
99        dst[..src.len()].copy_from_slice(src);
100        Ok(&dst[..src.len()])
101    } else {
102        Ok(src)
103    }
104}