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}