// 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. window window // 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 r.buffer = r.buffer[:0] r.off = 0 // repeatedOffset1 // repeatedOffset2 // repeatedOffset3 // huffmanTable // huffmanTableBits // 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 contains 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 } r.readOneFrame = true 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() } // Dictionary_ID_Flag. RFC 3.1.1.1.1.6. dictionaryIdSize := 0 if dictIdFlag := descriptor & 3; dictIdFlag != 0 { dictionaryIdSize = 1 << (dictIdFlag - 1) } relativeOffset++ headerSize := windowDescriptorSize + dictionaryIdSize + 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. var windowSize int if !singleSegment { // 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 = int(windowBase + windowAdd) // Default zstd sets limits on the window size. if fuzzing && (windowLog > 31 || windowSize > 1<<27) { return r.makeError(relativeOffset, "windowSize too large") } } // Dictionary_ID. RFC 3.1.1.1.3. if dictionaryIdSize != 0 { dictionaryId := r.scratch[windowDescriptorSize : windowDescriptorSize+dictionaryIdSize] // Allow only zero Dictionary ID. for _, b := range dictionaryId { if b != 0 { return r.makeError(relativeOffset, "dictionaries are not supported") } } } // Frame_Content_Size. RFC 3.1.1.1.4. r.frameSizeUnknown = false r.remainingFrameSize = 0 fb := r.scratch[windowDescriptorSize+dictionaryIdSize:] 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") } // RFC 3.1.1.1.2. // When Single_Segment_Flag is set, Window_Descriptor is not present. // In this case, Window_Size is Frame_Content_Size. if singleSegment { windowSize = int(r.remainingFrameSize) } // RFC 8878 3.1.1.1.1.2. permits us to set an 8M max on window size. if windowSize > 8<<20 { windowSize = 8 << 20 } 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.reset(windowSize) 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 size == 0 { r.blockOffset += int64(relativeOffset) return nil } if seeker, ok := r.r.(io.Seeker); ok { r.blockOffset += int64(relativeOffset) // Implementations of Seeker do not always detect invalid offsets, // so check that the new offset is valid by comparing to the end. prev, err := seeker.Seek(0, io.SeekCurrent) if err != nil { return r.wrapError(0, err) } end, err := seeker.Seek(0, io.SeekEnd) if err != nil { return r.wrapError(0, err) } if prev > end-int64(size) { r.blockOffset += end - prev return r.makeEOFError(0) } // The new offset is valid, so seek to it. _, err = seeker.Seek(prev+int64(size), io.SeekStart) if err != nil { return r.wrapError(0, err) } r.blockOffset += 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.window.size > 0 && blockSize > r.window.size) { 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.window.save(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] } // 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} }