123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // Copyright 2020+ Klaus Post. All rights reserved.
- // License information can be found in the LICENSE file.
- package zstd
- import (
- "encoding/binary"
- "errors"
- "io"
- )
- // HeaderMaxSize is the maximum size of a Frame and Block Header.
- // If less is sent to Header.Decode it *may* still contain enough information.
- const HeaderMaxSize = 14 + 3
- // Header contains information about the first frame and block within that.
- type Header struct {
- // SingleSegment specifies whether the data is to be decompressed into a
- // single contiguous memory segment.
- // It implies that WindowSize is invalid and that FrameContentSize is valid.
- SingleSegment bool
- // WindowSize is the window of data to keep while decoding.
- // Will only be set if SingleSegment is false.
- WindowSize uint64
- // Dictionary ID.
- // If 0, no dictionary.
- DictionaryID uint32
- // HasFCS specifies whether FrameContentSize has a valid value.
- HasFCS bool
- // FrameContentSize is the expected uncompressed size of the entire frame.
- FrameContentSize uint64
- // Skippable will be true if the frame is meant to be skipped.
- // This implies that FirstBlock.OK is false.
- Skippable bool
- // SkippableID is the user-specific ID for the skippable frame.
- // Valid values are between 0 to 15, inclusive.
- SkippableID int
- // SkippableSize is the length of the user data to skip following
- // the header.
- SkippableSize uint32
- // HeaderSize is the raw size of the frame header.
- //
- // For normal frames, it includes the size of the magic number and
- // the size of the header (per section 3.1.1.1).
- // It does not include the size for any data blocks (section 3.1.1.2) nor
- // the size for the trailing content checksum.
- //
- // For skippable frames, this counts the size of the magic number
- // along with the size of the size field of the payload.
- // It does not include the size of the skippable payload itself.
- // The total frame size is the HeaderSize plus the SkippableSize.
- HeaderSize int
- // First block information.
- FirstBlock struct {
- // OK will be set if first block could be decoded.
- OK bool
- // Is this the last block of a frame?
- Last bool
- // Is the data compressed?
- // If true CompressedSize will be populated.
- // Unfortunately DecompressedSize cannot be determined
- // without decoding the blocks.
- Compressed bool
- // DecompressedSize is the expected decompressed size of the block.
- // Will be 0 if it cannot be determined.
- DecompressedSize int
- // CompressedSize of the data in the block.
- // Does not include the block header.
- // Will be equal to DecompressedSize if not Compressed.
- CompressedSize int
- }
- // If set there is a checksum present for the block content.
- // The checksum field at the end is always 4 bytes long.
- HasCheckSum bool
- }
- // Decode the header from the beginning of the stream.
- // This will decode the frame header and the first block header if enough bytes are provided.
- // It is recommended to provide at least HeaderMaxSize bytes.
- // If the frame header cannot be read an error will be returned.
- // If there isn't enough input, io.ErrUnexpectedEOF is returned.
- // The FirstBlock.OK will indicate if enough information was available to decode the first block header.
- func (h *Header) Decode(in []byte) error {
- _, err := h.DecodeAndStrip(in)
- return err
- }
- // DecodeAndStrip will decode the header from the beginning of the stream
- // and on success return the remaining bytes.
- // This will decode the frame header and the first block header if enough bytes are provided.
- // It is recommended to provide at least HeaderMaxSize bytes.
- // If the frame header cannot be read an error will be returned.
- // If there isn't enough input, io.ErrUnexpectedEOF is returned.
- // The FirstBlock.OK will indicate if enough information was available to decode the first block header.
- func (h *Header) DecodeAndStrip(in []byte) (remain []byte, err error) {
- *h = Header{}
- if len(in) < 4 {
- return nil, io.ErrUnexpectedEOF
- }
- h.HeaderSize += 4
- b, in := in[:4], in[4:]
- if string(b) != frameMagic {
- if string(b[1:4]) != skippableFrameMagic || b[0]&0xf0 != 0x50 {
- return nil, ErrMagicMismatch
- }
- if len(in) < 4 {
- return nil, io.ErrUnexpectedEOF
- }
- h.HeaderSize += 4
- h.Skippable = true
- h.SkippableID = int(b[0] & 0xf)
- h.SkippableSize = binary.LittleEndian.Uint32(in)
- return in[4:], nil
- }
- // Read Window_Descriptor
- // https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#window_descriptor
- if len(in) < 1 {
- return nil, io.ErrUnexpectedEOF
- }
- fhd, in := in[0], in[1:]
- h.HeaderSize++
- h.SingleSegment = fhd&(1<<5) != 0
- h.HasCheckSum = fhd&(1<<2) != 0
- if fhd&(1<<3) != 0 {
- return nil, errors.New("reserved bit set on frame header")
- }
- if !h.SingleSegment {
- if len(in) < 1 {
- return nil, io.ErrUnexpectedEOF
- }
- var wd byte
- wd, in = in[0], in[1:]
- h.HeaderSize++
- windowLog := 10 + (wd >> 3)
- windowBase := uint64(1) << windowLog
- windowAdd := (windowBase / 8) * uint64(wd&0x7)
- h.WindowSize = windowBase + windowAdd
- }
- // Read Dictionary_ID
- // https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#dictionary_id
- if size := fhd & 3; size != 0 {
- if size == 3 {
- size = 4
- }
- if len(in) < int(size) {
- return nil, io.ErrUnexpectedEOF
- }
- b, in = in[:size], in[size:]
- h.HeaderSize += int(size)
- switch len(b) {
- case 1:
- h.DictionaryID = uint32(b[0])
- case 2:
- h.DictionaryID = uint32(b[0]) | (uint32(b[1]) << 8)
- case 4:
- h.DictionaryID = uint32(b[0]) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
- }
- }
- // Read Frame_Content_Size
- // https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#frame_content_size
- var fcsSize int
- v := fhd >> 6
- switch v {
- case 0:
- if h.SingleSegment {
- fcsSize = 1
- }
- default:
- fcsSize = 1 << v
- }
- if fcsSize > 0 {
- h.HasFCS = true
- if len(in) < fcsSize {
- return nil, io.ErrUnexpectedEOF
- }
- b, in = in[:fcsSize], in[fcsSize:]
- h.HeaderSize += int(fcsSize)
- switch len(b) {
- case 1:
- h.FrameContentSize = uint64(b[0])
- case 2:
- // When FCS_Field_Size is 2, the offset of 256 is added.
- h.FrameContentSize = uint64(b[0]) | (uint64(b[1]) << 8) + 256
- case 4:
- h.FrameContentSize = uint64(b[0]) | (uint64(b[1]) << 8) | (uint64(b[2]) << 16) | (uint64(b[3]) << 24)
- case 8:
- d1 := uint32(b[0]) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
- d2 := uint32(b[4]) | (uint32(b[5]) << 8) | (uint32(b[6]) << 16) | (uint32(b[7]) << 24)
- h.FrameContentSize = uint64(d1) | (uint64(d2) << 32)
- }
- }
- // Frame Header done, we will not fail from now on.
- if len(in) < 3 {
- return in, nil
- }
- tmp := in[:3]
- bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16)
- h.FirstBlock.Last = bh&1 != 0
- blockType := blockType((bh >> 1) & 3)
- // find size.
- cSize := int(bh >> 3)
- switch blockType {
- case blockTypeReserved:
- return in, nil
- case blockTypeRLE:
- h.FirstBlock.Compressed = true
- h.FirstBlock.DecompressedSize = cSize
- h.FirstBlock.CompressedSize = 1
- case blockTypeCompressed:
- h.FirstBlock.Compressed = true
- h.FirstBlock.CompressedSize = cSize
- case blockTypeRaw:
- h.FirstBlock.DecompressedSize = cSize
- h.FirstBlock.CompressedSize = cSize
- default:
- panic("Invalid block type")
- }
- h.FirstBlock.OK = true
- return in, nil
- }
- // AppendTo will append the encoded header to the dst slice.
- // There is no error checking performed on the header values.
- func (h *Header) AppendTo(dst []byte) ([]byte, error) {
- if h.Skippable {
- magic := [4]byte{0x50, 0x2a, 0x4d, 0x18}
- magic[0] |= byte(h.SkippableID & 0xf)
- dst = append(dst, magic[:]...)
- f := h.SkippableSize
- return append(dst, uint8(f), uint8(f>>8), uint8(f>>16), uint8(f>>24)), nil
- }
- f := frameHeader{
- ContentSize: h.FrameContentSize,
- WindowSize: uint32(h.WindowSize),
- SingleSegment: h.SingleSegment,
- Checksum: h.HasCheckSum,
- DictID: h.DictionaryID,
- }
- return f.appendTo(dst), nil
- }
|