Zstd streaming compression using ZSTD_compressBlock
Zstandard is, at its core, a block compressor. This core is usually packaged up and presented as a higher-level API (either streaming compression with ZSTD_compressStream2
, or one-shot compression with ZSTD_compress
), but the core remains available in the form of ZSTD_compressBlock
:
ZSTDLIB_STATIC_API
size_t ZSTD_compressBlock(ZSTD_CCtx* cctx,
void* dst, size_t dstCapacity,
const void* src, size_t srcSize);
The ZSTDLIB_STATIC_API
specifier means that this API isn't considered stable, and is only available when statically linking against the zstd library. It also means that #define ZSTD_STATIC_LINKING_ONLY
needs to appear before #include <zstd.h>
. The cctx
argument contains a bunch of state which is carried over between successive blocks. The result of compression will be written into the memory range [dst, dst + dstCapacity)
. The dstCapacity
argument exists mostly as a safety check; ZSTD_compressBlock
will return an error if this value is not large enough. The block to be compressed is read from [src, src + srcSize)
, and the caller needs to keep this memory range unmodified for as long as it remains within the compression window. srcSize
must not exceed 1<<17
. The return value falls into one of a few ranges:
0
: The input range could not be compressed- Greater than
1
, less thansrcSize
, and less than129022
: The input range was compressed, and the return value is the compressed length - Greater than
1<<17
: Some kind of error condition
The compression window is formed by the concatenation of two spans of bytes, both initially empty. Diverging from the internal terminology, I'll call the two spans the old generation and the current generation. Within ZSTD_compressBlock
, if the current generation ends at src
, then the current generation is expanded to the right by srcSize
. Otherwise, the old generation is discarded, the current generation becomes the old generation, and [src, src + srcSize)
becomes the current generation. If the old generation overlaps with the current generation, then the old generation is trimmed (from the left) to eliminate the overlap. The caller is responsible for calculating the maximum window size resulting from all this.
To generate a compressed stream which is compatible with the higher-level API, two layers of framing need to be applied. Each block needs a three-byte header, and then multiple blocks are concatencated to form frames, with frames having an additional header and optional footer (and in turn, multiple frames can be concatenated).
The three-byte block header consists of three fields, which starting from the least significant bit are:
is_last_block
flag (1 bit)block_type
field (2 bits)block_size
field (21 bits, though value not allowed to exceed1<<17
)
If the is_last_block
flag is not set, then another block is expected to follow in the stream. If it is set, then the current frame footer (if any) is expected to follow, and then either the end of the stream or the start of the next frame.
The block_type
field has three valid values, though only two of them arise when using ZSTD_compressBlock
directly. A block_type
of 0
is used for blocks which could not be compressed (ZSTD_compressBlock
returning 0
). In this case, the "compressed data" exactly equals the decompressed data, and block_size
gives the length of this data. A block_type
of 2
is used for compressed blocks. In this case, block_size
gives the length of the compressed data (the return value of ZSTD_compressBlock
).
A frame header consists of:
- Magic number (4 bytes
28
B5
2F
FD
) - Compression parameters, notably including the window size (1-6 bytes in general, typically 2 bytes)
- Optional uncompressed size (if present, 1-8 bytes)
See RFC8878 for details on the frame header fields. One of the compression parameters controls whether a frame footer is present. When a footer is present, it consists of a single field:
- XXH64 checksum of uncompressed data (4 bytes)
Special skippable frames can also be present. The reference implementation of the decompressor simply ignores such frames, but other implementations are allowed to do other things with them. They consist of:
- Magic number (4 bytes, first byte in range
50
-5F
, then2A
4D
18
) length
field (4 bytes)length
bytes of data