Skip to content

Commit 85452d2

Browse files
authored
zstd: Add Frame header encoding and stripping (#908)
Allows to remove the frame header from input and adds a function that encodes current frame header values.
1 parent eabf71d commit 85452d2

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

zstd/decodeheader.go

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,42 +95,54 @@ type Header struct {
9595
// If there isn't enough input, io.ErrUnexpectedEOF is returned.
9696
// The FirstBlock.OK will indicate if enough information was available to decode the first block header.
9797
func (h *Header) Decode(in []byte) error {
98+
_, err := h.DecodeAndStrip(in)
99+
return err
100+
}
101+
102+
// DecodeAndStrip will decode the header from the beginning of the stream
103+
// and on success return the remaining bytes.
104+
// This will decode the frame header and the first block header if enough bytes are provided.
105+
// It is recommended to provide at least HeaderMaxSize bytes.
106+
// If the frame header cannot be read an error will be returned.
107+
// If there isn't enough input, io.ErrUnexpectedEOF is returned.
108+
// The FirstBlock.OK will indicate if enough information was available to decode the first block header.
109+
func (h *Header) DecodeAndStrip(in []byte) (remain []byte, err error) {
98110
*h = Header{}
99111
if len(in) < 4 {
100-
return io.ErrUnexpectedEOF
112+
return nil, io.ErrUnexpectedEOF
101113
}
102114
h.HeaderSize += 4
103115
b, in := in[:4], in[4:]
104116
if string(b) != frameMagic {
105117
if string(b[1:4]) != skippableFrameMagic || b[0]&0xf0 != 0x50 {
106-
return ErrMagicMismatch
118+
return nil, ErrMagicMismatch
107119
}
108120
if len(in) < 4 {
109-
return io.ErrUnexpectedEOF
121+
return nil, io.ErrUnexpectedEOF
110122
}
111123
h.HeaderSize += 4
112124
h.Skippable = true
113125
h.SkippableID = int(b[0] & 0xf)
114126
h.SkippableSize = binary.LittleEndian.Uint32(in)
115-
return nil
127+
return in[4:], nil
116128
}
117129

118130
// Read Window_Descriptor
119131
// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md#window_descriptor
120132
if len(in) < 1 {
121-
return io.ErrUnexpectedEOF
133+
return nil, io.ErrUnexpectedEOF
122134
}
123135
fhd, in := in[0], in[1:]
124136
h.HeaderSize++
125137
h.SingleSegment = fhd&(1<<5) != 0
126138
h.HasCheckSum = fhd&(1<<2) != 0
127139
if fhd&(1<<3) != 0 {
128-
return errors.New("reserved bit set on frame header")
140+
return nil, errors.New("reserved bit set on frame header")
129141
}
130142

131143
if !h.SingleSegment {
132144
if len(in) < 1 {
133-
return io.ErrUnexpectedEOF
145+
return nil, io.ErrUnexpectedEOF
134146
}
135147
var wd byte
136148
wd, in = in[0], in[1:]
@@ -148,7 +160,7 @@ func (h *Header) Decode(in []byte) error {
148160
size = 4
149161
}
150162
if len(in) < int(size) {
151-
return io.ErrUnexpectedEOF
163+
return nil, io.ErrUnexpectedEOF
152164
}
153165
b, in = in[:size], in[size:]
154166
h.HeaderSize += int(size)
@@ -178,7 +190,7 @@ func (h *Header) Decode(in []byte) error {
178190
if fcsSize > 0 {
179191
h.HasFCS = true
180192
if len(in) < fcsSize {
181-
return io.ErrUnexpectedEOF
193+
return nil, io.ErrUnexpectedEOF
182194
}
183195
b, in = in[:fcsSize], in[fcsSize:]
184196
h.HeaderSize += int(fcsSize)
@@ -199,7 +211,7 @@ func (h *Header) Decode(in []byte) error {
199211

200212
// Frame Header done, we will not fail from now on.
201213
if len(in) < 3 {
202-
return nil
214+
return in, nil
203215
}
204216
tmp := in[:3]
205217
bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16)
@@ -209,7 +221,7 @@ func (h *Header) Decode(in []byte) error {
209221
cSize := int(bh >> 3)
210222
switch blockType {
211223
case blockTypeReserved:
212-
return nil
224+
return in, nil
213225
case blockTypeRLE:
214226
h.FirstBlock.Compressed = true
215227
h.FirstBlock.DecompressedSize = cSize
@@ -225,5 +237,25 @@ func (h *Header) Decode(in []byte) error {
225237
}
226238

227239
h.FirstBlock.OK = true
228-
return nil
240+
return in, nil
241+
}
242+
243+
// AppendTo will append the encoded header to the dst slice.
244+
// There is no error checking performed on the header values.
245+
func (h *Header) AppendTo(dst []byte) ([]byte, error) {
246+
if h.Skippable {
247+
magic := [4]byte{0x50, 0x2a, 0x4d, 0x18}
248+
magic[0] |= byte(h.SkippableID & 0xf)
249+
dst = append(dst, magic[:]...)
250+
f := h.SkippableSize
251+
return append(dst, uint8(f), uint8(f>>8), uint8(f>>16), uint8(f>>24)), nil
252+
}
253+
f := frameHeader{
254+
ContentSize: h.FrameContentSize,
255+
WindowSize: uint32(h.WindowSize),
256+
SingleSegment: h.SingleSegment,
257+
Checksum: h.HasCheckSum,
258+
DictID: h.DictionaryID,
259+
}
260+
return f.appendTo(dst), nil
229261
}

zstd/frameenc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (f frameHeader) appendTo(dst []byte) []byte {
7676
if f.SingleSegment {
7777
dst = append(dst, uint8(f.ContentSize))
7878
}
79-
// Unless SingleSegment is set, framessizes < 256 are nto stored.
79+
// Unless SingleSegment is set, framessizes < 256 are not stored.
8080
case 1:
8181
f.ContentSize -= 256
8282
dst = append(dst, uint8(f.ContentSize), uint8(f.ContentSize>>8))

0 commit comments

Comments
 (0)