main
Raw Download raw file
  1// Copyright 2014-2022 Ulrich Kunitz. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5package lzma
  6
  7import (
  8	"errors"
  9	"io"
 10
 11	"github.com/ulikunitz/xz/internal/xlog"
 12)
 13
 14// Reader2Config stores the parameters for the LZMA2 reader.
 15// format.
 16type Reader2Config struct {
 17	DictCap int
 18}
 19
 20// fill converts the zero values of the configuration to the default values.
 21func (c *Reader2Config) fill() {
 22	if c.DictCap == 0 {
 23		c.DictCap = 8 * 1024 * 1024
 24	}
 25}
 26
 27// Verify checks the reader configuration for errors. Zero configuration values
 28// will be replaced by default values.
 29func (c *Reader2Config) Verify() error {
 30	c.fill()
 31	if !(MinDictCap <= c.DictCap && int64(c.DictCap) <= MaxDictCap) {
 32		return errors.New("lzma: dictionary capacity is out of range")
 33	}
 34	return nil
 35}
 36
 37// Reader2 supports the reading of LZMA2 chunk sequences. Note that the
 38// first chunk should have a dictionary reset and the first compressed
 39// chunk a properties reset. The chunk sequence may not be terminated by
 40// an end-of-stream chunk.
 41type Reader2 struct {
 42	r   io.Reader
 43	err error
 44
 45	dict        *decoderDict
 46	ur          *uncompressedReader
 47	decoder     *decoder
 48	chunkReader io.Reader
 49
 50	cstate chunkState
 51}
 52
 53// NewReader2 creates a reader for an LZMA2 chunk sequence.
 54func NewReader2(lzma2 io.Reader) (r *Reader2, err error) {
 55	return Reader2Config{}.NewReader2(lzma2)
 56}
 57
 58// NewReader2 creates an LZMA2 reader using the given configuration.
 59func (c Reader2Config) NewReader2(lzma2 io.Reader) (r *Reader2, err error) {
 60	if err = c.Verify(); err != nil {
 61		return nil, err
 62	}
 63	r = &Reader2{r: lzma2, cstate: start}
 64	r.dict, err = newDecoderDict(c.DictCap)
 65	if err != nil {
 66		return nil, err
 67	}
 68	if err = r.startChunk(); err != nil {
 69		r.err = err
 70	}
 71	return r, nil
 72}
 73
 74// uncompressed tests whether the chunk type specifies an uncompressed
 75// chunk.
 76func uncompressed(ctype chunkType) bool {
 77	return ctype == cU || ctype == cUD
 78}
 79
 80// startChunk parses a new chunk.
 81func (r *Reader2) startChunk() error {
 82	r.chunkReader = nil
 83	header, err := readChunkHeader(r.r)
 84	if err != nil {
 85		if err == io.EOF {
 86			err = io.ErrUnexpectedEOF
 87		}
 88		return err
 89	}
 90	xlog.Debugf("chunk header %v", header)
 91	if err = r.cstate.next(header.ctype); err != nil {
 92		return err
 93	}
 94	if r.cstate == stop {
 95		return io.EOF
 96	}
 97	if header.ctype == cUD || header.ctype == cLRND {
 98		r.dict.Reset()
 99	}
100	size := int64(header.uncompressed) + 1
101	if uncompressed(header.ctype) {
102		if r.ur != nil {
103			r.ur.Reopen(r.r, size)
104		} else {
105			r.ur = newUncompressedReader(r.r, r.dict, size)
106		}
107		r.chunkReader = r.ur
108		return nil
109	}
110	br := ByteReader(io.LimitReader(r.r, int64(header.compressed)+1))
111	if r.decoder == nil {
112		state := newState(header.props)
113		r.decoder, err = newDecoder(br, state, r.dict, size)
114		if err != nil {
115			return err
116		}
117		r.chunkReader = r.decoder
118		return nil
119	}
120	switch header.ctype {
121	case cLR:
122		r.decoder.State.Reset()
123	case cLRN, cLRND:
124		r.decoder.State = newState(header.props)
125	}
126	err = r.decoder.Reopen(br, size)
127	if err != nil {
128		return err
129	}
130	r.chunkReader = r.decoder
131	return nil
132}
133
134// Read reads data from the LZMA2 chunk sequence.
135func (r *Reader2) Read(p []byte) (n int, err error) {
136	if r.err != nil {
137		return 0, r.err
138	}
139	for n < len(p) {
140		var k int
141		k, err = r.chunkReader.Read(p[n:])
142		n += k
143		if err != nil {
144			if err == io.EOF {
145				err = r.startChunk()
146				if err == nil {
147					continue
148				}
149			}
150			r.err = err
151			return n, err
152		}
153		if k == 0 {
154			r.err = errors.New("lzma: Reader2 doesn't get data")
155			return n, r.err
156		}
157	}
158	return n, nil
159}
160
161// EOS returns whether the LZMA2 stream has been terminated by an
162// end-of-stream chunk.
163func (r *Reader2) EOS() bool {
164	return r.cstate == stop
165}
166
167// uncompressedReader is used to read uncompressed chunks.
168type uncompressedReader struct {
169	lr   io.LimitedReader
170	Dict *decoderDict
171	eof  bool
172	err  error
173}
174
175// newUncompressedReader initializes a new uncompressedReader.
176func newUncompressedReader(r io.Reader, dict *decoderDict, size int64) *uncompressedReader {
177	ur := &uncompressedReader{
178		lr:   io.LimitedReader{R: r, N: size},
179		Dict: dict,
180	}
181	return ur
182}
183
184// Reopen reinitializes an uncompressed reader.
185func (ur *uncompressedReader) Reopen(r io.Reader, size int64) {
186	ur.err = nil
187	ur.eof = false
188	ur.lr = io.LimitedReader{R: r, N: size}
189}
190
191// fill reads uncompressed data into the dictionary.
192func (ur *uncompressedReader) fill() error {
193	if !ur.eof {
194		n, err := io.CopyN(ur.Dict, &ur.lr, int64(ur.Dict.Available()))
195		if err != io.EOF {
196			return err
197		}
198		ur.eof = true
199		if n > 0 {
200			return nil
201		}
202	}
203	if ur.lr.N != 0 {
204		return io.ErrUnexpectedEOF
205	}
206	return io.EOF
207}
208
209// Read reads uncompressed data from the limited reader.
210func (ur *uncompressedReader) Read(p []byte) (n int, err error) {
211	if ur.err != nil {
212		return 0, ur.err
213	}
214	for {
215		var k int
216		k, err = ur.Dict.Read(p[n:])
217		n += k
218		if n >= len(p) {
219			return n, nil
220		}
221		if err != nil {
222			break
223		}
224		err = ur.fill()
225		if err != nil {
226			break
227		}
228	}
229	ur.err = err
230	return n, err
231}