-
Notifications
You must be signed in to change notification settings - Fork 109
feat: Add Snappy support #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
a9220c9
71d868e
dc0a8c8
130777e
ad114ab
fba07bb
3b20b9c
f61f7b4
04edb10
cae4589
6e96ede
9e740a5
a1e2112
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -142,4 +142,7 @@ mod proptest { | |
|
|
||
| #[cfg(feature = "zstd")] | ||
| tests!(zstd); | ||
|
|
||
| #[cfg(feature = "snappy")] | ||
| tests!(snappy); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| #[macro_use] | ||
| mod utils; | ||
|
|
||
| test_cases!(snappy); |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,203 @@ | ||||||||||||||
| use crate::snappy::{mask_crc, ChunkType, FrameHeader}; | ||||||||||||||
| use crate::DecodeV2; | ||||||||||||||
| use compression_core::util::{PartialBuffer, WriteBuffer}; | ||||||||||||||
| use std::convert::TryInto; | ||||||||||||||
| use std::{io, mem}; | ||||||||||||||
|
|
||||||||||||||
| #[derive(Debug, Default)] | ||||||||||||||
| pub struct SnappyDecoder { | ||||||||||||||
| state: State, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl SnappyDecoder { | ||||||||||||||
| pub fn new() -> Self { | ||||||||||||||
| Self::default() | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| fn decode_chunk(chunk_type: ChunkType, mut buffer: Vec<u8>) -> std::io::Result<Vec<u8>> { | ||||||||||||||
| let data = buffer.split_off(4); | ||||||||||||||
|
NobodyXu marked this conversation as resolved.
Outdated
|
||||||||||||||
|
|
||||||||||||||
| let expected_sum: [u8; 4] = buffer | ||||||||||||||
| .try_into() | ||||||||||||||
| .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid checksum length"))?; | ||||||||||||||
| let expected_sum = u32::from_le_bytes(expected_sum); | ||||||||||||||
|
|
||||||||||||||
| let output = match chunk_type { | ||||||||||||||
| ChunkType::Compressed => { | ||||||||||||||
| let uncompress_length = snap::raw::decompress_len(&data)?; | ||||||||||||||
| let mut out_buf = vec![0; uncompress_length]; | ||||||||||||||
|
NobodyXu marked this conversation as resolved.
Outdated
|
||||||||||||||
| let mut decoder = snap::raw::Decoder::new(); | ||||||||||||||
| decoder.decompress(&data, &mut out_buf)?; | ||||||||||||||
| out_buf | ||||||||||||||
| } | ||||||||||||||
| ChunkType::Uncompressed => data, | ||||||||||||||
| _ => unreachable!( | ||||||||||||||
| "can only decode compressed or uncompressed chunks, not {:?}", | ||||||||||||||
| chunk_type | ||||||||||||||
| ), | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| let got_sum = crc32c::crc32c(&output); | ||||||||||||||
| let got_sum = mask_crc(got_sum); | ||||||||||||||
|
NobodyXu marked this conversation as resolved.
Outdated
|
||||||||||||||
| if expected_sum != got_sum { | ||||||||||||||
| return Err(io::Error::new( | ||||||||||||||
| io::ErrorKind::InvalidData, | ||||||||||||||
| "checksum mismatch", | ||||||||||||||
| )); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| Ok(output) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| #[derive(Debug)] | ||||||||||||||
| enum State { | ||||||||||||||
| StreamIdentifier(PartialBuffer<[u8; 4]>), | ||||||||||||||
| ChunkHeader(PartialBuffer<[u8; 4]>), | ||||||||||||||
| Skipping(usize), | ||||||||||||||
| Buffering { | ||||||||||||||
| remaining: usize, | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Using
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think I can do that because remaining and skipping can be 0.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the neat part: if it's 0, then we just skip that stage and go to the next stage
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed! That's better that way. |
||||||||||||||
| chunk_type: ChunkType, | ||||||||||||||
| buffer: Vec<u8>, | ||||||||||||||
|
NobodyXu marked this conversation as resolved.
Outdated
|
||||||||||||||
| }, | ||||||||||||||
| Sending(PartialBuffer<Vec<u8>>), | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl Default for State { | ||||||||||||||
| fn default() -> Self { | ||||||||||||||
| State::StreamIdentifier(PartialBuffer::new([0; 4])) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| impl DecodeV2 for SnappyDecoder { | ||||||||||||||
| fn reinit(&mut self) -> std::io::Result<()> { | ||||||||||||||
| *self = Self::new(); | ||||||||||||||
|
NobodyXu marked this conversation as resolved.
Outdated
|
||||||||||||||
| Ok(()) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| fn decode( | ||||||||||||||
| &mut self, | ||||||||||||||
| input: &mut PartialBuffer<&[u8]>, | ||||||||||||||
| output: &mut WriteBuffer<'_>, | ||||||||||||||
| ) -> std::io::Result<bool> { | ||||||||||||||
| loop { | ||||||||||||||
| match &mut self.state { | ||||||||||||||
| State::StreamIdentifier(header) => { | ||||||||||||||
| header.copy_unwritten_from(input); | ||||||||||||||
| if !header.unwritten().is_empty() { | ||||||||||||||
| return Ok(false); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| let header = FrameHeader::parse(header.written())?; | ||||||||||||||
| if let ChunkType::Stream = header.chunk_type { | ||||||||||||||
| self.state = State::Skipping(header.data_frame_length as usize) | ||||||||||||||
| } else { | ||||||||||||||
| return Err(std::io::Error::new( | ||||||||||||||
| std::io::ErrorKind::InvalidData, | ||||||||||||||
| format!( | ||||||||||||||
| "Invalid chunk type, expected Stream, got: {:?}", | ||||||||||||||
| header.chunk_type | ||||||||||||||
| ), | ||||||||||||||
| )); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| State::ChunkHeader(header) => { | ||||||||||||||
| header.copy_unwritten_from(input); | ||||||||||||||
| if !header.unwritten().is_empty() { | ||||||||||||||
| return Ok(false); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| let header = FrameHeader::parse(header.written())?; | ||||||||||||||
|
|
||||||||||||||
| let data_frame_length = header.data_frame_length as usize; | ||||||||||||||
|
|
||||||||||||||
| match header.chunk_type { | ||||||||||||||
| ChunkType::Stream | ||||||||||||||
| | ChunkType::ReservedSkippable(_) | ||||||||||||||
| | ChunkType::Padding => self.state = State::Skipping(data_frame_length), | ||||||||||||||
| ChunkType::Compressed | ChunkType::Uncompressed => { | ||||||||||||||
| self.state = State::Buffering { | ||||||||||||||
| remaining: data_frame_length, | ||||||||||||||
| chunk_type: header.chunk_type, | ||||||||||||||
| buffer: Vec::with_capacity(data_frame_length), | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| ChunkType::ReservedUnskippable(chunk_type) => { | ||||||||||||||
| return Err(std::io::Error::new( | ||||||||||||||
| std::io::ErrorKind::InvalidData, | ||||||||||||||
| format!( | ||||||||||||||
| "Reserved unskippable chunk type encountered: {}", | ||||||||||||||
| chunk_type | ||||||||||||||
| ), | ||||||||||||||
| )) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| State::Skipping(n) => { | ||||||||||||||
| let input_len = input.unwritten().len(); | ||||||||||||||
| if input_len < *n { | ||||||||||||||
| input.advance(input_len); | ||||||||||||||
| *n -= input_len; | ||||||||||||||
| return Ok(false); | ||||||||||||||
| } | ||||||||||||||
| input.advance(*n); | ||||||||||||||
| self.state = State::ChunkHeader([0u8; 4].into()) | ||||||||||||||
| } | ||||||||||||||
| State::Buffering { | ||||||||||||||
| remaining, | ||||||||||||||
| chunk_type, | ||||||||||||||
| buffer, | ||||||||||||||
| } => { | ||||||||||||||
| let input_buf = input.unwritten(); | ||||||||||||||
| let boundary = (*remaining).min(input_buf.len()); | ||||||||||||||
| let input_buf = &input_buf[..boundary]; | ||||||||||||||
|
|
||||||||||||||
| *remaining -= input_buf.len(); | ||||||||||||||
|
|
||||||||||||||
| buffer.extend_from_slice(input_buf); | ||||||||||||||
| input.advance(input_buf.len()); | ||||||||||||||
|
|
||||||||||||||
| if *remaining != 0 { | ||||||||||||||
| return Ok(false); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // We're done buffering, so let's decode the chunk | ||||||||||||||
| let chunk_type = *chunk_type; | ||||||||||||||
| let buffer = mem::take(buffer); | ||||||||||||||
| let output = decode_chunk(chunk_type, buffer)?; | ||||||||||||||
| self.state = State::Sending(PartialBuffer::new(output)) | ||||||||||||||
| } | ||||||||||||||
| State::Sending(buffer) => { | ||||||||||||||
| output.copy_unwritten_from(buffer); | ||||||||||||||
| if buffer.unwritten().is_empty() { | ||||||||||||||
| self.state = State::ChunkHeader([0u8; 4].into()) | ||||||||||||||
| } else { | ||||||||||||||
| return Ok(false); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| fn flush(&mut self, output: &mut WriteBuffer<'_>) -> std::io::Result<bool> { | ||||||||||||||
| match &mut self.state { | ||||||||||||||
| State::Sending(buffer) => { | ||||||||||||||
| output.copy_unwritten_from(buffer); | ||||||||||||||
| if buffer.unwritten().is_empty() { | ||||||||||||||
| self.state = State::ChunkHeader([0u8; 4].into()); | ||||||||||||||
| Ok(true) | ||||||||||||||
| } else { | ||||||||||||||
| Ok(false) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| _ => Ok(true), | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| fn finish(&mut self, _output: &mut WriteBuffer<'_>) -> std::io::Result<bool> { | ||||||||||||||
| match &mut self.state { | ||||||||||||||
| State::ChunkHeader(header) if header.unwritten().len() == 4 => Ok(true), | ||||||||||||||
| _ => Err(io::Error::from(io::ErrorKind::UnexpectedEof)), | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.