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