// Copyright 2014-2021 Ulrich Kunitz. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package lzma import ( "bytes" "errors" "io" ) // Writer2Config is used to create a Writer2 using parameters. type Writer2Config struct { // The properties for the encoding. If the it is nil the value // {LC: 3, LP: 0, PB: 2} will be chosen. Properties *Properties // The capacity of the dictionary. If DictCap is zero, the value // 8 MiB will be chosen. DictCap int // Size of the lookahead buffer; value 0 indicates default size // 4096 BufSize int // Match algorithm Matcher MatchAlgorithm } // fill replaces zero values with default values. func (c *Writer2Config) fill() { if c.Properties == nil { c.Properties = &Properties{LC: 3, LP: 0, PB: 2} } if c.DictCap == 0 { c.DictCap = 8 * 1024 * 1024 } if c.BufSize == 0 { c.BufSize = 4096 } } // Verify checks the Writer2Config for correctness. Zero values will be // replaced by default values. func (c *Writer2Config) Verify() error { c.fill() var err error if c == nil { return errors.New("lzma: WriterConfig is nil") } if c.Properties == nil { return errors.New("lzma: WriterConfig has no Properties set") } if err = c.Properties.verify(); err != nil { return err } if !(MinDictCap <= c.DictCap && int64(c.DictCap) <= MaxDictCap) { return errors.New("lzma: dictionary capacity is out of range") } if !(maxMatchLen <= c.BufSize) { return errors.New("lzma: lookahead buffer size too small") } if c.Properties.LC+c.Properties.LP > 4 { return errors.New("lzma: sum of lc and lp exceeds 4") } if err = c.Matcher.verify(); err != nil { return err } return nil } // Writer2 supports the creation of an LZMA2 stream. But note that // written data is buffered, so call Flush or Close to write data to the // underlying writer. The Close method writes the end-of-stream marker // to the stream. So you may be able to concatenate the output of two // writers as long the output of the first writer has only been flushed // but not closed. // // Any change to the fields Properties, DictCap must be done before the // first call to Write, Flush or Close. type Writer2 struct { w io.Writer start *state encoder *encoder cstate chunkState ctype chunkType buf bytes.Buffer lbw LimitedByteWriter } // NewWriter2 creates an LZMA2 chunk sequence writer with the default // parameters and options. func NewWriter2(lzma2 io.Writer) (w *Writer2, err error) { return Writer2Config{}.NewWriter2(lzma2) } // NewWriter2 creates a new LZMA2 writer using the given configuration. func (c Writer2Config) NewWriter2(lzma2 io.Writer) (w *Writer2, err error) { if err = c.Verify(); err != nil { return nil, err } w = &Writer2{ w: lzma2, start: newState(*c.Properties), cstate: start, ctype: start.defaultChunkType(), } w.buf.Grow(maxCompressed) w.lbw = LimitedByteWriter{BW: &w.buf, N: maxCompressed} m, err := c.Matcher.new(c.DictCap) if err != nil { return nil, err } d, err := newEncoderDict(c.DictCap, c.BufSize, m) if err != nil { return nil, err } w.encoder, err = newEncoder(&w.lbw, cloneState(w.start), d, 0) if err != nil { return nil, err } return w, nil } // written returns the number of bytes written to the current chunk func (w *Writer2) written() int { if w.encoder == nil { return 0 } return int(w.encoder.Compressed()) + w.encoder.dict.Buffered() } // errClosed indicates that the writer is closed. var errClosed = errors.New("lzma: writer closed") // Writes data to LZMA2 stream. Note that written data will be buffered. // Use Flush or Close to ensure that data is written to the underlying // writer. func (w *Writer2) Write(p []byte) (n int, err error) { if w.cstate == stop { return 0, errClosed } for n < len(p) { m := maxUncompressed - w.written() if m <= 0 { panic("lzma: maxUncompressed reached") } var q []byte if n+m < len(p) { q = p[n : n+m] } else { q = p[n:] } k, err := w.encoder.Write(q) n += k if err != nil && err != ErrLimit { return n, err } if err == ErrLimit || k == m { if err = w.flushChunk(); err != nil { return n, err } } } return n, nil } // writeUncompressedChunk writes an uncompressed chunk to the LZMA2 // stream. func (w *Writer2) writeUncompressedChunk() error { u := w.encoder.Compressed() if u <= 0 { return errors.New("lzma: can't write empty uncompressed chunk") } if u > maxUncompressed { panic("overrun of uncompressed data limit") } switch w.ctype { case cLRND: w.ctype = cUD default: w.ctype = cU } w.encoder.state = w.start header := chunkHeader{ ctype: w.ctype, uncompressed: uint32(u - 1), } hdata, err := header.MarshalBinary() if err != nil { return err } if _, err = w.w.Write(hdata); err != nil { return err } _, err = w.encoder.dict.CopyN(w.w, int(u)) return err } // writeCompressedChunk writes a compressed chunk to the underlying // writer. func (w *Writer2) writeCompressedChunk() error { if w.ctype == cU || w.ctype == cUD { panic("chunk type uncompressed") } u := w.encoder.Compressed() if u <= 0 { return errors.New("writeCompressedChunk: empty chunk") } if u > maxUncompressed { panic("overrun of uncompressed data limit") } c := w.buf.Len() if c <= 0 { panic("no compressed data") } if c > maxCompressed { panic("overrun of compressed data limit") } header := chunkHeader{ ctype: w.ctype, uncompressed: uint32(u - 1), compressed: uint16(c - 1), props: w.encoder.state.Properties, } hdata, err := header.MarshalBinary() if err != nil { return err } if _, err = w.w.Write(hdata); err != nil { return err } _, err = io.Copy(w.w, &w.buf) return err } // writes a single chunk to the underlying writer. func (w *Writer2) writeChunk() error { u := int(uncompressedHeaderLen + w.encoder.Compressed()) c := headerLen(w.ctype) + w.buf.Len() if u < c { return w.writeUncompressedChunk() } return w.writeCompressedChunk() } // flushChunk terminates the current chunk. The encoder will be reset // to support the next chunk. func (w *Writer2) flushChunk() error { if w.written() == 0 { return nil } var err error if err = w.encoder.Close(); err != nil { return err } if err = w.writeChunk(); err != nil { return err } w.buf.Reset() w.lbw.N = maxCompressed if err = w.encoder.Reopen(&w.lbw); err != nil { return err } if err = w.cstate.next(w.ctype); err != nil { return err } w.ctype = w.cstate.defaultChunkType() w.start = cloneState(w.encoder.state) return nil } // Flush writes all buffered data out to the underlying stream. This // could result in multiple chunks to be created. func (w *Writer2) Flush() error { if w.cstate == stop { return errClosed } for w.written() > 0 { if err := w.flushChunk(); err != nil { return err } } return nil } // Close terminates the LZMA2 stream with an EOS chunk. func (w *Writer2) Close() error { if w.cstate == stop { return errClosed } if err := w.Flush(); err != nil { return nil } // write zero byte EOS chunk _, err := w.w.Write([]byte{0}) if err != nil { return err } w.cstate = stop return nil }