blob: a8607893cd4a2f8bad25875b726165c51764bdc6 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package zstd provides a decompressor for zstd streams,
// described in RFC 8878. It does not support dictionaries.
package zstd
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
// fuzzing is a fuzzer hook set to true when fuzzing.
// This is used to reject cases where we don't match zstd.
var fuzzing = false
// Reader implements [io.Reader] to read a zstd compressed stream.
type Reader struct {
// The underlying Reader.
r io.Reader
// Whether we have read the frame header.
// This is of interest when buffer is empty.
// If true we expect to see a new block.
sawFrameHeader bool
// Whether the current frame expects a checksum.
hasChecksum bool
// Whether we have read at least one frame.
readOneFrame bool
// True if the frame size is not known.
frameSizeUnknown bool
// The number of uncompressed bytes remaining in the current frame.
// If frameSizeUnknown is true, this is not valid.
remainingFrameSize uint64
// The number of bytes read from r up to the start of the current
// block, for error reporting.
blockOffset int64
// Buffered decompressed data.
buffer []byte
// Current read offset in buffer.
off int
// The current repeated offsets.
repeatedOffset1 uint32
repeatedOffset2 uint32
repeatedOffset3 uint32
// The current Huffman tree used for compressing literals.
huffmanTable []uint16
huffmanTableBits int
// The window for back references.
windowSize int // maximum required window size
window []byte // window data
// A buffer available to hold a compressed block.
compressedBuf []byte
// A buffer for literals.
literals []byte
// Sequence decode FSE tables.
seqTables [3][]fseBaselineEntry
seqTableBits [3]uint8
// Buffers for sequence decode FSE tables.
seqTableBuffers [3][]fseBaselineEntry
// Scratch space used for small reads, to avoid allocation.
scratch [16]byte
// A scratch table for reading an FSE. Only temporarily valid.
fseScratch []fseEntry
// For checksum computation.
checksum xxhash64
}
// NewReader creates a new Reader that decompresses data from the given reader.
func NewReader(input io.Reader) *Reader {
r := new(Reader)
r.Reset(input)
return r
}
// Reset discards the current state and starts reading a new stream from r.
// This permits reusing a Reader rather than allocating a new one.
func (r *Reader) Reset(input io.Reader) {
r.r = input
// Several fields are preserved to avoid allocation.
// Others are always set before they are used.
r.sawFrameHeader = false
r.hasChecksum = false
r.readOneFrame = false
r.frameSizeUnknown = false
r.remainingFrameSize = 0
r.blockOffset = 0
// buffer
r.off = 0
// repeatedOffset1
// repeatedOffset2
// repeatedOffset3
// huffmanTable
// huffmanTableBits
// windowSize
// window
// compressedBuf
// literals
// seqTables
// seqTableBits
// seqTableBuffers
// scratch
// fseScratch
}
// Read implements [io.Reader].
func (r *Reader) Read(p []byte) (int, error) {
if err := r.refillIfNeeded(); err != nil {
return 0, err
}
n := copy(p, r.buffer[r.off:])
r.off += n
return n, nil
}
// ReadByte implements [io.ByteReader].
func (r *Reader) ReadByte() (byte, error) {
if err := r.refillIfNeeded(); err != nil {
return 0, err
}
ret := r.buffer[r.off]
r.off++
return ret, nil
}
// refillIfNeeded reads the next block if necessary.
func (r *Reader) refillIfNeeded() error {
for r.off >= len(r.buffer) {
if err := r.refill(); err != nil {
return err
}
r.off = 0
}
return nil
}
// refill reads and decompresses the next block.
func (r *Reader) refill() error {
if !r.sawFrameHeader {
if err := r.readFrameHeader(); err != nil {
return err
}
}
return r.readBlock()
}
// readFrameHeader reads the frame header and prepares to read a block.
func (r *Reader) readFrameHeader() error {
retry:
relativeOffset := 0
// Read magic number. RFC 3.1.1.
if _, err := io.ReadFull(r.r, r.scratch[:4]); err != nil {
// We require that the stream contain at least one frame.
if err == io.EOF && !r.readOneFrame {
err = io.ErrUnexpectedEOF
}
return r.wrapError(relativeOffset, err)
}
if magic := binary.LittleEndian.Uint32(r.scratch[:4]); magic != 0xfd2fb528 {
if magic >= 0x184d2a50 && magic <= 0x184d2a5f {
// This is a skippable frame.
r.blockOffset += int64(relativeOffset) + 4
if err := r.skipFrame(); err != nil {
return err
}
goto retry
}
return r.makeError(relativeOffset, "invalid magic number")
}
relativeOffset += 4
// Read Frame_Header_Descriptor. RFC 3.1.1.1.1.
if _, err := io.ReadFull(r.r, r.scratch[:1]); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
descriptor := r.scratch[0]
singleSegment := descriptor&(1<<5) != 0
fcsFieldSize := 1 << (descriptor >> 6)
if fcsFieldSize == 1 && !singleSegment {
fcsFieldSize = 0
}
var windowDescriptorSize int
if singleSegment {
windowDescriptorSize = 0
} else {
windowDescriptorSize = 1
}
if descriptor&(1<<3) != 0 {
return r.makeError(relativeOffset, "reserved bit set in frame header descriptor")
}
r.hasChecksum = descriptor&(1<<2) != 0
if r.hasChecksum {
r.checksum.reset()
}
if descriptor&3 != 0 {
return r.makeError(relativeOffset, "dictionaries are not supported")
}
relativeOffset++
headerSize := windowDescriptorSize + fcsFieldSize
if _, err := io.ReadFull(r.r, r.scratch[:headerSize]); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
// Figure out the maximum amount of data we need to retain
// for backreferences.
if singleSegment {
// No window required, as all the data is in a single buffer.
r.windowSize = 0
} else {
// Window descriptor. RFC 3.1.1.1.2.
windowDescriptor := r.scratch[0]
exponent := uint64(windowDescriptor >> 3)
mantissa := uint64(windowDescriptor & 7)
windowLog := exponent + 10
windowBase := uint64(1) << windowLog
windowAdd := (windowBase / 8) * mantissa
windowSize := windowBase + windowAdd
// Default zstd sets limits on the window size.
if fuzzing && (windowLog > 31 || windowSize > 1<<27) {
return r.makeError(relativeOffset, "windowSize too large")
}
// RFC 8878 permits us to set an 8M max on window size.
if windowSize > 8<<20 {
windowSize = 8 << 20
}
r.windowSize = int(windowSize)
}
// Frame_Content_Size. RFC 3.1.1.4.
r.frameSizeUnknown = false
r.remainingFrameSize = 0
fb := r.scratch[windowDescriptorSize:]
switch fcsFieldSize {
case 0:
r.frameSizeUnknown = true
case 1:
r.remainingFrameSize = uint64(fb[0])
case 2:
r.remainingFrameSize = 256 + uint64(binary.LittleEndian.Uint16(fb))
case 4:
r.remainingFrameSize = uint64(binary.LittleEndian.Uint32(fb))
case 8:
r.remainingFrameSize = binary.LittleEndian.Uint64(fb)
default:
panic("unreachable")
}
relativeOffset += headerSize
r.sawFrameHeader = true
r.readOneFrame = true
r.blockOffset += int64(relativeOffset)
// Prepare to read blocks from the frame.
r.repeatedOffset1 = 1
r.repeatedOffset2 = 4
r.repeatedOffset3 = 8
r.huffmanTableBits = 0
r.window = r.window[:0]
r.seqTables[0] = nil
r.seqTables[1] = nil
r.seqTables[2] = nil
return nil
}
// skipFrame skips a skippable frame. RFC 3.1.2.
func (r *Reader) skipFrame() error {
relativeOffset := 0
if _, err := io.ReadFull(r.r, r.scratch[:4]); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset += 4
size := binary.LittleEndian.Uint32(r.scratch[:4])
if seeker, ok := r.r.(io.Seeker); ok {
if _, err := seeker.Seek(int64(size), io.SeekCurrent); err != nil {
return err
}
r.blockOffset += int64(relativeOffset) + int64(size)
return nil
}
var skip []byte
const chunk = 1 << 20 // 1M
for size >= chunk {
if len(skip) == 0 {
skip = make([]byte, chunk)
}
if _, err := io.ReadFull(r.r, skip); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset += chunk
size -= chunk
}
if size > 0 {
if len(skip) == 0 {
skip = make([]byte, size)
}
if _, err := io.ReadFull(r.r, skip); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset += int(size)
}
r.blockOffset += int64(relativeOffset)
return nil
}
// readBlock reads the next block from a frame.
func (r *Reader) readBlock() error {
relativeOffset := 0
// Read Block_Header. RFC 3.1.1.2.
if _, err := io.ReadFull(r.r, r.scratch[:3]); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset += 3
header := uint32(r.scratch[0]) | (uint32(r.scratch[1]) << 8) | (uint32(r.scratch[2]) << 16)
lastBlock := header&1 != 0
blockType := (header >> 1) & 3
blockSize := int(header >> 3)
// Maximum block size is smaller of window size and 128K.
// We don't record the window size for a single segment frame,
// so just use 128K. RFC 3.1.1.2.3, 3.1.1.2.4.
if blockSize > 128<<10 || (r.windowSize > 0 && blockSize > r.windowSize) {
return r.makeError(relativeOffset, "block size too large")
}
// Handle different block types. RFC 3.1.1.2.2.
switch blockType {
case 0:
r.setBufferSize(blockSize)
if _, err := io.ReadFull(r.r, r.buffer); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset += blockSize
r.blockOffset += int64(relativeOffset)
case 1:
r.setBufferSize(blockSize)
if _, err := io.ReadFull(r.r, r.scratch[:1]); err != nil {
return r.wrapNonEOFError(relativeOffset, err)
}
relativeOffset++
v := r.scratch[0]
for i := range r.buffer {
r.buffer[i] = v
}
r.blockOffset += int64(relativeOffset)
case 2:
r.blockOffset += int64(relativeOffset)
if err := r.compressedBlock(blockSize); err != nil {
return err
}
r.blockOffset += int64(blockSize)
case 3:
return r.makeError(relativeOffset, "invalid block type")
}
if !r.frameSizeUnknown {
if uint64(len(r.buffer)) > r.remainingFrameSize {
return r.makeError(relativeOffset, "too many uncompressed bytes in frame")
}
r.remainingFrameSize -= uint64(len(r.buffer))
}
if r.hasChecksum {
r.checksum.update(r.buffer)
}
if !lastBlock {
r.saveWindow(r.buffer)
} else {
if !r.frameSizeUnknown && r.remainingFrameSize != 0 {
return r.makeError(relativeOffset, "not enough uncompressed bytes for frame")
}
// Check for checksum at end of frame. RFC 3.1.1.
if r.hasChecksum {
if _, err := io.ReadFull(r.r, r.scratch[:4]); err != nil {
return r.wrapNonEOFError(0, err)
}
inputChecksum := binary.LittleEndian.Uint32(r.scratch[:4])
dataChecksum := uint32(r.checksum.digest())
if inputChecksum != dataChecksum {
return r.wrapError(0, fmt.Errorf("invalid checksum: got %#x want %#x", dataChecksum, inputChecksum))
}
r.blockOffset += 4
}
r.sawFrameHeader = false
}
return nil
}
// setBufferSize sets the decompressed buffer size.
// When this is called the buffer is empty.
func (r *Reader) setBufferSize(size int) {
if cap(r.buffer) < size {
need := size - cap(r.buffer)
r.buffer = append(r.buffer[:cap(r.buffer)], make([]byte, need)...)
}
r.buffer = r.buffer[:size]
}
// saveWindow saves bytes in the backreference window.
// TODO: use a circular buffer for less data movement.
func (r *Reader) saveWindow(buf []byte) {
if r.windowSize == 0 {
return
}
if len(buf) >= r.windowSize {
from := len(buf) - r.windowSize
r.window = append(r.window[:0], buf[from:]...)
return
}
keep := r.windowSize - len(buf) // must be positive
if keep < len(r.window) {
remove := len(r.window) - keep
copy(r.window[:], r.window[remove:])
}
r.window = append(r.window, buf...)
}
// zstdError is an error while decompressing.
type zstdError struct {
offset int64
err error
}
func (ze *zstdError) Error() string {
return fmt.Sprintf("zstd decompression error at %d: %v", ze.offset, ze.err)
}
func (ze *zstdError) Unwrap() error {
return ze.err
}
func (r *Reader) makeEOFError(off int) error {
return r.wrapError(off, io.ErrUnexpectedEOF)
}
func (r *Reader) wrapNonEOFError(off int, err error) error {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return r.wrapError(off, err)
}
func (r *Reader) makeError(off int, msg string) error {
return r.wrapError(off, errors.New(msg))
}
func (r *Reader) wrapError(off int, err error) error {
if err == io.EOF {
return err
}
return &zstdError{r.blockOffset + int64(off), err}
}