frameenc.go raw

   1  // Copyright 2019+ Klaus Post. All rights reserved.
   2  // License information can be found in the LICENSE file.
   3  // Based on work by Yann Collet, released under BSD License.
   4  
   5  package zstd
   6  
   7  import (
   8  	"encoding/binary"
   9  	"fmt"
  10  	"io"
  11  	"math"
  12  	"math/bits"
  13  )
  14  
  15  type frameHeader struct {
  16  	ContentSize   uint64
  17  	WindowSize    uint32
  18  	SingleSegment bool
  19  	Checksum      bool
  20  	DictID        uint32
  21  }
  22  
  23  const maxHeaderSize = 14
  24  
  25  func (f frameHeader) appendTo(dst []byte) []byte {
  26  	dst = append(dst, frameMagic...)
  27  	var fhd uint8
  28  	if f.Checksum {
  29  		fhd |= 1 << 2
  30  	}
  31  	if f.SingleSegment {
  32  		fhd |= 1 << 5
  33  	}
  34  
  35  	var dictIDContent []byte
  36  	if f.DictID > 0 {
  37  		var tmp [4]byte
  38  		if f.DictID < 256 {
  39  			fhd |= 1
  40  			tmp[0] = uint8(f.DictID)
  41  			dictIDContent = tmp[:1]
  42  		} else if f.DictID < 1<<16 {
  43  			fhd |= 2
  44  			binary.LittleEndian.PutUint16(tmp[:2], uint16(f.DictID))
  45  			dictIDContent = tmp[:2]
  46  		} else {
  47  			fhd |= 3
  48  			binary.LittleEndian.PutUint32(tmp[:4], f.DictID)
  49  			dictIDContent = tmp[:4]
  50  		}
  51  	}
  52  	var fcs uint8
  53  	if f.ContentSize >= 256 {
  54  		fcs++
  55  	}
  56  	if f.ContentSize >= 65536+256 {
  57  		fcs++
  58  	}
  59  	if f.ContentSize >= 0xffffffff {
  60  		fcs++
  61  	}
  62  
  63  	fhd |= fcs << 6
  64  
  65  	dst = append(dst, fhd)
  66  	if !f.SingleSegment {
  67  		const winLogMin = 10
  68  		windowLog := (bits.Len32(f.WindowSize-1) - winLogMin) << 3
  69  		dst = append(dst, uint8(windowLog))
  70  	}
  71  	if f.DictID > 0 {
  72  		dst = append(dst, dictIDContent...)
  73  	}
  74  	switch fcs {
  75  	case 0:
  76  		if f.SingleSegment {
  77  			dst = append(dst, uint8(f.ContentSize))
  78  		}
  79  		// Unless SingleSegment is set, framessizes < 256 are not stored.
  80  	case 1:
  81  		f.ContentSize -= 256
  82  		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8))
  83  	case 2:
  84  		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24))
  85  	case 3:
  86  		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24),
  87  			uint8(f.ContentSize>>32), uint8(f.ContentSize>>40), uint8(f.ContentSize>>48), uint8(f.ContentSize>>56))
  88  	default:
  89  		panic("invalid fcs")
  90  	}
  91  	return dst
  92  }
  93  
  94  const skippableFrameHeader = 4 + 4
  95  
  96  // calcSkippableFrame will return a total size to be added for written
  97  // to be divisible by multiple.
  98  // The value will always be > skippableFrameHeader.
  99  // The function will panic if written < 0 or wantMultiple <= 0.
 100  func calcSkippableFrame(written, wantMultiple int64) int {
 101  	if wantMultiple <= 0 {
 102  		panic("wantMultiple <= 0")
 103  	}
 104  	if written < 0 {
 105  		panic("written < 0")
 106  	}
 107  	leftOver := written % wantMultiple
 108  	if leftOver == 0 {
 109  		return 0
 110  	}
 111  	toAdd := wantMultiple - leftOver
 112  	for toAdd < skippableFrameHeader {
 113  		toAdd += wantMultiple
 114  	}
 115  	return int(toAdd)
 116  }
 117  
 118  // skippableFrame will add a skippable frame with a total size of bytes.
 119  // total should be >= skippableFrameHeader and < math.MaxUint32.
 120  func skippableFrame(dst []byte, total int, r io.Reader) ([]byte, error) {
 121  	if total == 0 {
 122  		return dst, nil
 123  	}
 124  	if total < skippableFrameHeader {
 125  		return dst, fmt.Errorf("requested skippable frame (%d) < 8", total)
 126  	}
 127  	if int64(total) > math.MaxUint32 {
 128  		return dst, fmt.Errorf("requested skippable frame (%d) > max uint32", total)
 129  	}
 130  	dst = append(dst, 0x50, 0x2a, 0x4d, 0x18)
 131  	f := uint32(total - skippableFrameHeader)
 132  	dst = append(dst, uint8(f), uint8(f>>8), uint8(f>>16), uint8(f>>24))
 133  	start := len(dst)
 134  	dst = append(dst, make([]byte, f)...)
 135  	_, err := io.ReadFull(r, dst[start:])
 136  	return dst, err
 137  }
 138