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