1// Copyright 2015, Joe Tsai. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE.md file.
4
5package xflate
6
7import (
8	"bytes"
9	"io"
10	"io/ioutil"
11	"math"
12	"math/rand"
13	"testing"
14
15	"github.com/dsnet/compress/internal/errors"
16	"github.com/dsnet/compress/internal/testutil"
17)
18
19type countReadSeeker struct {
20	io.ReadSeeker
21	N int64
22}
23
24func (rs *countReadSeeker) Read(buf []byte) (int, error) {
25	n, err := rs.ReadSeeker.Read(buf)
26	rs.N += int64(n)
27	return n, err
28}
29
30func TestReader(t *testing.T) {
31	dh := testutil.MustDecodeHex
32
33	errFuncs := map[string]func(error) bool{
34		"IsCorrupted": errors.IsCorrupted,
35	}
36	vectors := []struct {
37		desc   string // Description of the test
38		input  []byte // Input test string
39		output []byte // Expected output string
40		errf   string // Name of error checking callback
41	}{{
42		desc: "empty string",
43		errf: "IsCorrupted",
44	}, {
45		desc: "empty stream",
46		input: dh("" +
47			"0d008705000048c82a51e8ff37dbf1",
48		),
49	}, {
50		desc: "empty stream with empty chunk",
51		input: dh("" +
52			"000000ffff000000ffff34c086050020916cb2a50bd20369da192deaff3bda05" +
53			"f81dc08605002021ab44219b4aff7fd6de3bf8",
54		),
55	}, {
56		desc: "empty stream with empty index",
57		input: dh("" +
58			"04c086050020191d53a1a508c9e8ff5bda7bf815c08605002021ab44219ba2ff" +
59			"2f6bef5df8",
60		),
61	}, {
62		desc: "empty stream with multiple empty chunks",
63		input: dh("" +
64			"000000ffff000000ffff000000ffff148086058044655366e3817441ba205d50" +
65			"4a83348c445ddcde7b6ffc15c08605002021ab44a103aaff2f6bef5df8",
66		),
67	}, {
68		desc: "empty stream with multiple empty chunks, with final bit",
69		input: dh("" +
70			"000000ffff010000ffff000000ffff148086058044655366e3817441ba205d50" +
71			"4a83348c445ddcde7b6ffc15c08605002021ab44a103aaff2f6bef5df8",
72		),
73		errf: "IsCorrupted",
74	}, {
75		desc: "empty stream with multiple empty indexes",
76		input: dh("" +
77			"04c086050020191d53a1a508c9e8ff5bda7bf83cc08605002019293a24a55464" +
78			"a585faff9bf600f804c08605002019493a2494d050560afd7f4c7bfb25008705" +
79			"000048c82a51e880f4ff834df0",
80		),
81	}, {
82		desc: "3k zeros, 1KiB chunks",
83		input: dh("" +
84			"621805a360148c5800000000ffff621805a360148c5800000000ffff621805a3" +
85			"60140c3900000000ffff1c8086058044642b3bc9aa3464540784acea809055d9" +
86			"9586dd5492446555a7b607fc0d008705000048c82a51c81ea1ff0f6cf2",
87		),
88		output: make([]byte, 3000),
89	}, {
90		desc: "quick brown fox - spec example",
91		input: dh("" +
92			"0ac94855282ccd4cce560028a928bf3c4f212dbf4201a0acd2dc82d41485fcb2" +
93			"d42205804a80f2398955950a00000000ffff4ac94f5704000000ffff24808605" +
94			"8084b247b60629218a48486656d2b442ca489fb7f7de0bfc3cc08605002019a1" +
95			"3aa454548a122ad5fff7b403f815c08605002021ab44219ba4ff2f6bef5df8",
96		),
97		output: []byte("The quick brown fox jumped over the lazy dog!"),
98	}, {
99		desc: "quick brown fox - manual chunking/indexing",
100		input: dh("" +
101			"2ac94855282ccd4cce06000000ffff52482aca2fcf5348cbaf00000000ffff00" +
102			"0000ffff52c82acd2d484d51c82f4b2d5228c94805000000ffff248086058044" +
103			"6553762a0ad14211d207253b234546a1528ad4d3edbd0bfc52c849acaa5448c9" +
104			"4f07000000ffff2c8086058044a281ec8611190d23b21221ca0851fdafbdf7de" +
105			"05fc1dc08605002021ab44219b52ff7fd6de3bf8",
106		),
107		output: []byte("the quick brown fox jumped over the lazy dog"),
108	}, {
109		desc: "quick brown fox - automatic chunking/indexing",
110		input: dh("" +
111			"2ac9485500000000ffff2a2ccd4c06000000ffffca56482a02000000ffff2c80" +
112			"86058044655376c32a2b9999c9cc4c665691d04ea5a474747bef01fcca2fcf53" +
113			"00000000ffff4acbaf5000000000ffffca2acd2d00000000ffff048086058044" +
114			"45036537acb2929999cccc6466cb48112a45a193db7beffc4a4d51c807000000" +
115			"ffff2a4b2d5200000000ffff2ac9485500000000ffff04808605804445036537" +
116			"acb2929999cccc6466cb48112a45a193db7beffcca49acaa04000000ffff5248" +
117			"c94f07000000ffff148086058084a261644b665632339399d9425629a44877b7" +
118			"f7de3bfc15c08605002021ab44a103aaff2f6bef5df8",
119		),
120		output: []byte("the quick brown fox jumped over the lazy dog"),
121	}, {
122		desc: "alphabet",
123		input: dh("" +
124			"4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" +
125			"048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc15c08605002021" +
126			"ab44219ba8ff2f6bef5df8",
127		),
128		output: []byte("abcdefghijklmnopqrstuvwxyz"),
129	}, {
130		desc:  "garbage footer",
131		input: dh("5174453181b67484bf6de23a608876f8b7f44c77"),
132		errf:  "IsCorrupted",
133	}, {
134		desc:  "corrupt meta footer",
135		input: dh("1d008705000048ca2c50e8ff3bdbf0"),
136		errf:  "IsCorrupted",
137	}, {
138		desc:  "trailing meta data in footer",
139		input: dh("0d008705000048c82a51e8ff37dbf1deadcafe"),
140		errf:  "IsCorrupted",
141	}, {
142		desc:  "trailing raw data in footer",
143		input: dh("25c086050020a9ac12856ec8284229d4ff0fb527f8"),
144		errf:  "IsCorrupted",
145	}, {
146		desc:  "footer using LastMeta",
147		input: dh("0c008705000048c82a51e8ff37dbf1"),
148		errf:  "IsCorrupted",
149	}, {
150		desc:  "footer without magic",
151		input: dh("1d00870500004864a644eaff3bdbf0"),
152		errf:  "IsCorrupted",
153	}, {
154		desc:  "footer with VLI overflow",
155		input: dh("2d80860580944a458a4abb6e6c9fdbde7bef01fc"),
156		errf:  "IsCorrupted",
157	}, {
158		desc: "index using LastStream",
159		input: dh("" +
160			"05c086050020191d53a1a508c9e8ff5bda7bf815c08605002021ab44219ba2ff" +
161			"2f6bef5df8",
162		),
163		errf: "IsCorrupted",
164	}, {
165		desc: "index with wrong CRC",
166		input: dh("" +
167			"2cc086050020191d132551320a51ff9fd2de0bf825008705000048c82a51e880" +
168			"f4ff834df0",
169		),
170		errf: "IsCorrupted",
171	}, {
172		desc: "corrupt meta index",
173		input: dh("" +
174			"04c086050020191d53a1a518c9e8ff5bda7bf815c08605002021ab44219ba2ff" +
175			"2f6bef5df8",
176		),
177		errf: "IsCorrupted",
178	}, {
179		desc: "index with VLI overflow",
180		input: dh("" +
181			"048086058094e8c6f6de7b531215458a840e6deffc15c08605002021ab44219b" +
182			"a4ff2f6bef5df8",
183		),
184		errf: "IsCorrupted",
185	}, {
186		desc: "trailing meta data in index",
187		input: dh("" +
188			"34c086050020291d53a1a508c908a16414a2fe3fa205f81dc08605002021ab44" +
189			"219b4aff7fd6de3bf8",
190		),
191		errf: "IsCorrupted",
192	}, {
193		desc: "trailing raw data in index",
194		input: dh("" +
195			"04c086050020191d53a1a508c9e8ff5bda7bf862616405c08605002021ab4421" +
196			"7b94febfacbd77f9",
197		),
198		errf: "IsCorrupted",
199	}, {
200		desc: "index total size is wrong",
201		input: dh("" +
202			"000000ffff14c086050020916cb2d505e983840aa12592faff8c76f81dc08605" +
203			"002021ab44219b4aff7fd6de3bf8",
204		),
205		errf: "IsCorrupted",
206	}, {
207		desc: "index with compressed chunk size of zero",
208		input: dh("" +
209			"000000ffff04c086050020916cb2e9848e8894a2a441fd7f457bf905c0860500" +
210			"2021ab44217b94febfacbd77f9",
211		),
212		errf: "IsCorrupted",
213	}, {
214		desc: "index with numeric overflow on sizes",
215		input: dh("" +
216			"000000ffff000000ffff0c40860552a43db4a53dcf6b97b47724641589a84e69" +
217			"efbdf7de7b4ffe1dc08605002021ab44219b54ff7fd6de3bf8",
218		),
219		errf: "IsCorrupted",
220	}, {
221		desc: "empty chunk without sync marker",
222		input: dh("" +
223			"000000ffff020820800004c086050020a1ec919d1e4817a40b421269a3a8ff1f" +
224			"68fa2d008705000048c82a51e881faffc126f0",
225		),
226		errf: "IsCorrupted",
227	}, {
228		desc: "chunk without sync marker",
229		input: dh("" +
230			"000000ffff000200fdff486902082080000cc086050020a1ec91193232d30965" +
231			"652b2b221125f5ff1eedf805c08605002021ab44217ba4febfacbd77f9",
232		),
233		output: []byte("Hi"),
234		errf:   "IsCorrupted",
235	}, {
236		desc: "chunk with wrong sizes",
237		input: dh("" +
238			"000000ffff000200fdff4869000000ffff2c8086058084b2476608d9e98432b2" +
239			"15252a958a92eaeef6de7b07fc15c08605002021ab44a103aaff2f6bef5df8",
240		),
241		output: []byte("Hi"),
242		errf:   "IsCorrupted",
243	}, {
244		desc: "size overflow across multiple indexes",
245		input: dh("" +
246			"000000ffff0c8086058094b487b6b4ce4b5ae7150d49d124195dd29efc000000" +
247			"ffff000000ffff24808605808432cac84e4676ba2059d9914a4a29259a8fb7f7" +
248			"de0bfc15c08605002021ab44a103aaff2f6bef5df8",
249		),
250		errf: "IsCorrupted",
251	}, {
252		desc: "index back size causes integer overflow",
253		input: dh("" +
254			"4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" +
255			"048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc4a4c4a4e494d4b" +
256			"cfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff2c8086058094e8" +
257			"bcb4a74ab4538986529284cc3e6def05fc2d008705000048c82a51e881faffc1" +
258			"26f0"),
259		errf: "IsCorrupted",
260	}, {
261		desc: "raw chunk with final bit and bad size",
262		input: dh("" +
263			"010900f6ff0000ffff248086058044b2c98e8cc8888cc828ed9d284afa7fb4f7" +
264			"de0bfc05c08605002021ab44217ba4febfacbd77f9",
265		),
266		output: dh("0000ffff010000ffff"),
267		// TODO(dsnet): The Reader incorrectly believes that this is valid.
268		// The chunk has a final raw block with a specified size of 9, but only
269		// has 4 bytes following it (0000ffff to fool the sync check).
270		// Since the decompressor would expect an additional 5 bytes, this is
271		// satisfied by the fact that the chunkReader appends the endBlock
272		// sequence (010000ffff) to every chunk. This really difficult to fix
273		// without low-level details about the DEFLATE stream.
274		errf: "", // "IsCorrupted",
275	}}
276
277	for i, v := range vectors {
278		var xr *Reader
279		var err error
280		var buf []byte
281
282		xr, err = NewReader(bytes.NewReader(v.input), nil)
283		if err != nil {
284			goto done
285		}
286
287		buf, err = ioutil.ReadAll(xr)
288		if err != nil {
289			goto done
290		}
291
292	done:
293		if v.errf != "" && !errFuncs[v.errf](err) {
294			t.Errorf("test %d (%s), mismatching error:\ngot %v\nwant %s(err) == true", i, v.desc, err, v.errf)
295		} else if v.errf == "" && err != nil {
296			t.Errorf("test %d (%s), unexpected error: got %v", i, v.desc, err)
297		}
298		if got, want, ok := testutil.BytesCompare(buf, v.output); !ok && err == nil {
299			t.Errorf("test %d (%s), mismatching output:\ngot  %s\nwant %s", i, v.desc, got, want)
300		}
301	}
302}
303
304func TestReaderReset(t *testing.T) {
305	var (
306		empty   = testutil.MustDecodeHex("0d008705000048c82a51e8ff37dbf1")
307		badSize = testutil.MustDecodeHex("" +
308			"4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2bafa8ac02000000ffff" +
309			"3c8086058084b2e981acd0203b2b34884a834a2a91d2ededbd7701fc15c08605" +
310			"002021ab44a103aaff2f6bef5df8",
311		)
312		badData = testutil.MustDecodeHex("" +
313			"4a4c4a4e494d4bcfc8cccacec9cdcb2f282c2a2e292d2baf000002000000ffff" +
314			"048086058044b2e98190b285148a844a0b95a4f7db7bef3dfc15c08605002021" +
315			"ab44219ba8ff2f6bef5df8",
316		)
317	)
318
319	// Test Reader for idempotent Close.
320	xr := new(Reader)
321	if err := xr.Reset(bytes.NewReader(empty)); err != nil {
322		t.Fatalf("unexpected error: Reset() = %v", err)
323	}
324	buf, err := ioutil.ReadAll(xr)
325	if err != nil {
326		t.Fatalf("unexpected error: ReadAll() = %v", err)
327	}
328	if len(buf) > 0 {
329		t.Fatalf("unexpected output data: ReadAll() = %q, want nil", buf)
330	}
331	if err := xr.Close(); err != nil {
332		t.Fatalf("unexpected error: Close() = %v", err)
333	}
334	if err := xr.Close(); err != nil {
335		t.Fatalf("unexpected error: Close() = %v", err)
336	}
337	if _, err := ioutil.ReadAll(xr); err != errClosed {
338		t.Fatalf("mismatching error: ReadAll() = %v, want %v", err, errClosed)
339	}
340
341	// Test Reset on garbage data.
342	rd := bytes.NewReader(append([]byte("garbage"), empty...))
343	if err := xr.Reset(rd); !errors.IsCorrupted(err) {
344		t.Fatalf("mismatching error: Reset() = %v, want IsCorrupted(err) == true", err)
345	}
346	if _, err := xr.Seek(0, io.SeekStart); !errors.IsCorrupted(err) {
347		t.Fatalf("mismatching error: Seek() = %v, want IsCorrupted(err) == true", err)
348	}
349	if err := xr.Close(); !errors.IsCorrupted(err) {
350		t.Fatalf("mismatching error: Close() = %v, want IsCorrupted(err) == true", err)
351	}
352
353	// Test Reset on corrupt data in discard section.
354	for i, v := range [][]byte{badData, badSize} {
355		if err := xr.Reset(bytes.NewReader(v)); err != nil {
356			t.Fatalf("test %d, unexpected error: Reset() = %v", i, err)
357		}
358		if _, err := xr.Seek(-1, io.SeekEnd); err != nil {
359			t.Fatalf("test %d, unexpected error: Seek() = %v", i, err)
360		}
361		if _, err = ioutil.ReadAll(xr); !errors.IsCorrupted(err) {
362			t.Fatalf("test %d, mismatching error: ReadAll() = %v, want IsCorrupted(err) == true", i, err)
363		}
364	}
365}
366
367func TestReaderSeek(t *testing.T) {
368	rand := rand.New(rand.NewSource(0))
369	twain := testutil.MustLoadFile("../testdata/twain.txt")
370
371	// Generate compressed version of input file.
372	var buf bytes.Buffer
373	xw, err := NewWriter(&buf, &WriterConfig{ChunkSize: 1 << 10})
374	if err != nil {
375		t.Fatalf("unexpected error: NewWriter() = %v", err)
376	}
377	if _, err := xw.Write(twain); err != nil {
378		t.Fatalf("unexpected error: Write() = %v", err)
379	}
380	if err := xw.Close(); err != nil {
381		t.Fatalf("unexpected error: Close() = %v", err)
382	}
383
384	// Read the compressed file.
385	rs := &countReadSeeker{ReadSeeker: bytes.NewReader(buf.Bytes())}
386	xr, err := NewReader(rs, nil)
387	if err != nil {
388		t.Fatalf("unexpected error: NewReader() = %v", err)
389	}
390
391	// As a heuristic, make sure we are not reading too much data.
392	if thres := int64(len(twain) / 100); rs.N > thres {
393		t.Fatalf("read more data than expected: %d > %d", rs.N, thres)
394	}
395	rs.N = 0 // Reset the read count
396
397	// Generate list of seek commands to try.
398	type seekCommand struct {
399		length int   // Number of bytes to read
400		offset int64 // Seek to this offset
401		whence int   // Whence value to use
402	}
403	vectors := []seekCommand{
404		{length: 40, offset: int64(len(twain)) - 1, whence: io.SeekStart},
405		{length: 40, offset: int64(len(twain)), whence: io.SeekStart},
406		{length: 40, offset: int64(len(twain)) + 1, whence: io.SeekStart},
407		{length: 40, offset: math.MaxInt64, whence: io.SeekStart},
408		{length: 0, offset: 0, whence: io.SeekCurrent},
409		{length: 13, offset: 15, whence: io.SeekStart},
410		{length: 32, offset: 23, whence: io.SeekCurrent},
411		{length: 32, offset: -23, whence: io.SeekCurrent},
412		{length: 13, offset: -15, whence: io.SeekStart},
413		{length: 100, offset: -15, whence: io.SeekEnd},
414		{length: 0, offset: 0, whence: io.SeekCurrent},
415		{length: 0, offset: 0, whence: io.SeekCurrent},
416		{length: 32, offset: -34, whence: io.SeekCurrent},
417		{length: 32, offset: -34, whence: io.SeekCurrent},
418		{length: 2000, offset: 53, whence: io.SeekStart},
419		{length: 2000, offset: int64(len(twain)) - 1000, whence: io.SeekStart},
420		{length: 0, offset: 0, whence: io.SeekCurrent},
421		{length: 100, offset: -int64(len(twain)), whence: io.SeekEnd},
422		{length: 100, offset: -int64(len(twain)) - 1, whence: io.SeekEnd},
423		{length: 0, offset: 0, whence: io.SeekStart},
424		{length: 10, offset: 10, whence: io.SeekCurrent},
425		{length: 10, offset: 10, whence: io.SeekCurrent},
426		{length: 10, offset: 10, whence: io.SeekCurrent},
427		{length: 10, offset: 10, whence: io.SeekCurrent},
428		{length: 0, offset: 0, whence: -1},
429	}
430
431	// Add random values to seek list.
432	for i := 0; i < 100; i++ {
433		length, offset := rand.Intn(1<<11), rand.Int63n(int64(len(twain)))
434		if length+int(offset) <= len(twain) {
435			vectors = append(vectors, seekCommand{length, offset, io.SeekStart})
436		}
437	}
438
439	// Read in reverse.
440	vectors = append(vectors, seekCommand{0, 0, io.SeekEnd})
441	for pos := int64(len(twain)); pos > 0; {
442		n := int64(rand.Intn(1 << 11))
443		if n > pos {
444			n = pos
445		}
446		pos -= n
447		vectors = append(vectors, seekCommand{int(n), pos, io.SeekStart})
448	}
449
450	// Execute all seek commands.
451	var pos, totalLength int64
452	for i, v := range vectors {
453		// Emulate Seek logic.
454		var wantPos int64
455		switch v.whence {
456		case io.SeekStart:
457			wantPos = v.offset
458		case io.SeekCurrent:
459			wantPos = v.offset + pos
460		case io.SeekEnd:
461			wantPos = v.offset + int64(len(twain))
462		default:
463			wantPos = -1
464		}
465
466		// Perform actually (short-circuit if seek fails).
467		wantFail := bool(wantPos < 0)
468		gotPos, err := xr.Seek(v.offset, v.whence)
469		if gotFail := bool(err != nil); gotFail != wantFail {
470			if gotFail {
471				t.Fatalf("test %d, unexpected failure: Seek(%d, %d) = (%d, %v)", i, v.offset, v.whence, pos, err)
472			} else {
473				t.Fatalf("test %d, unexpected success: Seek(%d, %d) = (%d, nil)", i, v.offset, v.whence, pos)
474			}
475		}
476		if wantFail {
477			continue
478		}
479		if gotPos != wantPos {
480			t.Fatalf("test %d, offset mismatch: got %d, want %d", i, gotPos, wantPos)
481		}
482
483		// Read and verify some length of bytes.
484		var want []byte
485		if wantPos < int64(len(twain)) {
486			want = twain[wantPos:]
487		}
488		if len(want) > v.length {
489			want = want[:v.length]
490		}
491		got, err := ioutil.ReadAll(io.LimitReader(xr, int64(v.length)))
492		if err != nil {
493			t.Fatalf("test %v, unexpected error: ReadAll() = %v", i, err)
494		}
495		if got, want, ok := testutil.BytesCompare(got, want); !ok {
496			t.Fatalf("test %v, mismatching output:\ngot  %s\nwant %s", i, got, want)
497		}
498
499		pos = gotPos + int64(len(got))
500		totalLength += int64(v.length)
501	}
502
503	// As a heuristic, make sure we are not reading too much data.
504	if thres := 2 * totalLength; rs.N > thres {
505		t.Fatalf("read more data than expected: %d > %d", rs.N, thres)
506	}
507}
508
509func TestRecursiveReader(t *testing.T) {
510	twain := testutil.MustLoadFile("../testdata/twain.txt")
511
512	const numIters = 5
513	var bb bytes.Buffer
514
515	// Recursively compress the same input data multiple times using XFLATE.
516	// Run as a closured function to ensure defer statements execute.
517	func() {
518		wlast := io.Writer(&bb) // Latest writer
519		for i := 0; i < numIters; i++ {
520			xw, err := NewWriter(wlast, &WriterConfig{ChunkSize: 1 << uint(10+i)})
521			if err != nil {
522				t.Fatalf("unexpected error: NewWriter() = %v", err)
523			}
524			defer func() {
525				if err := xw.Close(); err != nil {
526					t.Fatalf("unexpected error: Close() = %v", err)
527				}
528			}()
529			wlast = xw
530		}
531		if _, err := wlast.Write(twain); err != nil {
532			t.Fatalf("unexpected error: Write() = %v", err)
533		}
534	}()
535
536	// Recursively decompress the same input stream multiple times.
537	func() {
538		rlast := io.ReadSeeker(bytes.NewReader(bb.Bytes()))
539		for i := 0; i < numIters; i++ {
540			xr, err := NewReader(rlast, nil)
541			if err != nil {
542				t.Fatalf("unexpected error: NewReader() = %v", err)
543			}
544			defer func() {
545				if err := xr.Close(); err != nil {
546					t.Fatalf("unexpected error: Close() = %v", err)
547				}
548			}()
549			rlast = xr
550		}
551
552		buf := make([]byte, 321)
553		if _, err := rlast.Seek(int64(len(twain))/2, io.SeekStart); err != nil {
554			t.Fatalf("unexpected error: Seek() = %v", err)
555		}
556		if _, err := io.ReadFull(rlast, buf); err != nil {
557			t.Fatalf("unexpected error: Read() = %v", err)
558		}
559		if got, want := string(buf), string(twain[len(twain)/2:][:321]); got != want {
560			t.Errorf("output mismatch:\ngot  %q\nwant %q", got, want)
561		}
562	}()
563}
564
565// BenchmarkReader benchmarks the overhead of the XFLATE format over DEFLATE.
566// Thus, it intentionally uses a very small chunk size with no compression.
567// This benchmark reads the input file in reverse to excite poor behavior.
568func BenchmarkReader(b *testing.B) {
569	rand := rand.New(rand.NewSource(0))
570	twain := testutil.MustLoadFile("../testdata/twain.txt")
571	bb := bytes.NewBuffer(make([]byte, 0, 2*len(twain)))
572	xr := new(Reader)
573	lr := new(io.LimitedReader)
574
575	xw, _ := NewWriter(bb, &WriterConfig{Level: NoCompression, ChunkSize: 1 << 10})
576	xw.Write(twain)
577	xw.Close()
578
579	b.ReportAllocs()
580	b.SetBytes(int64(len(twain)))
581	b.ResetTimer()
582
583	for i := 0; i < b.N; i++ {
584		rand.Seed(0)
585		rd := bytes.NewReader(bb.Bytes())
586		if err := xr.Reset(rd); err != nil {
587			b.Fatalf("unexpected error: Reset() = %v", err)
588		}
589
590		// Read sections of the input in reverse.
591		for pos := int64(len(twain)); pos > 0; {
592			// Random section size.
593			n := int64(rand.Intn(1 << 11))
594			if n > pos {
595				n = pos
596			}
597			pos -= n
598
599			// Read the given section.
600			if _, err := xr.Seek(pos, io.SeekStart); err != nil {
601				b.Fatalf("unexpected error: Seek() = %v", err)
602			}
603			*lr = io.LimitedReader{R: xr, N: n}
604			if _, err := io.Copy(ioutil.Discard, lr); err != nil {
605				b.Fatalf("unexpected error: Copy() = %v", err)
606			}
607		}
608	}
609}
610