bzip2/
mem.rs

1//! Raw low-level manipulations of bz streams.
2
3use std::error;
4use std::fmt;
5use std::marker;
6use std::mem;
7use std::slice;
8
9use libc::{c_int, c_uint};
10
11use {ffi, Compression};
12
13/// Representation of an in-memory compression stream.
14///
15/// An instance of `Compress` can be used to compress a stream of bz2 data.
16pub struct Compress {
17    inner: Stream<DirCompress>,
18}
19
20/// Representation of an in-memory decompression stream.
21///
22/// An instance of `Decompress` can be used to inflate a stream of bz2-encoded
23/// data.
24pub struct Decompress {
25    inner: Stream<DirDecompress>,
26}
27
28struct Stream<D: Direction> {
29    // libbz2 requires a stable address for this stream.
30    raw: Box<ffi::bz_stream>,
31    _marker: marker::PhantomData<D>,
32}
33
34unsafe impl<D: Direction> Send for Stream<D> {}
35unsafe impl<D: Direction> Sync for Stream<D> {}
36
37trait Direction {
38    unsafe fn destroy(stream: *mut ffi::bz_stream) -> c_int;
39}
40
41enum DirCompress {}
42enum DirDecompress {}
43
44/// Possible actions to take on compression.
45#[derive(PartialEq, Eq, Copy, Debug, Clone)]
46pub enum Action {
47    /// Normal compression.
48    Run = ffi::BZ_RUN as isize,
49    /// Request that the current compression block is terminate.
50    Flush = ffi::BZ_FLUSH as isize,
51    /// Request that the compression stream be finalized.
52    Finish = ffi::BZ_FINISH as isize,
53}
54
55/// Result of compression or decompression
56#[derive(PartialEq, Eq, Copy, Debug, Clone)]
57pub enum Status {
58    /// Decompression went fine, nothing much to report.
59    Ok,
60
61    /// The Flush action on a compression went ok.
62    FlushOk,
63
64    /// THe Run action on compression went ok.
65    RunOk,
66
67    /// The Finish action on compression went ok.
68    FinishOk,
69
70    /// The stream's end has been met, meaning that no more data can be input.
71    StreamEnd,
72
73    /// There was insufficient memory in the input or output buffer to complete
74    /// the request, but otherwise everything went normally.
75    MemNeeded,
76}
77
78/// Fatal errors encountered when compressing/decompressing bytes.
79///
80/// These errors indicate that progress could not be made in any form due to
81/// input or output parameters.
82#[derive(PartialEq, Eq, Copy, Debug, Clone)]
83pub enum Error {
84    /// The sequence of operations called on a decompression/compression stream
85    /// were invalid. See methods for details.
86    Sequence,
87
88    /// The data being decompressed was invalid, or it was not a valid bz2
89    /// stream.
90    Data,
91
92    /// The magic bz2 header wasn't present when decompressing.
93    DataMagic,
94
95    /// The parameters to this function were invalid.
96    Param,
97}
98
99impl Compress {
100    /// Creates a new stream prepared for compression.
101    ///
102    /// The `work_factor` parameter controls how the compression phase behaves
103    /// when presented with worst case, highly repetitive, input data. If
104    /// compression runs into difficulties caused by repetitive data, the
105    /// library switches from the standard sorting algorithm to a fallback
106    /// algorithm. The fallback is slower than the standard algorithm by perhaps
107    /// a factor of three, but always behaves reasonably, no matter how bad the
108    /// input.
109    ///
110    /// Lower values of `work_factor` reduce the amount of effort the standard
111    /// algorithm will expend before resorting to the fallback. You should set
112    /// this parameter carefully; too low, and many inputs will be handled by
113    /// the fallback algorithm and so compress rather slowly, too high, and your
114    /// average-to-worst case compression times can become very large. The
115    /// default value of 30 gives reasonable behaviour over a wide range of
116    /// circumstances.
117    ///
118    /// Allowable values range from 0 to 250 inclusive. 0 is a special case,
119    /// equivalent to using the default value of 30.
120    pub fn new(lvl: Compression, work_factor: u32) -> Compress {
121        unsafe {
122            let mut raw = Box::new(mem::zeroed());
123            assert_eq!(
124                ffi::BZ2_bzCompressInit(&mut *raw, lvl.level() as c_int, 0, work_factor as c_int),
125                0
126            );
127            Compress {
128                inner: Stream {
129                    raw: raw,
130                    _marker: marker::PhantomData,
131                },
132            }
133        }
134    }
135
136    /// Compress a block of input into a block of output.
137    ///
138    /// If anything other than BZ_OK is seen, `Err` is returned. The action
139    /// given must be one of Run, Flush or Finish.
140    pub fn compress(
141        &mut self,
142        input: &[u8],
143        output: &mut [u8],
144        action: Action,
145    ) -> Result<Status, Error> {
146        // apparently 0-length compression requests which don't actually make
147        // any progress are returned as BZ_PARAM_ERROR, which we don't want, to
148        // just translate to a success here.
149        if input.len() == 0 && action == Action::Run {
150            return Ok(Status::RunOk);
151        }
152        self.inner.raw.next_in = input.as_ptr() as *mut _;
153        self.inner.raw.avail_in = input.len().min(c_uint::MAX as usize) as c_uint;
154        self.inner.raw.next_out = output.as_mut_ptr() as *mut _;
155        self.inner.raw.avail_out = output.len().min(c_uint::MAX as usize) as c_uint;
156        unsafe {
157            match ffi::BZ2_bzCompress(&mut *self.inner.raw, action as c_int) {
158                ffi::BZ_RUN_OK => Ok(Status::RunOk),
159                ffi::BZ_FLUSH_OK => Ok(Status::FlushOk),
160                ffi::BZ_FINISH_OK => Ok(Status::FinishOk),
161                ffi::BZ_STREAM_END => Ok(Status::StreamEnd),
162                ffi::BZ_SEQUENCE_ERROR => Err(Error::Sequence),
163                c => panic!("unknown return status: {}", c),
164            }
165        }
166    }
167
168    /// Compress a block of input into an output vector.
169    ///
170    /// This function will not grow `output`, but it will fill the space after
171    /// its current length up to its capacity. The length of the vector will be
172    /// adjusted appropriately.
173    pub fn compress_vec(
174        &mut self,
175        input: &[u8],
176        output: &mut Vec<u8>,
177        action: Action,
178    ) -> Result<Status, Error> {
179        let cap = output.capacity();
180        let len = output.len();
181
182        unsafe {
183            let before = self.total_out();
184            let ret = {
185                let ptr = output.as_mut_ptr().offset(len as isize);
186                let out = slice::from_raw_parts_mut(ptr, cap - len);
187                self.compress(input, out, action)
188            };
189            output.set_len((self.total_out() - before) as usize + len);
190            return ret;
191        }
192    }
193
194    /// Total number of bytes processed as input
195    pub fn total_in(&self) -> u64 {
196        self.inner.total_in()
197    }
198
199    /// Total number of bytes processed as output
200    pub fn total_out(&self) -> u64 {
201        self.inner.total_out()
202    }
203}
204
205impl Decompress {
206    /// Creates a new stream prepared for decompression.
207    ///
208    /// If `small` is true, then the library will use an alternative
209    /// decompression algorithm which uses less memory but at the cost of
210    /// decompressing more slowly (roughly speaking, half the speed, but the
211    /// maximum memory requirement drops to around 2300k). See
212    pub fn new(small: bool) -> Decompress {
213        unsafe {
214            let mut raw = Box::new(mem::zeroed());
215            assert_eq!(ffi::BZ2_bzDecompressInit(&mut *raw, 0, small as c_int), 0);
216            Decompress {
217                inner: Stream {
218                    raw: raw,
219                    _marker: marker::PhantomData,
220                },
221            }
222        }
223    }
224
225    /// Decompress a block of input into a block of output.
226    pub fn decompress(&mut self, input: &[u8], output: &mut [u8]) -> Result<Status, Error> {
227        self.inner.raw.next_in = input.as_ptr() as *mut _;
228        self.inner.raw.avail_in = input.len().min(c_uint::MAX as usize) as c_uint;
229        self.inner.raw.next_out = output.as_mut_ptr() as *mut _;
230        self.inner.raw.avail_out = output.len().min(c_uint::MAX as usize) as c_uint;
231        unsafe {
232            match ffi::BZ2_bzDecompress(&mut *self.inner.raw) {
233                ffi::BZ_OK => Ok(Status::Ok),
234                ffi::BZ_MEM_ERROR => Ok(Status::MemNeeded),
235                ffi::BZ_STREAM_END => Ok(Status::StreamEnd),
236                ffi::BZ_PARAM_ERROR => Err(Error::Param),
237                ffi::BZ_DATA_ERROR => Err(Error::Data),
238                ffi::BZ_DATA_ERROR_MAGIC => Err(Error::DataMagic),
239                ffi::BZ_SEQUENCE_ERROR => Err(Error::Sequence),
240                c => panic!("wut: {}", c),
241            }
242        }
243    }
244
245    /// Decompress a block of input into an output vector.
246    ///
247    /// This function will not grow `output`, but it will fill the space after
248    /// its current length up to its capacity. The length of the vector will be
249    /// adjusted appropriately.
250    pub fn decompress_vec(&mut self, input: &[u8], output: &mut Vec<u8>) -> Result<Status, Error> {
251        let cap = output.capacity();
252        let len = output.len();
253
254        unsafe {
255            let before = self.total_out();
256            let ret = {
257                let ptr = output.as_mut_ptr().offset(len as isize);
258                let out = slice::from_raw_parts_mut(ptr, cap - len);
259                self.decompress(input, out)
260            };
261            output.set_len((self.total_out() - before) as usize + len);
262            return ret;
263        }
264    }
265
266    /// Total number of bytes processed as input
267    pub fn total_in(&self) -> u64 {
268        self.inner.total_in()
269    }
270
271    /// Total number of bytes processed as output
272    pub fn total_out(&self) -> u64 {
273        self.inner.total_out()
274    }
275}
276
277impl<D: Direction> Stream<D> {
278    fn total_in(&self) -> u64 {
279        (self.raw.total_in_lo32 as u64) | ((self.raw.total_in_hi32 as u64) << 32)
280    }
281
282    fn total_out(&self) -> u64 {
283        (self.raw.total_out_lo32 as u64) | ((self.raw.total_out_hi32 as u64) << 32)
284    }
285}
286
287impl error::Error for Error {}
288
289impl fmt::Display for Error {
290    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291        let description = match self {
292            Error::Sequence => "bzip2: sequence of operations invalid",
293            Error::Data => "bzip2: invalid data",
294            Error::DataMagic => "bzip2: bz2 header missing",
295            Error::Param => "bzip2: invalid parameters",
296        };
297        f.write_str(description)
298    }
299}
300
301impl From<Error> for std::io::Error {
302    fn from(data: Error) -> std::io::Error {
303        std::io::Error::new(std::io::ErrorKind::Other, data)
304    }
305}
306
307impl Direction for DirCompress {
308    unsafe fn destroy(stream: *mut ffi::bz_stream) -> c_int {
309        ffi::BZ2_bzCompressEnd(stream)
310    }
311}
312impl Direction for DirDecompress {
313    unsafe fn destroy(stream: *mut ffi::bz_stream) -> c_int {
314        ffi::BZ2_bzDecompressEnd(stream)
315    }
316}
317
318impl<D: Direction> Drop for Stream<D> {
319    fn drop(&mut self) {
320        unsafe {
321            let _ = D::destroy(&mut *self.raw);
322        }
323    }
324}