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