1 | use crate::bytes; |
2 | use crate::compress::{max_compress_len, Encoder}; |
3 | use crate::crc32::CheckSummer; |
4 | use crate::error::Error; |
5 | use 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. |
12 | pub 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. |
18 | pub const STREAM_IDENTIFIER: &'static [u8] = b" \xFF\x06\x00\x00sNaPpY" ; |
19 | |
20 | /// The body of the special stream identifier. |
21 | pub 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. |
26 | pub 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)] |
30 | pub enum ChunkType { |
31 | Stream = 0xFF, |
32 | Compressed = 0x00, |
33 | Uncompressed = 0x01, |
34 | Padding = 0xFE, |
35 | } |
36 | |
37 | impl 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: u8 => 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`. |
62 | pub 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 | } |
105 | |