Skip to content

Commit 62905e4

Browse files
authored
read data written with partial flush (#996)
* read data written with partial flush
1 parent 3868468 commit 62905e4

File tree

3 files changed

+77
-19
lines changed

3 files changed

+77
-19
lines changed

flate/inflate.go

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,14 @@ const (
298298
huffmanGenericReader
299299
)
300300

301+
// flushMode tells decompressor when to return data
302+
type flushMode uint8
303+
304+
const (
305+
syncFlush flushMode = iota // return data after sync flush block
306+
partialFlush // return data after each block
307+
)
308+
301309
// Decompress state.
302310
type decompressor struct {
303311
// Input source.
@@ -332,6 +340,8 @@ type decompressor struct {
332340

333341
nb uint
334342
final bool
343+
344+
flushMode flushMode
335345
}
336346

337347
func (f *decompressor) nextBlock() {
@@ -618,7 +628,10 @@ func (f *decompressor) dataBlock() {
618628
}
619629

620630
if n == 0 {
621-
f.toRead = f.dict.readFlush()
631+
if f.flushMode == syncFlush {
632+
f.toRead = f.dict.readFlush()
633+
}
634+
622635
f.finishBlock()
623636
return
624637
}
@@ -657,8 +670,12 @@ func (f *decompressor) finishBlock() {
657670
if f.dict.availRead() > 0 {
658671
f.toRead = f.dict.readFlush()
659672
}
673+
660674
f.err = io.EOF
675+
} else if f.flushMode == partialFlush && f.dict.availRead() > 0 {
676+
f.toRead = f.dict.readFlush()
661677
}
678+
662679
f.step = nextBlock
663680
}
664681

@@ -789,15 +806,25 @@ func (f *decompressor) Reset(r io.Reader, dict []byte) error {
789806
return nil
790807
}
791808

792-
// NewReader returns a new ReadCloser that can be used
793-
// to read the uncompressed version of r.
794-
// If r does not also implement io.ByteReader,
795-
// the decompressor may read more data than necessary from r.
796-
// It is the caller's responsibility to call Close on the ReadCloser
797-
// when finished reading.
798-
//
799-
// The ReadCloser returned by NewReader also implements Resetter.
800-
func NewReader(r io.Reader) io.ReadCloser {
809+
type ReaderOpt func(*decompressor)
810+
811+
// WithPartialBlock tells decompressor to return after each block,
812+
// so it can read data written with partial flush
813+
func WithPartialBlock() ReaderOpt {
814+
return func(f *decompressor) {
815+
f.flushMode = partialFlush
816+
}
817+
}
818+
819+
// WithDict initializes the reader with a preset dictionary
820+
func WithDict(dict []byte) ReaderOpt {
821+
return func(f *decompressor) {
822+
f.dict.init(maxMatchOffset, dict)
823+
}
824+
}
825+
826+
// NewReaderOpts returns new reader with provided options
827+
func NewReaderOpts(r io.Reader, opts ...ReaderOpt) io.ReadCloser {
801828
fixedHuffmanDecoderInit()
802829

803830
var f decompressor
@@ -806,9 +833,26 @@ func NewReader(r io.Reader) io.ReadCloser {
806833
f.codebits = new([numCodes]int)
807834
f.step = nextBlock
808835
f.dict.init(maxMatchOffset, nil)
836+
837+
for _, opt := range opts {
838+
opt(&f)
839+
}
840+
809841
return &f
810842
}
811843

844+
// NewReader returns a new ReadCloser that can be used
845+
// to read the uncompressed version of r.
846+
// If r does not also implement io.ByteReader,
847+
// the decompressor may read more data than necessary from r.
848+
// It is the caller's responsibility to call Close on the ReadCloser
849+
// when finished reading.
850+
//
851+
// The ReadCloser returned by NewReader also implements Resetter.
852+
func NewReader(r io.Reader) io.ReadCloser {
853+
return NewReaderOpts(r)
854+
}
855+
812856
// NewReaderDict is like NewReader but initializes the reader
813857
// with a preset dictionary. The returned Reader behaves as if
814858
// the uncompressed data stream started with the given dictionary,
@@ -817,13 +861,5 @@ func NewReader(r io.Reader) io.ReadCloser {
817861
//
818862
// The ReadCloser returned by NewReader also implements Resetter.
819863
func NewReaderDict(r io.Reader, dict []byte) io.ReadCloser {
820-
fixedHuffmanDecoderInit()
821-
822-
var f decompressor
823-
f.r = makeReader(r)
824-
f.bits = new([maxNumLit + maxNumDist]int)
825-
f.codebits = new([numCodes]int)
826-
f.step = nextBlock
827-
f.dict.init(maxMatchOffset, dict)
828-
return &f
864+
return NewReaderOpts(r, WithDict(dict))
829865
}

flate/inflate_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bytes"
99
"crypto/rand"
1010
"io"
11+
"os"
1112
"strconv"
1213
"strings"
1314
"testing"
@@ -279,3 +280,23 @@ func TestWriteTo(t *testing.T) {
279280
t.Fatal("output did not match input")
280281
}
281282
}
283+
284+
func TestReaderPartialBlock(t *testing.T) {
285+
data, err := os.ReadFile("testdata/partial-block")
286+
if err != nil {
287+
t.Error(err)
288+
}
289+
290+
r := NewReaderOpts(bytes.NewReader(data), WithPartialBlock())
291+
rb := make([]byte, 32)
292+
n, err := r.Read(rb)
293+
if err != nil {
294+
t.Fatalf("Read: %v", err)
295+
}
296+
297+
expected := "hello, world"
298+
actual := string(rb[:n])
299+
if expected != actual {
300+
t.Fatalf("expected: %v, got: %v", expected, actual)
301+
}
302+
}

flate/testdata/partial-block

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
�H����Q(�/�I

0 commit comments

Comments
 (0)