brotlic/
encode.rs

1//! Module that contains the brotli encoder instances
2//!
3//! Contains compression abstractions over [`Read`] and [`Write`] and a
4//! dedicated low-level encoder.
5//!
6//! [`Read`]: https://doc.rust-lang.org/stable/std/io/trait.Read.html
7//! [`Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
8
9use std::error::Error;
10use std::io::{BufRead, Read, Write};
11use std::{fmt, io, mem, ptr, slice};
12
13use brotlic_sys::*;
14
15use crate::{
16    BlockSize, CompressionMode, IntoInnerError, LargeWindowSize, Quality, SetParameterError,
17    WindowSize,
18};
19
20/// A reference to a brotli encoder.
21///
22/// This encoder contains internal state of the encoding process. This low-level
23/// wrapper intended to be used for people who are familiar with the C API. For
24/// higher level abstractions, see [`CompressorReader`] and
25/// [`CompressorWriter`].
26pub struct BrotliEncoder {
27    state: *mut BrotliEncoderState,
28}
29
30unsafe impl Send for BrotliEncoder {}
31unsafe impl Sync for BrotliEncoder {}
32
33impl BrotliEncoder {
34    /// Constructs a new brotli encoder instance.
35    ///
36    /// # Panics
37    ///
38    /// Panics if the encoder fails to be allocated or initialized
39    #[doc(alias = "BrotliEncoderCreateInstance")]
40    pub fn new() -> Self {
41        let instance = unsafe { BrotliEncoderCreateInstance(None, None, ptr::null_mut()) };
42
43        if !instance.is_null() {
44            BrotliEncoder { state: instance }
45        } else {
46            panic!("BrotliEncoderCreateInstance returned NULL: failed to allocate or initialize");
47        }
48    }
49
50    /// Checks if the encoder instance reached its final state.
51    #[doc(alias = "BrotliEncoderIsFinished")]
52    pub fn is_finished(&self) -> bool {
53        unsafe { BrotliEncoderIsFinished(self.state) != 0 }
54    }
55
56    /// Compresses input stream to output stream.
57    ///
58    /// This is a low-level API, for higher level abstractions see
59    /// [`CompressorReader`] or [`CompressorWriter`]. Returns the number of
60    /// bytes that were read and written. Bytes are read from `input`, the
61    /// number of bytes read is returned in the `bytes_read` field of the
62    /// result. Bytes are written to `output`, the number of bytes written is
63    /// returned in the `bytes_written` field of the result. The operation `op`
64    /// specifies the intention behind this call, whether it is to simply
65    /// process input, flush the input or finish the input. Care must be taken
66    /// to not swap, reduce or extend the input stream while flushing or
67    /// finishing. Additionally the operation should not change until all the
68    /// input has been processed and all the output has been read from the
69    /// internal buffer.
70    ///
71    /// The internal workflow consists of three steps:
72    ///
73    /// 1. read from input into the internal buffer
74    /// 2. compress input
75    /// 3. write into output from the internal buffer
76    ///
77    /// Whenever any of these tasks can't move forward, control flow is returned
78    /// to the caller. This is a wrapper around the
79    /// `BrotliEncoderCompressStream` function of the C brotli API. For more
80    /// information consult its documentation.
81    #[doc(alias = "BrotliEncoderCompressStream")]
82    pub fn compress(
83        &mut self,
84        input: &[u8],
85        output: &mut [u8],
86        op: BrotliOperation,
87    ) -> Result<EncodeResult, EncodeError> {
88        let mut input_ptr = input.as_ptr();
89        let mut input_len = input.len();
90        let mut output_ptr = output.as_mut_ptr();
91        let mut output_len = output.len();
92
93        let result = unsafe {
94            BrotliEncoderCompressStream(
95                self.state,
96                op as BrotliEncoderOperation,
97                &mut input_len,
98                &mut input_ptr,
99                &mut output_len,
100                &mut output_ptr,
101                ptr::null_mut(),
102            )
103        };
104
105        if result != 0 {
106            let bytes_read = input.len() - input_len;
107            let bytes_written = output.len() - output_len;
108
109            Ok(EncodeResult {
110                bytes_read,
111                bytes_written,
112            })
113        } else {
114            Err(EncodeError)
115        }
116    }
117
118    /// Convenience function to call method [`Self::compress`] with only input
119    /// and no output.
120    pub fn give_input(&mut self, input: &[u8], op: BrotliOperation) -> Result<usize, EncodeError> {
121        Ok(self.compress(input, &mut [], op)?.bytes_read)
122    }
123
124    /// Attempts the flush the encoding stream.
125    ///
126    /// Actual flush is performed when all output has been successfully read.
127    /// Use [`Self::has_output`] to verify that flushing completedNo other
128    /// modifying operation should be queried before flushing has been
129    /// finalized. When flush is complete, output data will be sufficient for a
130    /// decoder to reproduce all given input. Calling this function might
131    /// resulting in a worse compression ratio, because the encoder is forced to
132    /// emit all output immediately.
133    pub fn flush(&mut self) -> Result<(), EncodeError> {
134        self.give_op(BrotliOperation::Flush)
135    }
136
137    /// Finalizes the encoding stream.
138    ///
139    /// Actual finalization is performed when all output from the encoder has
140    /// been successfully read. Use [`Self::is_finished`] to verify that the
141    /// encoder is finished. Once this method has been called, no further input
142    /// should be processed.
143    ///
144    /// For more information, see
145    /// `BrotliEncoderOperation::BROTLI_OPERATION_FINISH`
146    pub fn finish(&mut self) -> Result<(), EncodeError> {
147        self.give_op(BrotliOperation::Finish)
148    }
149
150    /// Checks if the encoder has more output.
151    #[doc(alias = "BrotliEncoderHasMoreOutput")]
152    pub fn has_output(&self) -> bool {
153        unsafe { BrotliEncoderHasMoreOutput(self.state) != 0 }
154    }
155
156    /// Checks if the encoder has more output and if so, returns a slice to its
157    /// internal output buffer.
158    ///
159    /// Each byte returned from the slice is considered "consumed" and must be
160    /// used as it will not be returned again. Encoder output is not guaranteed
161    /// to be contagious, which means that this function can return
162    /// `Some(&[u8])` multiple times. Only when the method returns `None` is
163    /// when there is no more output available by the encoder.
164    ///
165    /// # Safety
166    ///
167    /// For every consecutive call of this function, the previous slice becomes
168    /// invalidated.
169    #[doc(alias = "BrotliEncoderTakeOutput")]
170    pub unsafe fn take_output(&mut self) -> Option<&[u8]> {
171        if self.has_output() {
172            let mut len: usize = 0;
173            let output = BrotliEncoderTakeOutput(self.state, &mut len as _);
174
175            Some(slice::from_raw_parts(output, len))
176        } else {
177            None
178        }
179    }
180
181    /// Returns the version of the C brotli encoder library.
182    #[doc(alias = "BrotliEncoderVersion")]
183    pub fn version() -> u32 {
184        unsafe { BrotliEncoderVersion() }
185    }
186
187    fn set_param(
188        &mut self,
189        param: BrotliEncoderParameter,
190        value: u32,
191    ) -> Result<(), SetParameterError> {
192        let r = unsafe { BrotliEncoderSetParameter(self.state, param, value) };
193
194        if r != 0 {
195            Ok(())
196        } else {
197            Err(SetParameterError::Generic)
198        }
199    }
200
201    fn give_op(&mut self, op: BrotliOperation) -> Result<(), EncodeError> {
202        self.give_input(&[], op)?;
203        Ok(())
204    }
205}
206
207impl fmt::Debug for BrotliEncoder {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_struct("BrotliEncoder")
210            .field("state", &self.state)
211            .finish_non_exhaustive()
212    }
213}
214
215impl Default for BrotliEncoder {
216    fn default() -> Self {
217        BrotliEncoder::new()
218    }
219}
220
221impl Drop for BrotliEncoder {
222    #[doc(alias = "BrotliEncoderDestroyInstance")]
223    fn drop(&mut self) {
224        unsafe {
225            BrotliEncoderDestroyInstance(self.state);
226        }
227    }
228}
229
230/// The operation for the encoder to process.
231#[derive(Debug, Copy, Clone, Eq, PartialEq)]
232pub enum BrotliOperation {
233    /// Instructs the encoder to keep processing input data.
234    Process = BrotliEncoderOperation_BROTLI_OPERATION_PROCESS as isize,
235
236    /// Instructs the encoder to commit a flushing operation. Care must be taken
237    /// once a flush is initiated, to keep submitting flush operations till the
238    /// encoder has no more output available. Additionally, the input stream
239    /// should not be swapped, reduced or extended.
240    Flush = BrotliEncoderOperation_BROTLI_OPERATION_FLUSH as isize,
241
242    /// Instructs the encoder to commit a finish operation. Care must be taken
243    /// once a finishing operation is initiated, to keep submitting flush
244    /// operations till the encoder has no more output available. Additionally,
245    /// the input stream should not be swapped, reduced or extended.
246    Finish = BrotliEncoderOperation_BROTLI_OPERATION_FINISH as isize,
247}
248
249/// Compression options to be used for a [`BrotliEncoder`].
250///
251/// # Examples
252///
253/// Building an encoder using text mode and use a custom quality:
254/// ```
255/// use brotlic::{BrotliEncoderOptions, CompressionMode, Quality};
256///
257/// let encoder = BrotliEncoderOptions::new()
258///     .mode(CompressionMode::Text)
259///     .quality(Quality::new(5)?)
260///     .build()?;
261///
262/// # Ok::<(), brotlic::SetParameterError>(())
263/// ```
264#[derive(Debug, Clone)]
265pub struct BrotliEncoderOptions {
266    mode: Option<CompressionMode>,
267    quality: Option<Quality>,
268    window_size: Option<LargeWindowSize>,
269    block_bits: Option<BlockSize>,
270    disable_context_modeling: Option<bool>,
271    size_hint: Option<u32>,
272    postfix_bits: Option<u32>,
273    direct_distance_codes: Option<u32>,
274    stream_offset: Option<u32>,
275}
276
277impl BrotliEncoderOptions {
278    /// Creates a new blank set encoder options.
279    ///
280    /// initially no modifications are applied to the encoder and everything is
281    /// set to its default values.
282    pub fn new() -> Self {
283        BrotliEncoderOptions {
284            mode: None,
285            quality: None,
286            window_size: None,
287            block_bits: None,
288            disable_context_modeling: None,
289            size_hint: None,
290            postfix_bits: None,
291            direct_distance_codes: None,
292            stream_offset: None,
293        }
294    }
295
296    /// Allows to tune a brotli compressor for a specific type of input.
297    pub fn mode(&mut self, mode: CompressionMode) -> &mut Self {
298        self.mode = Some(mode);
299        self
300    }
301
302    /// The main compression speed-desnity lever. Higher quality means better
303    /// compression ratios at the expense of slower compression times. For more
304    /// information see [`Quality`]
305    ///
306    /// [`Quality`]: crate::Quality
307    pub fn quality(&mut self, quality: Quality) -> &mut Self {
308        self.quality = Some(quality);
309        self
310    }
311
312    /// Recommended sliding LZ77 window size according to RFC7932 (Brotli
313    /// proper). For more information see [`WindowSize`].
314    ///
315    /// [`WindowSize`]: crate::WindowSize
316    pub fn window_size(&mut self, window_size: WindowSize) -> &mut Self {
317        self.window_size = Some(window_size.into());
318        self
319    }
320
321    /// The non-standard large window size to use. For more information see
322    /// [`LargeWindowSize`].
323    ///
324    /// Warning: The decompressor needs explicit support in order to use this
325    /// feature. This is not supported by the convenience [`decompress`]
326    /// function. A matching [`BrotliDecoder`] must set [`large_window_size`] to
327    /// true to decode non standard window sizes properly.
328    ///
329    /// [`LargeWindowSize`]: crate::LargeWindowSize
330    /// [`decompress`]: crate::decompress
331    /// [`BrotliDecoder`]: crate::decode::BrotliDecoder
332    /// [`large_window_size`]: crate::decode::BrotliDecoderOptions::large_window_size
333    pub fn large_window_size(&mut self, large_window_size: LargeWindowSize) -> &mut Self {
334        self.window_size = Some(large_window_size);
335        self
336    }
337
338    /// The recommended input block size to use.
339    ///
340    /// The encoder may reduce this value, e.g. when the input is much smaller
341    /// than the input block size.
342    pub fn block_size(&mut self, block_size: BlockSize) -> &mut Self {
343        self.block_bits = Some(block_size);
344        self
345    }
346
347    /// Disable "literal context modeling" format feature.
348    ///
349    /// Disabling literal context modeling decreases compression ratio in favor
350    /// of decompression speed.
351    pub fn disable_context_modeling(&mut self, disable_context_modeling: bool) -> &mut Self {
352        self.disable_context_modeling = Some(disable_context_modeling);
353        self
354    }
355
356    /// Estimated total input size.
357    ///
358    /// This is 0 by default, which corresponds to the size being unknown.
359    pub fn size_hint(&mut self, size_hint: u32) -> &mut Self {
360        self.size_hint = Some(size_hint);
361        self
362    }
363
364    /// The number of postfix bits to use
365    ///
366    /// The encoder may change this value on the fly.
367    ///
368    /// Valid ranges are from `0` to `3` (`BROTLI_MAX_NPOSTFIX`) inclusive.
369    pub fn postfix_bits(&mut self, postfix_bits: u32) -> &mut Self {
370        self.postfix_bits = Some(postfix_bits);
371        self
372    }
373
374    /// Recommended number of direct distance codes.
375    ///
376    /// The encoder may change this value on the fly.
377    ///
378    /// Valid range is from 0 to (15 << postfix) inclusive in steps of (1 <<
379    /// postfix), where postfix is the number of postfix bits.
380    pub fn direct_distance_codes(&mut self, direct_distance_codes: u32) -> &mut Self {
381        self.direct_distance_codes = Some(direct_distance_codes);
382        self
383    }
384
385    /// Number of bytes already processed by a different instance.
386    ///
387    /// It is worth noting that when using this parameter, all other encoders
388    /// must share the same parameters, so that all encoded parts obey the same
389    /// restrictions as implied by the header of the compression stream.
390    ///
391    /// If the offset is non-zero, the stream header is omitted. Values greater
392    /// than 2**30 are not allowed.
393    pub fn stream_offset(&mut self, stream_offset: u32) -> &mut Self {
394        self.stream_offset = Some(stream_offset);
395        self
396    }
397
398    /// Creates a brotli encoder with the specified settings using allocator
399    /// `alloc`.
400    ///
401    /// # Errors
402    ///
403    /// If any of the preconditions of the parameters are violated, an error is
404    /// returned.
405    #[doc(alias = "BrotliEncoderSetParameter")]
406    pub fn build(&self) -> Result<BrotliEncoder, SetParameterError> {
407        let mut encoder = BrotliEncoder::new();
408
409        self.configure(&mut encoder)?;
410
411        Ok(encoder)
412    }
413
414    fn configure(&self, encoder: &mut BrotliEncoder) -> Result<(), SetParameterError> {
415        if let Some(mode) = self.mode {
416            let key = BrotliEncoderParameter_BROTLI_PARAM_MODE;
417            let value = mode as u32;
418
419            encoder.set_param(key, value)?;
420        }
421
422        if let Some(quality) = self.quality {
423            let key = BrotliEncoderParameter_BROTLI_PARAM_QUALITY;
424            let value = quality.0 as u32;
425
426            encoder.set_param(key, value)?;
427        }
428
429        if let Some(window_size) = self.window_size {
430            let key = BrotliEncoderParameter_BROTLI_PARAM_LGWIN;
431            let value = window_size.0 as u32;
432
433            encoder.set_param(key, value)?;
434
435            let large_window = WindowSize::try_from(window_size).is_err();
436
437            let key = BrotliEncoderParameter_BROTLI_PARAM_LARGE_WINDOW;
438            let value = large_window as u32;
439
440            encoder.set_param(key, value)?;
441        }
442
443        if let Some(block_bits) = self.block_bits {
444            let key = BrotliEncoderParameter_BROTLI_PARAM_LGBLOCK;
445            let value = block_bits.0 as u32;
446
447            encoder.set_param(key, value)?;
448        }
449
450        if let Some(disable_context_modeling) = self.disable_context_modeling {
451            let key = BrotliEncoderParameter_BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING;
452            let value = disable_context_modeling as u32;
453
454            encoder.set_param(key, value)?;
455        }
456
457        if let Some(size_hint) = self.size_hint {
458            let key = BrotliEncoderParameter_BROTLI_PARAM_SIZE_HINT;
459            let value = size_hint;
460
461            encoder.set_param(key, value)?;
462        }
463
464        if let Some(postfix_bits) = self.postfix_bits {
465            if postfix_bits > 3 {
466                return Err(SetParameterError::InvalidPostfix);
467            }
468
469            let key = BrotliEncoderParameter_BROTLI_PARAM_NPOSTFIX;
470            let value = postfix_bits;
471
472            encoder.set_param(key, value)?;
473        }
474
475        if let Some(direct_distance_codes) = self.direct_distance_codes {
476            let postfix = self.postfix_bits.unwrap_or(0);
477
478            if (direct_distance_codes > (15 << postfix))
479                || (direct_distance_codes & ((1 << postfix) - 1)) != 0
480            {
481                return Err(SetParameterError::InvalidDirectDistanceCodes);
482            }
483
484            let key = BrotliEncoderParameter_BROTLI_PARAM_NDIRECT;
485            let value = direct_distance_codes;
486
487            encoder.set_param(key, value)?;
488        }
489
490        if let Some(stream_offset) = self.stream_offset {
491            if stream_offset > (1 << 30) {
492                return Err(SetParameterError::InvalidStreamOffset);
493            }
494
495            let key = BrotliEncoderParameter_BROTLI_PARAM_STREAM_OFFSET;
496            let value = stream_offset;
497
498            encoder.set_param(key, value)?;
499        }
500
501        Ok(())
502    }
503}
504
505impl Default for BrotliEncoderOptions {
506    fn default() -> Self {
507        BrotliEncoderOptions::new()
508    }
509}
510
511/// A struct used by [`BrotliEncoder::compress`].
512#[derive(Debug, Copy, Clone, Eq, PartialEq)]
513pub struct EncodeResult {
514    /// the number of bytes read from `input`.
515    pub bytes_read: usize,
516    /// the number of bytes written to `output`.
517    pub bytes_written: usize,
518}
519
520/// An error returned by [`BrotliEncoder::compress`].
521#[derive(Debug, Copy, Clone, Eq, PartialEq)]
522pub struct EncodeError;
523
524impl Error for EncodeError {}
525
526impl fmt::Display for EncodeError {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        f.write_str("brotli encoder error")
529    }
530}
531
532impl From<EncodeError> for io::Error {
533    fn from(err: EncodeError) -> Self {
534        io::Error::new(io::ErrorKind::Other, err)
535    }
536}
537
538/// Wraps a reader and compresses its output.
539///
540/// The compression stream produced by brotli must be finished in order to be
541/// decompressed. This is done when the underlying reader reaches EOF.
542/// Therefore, `CompressorReader<R>` does not work with `BufRead`s that return
543/// an infinite amount of data. When [`read`] returns zero on a non-zero buffer,
544/// the compression is considered finished.
545///
546/// # Examples
547///
548/// Suppose the file `test.txt` contains uncompressed text. Let's try to
549/// compress it:
550///
551/// ```no_run
552/// use std::fs::File;
553/// use std::io::Read;
554///
555/// use brotlic::DecompressorWriter;
556///
557/// let mut input = File::open("test.txt")?; // test.brotli is brotli compressed
558/// let mut output = Vec::new();
559///
560/// input.read_to_end(&mut output)?;
561///
562/// println!("Compressed length: {}", output.len());
563///
564/// # Ok::<(), std::io::Error>(())
565/// ```
566///
567/// [`read`]: CompressorReader::read
568#[derive(Debug)]
569pub struct CompressorReader<R: BufRead> {
570    inner: R,
571    encoder: BrotliEncoder,
572    op: BrotliOperation,
573}
574
575impl<R: BufRead> CompressorReader<R> {
576    /// Creates a new `CompressorReader<R>` with a newly created encoder.
577    ///
578    /// # Panics
579    ///
580    /// Panics if the encoder fails to be allocated or initialized
581    pub fn new(inner: R) -> Self {
582        CompressorReader {
583            inner,
584            encoder: BrotliEncoder::new(),
585            op: BrotliOperation::Process,
586        }
587    }
588
589    /// Creates a new `CompressorReader<R>` with a specified encoder.
590    ///
591    /// # Examples
592    ///
593    /// ```
594    /// use brotlic::{BrotliEncoderOptions, CompressorReader, Quality, WindowSize};
595    ///
596    /// let encoder = BrotliEncoderOptions::new()
597    ///     .quality(Quality::new(6)?)
598    ///     .window_size(WindowSize::new(18)?)
599    ///     .build()?;
600    ///
601    /// let underlying_source = [1, 2, 3, 4, 5];
602    /// let writer = CompressorReader::with_encoder(encoder, underlying_source.as_slice());
603    /// # Ok::<(), brotlic::SetParameterError>(())
604    /// ```
605    pub fn with_encoder(encoder: BrotliEncoder, inner: R) -> Self {
606        CompressorReader {
607            inner,
608            encoder,
609            op: BrotliOperation::Process,
610        }
611    }
612
613    /// Gets a reference to the underlying reader
614    pub fn get_ref(&self) -> &R {
615        &self.inner
616    }
617
618    /// Gets a mutable reference to the underlying reader.
619    ///
620    /// It is inadvisable to directly read from the underlying reader.
621    pub fn get_mut(&mut self) -> &mut R {
622        &mut self.inner
623    }
624
625    /// Unwraps this `CompressorReader<R>`, returning the underlying reader.
626    ///
627    /// # Errors
628    ///
629    /// An [`Err`] will be returned if the compression stream has not been
630    /// finished.
631    pub fn into_inner(self) -> Result<R, IntoInnerError<CompressorReader<R>>> {
632        if self.encoder.is_finished() {
633            Ok(self.inner)
634        } else {
635            Err(IntoInnerError::new(
636                self,
637                io::ErrorKind::UnexpectedEof.into(),
638            ))
639        }
640    }
641
642    /// Disassembles this `CompressorReader<R>`, returning the underlying reader
643    /// and encoder.
644    ///
645    /// `into_parts` makes no attempt to validate that the compression stream
646    /// finished and cannot fail.
647    pub fn into_parts(self) -> (R, BrotliEncoder) {
648        (self.inner, self.encoder)
649    }
650}
651
652impl<R: BufRead> Read for CompressorReader<R> {
653    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
654        loop {
655            let input = self.inner.fill_buf()?;
656            let eof = input.is_empty();
657            let EncodeResult {
658                bytes_read,
659                bytes_written,
660            } = self.encoder.compress(input, buf, self.op)?;
661            self.inner.consume(bytes_read);
662
663            match self.op {
664                _ if bytes_written > 0 => return Ok(bytes_written),
665                _ if buf.is_empty() => return Ok(0),
666                _ if !eof => continue,
667                BrotliOperation::Process => {
668                    self.op = BrotliOperation::Finish;
669                    continue;
670                }
671                BrotliOperation::Finish => return Ok(0),
672                _ => unreachable!(),
673            }
674        }
675    }
676}
677
678/// Wraps a writer and compresses its output.
679///
680/// `CompressorWriter<W>` wraps a writer and adds brotli compression to the
681/// output. It is critical to finish the compression stream, otherwise
682/// decompression will not be successful. Dropping will attempt to finish the
683/// compression stream, any errors that might arise however will be ignored.
684/// Calling [`into_inner`] ensures that the compression stream is finished.
685///
686/// Calling [`flush`] will not only flush the underlying writer, but also flush
687/// all of its compression stream. This will lead to a slight decrease of
688/// compression quality, as output will be forced to be flushed as is and not
689/// compressed till the block is finished.
690///
691/// # Examples
692///
693/// Let's compress some text file named `text.txt` and write the output to
694/// `test.brotli`:
695///
696/// ```no_run
697/// use std::fs::File;
698/// use std::io;
699///
700/// use brotlic::CompressorWriter;
701///
702/// let mut input = File::open("test.txt")?; // test.txt is uncompressed
703/// let mut output = File::create("test.brotli")?;
704/// let mut compressed_output = CompressorWriter::new(output);
705///
706/// io::copy(&mut input, &mut compressed_output)?;
707///
708/// # Ok::<(), io::Error>(())
709/// ```
710///
711/// To decompress it again, use [`DecompressorWriter`].
712///
713/// [`into_inner`]: CompressorWriter::into_inner
714/// [`flush`]: CompressorWriter::flush
715/// [`DecompressorWriter`]: crate::decode::DecompressorWriter
716#[derive(Debug)]
717pub struct CompressorWriter<W: Write> {
718    inner: W,
719    encoder: BrotliEncoder,
720    panicked: bool,
721}
722
723impl<W: Write> CompressorWriter<W> {
724    /// Creates a new `CompressorWriter<W>` with a newly created encoder.
725    ///
726    /// # Panics
727    ///
728    /// Panics if the encoder fails to be allocated or initialized
729    pub fn new(inner: W) -> Self {
730        CompressorWriter {
731            inner,
732            encoder: BrotliEncoder::new(),
733            panicked: false,
734        }
735    }
736
737    /// Creates a new `CompressorWriter<W>` with a specified encoder.
738    ///
739    /// # Examples
740    ///
741    /// ```
742    /// use brotlic::{BrotliEncoderOptions, CompressorWriter, Quality, WindowSize};
743    ///
744    /// let encoder = BrotliEncoderOptions::new()
745    ///     .quality(Quality::new(4)?)
746    ///     .window_size(WindowSize::new(16)?)
747    ///     .build()?;
748    ///
749    /// let underlying_storage = Vec::new();
750    /// let writer = CompressorWriter::with_encoder(encoder, underlying_storage);
751    /// # Ok::<(), brotlic::SetParameterError>(())
752    /// ```
753    pub fn with_encoder(encoder: BrotliEncoder, inner: W) -> Self {
754        CompressorWriter {
755            inner,
756            encoder,
757            panicked: false,
758        }
759    }
760
761    /// Gets a reference to the underlying writer
762    pub fn get_ref(&self) -> &W {
763        &self.inner
764    }
765
766    /// Gets a mutable reference to the underlying writer.
767    ///
768    /// It is inadvisable to directly write to the underlying writer.
769    pub fn get_mut(&mut self) -> &mut W {
770        &mut self.inner
771    }
772
773    /// Unwraps this `CompressorWriter<W>`, returning the underlying writer.
774    ///
775    /// The compression stream is finished before returning the writer.
776    ///
777    /// # Errors
778    ///
779    /// An [`Err`] will be returned if an error occurs while finishing the
780    /// compression stream.
781    pub fn into_inner(mut self) -> Result<W, IntoInnerError<CompressorWriter<W>>> {
782        match self.finish() {
783            Err(e) => Err(IntoInnerError::new(self, e)),
784            Ok(()) => Ok(self.into_parts().0),
785        }
786    }
787
788    /// Disassembles this `CompressorWriter<W>`, returning the underlying writer
789    /// and encoder.
790    ///
791    /// If the underlying writer panicked, it is not known what portion of the
792    /// data was written. In this case, we return `WriterPanicked` to get the
793    /// encoder back. It is worth noting that the compression stream is not
794    /// finished and hence cannot be successfully decompressed. To obtain the
795    /// writer once the compression stream is finished, use [`into_inner`].
796    ///
797    /// `into_parts` makes no attempt to finish the compression stream and
798    /// cannot fail.
799    ///
800    /// [`into_inner`]: Self::into_inner
801    pub fn into_parts(self) -> (W, Result<BrotliEncoder, WriterPanicked>) {
802        let inner = unsafe { ptr::read(&self.inner) };
803        let encoder = unsafe { ptr::read(&self.encoder) };
804        let panicked = self.panicked;
805        mem::forget(self);
806
807        let encoder = if !panicked {
808            Ok(encoder)
809        } else {
810            Err(WriterPanicked { encoder })
811        };
812
813        (inner, encoder)
814    }
815
816    fn finish(&mut self) -> io::Result<()> {
817        self.encoder.finish()?;
818        self.flush_encoder_output()
819    }
820
821    fn flush_encoder_output(&mut self) -> io::Result<()> {
822        while let Some(output) = unsafe { self.encoder.take_output() } {
823            self.panicked = true;
824            let r = self.inner.write_all(output);
825            self.panicked = false;
826            r?;
827        }
828
829        Ok(())
830    }
831}
832
833impl<W: Write> Write for CompressorWriter<W> {
834    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
835        let bytes_read = self.encoder.give_input(buf, BrotliOperation::Process)?;
836        self.flush_encoder_output()?;
837
838        Ok(bytes_read)
839    }
840
841    fn flush(&mut self) -> io::Result<()> {
842        self.encoder.flush()?;
843        self.flush_encoder_output()?;
844
845        self.inner.flush()
846    }
847}
848
849impl<W: Write> Drop for CompressorWriter<W> {
850    fn drop(&mut self) {
851        if !self.panicked {
852            let _r = self.finish();
853        }
854    }
855}
856
857/// Error returned from [`CompressorWriter::into_inner`], when the underlying
858/// writer has previously panicked. Contains the encoder that was used for
859/// compression.
860#[derive(Debug)]
861pub struct WriterPanicked {
862    encoder: BrotliEncoder,
863}
864
865impl WriterPanicked {
866    /// Returns the encoder that was used for compression. It is unknown what
867    /// data was fed to the encoder, so simply using it to finish it is not a
868    /// good idea.
869    pub fn into_inner(self) -> BrotliEncoder {
870        self.encoder
871    }
872}
873
874impl Error for WriterPanicked {}
875
876impl fmt::Display for WriterPanicked {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        f.write_str(
879            "CompressorWriter inner writer panicked, what data remains unwritten is not known",
880        )
881    }
882}
883
884#[cfg(test)]
885mod tests {
886    use super::*;
887
888    #[test]
889    fn invalid_quality() {
890        let invalid = Quality::new(12);
891
892        assert_eq!(invalid.unwrap_err(), SetParameterError::InvalidQuality);
893    }
894
895    #[test]
896    fn invalid_window_size() {
897        let invalid = WindowSize::new(25);
898
899        assert_eq!(invalid.unwrap_err(), SetParameterError::InvalidWindowSize);
900    }
901
902    #[test]
903    fn invalid_large_window_size() {
904        let invalid = LargeWindowSize::new(31);
905
906        assert_eq!(invalid.unwrap_err(), SetParameterError::InvalidWindowSize);
907    }
908
909    #[test]
910    fn invalid_block_size() {
911        let invalid = BlockSize::new(25);
912
913        assert_eq!(invalid.unwrap_err(), SetParameterError::InvalidBlockSize);
914    }
915
916    #[test]
917    fn valid_stream_offset() {
918        let res = BrotliEncoderOptions::new().stream_offset(1 << 30).build();
919
920        assert!(res.is_ok());
921    }
922
923    #[test]
924    fn invalid_stream_offset() {
925        let res = BrotliEncoderOptions::new()
926            .stream_offset((1 << 30) + 2)
927            .build();
928
929        assert_eq!(res.unwrap_err(), SetParameterError::InvalidStreamOffset);
930    }
931
932    #[test]
933    fn valid_postfix_bits() {
934        let res = BrotliEncoderOptions::new().postfix_bits(3).build();
935
936        assert!(res.is_ok());
937    }
938
939    #[test]
940    fn invalid_postfix_bits() {
941        let res = BrotliEncoderOptions::new().postfix_bits(7).build();
942
943        assert_eq!(res.unwrap_err(), SetParameterError::InvalidPostfix);
944    }
945
946    #[test]
947    fn valid_direct_distance_codes() {
948        let res = BrotliEncoderOptions::new()
949            .postfix_bits(3)
950            .direct_distance_codes(120)
951            .build();
952
953        assert!(res.is_ok());
954    }
955
956    #[test]
957    fn invalid_direct_distance_codes() {
958        let res = BrotliEncoderOptions::new()
959            .postfix_bits(2)
960            .direct_distance_codes(120)
961            .build();
962
963        assert_eq!(
964            res.unwrap_err(),
965            SetParameterError::InvalidDirectDistanceCodes
966        );
967    }
968}