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
5package zstd
6
7import (
8	"errors"
9	"fmt"
10	"io"
11	"math"
12	"math/bits"
13)
14
15type frameHeader struct {
16	ContentSize   uint64
17	WindowSize    uint32
18	SingleSegment bool
19	Checksum      bool
20	DictID        uint32 // Not stored.
21}
22
23const maxHeaderSize = 14
24
25func (f frameHeader) appendTo(dst []byte) ([]byte, error) {
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	var fcs uint8
35	if f.ContentSize >= 256 {
36		fcs++
37	}
38	if f.ContentSize >= 65536+256 {
39		fcs++
40	}
41	if f.ContentSize >= 0xffffffff {
42		fcs++
43	}
44	fhd |= fcs << 6
45
46	dst = append(dst, fhd)
47	if !f.SingleSegment {
48		const winLogMin = 10
49		windowLog := (bits.Len32(f.WindowSize-1) - winLogMin) << 3
50		dst = append(dst, uint8(windowLog))
51	}
52	if f.SingleSegment && f.ContentSize == 0 {
53		return nil, errors.New("single segment, but no size set")
54	}
55	switch fcs {
56	case 0:
57		if f.SingleSegment {
58			dst = append(dst, uint8(f.ContentSize))
59		}
60		// Unless SingleSegment is set, framessizes < 256 are nto stored.
61	case 1:
62		f.ContentSize -= 256
63		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8))
64	case 2:
65		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24))
66	case 3:
67		dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8), uint8(f.ContentSize>>16), uint8(f.ContentSize>>24),
68			uint8(f.ContentSize>>32), uint8(f.ContentSize>>40), uint8(f.ContentSize>>48), uint8(f.ContentSize>>56))
69	default:
70		panic("invalid fcs")
71	}
72	return dst, nil
73}
74
75const skippableFrameHeader = 4 + 4
76
77// calcSkippableFrame will return a total size to be added for written
78// to be divisible by multiple.
79// The value will always be > skippableFrameHeader.
80// The function will panic if written < 0 or wantMultiple <= 0.
81func calcSkippableFrame(written, wantMultiple int64) int {
82	if wantMultiple <= 0 {
83		panic("wantMultiple <= 0")
84	}
85	if written < 0 {
86		panic("written < 0")
87	}
88	leftOver := written % wantMultiple
89	if leftOver == 0 {
90		return 0
91	}
92	toAdd := wantMultiple - leftOver
93	for toAdd < skippableFrameHeader {
94		toAdd += wantMultiple
95	}
96	return int(toAdd)
97}
98
99// skippableFrame will add a skippable frame with a total size of bytes.
100// total should be >= skippableFrameHeader and < math.MaxUint32.
101func skippableFrame(dst []byte, total int, r io.Reader) ([]byte, error) {
102	if total == 0 {
103		return dst, nil
104	}
105	if total < skippableFrameHeader {
106		return dst, fmt.Errorf("requested skippable frame (%d) < 8", total)
107	}
108	if int64(total) > math.MaxUint32 {
109		return dst, fmt.Errorf("requested skippable frame (%d) > max uint32", total)
110	}
111	dst = append(dst, 0x50, 0x2a, 0x4d, 0x18)
112	f := uint32(total - skippableFrameHeader)
113	dst = append(dst, uint8(f), uint8(f>>8), uint8(f>>16), uint8(f>>24))
114	start := len(dst)
115	dst = append(dst, make([]byte, f)...)
116	_, err := io.ReadFull(r, dst[start:])
117	return dst, err
118}
119