main
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}