Skip to content

Commit

Permalink
Add full zero payload block encoding option. (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
klauspost committed Sep 5, 2019
1 parent b83692d commit e266c37
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 5 deletions.
21 changes: 20 additions & 1 deletion zstd/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,31 @@ func (e *Encoder) Close() error {

// EncodeAll will encode all input in src and append it to dst.
// This function can be called concurrently, but each call will only run on a single goroutine.
// If empty input is given, nothing is returned.
// If empty input is given, nothing is returned, unless WithZeroFrames is specified.
// Encoded blocks can be concatenated and the result will be the combined input stream.
// Data compressed with EncodeAll can be decoded with the Decoder,
// using either a stream or DecodeAll.
func (e *Encoder) EncodeAll(src, dst []byte) []byte {
if len(src) == 0 {
if e.o.fullZero {
// Add frame header.
fh := frameHeader{
ContentSize: 0,
WindowSize: minWindowSize,
SingleSegment: true,
// Adding a checksum would be a waste of space.
Checksum: false,
DictID: 0,
}
dst, _ = fh.appendTo(dst)

// Write raw block as last one only.
var blk blockHeader
blk.setSize(0)
blk.setType(blockTypeRaw)
blk.setLast(true)
dst = blk.appendTo(dst)
}
return dst
}
e.init.Do(func() {
Expand Down
11 changes: 11 additions & 0 deletions zstd/encoder_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type encoderOptions struct {
blockSize int
windowSize int
level EncoderLevel
fullZero bool
}

func (o *encoderOptions) setDefault() {
Expand Down Expand Up @@ -166,6 +167,16 @@ func WithEncoderLevel(l EncoderLevel) EOption {
}
}

// WithZeroFrames will encode 0 length input as full frames.
// This can be needed for compatibility with zstandard usage,
// but is not needed for this package.
func WithZeroFrames(b bool) EOption {
return func(o *encoderOptions) error {
o.fullZero = b
return nil
}
}

// WithSingleSegment will set the "single segment" flag when EncodeAll is used.
// If this flag is set, data must be regenerated within a single continuous memory segment.
// In this case, Window_Descriptor byte is skipped, but Frame_Content_Size is necessarily present.
Expand Down
49 changes: 49 additions & 0 deletions zstd/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,55 @@ func TestEncoder_EncodeAllSilesia(t *testing.T) {
t.Log("Encoded content matched")
}

func TestEncoder_EncodeAllEmpty(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
var in []byte

e, err := NewWriter(nil, WithZeroFrames(true))
if err != nil {
t.Fatal(err)
}
dst := e.EncodeAll(in, nil)
if len(dst) == 0 {
t.Fatal("Requested zero frame, but got nothing.")
}
t.Log("Block Encoder len", len(in), "-> zstd len", len(dst), dst)

dec, err := NewReader(nil, WithDecoderMaxMemory(220<<20))
if err != nil {
t.Fatal(err)
}
decoded, err := dec.DecodeAll(dst, nil)
if err != nil {
t.Error(err, len(decoded))
}
if !bytes.Equal(decoded, in) {
t.Fatal("Decoded does not match")
}

// Test buffer writer.
var buf bytes.Buffer
e.Reset(&buf)
err = e.Close()
dst = buf.Bytes()
if len(dst) == 0 {
t.Fatal("Requested zero frame, but got nothing.")
}
t.Log("Buffer Encoder len", len(in), "-> zstd len", len(dst))

decoded, err = dec.DecodeAll(dst, nil)
if err != nil {
t.Error(err, len(decoded))
}
if !bytes.Equal(decoded, in) {
t.Fatal("Decoded does not match")
}

t.Log("Encoded content matched")
}

func TestEncoder_EncodeAllEnwik9(t *testing.T) {
if false || testing.Short() {
t.SkipNow()
Expand Down
5 changes: 1 addition & 4 deletions zstd/frameenc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package zstd

import (
"errors"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -49,9 +48,7 @@ func (f frameHeader) appendTo(dst []byte) ([]byte, error) {
windowLog := (bits.Len32(f.WindowSize-1) - winLogMin) << 3
dst = append(dst, uint8(windowLog))
}
if f.SingleSegment && f.ContentSize == 0 {
return nil, errors.New("single segment, but no size set")
}

switch fcs {
case 0:
if f.SingleSegment {
Expand Down

0 comments on commit e266c37

Please sign in to comment.