Skip to main content

ppmd_turbo/
lib.rs

1//! PPMd variant H (7-Zip's "Ppmd7") compression and decompression.
2//!
3//! `ppmd-turbo` implements Dmitry Shkarin's PPMd variant H context model, its
4//! sub-allocator and secondary escape estimation, and the two range coders
5//! that carry it in the wild:
6//!
7//! - the LZMA-style range coder 7-Zip uses for the `PPMD` method in `.7z`
8//!   ([`SevenZDecoder`], [`SevenZEncoder`]), and
9//! - the carry-less range coder RAR 2.9 through 4.x uses for its PPMd blocks
10//!   ([`RarPpmd`]; RAR5 has no PPMd).
11//!
12//! Output is bit-exact with 7-Zip and RARLAB unrar: a 7z stream encoded here
13//! is byte-identical to 7-Zip's for the same parameters, and a RAR stream
14//! decodes to exactly what unrar produces. Writing RAR blocks or archives is
15//! out of scope by design; [`CarrylessEncoder`] and [`CarrylessDecoder`]
16//! handle raw carry-less streams for round-trip testing only.
17//!
18//! # The step API
19//!
20//! Every codec is a step machine over slices the caller owns: a call takes
21//! input and an output slice and reports a [`Progress`] with exact
22//! `consumed` and `produced` counts and a status. The codecs never keep
23//! caller bytes between calls; a non-final call decodes only while a whole
24//! symbol's worth of input ([`Params::max_input_per_symbol`], at most
25//! [`MAX_INPUT_PER_SYMBOL`]) remains. Model memory is an [`Arena`] the
26//! caller can reuse across streams. Errors are typed ([`ErrorKind`]),
27//! `Copy`, carry the stream [`Position`], and are sticky until a reset. The
28//! [`io`] module wraps the 7z pair in `Read` and `Write`.
29//!
30//! ```
31//! use ppmd_turbo::{Params, SevenZDecoder, SevenZEncoder, SevenZStatus};
32//!
33//! let data = b"the quick brown fox jumps over the lazy dog";
34//! let params = Params::new(6, 1 << 20)?.reduced_for(data.len() as u64);
35//!
36//! let mut encoder = SevenZEncoder::new(params)?;
37//! let mut stream = vec![0u8; 128];
38//! let mut len = encoder.encode(data, &mut stream)?.produced;
39//! while !{
40//!     let fin = encoder.finish(&mut stream[len..], false)?;
41//!     len += fin.produced;
42//!     fin.done
43//! } {}
44//!
45//! let mut decoder = SevenZDecoder::new(params, Some(data.len() as u64))?;
46//! let mut out = vec![0u8; data.len()];
47//! let step = decoder.decode(&stream[..len], true, &mut out)?;
48//! assert_eq!(step.status, SevenZStatus::ReachedSize);
49//! assert_eq!(step.consumed, len);
50//! assert_eq!(&out[..], data);
51//! # Ok::<(), ppmd_turbo::Error>(())
52//! ```
53//!
54//! # Features
55//!
56//! - `std` (default): the [`io`] adapters, the `io::Error` conversion and
57//!   run-time x86 SIMD detection. Without it the crate builds on `core` and
58//!   `alloc`, choosing SIMD tiers at compile time; this is checked, not
59//!   promised. [`Error`] implements `core::error::Error` either way.
60//! - `internals`: the engine, for the crate's own fuzz targets, benches and
61//!   differential tests. Hidden and unstable.
62//!
63//! `unsafe` is permitted where it pays for itself, and only with a
64//! `// SAFETY:` proof on every block, Miri coverage where Miri can run, and a
65//! fuzz target over every decoder and encoder entry point.
66
67#![cfg_attr(not(feature = "std"), no_std)]
68#![cfg_attr(docsrs, feature(doc_cfg))]
69
70extern crate alloc as alloc_crate;
71#[cfg(not(feature = "std"))]
72extern crate core as std;
73
74#[cfg_attr(not(feature = "internals"), allow(dead_code))]
75pub(crate) mod alloc;
76mod arena;
77pub mod carryless;
78mod engine;
79mod error;
80#[cfg(feature = "std")]
81pub mod io;
82#[cfg_attr(not(feature = "internals"), allow(dead_code))]
83pub(crate) mod model;
84mod params;
85pub mod rar;
86#[cfg_attr(not(feature = "internals"), allow(dead_code))]
87pub(crate) mod rc;
88#[cfg_attr(not(feature = "internals"), allow(dead_code))]
89pub(crate) mod see;
90pub mod sevenz;
91mod stream;
92
93#[cfg(feature = "internals")]
94#[doc(hidden)]
95pub mod internals;
96
97pub use arena::Arena;
98pub use carryless::{CarrylessDecoder, CarrylessEncoder};
99pub use error::{Error, ErrorKind, Position, Progress, Result};
100pub use params::{MAX_INPUT_PER_SYMBOL, Params};
101pub use rar::{RarPpmd, RarStatus, Symbol};
102pub use sevenz::{Finish, SevenZDecoder, SevenZEncoder, SevenZStatus};
103
104pub(crate) const PPMD7_MIN_ORDER: u32 = Params::MIN_ORDER;
105pub(crate) const PPMD7_MAX_ORDER: u32 = Params::MAX_ORDER;
106pub(crate) const PPMD7_MIN_MEM_SIZE: u32 = Params::MIN_MEM;
107pub(crate) const PPMD7_MAX_MEM_SIZE: u32 = Params::MAX_MEM;
108
109/// The symbol value of the end marker in the model's encoder
110/// (`PPMD7_SYM_END` in 7-Zip).
111pub(crate) const SYM_END: i32 = -1;
112
113/// The model's decoder result for a count past the frequency total, which a
114/// valid stream never produces (`PPMD7_SYM_ERROR` in 7-Zip).
115pub(crate) const SYM_ERROR: i32 = -2;
116
117/// Every codec moves between threads; none is shared (`Sync`), because the
118/// sub-allocator's free lists use `Cell`.
119const _: () = {
120    const fn assert_send<T: Send>() {}
121    assert_send::<Arena>();
122    assert_send::<Error>();
123    assert_send::<SevenZDecoder>();
124    assert_send::<SevenZEncoder>();
125    assert_send::<RarPpmd>();
126    assert_send::<CarrylessDecoder>();
127    assert_send::<CarrylessEncoder>();
128    #[cfg(feature = "std")]
129    {
130        assert_send::<io::SevenZReader<&[u8]>>();
131        assert_send::<io::SevenZWriter<alloc_crate::vec::Vec<u8>>>();
132    }
133};