1// Copyright 2014-2019 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	"bytes"
9	"errors"
10	"io"
11)
12
13// Writer2Config is used to create a Writer2 using parameters.
14type Writer2Config struct {
15	// The properties for the encoding. If the it is nil the value
16	// {LC: 3, LP: 0, PB: 2} will be chosen.
17	Properties *Properties
18	// The capacity of the dictionary. If DictCap is zero, the value
19	// 8 MiB will be chosen.
20	DictCap int
21	// Size of the lookahead buffer; value 0 indicates default size
22	// 4096
23	BufSize int
24	// Match algorithm
25	Matcher MatchAlgorithm
26}
27
28// fill replaces zero values with default values.
29func (c *Writer2Config) fill() {
30	if c.Properties == nil {
31		c.Properties = &Properties{LC: 3, LP: 0, PB: 2}
32	}
33	if c.DictCap == 0 {
34		c.DictCap = 8 * 1024 * 1024
35	}
36	if c.BufSize == 0 {
37		c.BufSize = 4096
38	}
39}
40
41// Verify checks the Writer2Config for correctness. Zero values will be
42// replaced by default values.
43func (c *Writer2Config) Verify() error {
44	c.fill()
45	var err error
46	if c == nil {
47		return errors.New("lzma: WriterConfig is nil")
48	}
49	if c.Properties == nil {
50		return errors.New("lzma: WriterConfig has no Properties set")
51	}
52	if err = c.Properties.verify(); err != nil {
53		return err
54	}
55	if !(MinDictCap <= c.DictCap && int64(c.DictCap) <= MaxDictCap) {
56		return errors.New("lzma: dictionary capacity is out of range")
57	}
58	if !(maxMatchLen <= c.BufSize) {
59		return errors.New("lzma: lookahead buffer size too small")
60	}
61	if c.Properties.LC+c.Properties.LP > 4 {
62		return errors.New("lzma: sum of lc and lp exceeds 4")
63	}
64	if err = c.Matcher.verify(); err != nil {
65		return err
66	}
67	return nil
68}
69
70// Writer2 supports the creation of an LZMA2 stream. But note that
71// written data is buffered, so call Flush or Close to write data to the
72// underlying writer. The Close method writes the end-of-stream marker
73// to the stream. So you may be able to concatenate the output of two
74// writers as long the output of the first writer has only been flushed
75// but not closed.
76//
77// Any change to the fields Properties, DictCap must be done before the
78// first call to Write, Flush or Close.
79type Writer2 struct {
80	w io.Writer
81
82	start   *state
83	encoder *encoder
84
85	cstate chunkState
86	ctype  chunkType
87
88	buf bytes.Buffer
89	lbw LimitedByteWriter
90}
91
92// NewWriter2 creates an LZMA2 chunk sequence writer with the default
93// parameters and options.
94func NewWriter2(lzma2 io.Writer) (w *Writer2, err error) {
95	return Writer2Config{}.NewWriter2(lzma2)
96}
97
98// NewWriter2 creates a new LZMA2 writer using the given configuration.
99func (c Writer2Config) NewWriter2(lzma2 io.Writer) (w *Writer2, err error) {
100	if err = c.Verify(); err != nil {
101		return nil, err
102	}
103	w = &Writer2{
104		w:      lzma2,
105		start:  newState(*c.Properties),
106		cstate: start,
107		ctype:  start.defaultChunkType(),
108	}
109	w.buf.Grow(maxCompressed)
110	w.lbw = LimitedByteWriter{BW: &w.buf, N: maxCompressed}
111	m, err := c.Matcher.new(c.DictCap)
112	if err != nil {
113		return nil, err
114	}
115	d, err := newEncoderDict(c.DictCap, c.BufSize, m)
116	if err != nil {
117		return nil, err
118	}
119	w.encoder, err = newEncoder(&w.lbw, cloneState(w.start), d, 0)
120	if err != nil {
121		return nil, err
122	}
123	return w, nil
124}
125
126// written returns the number of bytes written to the current chunk
127func (w *Writer2) written() int {
128	if w.encoder == nil {
129		return 0
130	}
131	return int(w.encoder.Compressed()) + w.encoder.dict.Buffered()
132}
133
134// errClosed indicates that the writer is closed.
135var errClosed = errors.New("lzma: writer closed")
136
137// Writes data to LZMA2 stream. Note that written data will be buffered.
138// Use Flush or Close to ensure that data is written to the underlying
139// writer.
140func (w *Writer2) Write(p []byte) (n int, err error) {
141	if w.cstate == stop {
142		return 0, errClosed
143	}
144	for n < len(p) {
145		m := maxUncompressed - w.written()
146		if m <= 0 {
147			panic("lzma: maxUncompressed reached")
148		}
149		var q []byte
150		if n+m < len(p) {
151			q = p[n : n+m]
152		} else {
153			q = p[n:]
154		}
155		k, err := w.encoder.Write(q)
156		n += k
157		if err != nil && err != ErrLimit {
158			return n, err
159		}
160		if err == ErrLimit || k == m {
161			if err = w.flushChunk(); err != nil {
162				return n, err
163			}
164		}
165	}
166	return n, nil
167}
168
169// writeUncompressedChunk writes an uncompressed chunk to the LZMA2
170// stream.
171func (w *Writer2) writeUncompressedChunk() error {
172	u := w.encoder.Compressed()
173	if u <= 0 {
174		return errors.New("lzma: can't write empty uncompressed chunk")
175	}
176	if u > maxUncompressed {
177		panic("overrun of uncompressed data limit")
178	}
179	switch w.ctype {
180	case cLRND:
181		w.ctype = cUD
182	default:
183		w.ctype = cU
184	}
185	w.encoder.state = w.start
186
187	header := chunkHeader{
188		ctype:        w.ctype,
189		uncompressed: uint32(u - 1),
190	}
191	hdata, err := header.MarshalBinary()
192	if err != nil {
193		return err
194	}
195	if _, err = w.w.Write(hdata); err != nil {
196		return err
197	}
198	_, err = w.encoder.dict.CopyN(w.w, int(u))
199	return err
200}
201
202// writeCompressedChunk writes a compressed chunk to the underlying
203// writer.
204func (w *Writer2) writeCompressedChunk() error {
205	if w.ctype == cU || w.ctype == cUD {
206		panic("chunk type uncompressed")
207	}
208
209	u := w.encoder.Compressed()
210	if u <= 0 {
211		return errors.New("writeCompressedChunk: empty chunk")
212	}
213	if u > maxUncompressed {
214		panic("overrun of uncompressed data limit")
215	}
216	c := w.buf.Len()
217	if c <= 0 {
218		panic("no compressed data")
219	}
220	if c > maxCompressed {
221		panic("overrun of compressed data limit")
222	}
223	header := chunkHeader{
224		ctype:        w.ctype,
225		uncompressed: uint32(u - 1),
226		compressed:   uint16(c - 1),
227		props:        w.encoder.state.Properties,
228	}
229	hdata, err := header.MarshalBinary()
230	if err != nil {
231		return err
232	}
233	if _, err = w.w.Write(hdata); err != nil {
234		return err
235	}
236	_, err = io.Copy(w.w, &w.buf)
237	return err
238}
239
240// writes a single chunk to the underlying writer.
241func (w *Writer2) writeChunk() error {
242	u := int(uncompressedHeaderLen + w.encoder.Compressed())
243	c := headerLen(w.ctype) + w.buf.Len()
244	if u < c {
245		return w.writeUncompressedChunk()
246	}
247	return w.writeCompressedChunk()
248}
249
250// flushChunk terminates the current chunk. The encoder will be reset
251// to support the next chunk.
252func (w *Writer2) flushChunk() error {
253	if w.written() == 0 {
254		return nil
255	}
256	var err error
257	if err = w.encoder.Close(); err != nil {
258		return err
259	}
260	if err = w.writeChunk(); err != nil {
261		return err
262	}
263	w.buf.Reset()
264	w.lbw.N = maxCompressed
265	if err = w.encoder.Reopen(&w.lbw); err != nil {
266		return err
267	}
268	if err = w.cstate.next(w.ctype); err != nil {
269		return err
270	}
271	w.ctype = w.cstate.defaultChunkType()
272	w.start = cloneState(w.encoder.state)
273	return nil
274}
275
276// Flush writes all buffered data out to the underlying stream. This
277// could result in multiple chunks to be created.
278func (w *Writer2) Flush() error {
279	if w.cstate == stop {
280		return errClosed
281	}
282	for w.written() > 0 {
283		if err := w.flushChunk(); err != nil {
284			return err
285		}
286	}
287	return nil
288}
289
290// Close terminates the LZMA2 stream with an EOS chunk.
291func (w *Writer2) Close() error {
292	if w.cstate == stop {
293		return errClosed
294	}
295	if err := w.Flush(); err != nil {
296		return nil
297	}
298	// write zero byte EOS chunk
299	_, err := w.w.Write([]byte{0})
300	if err != nil {
301		return err
302	}
303	w.cstate = stop
304	return nil
305}
306