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	"bufio"
  9	"errors"
 10	"io"
 11)
 12
 13// MinDictCap and MaxDictCap provide the range of supported dictionary
 14// capacities.
 15const (
 16	MinDictCap = 1 << 12
 17	MaxDictCap = 1<<32 - 1
 18)
 19
 20// WriterConfig defines the configuration parameter for a writer.
 21type WriterConfig struct {
 22	// Properties for the encoding. If the it is nil the value
 23	// {LC: 3, LP: 0, PB: 2} will be chosen.
 24	Properties *Properties
 25	// The capacity of the dictionary. If DictCap is zero, the value
 26	// 8 MiB will be chosen.
 27	DictCap int
 28	// Size of the lookahead buffer; value 0 indicates default size
 29	// 4096
 30	BufSize int
 31	// Match algorithm
 32	Matcher MatchAlgorithm
 33	// SizeInHeader indicates that the header will contain an
 34	// explicit size.
 35	SizeInHeader bool
 36	// Size of the data to be encoded. A positive value will imply
 37	// than an explicit size will be set in the header.
 38	Size int64
 39	// EOSMarker requests whether the EOSMarker needs to be written.
 40	// If no explicit size is been given the EOSMarker will be
 41	// set automatically.
 42	EOSMarker bool
 43}
 44
 45// fill converts zero-value fields to their explicit default values.
 46func (c *WriterConfig) fill() {
 47	if c.Properties == nil {
 48		c.Properties = &Properties{LC: 3, LP: 0, PB: 2}
 49	}
 50	if c.DictCap == 0 {
 51		c.DictCap = 8 * 1024 * 1024
 52	}
 53	if c.BufSize == 0 {
 54		c.BufSize = 4096
 55	}
 56	if c.Size > 0 {
 57		c.SizeInHeader = true
 58	}
 59	if !c.SizeInHeader {
 60		c.EOSMarker = true
 61	}
 62}
 63
 64// Verify checks WriterConfig for errors. Verify will replace zero
 65// values with default values.
 66func (c *WriterConfig) Verify() error {
 67	c.fill()
 68	var err error
 69	if c == nil {
 70		return errors.New("lzma: WriterConfig is nil")
 71	}
 72	if c.Properties == nil {
 73		return errors.New("lzma: WriterConfig has no Properties set")
 74	}
 75	if err = c.Properties.verify(); err != nil {
 76		return err
 77	}
 78	if !(MinDictCap <= c.DictCap && int64(c.DictCap) <= MaxDictCap) {
 79		return errors.New("lzma: dictionary capacity is out of range")
 80	}
 81	if !(maxMatchLen <= c.BufSize) {
 82		return errors.New("lzma: lookahead buffer size too small")
 83	}
 84	if c.SizeInHeader {
 85		if c.Size < 0 {
 86			return errors.New("lzma: negative size not supported")
 87		}
 88	} else if !c.EOSMarker {
 89		return errors.New("lzma: EOS marker is required")
 90	}
 91	if err = c.Matcher.verify(); err != nil {
 92		return err
 93	}
 94
 95	return nil
 96}
 97
 98// header returns the header structure for this configuration.
 99func (c *WriterConfig) header() header {
100	h := header{
101		properties: *c.Properties,
102		dictCap:    c.DictCap,
103		size:       -1,
104	}
105	if c.SizeInHeader {
106		h.size = c.Size
107	}
108	return h
109}
110
111// Writer writes an LZMA stream in the classic format.
112type Writer struct {
113	h   header
114	bw  io.ByteWriter
115	buf *bufio.Writer
116	e   *encoder
117}
118
119// NewWriter creates a new LZMA writer for the classic format. The
120// method will write the header to the underlying stream.
121func (c WriterConfig) NewWriter(lzma io.Writer) (w *Writer, err error) {
122	if err = c.Verify(); err != nil {
123		return nil, err
124	}
125	w = &Writer{h: c.header()}
126
127	var ok bool
128	w.bw, ok = lzma.(io.ByteWriter)
129	if !ok {
130		w.buf = bufio.NewWriter(lzma)
131		w.bw = w.buf
132	}
133	state := newState(w.h.properties)
134	m, err := c.Matcher.new(w.h.dictCap)
135	if err != nil {
136		return nil, err
137	}
138	dict, err := newEncoderDict(w.h.dictCap, c.BufSize, m)
139	if err != nil {
140		return nil, err
141	}
142	var flags encoderFlags
143	if c.EOSMarker {
144		flags = eosMarker
145	}
146	if w.e, err = newEncoder(w.bw, state, dict, flags); err != nil {
147		return nil, err
148	}
149
150	if err = w.writeHeader(); err != nil {
151		return nil, err
152	}
153	return w, nil
154}
155
156// NewWriter creates a new LZMA writer using the classic format. The
157// function writes the header to the underlying stream.
158func NewWriter(lzma io.Writer) (w *Writer, err error) {
159	return WriterConfig{}.NewWriter(lzma)
160}
161
162// writeHeader writes the LZMA header into the stream.
163func (w *Writer) writeHeader() error {
164	data, err := w.h.marshalBinary()
165	if err != nil {
166		return err
167	}
168	_, err = w.bw.(io.Writer).Write(data)
169	return err
170}
171
172// Write puts data into the Writer.
173func (w *Writer) Write(p []byte) (n int, err error) {
174	if w.h.size >= 0 {
175		m := w.h.size
176		m -= w.e.Compressed() + int64(w.e.dict.Buffered())
177		if m < 0 {
178			m = 0
179		}
180		if m < int64(len(p)) {
181			p = p[:m]
182			err = ErrNoSpace
183		}
184	}
185	var werr error
186	if n, werr = w.e.Write(p); werr != nil {
187		err = werr
188	}
189	return n, err
190}
191
192// Close closes the writer stream. It ensures that all data from the
193// buffer will be compressed and the LZMA stream will be finished.
194func (w *Writer) Close() error {
195	if w.h.size >= 0 {
196		n := w.e.Compressed() + int64(w.e.dict.Buffered())
197		if n != w.h.size {
198			return errSize
199		}
200	}
201	err := w.e.Close()
202	if w.buf != nil {
203		ferr := w.buf.Flush()
204		if err == nil {
205			err = ferr
206		}
207	}
208	return err
209}