Skip to main content

tetsy_snappy/
lib.rs

1// Copyright 2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Snappy compression bindings.
10extern crate libc;
11extern crate tetsy_snappy_sys;
12#[cfg(test)]
13extern crate rand;
14
15use libc::{c_char, size_t};
16use tetsy_snappy_sys as snappy;
17use std::fmt;
18
19/// Attempted to decompress an uncompressed buffer.
20#[derive(Debug)]
21pub struct InvalidInput;
22
23impl std::error::Error for InvalidInput {
24	fn description(&self) -> &str {
25		"Attempted snappy decompression with invalid input"
26	}
27}
28
29impl fmt::Display for InvalidInput {
30	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31		write!(f, "Attempted snappy decompression with invalid input")
32	}
33}
34
35/// The maximum compressed length given a size.
36pub fn max_compressed_len(len: usize) -> usize {
37	unsafe { snappy::snappy_max_compressed_length(len as size_t) as usize }
38}
39
40/// How large the given data will be when decompressed.
41pub fn decompressed_len(compressed: &[u8]) -> Result<usize, InvalidInput> {
42	let mut size: size_t = 0;
43	let len = compressed.len() as size_t;
44
45	let status = unsafe {
46		snappy::snappy_uncompressed_length(compressed.as_ptr() as *const c_char, len, &mut size)
47	};
48
49	if status == snappy::SNAPPY_INVALID_INPUT {
50		Err(InvalidInput)
51	} else {
52		Ok(size)
53	}
54}
55
56/// Compress a buffer using snappy.
57pub fn compress(input: &[u8]) -> Vec<u8> {
58	let mut buf = Vec::new();
59	let size = compress_into(input, &mut buf);
60	buf.truncate(size);
61	buf
62}
63
64/// Compress a buffer using snappy, writing the result into
65/// the given output buffer, growing it if necessary.
66/// Otherwise, returns the length of the compressed data.
67pub fn compress_into(input: &[u8], output: &mut Vec<u8>) -> usize {
68	let mut len = max_compressed_len(input.len());
69
70	if output.len() < len {
71		output.resize(len, 0);
72	}
73
74	let status = unsafe {
75		snappy::snappy_compress(
76			input.as_ptr() as *const c_char,
77			input.len() as size_t,
78			output.as_mut_ptr() as *mut c_char,
79			&mut len as &mut size_t,
80		)
81	};
82
83	match status {
84		snappy::SNAPPY_OK => len,
85		snappy::SNAPPY_INVALID_INPUT => {
86			panic!("snappy compression has no concept of invalid input")
87		}
88		snappy::SNAPPY_BUFFER_TOO_SMALL => {
89			panic!("buffer cannot be too small, the capacity was just ensured.")
90		}
91		_ => panic!("snappy returned unspecified status"),
92	}
93}
94
95/// Decompress a buffer using snappy. Will return an error if the buffer is not snappy-compressed.
96pub fn decompress(input: &[u8]) -> Result<Vec<u8>, InvalidInput> {
97	let mut v = Vec::new();
98	decompress_into(input, &mut v).map(|_| v)
99}
100
101/// Decompress a buffer using snappy, writing the result into
102/// the given output buffer, growing it if necessary.
103/// Will error if the input buffer is not snappy-compressed.
104/// Otherwise, returns the length of the decompressed data.
105pub fn decompress_into(input: &[u8], output: &mut Vec<u8>) -> Result<usize, InvalidInput> {
106	let mut len = decompressed_len(input)?;
107
108	if output.len() < len {
109		output.resize(len, 0);
110	}
111
112	let status = unsafe {
113		snappy::snappy_uncompress(
114			input.as_ptr() as *const c_char,
115			input.len() as size_t,
116			output.as_mut_ptr() as *mut c_char,
117			&mut len as &mut size_t,
118		)
119	};
120
121	match status {
122		snappy::SNAPPY_OK => Ok(len as usize),
123		snappy::SNAPPY_INVALID_INPUT => Err(InvalidInput),
124		snappy::SNAPPY_BUFFER_TOO_SMALL => {
125			panic!("buffer cannot be too small, size was just set to large enough.")
126		}
127		_ => panic!("snappy returned unspecified status"),
128	}
129}
130
131/// Validate a compressed buffer. True if valid, false if not.
132pub fn validate_compressed_buffer(input: &[u8]) -> bool {
133	let status = unsafe {
134		snappy::snappy_validate_compressed_buffer(
135			input.as_ptr() as *const c_char,
136			input.len() as size_t,
137		)
138	};
139	status == snappy::SNAPPY_OK
140}
141
142#[cfg(test)]
143mod tests {
144	use super::*;
145	use rand::prelude::*;
146
147	const ITERATIONS: usize = 100;
148	const INPUT_SIZE: usize = 1 << 18;
149
150	#[test]
151	fn it_works() {
152		let mut rng = thread_rng();
153		let mut input = [0u8; INPUT_SIZE];
154
155		for _ in 0..ITERATIONS {
156			rng.fill(&mut input[..]);
157
158			let output = decompress(&compress(&input));
159
160			match output {
161				Err(err) => panic!("failed with error: {} for input: {:?}", err, input.to_vec()),
162				Ok(output) => assert_eq!(input.to_vec(), output),
163			}
164		}
165	}
166}