zstd_framed/
lib.rs

1//! Seekable reader types and writer types for use with zstd-compressed
2//! streams made up of multiple frames, especially those that use the [zstd seekable format].
3//! Works with sync I/O, and async I/O via the `tokio` and `futures` features.
4//!
5//! By compressing a file into multiple frames, we can handle seeking
6//! relatively efficiently: we first jump to the start of the last frame
7//! before the target seek position, then decompress until we reach the
8//! target. As long as the file was created with a reasonable frame size,
9//! this can be more efficient than decompressing the entire file.
10//!
11//! This crate uses the [`zstd`] crate for zstd compression and decompression,
12//! but includes a custom Rust implementation for the [zstd seekable format]
13//! parser and serializer.
14//!
15//! ## When not to use this crate
16//!
17//! **This crate exposes a lot of options and can be easily mis-used if
18//! you don't review your configuration carefully!** This is not meant to
19//! be a general-purpose tool for compression and decompression, but one
20//! that gives a lot of flexibility, such as when dealing with
21//! partially-written files, or when you need precise control on how a file
22//! gets broken up into multiple frames.
23//!
24//! For more general use, consider one of these alternative crates:
25//!
26//! - [`zstd`](https://crates.io/crates/zstd) for zstd compression and decompression (with synchronous I/O)
27//! - [`async-compression`](https://crates.io/crates/async-compression) for
28//!   compression and decompression with zstd and many other algorithms (with
29//!   asynchronous I/O)
30//! - [`zstd-seekable`](https://crates.io/crates/zstd-seekable) for compression
31//!   and decompression using the [zstd seekable format] (with synchronous I/O).
32//!   It also uses the official upstream zstd implementation for the seekable
33//!   format.
34//!
35//! ## Getting started
36//!
37//! - Sync I/O
38//!     - Reading a seek table: [`crate::table::read_seek_table`]
39//!     - Reading a compressed stream: [`crate::ZstdReader`]
40//!     - Writing a compressed stream [`crate::ZstdWriter`]
41//! - Tokio
42//!     - Reading a seek table: [`crate::table::tokio::read_seek_table`]
43//!     - Reading a compressed stream: [`crate::AsyncZstdReader`]
44//!     - Writing a compressed stream: [`crate::AsyncZstdWriter`]
45//! - `futures-rs`
46//!     - Reading a seek table: [`crate::table::futures::read_seek_table`]
47//!     - Reading a compressed stream: [`crate::AsyncZstdReader`]
48//!     - Writing a compressed stream: [`crate::AsyncZstdWriter`]
49//!
50//! ## Examples
51//!
52//! ```
53//! # use std::io::{Read as _, Seek as _, Write as _};
54//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
55//! // -----------------------------
56//! // - Write a compressed stream -
57//! // -----------------------------
58//! let mut compressed_stream = vec![];
59//!
60//! let mut writer = zstd_framed::ZstdWriter::builder(&mut compressed_stream)
61//!     .with_compression_level(3) // Set zstd compression level
62//!     .with_seek_table(1000) // Add a seek table with a frame size of 1000
63//!     .build()?;
64//!
65//! // Compress data by writing it to the writer
66//! writer.write_all(b"hello world!");
67//!
68//! // Finish writing the stream
69//! writer.shutdown()?;
70//! drop(writer);
71//!
72//! // -------------------------------------
73//! // - Read the seek table from a stream -
74//! // -------------------------------------
75//! let mut compressed_stream = std::io::Cursor::new(compressed_stream);
76//! let seek_table = zstd_framed::table::read_seek_table(&mut compressed_stream)?
77//!     .ok_or("expected stream to contain a seek table")?;
78//!
79//! // --------------------------------------------------
80//! // - Read and decompress a stream with a seek table -
81//! // --------------------------------------------------
82//! let mut reader = zstd_framed::ZstdReader::builder(&mut compressed_stream)
83//!     .with_seek_table(seek_table)
84//!     .build()?;
85//!
86//! // Seek past the word "hello", then read the rest of the stream
87//! let mut decompressed_part = String::new();
88//! reader.seek(std::io::SeekFrom::Start(6))?;
89//! reader.read_to_string(&mut decompressed_part)?;
90//!
91//! assert_eq!(decompressed_part, "world!");
92//! # Ok(())
93//! # }
94//! ```
95//!
96//! ## Feature flags
97//!
98//! - `tokio`: Enable Tokio support, which enables the [`AsyncZstdReader`]
99//!   and [`AsyncZstdWriter`] types to be used with the Tokio I/O traits
100//!   ([`tokio::io::AsyncRead`], [`tokio::io::AsyncWrite`], etc.)
101//! - `futures`: Enable support for the `futures-rs` crate, which enables the [`AsyncZstdReader`] and [`AsyncZstdWriter`] types to be used with the `futures-rs` traits ([`futures::AsyncRead`], [`futures::AsyncWrite`], etc.)
102//!
103//! [zstd seekable format]: https://github.com/facebook/zstd/tree/51eb7daf39c8e8a7c338ba214a9d4e2a6a086826/contrib/seekable_format
104
105pub use async_reader::{AsyncZstdReader, AsyncZstdSeekableReader};
106pub use async_writer::AsyncZstdWriter;
107pub use reader::ZstdReader;
108pub use writer::ZstdWriter;
109
110#[macro_use]
111mod macros;
112
113pub mod async_reader;
114pub mod async_writer;
115mod buffer;
116mod decoder;
117mod encoder;
118pub mod reader;
119pub mod table;
120pub mod writer;
121
122const SKIPPABLE_HEADER_MAGIC_BYTES: [u8; 4] = 0x184D2A5E_u32.to_le_bytes();
123const SEEKABLE_FOOTER_MAGIC_BYTES: [u8; 4] = 0x8F92EAB1_u32.to_le_bytes();
124
125/// Returns the outcome of trying to move some data, such as encoding or
126/// decoding a zstd stream.
127#[derive(Debug, Clone, Copy)]
128enum ZstdOutcome<T> {
129    /// The operation finished.
130    Complete(T),
131    /// The operation did not complete because more data still needs to
132    /// be moved.
133    HasMore {
134        /// A hint on how much more data needs to be moved for the operation
135        /// to complete.
136        remaining_bytes: usize,
137    },
138}