// Copyright 2019+ Klaus Post. All rights reserved. // License information can be found in the LICENSE file. // Based on work by Yann Collet, released under BSD License. package zstd import ( "encoding/binary" "fmt" "io" "math" "math/bits" ) type frameHeader struct { ContentSize uint64 WindowSize uint32 SingleSegment bool Checksum bool DictID uint32 } const maxHeaderSize = 14 func (f frameHeader) appendTo(dst []byte) ([]byte, error) { dst = append(dst, frameMagic...) var fhd uint8 if f.Checksum { fhd |= 1 << 2 } if f.SingleSegment { fhd |= 1 << 5 } var dictIDContent []byte if f.DictID > 0 { var tmp [4]byte if f.DictID < 256 { fhd |= 1 tmp[0] = uint8(f.DictID) dictIDContent = tmp[:1] } else if f.DictID < 1<<16 { fhd |= 2 binary.LittleEndian.PutUint16(tmp[:2], uint16(f.DictID)) dictIDContent = tmp[:2] } else { fhd |= 3 binary.LittleEndian.PutUint32(tmp[:4], f.DictID) dictIDContent = tmp[:4] } } var fcs uint8 if f.ContentSize >= 256 { fcs++ } if f.ContentSize >= 65536+256 { fcs++ } if f.ContentSize >= 0xffffffff { fcs++ } fhd |= fcs << 6 dst = append(dst, fhd) if !f.SingleSegment { const winLogMin = 10 windowLog := (bits.Len32(f.WindowSize-1) - winLogMin) << 3 dst = append(dst, uint8(windowLog)) } if f.DictID > 0 { dst = append(dst, dictIDContent...) } switch fcs { case 0: if f.SingleSegment { dst = append(dst, uint8(f.ContentSize)) } // Unless SingleSegment is set, framessizes < 256 are nto stored. case 1: f.ContentSize -= 256 dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8)) case 2: dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24)) case 3: dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24), uint8(f.ContentSize>>32), uint8(f.ContentSize>>40), uint8(f.ContentSize>>48), uint8(f.ContentSize>>56)) default: panic("invalid fcs") } return dst, nil } const skippableFrameHeader = 4 + 4 // calcSkippableFrame will return a total size to be added for written // to be divisible by multiple. // The value will always be > skippableFrameHeader. // The function will panic if written < 0 or wantMultiple <= 0. func calcSkippableFrame(written, wantMultiple int64) int { if wantMultiple <= 0 { panic("wantMultiple <= 0") } if written < 0 { panic("written < 0") } leftOver := written % wantMultiple if leftOver == 0 { return 0 } toAdd := wantMultiple - leftOver for toAdd < skippableFrameHeader { toAdd += wantMultiple } return int(toAdd) } // skippableFrame will add a skippable frame with a total size of bytes. // total should be >= skippableFrameHeader and < math.MaxUint32. func skippableFrame(dst []byte, total int, r io.Reader) ([]byte, error) { if total == 0 { return dst, nil } if total < skippableFrameHeader { return dst, fmt.Errorf("requested skippable frame (%d) < 8", total) } if int64(total) > math.MaxUint32 { return dst, fmt.Errorf("requested skippable frame (%d) > max uint32", total) } dst = append(dst, 0x50, 0x2a, 0x4d, 0x18) f := uint32(total - skippableFrameHeader) dst = append(dst, uint8(f), uint8(f>>8), uint8(f>>16), uint8(f>>24)) start := len(dst) dst = append(dst, make([]byte, f)...) _, err := io.ReadFull(r, dst[start:]) return dst, err }