Source file src/compress/flate/dict_decoder.go

Documentation: compress/flate

     1  // Copyright 2016 The Go Authors. 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  
     5  package flate
     6  
     7  // dictDecoder implements the LZ77 sliding dictionary as used in decompression.
     8  // LZ77 decompresses data through sequences of two forms of commands:
     9  //
    10  //	* Literal insertions: Runs of one or more symbols are inserted into the data
    11  //	stream as is. This is accomplished through the writeByte method for a
    12  //	single symbol, or combinations of writeSlice/writeMark for multiple symbols.
    13  //	Any valid stream must start with a literal insertion if no preset dictionary
    14  //	is used.
    15  //
    16  //	* Backward copies: Runs of one or more symbols are copied from previously
    17  //	emitted data. Backward copies come as the tuple (dist, length) where dist
    18  //	determines how far back in the stream to copy from and length determines how
    19  //	many bytes to copy. Note that it is valid for the length to be greater than
    20  //	the distance. Since LZ77 uses forward copies, that situation is used to
    21  //	perform a form of run-length encoding on repeated runs of symbols.
    22  //	The writeCopy and tryWriteCopy are used to implement this command.
    23  //
    24  // For performance reasons, this implementation performs little to no sanity
    25  // checks about the arguments. As such, the invariants documented for each
    26  // method call must be respected.
    27  type dictDecoder struct {
    28  	hist []byte // Sliding window history
    29  
    30  	// Invariant: 0 <= rdPos <= wrPos <= len(hist)
    31  	wrPos int  // Current output position in buffer
    32  	rdPos int  // Have emitted hist[:rdPos] already
    33  	full  bool // Has a full window length been written yet?
    34  }
    35  
    36  // init initializes dictDecoder to have a sliding window dictionary of the given
    37  // size. If a preset dict is provided, it will initialize the dictionary with
    38  // the contents of dict.
    39  func (dd *dictDecoder) init(size int, dict []byte) {
    40  	*dd = dictDecoder{hist: dd.hist}
    41  
    42  	if cap(dd.hist) < size {
    43  		dd.hist = make([]byte, size)
    44  	}
    45  	dd.hist = dd.hist[:size]
    46  
    47  	if len(dict) > len(dd.hist) {
    48  		dict = dict[len(dict)-len(dd.hist):]
    49  	}
    50  	dd.wrPos = copy(dd.hist, dict)
    51  	if dd.wrPos == len(dd.hist) {
    52  		dd.wrPos = 0
    53  		dd.full = true
    54  	}
    55  	dd.rdPos = dd.wrPos
    56  }
    57  
    58  // histSize reports the total amount of historical data in the dictionary.
    59  func (dd *dictDecoder) histSize() int {
    60  	if dd.full {
    61  		return len(dd.hist)
    62  	}
    63  	return dd.wrPos
    64  }
    65  
    66  // availRead reports the number of bytes that can be flushed by readFlush.
    67  func (dd *dictDecoder) availRead() int {
    68  	return dd.wrPos - dd.rdPos
    69  }
    70  
    71  // availWrite reports the available amount of output buffer space.
    72  func (dd *dictDecoder) availWrite() int {
    73  	return len(dd.hist) - dd.wrPos
    74  }
    75  
    76  // writeSlice returns a slice of the available buffer to write data to.
    77  //
    78  // This invariant will be kept: len(s) <= availWrite()
    79  func (dd *dictDecoder) writeSlice() []byte {
    80  	return dd.hist[dd.wrPos:]
    81  }
    82  
    83  // writeMark advances the writer pointer by cnt.
    84  //
    85  // This invariant must be kept: 0 <= cnt <= availWrite()
    86  func (dd *dictDecoder) writeMark(cnt int) {
    87  	dd.wrPos += cnt
    88  }
    89  
    90  // writeByte writes a single byte to the dictionary.
    91  //
    92  // This invariant must be kept: 0 < availWrite()
    93  func (dd *dictDecoder) writeByte(c byte) {
    94  	dd.hist[dd.wrPos] = c
    95  	dd.wrPos++
    96  }
    97  
    98  // writeCopy copies a string at a given (dist, length) to the output.
    99  // This returns the number of bytes copied and may be less than the requested
   100  // length if the available space in the output buffer is too small.
   101  //
   102  // This invariant must be kept: 0 < dist <= histSize()
   103  func (dd *dictDecoder) writeCopy(dist, length int) int {
   104  	dstBase := dd.wrPos
   105  	dstPos := dstBase
   106  	srcPos := dstPos - dist
   107  	endPos := dstPos + length
   108  	if endPos > len(dd.hist) {
   109  		endPos = len(dd.hist)
   110  	}
   111  
   112  	// Copy non-overlapping section after destination position.
   113  	//
   114  	// This section is non-overlapping in that the copy length for this section
   115  	// is always less than or equal to the backwards distance. This can occur
   116  	// if a distance refers to data that wraps-around in the buffer.
   117  	// Thus, a backwards copy is performed here; that is, the exact bytes in
   118  	// the source prior to the copy is placed in the destination.
   119  	if srcPos < 0 {
   120  		srcPos += len(dd.hist)
   121  		dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:])
   122  		srcPos = 0
   123  	}
   124  
   125  	// Copy possibly overlapping section before destination position.
   126  	//
   127  	// This section can overlap if the copy length for this section is larger
   128  	// than the backwards distance. This is allowed by LZ77 so that repeated
   129  	// strings can be succinctly represented using (dist, length) pairs.
   130  	// Thus, a forwards copy is performed here; that is, the bytes copied is
   131  	// possibly dependent on the resulting bytes in the destination as the copy
   132  	// progresses along. This is functionally equivalent to the following:
   133  	//
   134  	//	for i := 0; i < endPos-dstPos; i++ {
   135  	//		dd.hist[dstPos+i] = dd.hist[srcPos+i]
   136  	//	}
   137  	//	dstPos = endPos
   138  	//
   139  	for dstPos < endPos {
   140  		dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
   141  	}
   142  
   143  	dd.wrPos = dstPos
   144  	return dstPos - dstBase
   145  }
   146  
   147  // tryWriteCopy tries to copy a string at a given (distance, length) to the
   148  // output. This specialized version is optimized for short distances.
   149  //
   150  // This method is designed to be inlined for performance reasons.
   151  //
   152  // This invariant must be kept: 0 < dist <= histSize()
   153  func (dd *dictDecoder) tryWriteCopy(dist, length int) int {
   154  	dstPos := dd.wrPos
   155  	endPos := dstPos + length
   156  	if dstPos < dist || endPos > len(dd.hist) {
   157  		return 0
   158  	}
   159  	dstBase := dstPos
   160  	srcPos := dstPos - dist
   161  
   162  	// Copy possibly overlapping section before destination position.
   163  loop:
   164  	dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos])
   165  	if dstPos < endPos {
   166  		goto loop // Avoid for-loop so that this function can be inlined
   167  	}
   168  
   169  	dd.wrPos = dstPos
   170  	return dstPos - dstBase
   171  }
   172  
   173  // readFlush returns a slice of the historical buffer that is ready to be
   174  // emitted to the user. The data returned by readFlush must be fully consumed
   175  // before calling any other dictDecoder methods.
   176  func (dd *dictDecoder) readFlush() []byte {
   177  	toRead := dd.hist[dd.rdPos:dd.wrPos]
   178  	dd.rdPos = dd.wrPos
   179  	if dd.wrPos == len(dd.hist) {
   180  		dd.wrPos, dd.rdPos = 0, 0
   181  		dd.full = true
   182  	}
   183  	return toRead
   184  }
   185  

View as plain text