LZX: Perform buffering internally

This commit is contained in:
John Starks
2016-05-12 19:47:51 -07:00
parent 5e100a8bcc
commit d69c9fb04e
+68 -34
View File
@@ -6,7 +6,6 @@
package lzx package lzx
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
@@ -60,7 +59,7 @@ type Reader interface {
} }
type decompressor struct { type decompressor struct {
r Reader r io.Reader
err error err error
unaligned bool unaligned bool
nbits byte nbits byte
@@ -71,28 +70,59 @@ type decompressor struct {
mainlens [maincodecount]byte mainlens [maincodecount]byte
lenlens [lencodecount]byte lenlens [lencodecount]byte
window [windowSize]byte window [windowSize]byte
b []byte
bv int
bo int
}
//go:noinline
func (f *decompressor) fail(err error) {
if f.err == nil {
f.err = err
}
f.bo = 0
f.bv = 0
}
func (f *decompressor) ensureAtLeast(n int) error {
if f.bv-f.bo >= n {
return nil
}
if f.err != nil {
return f.err
}
if f.bv != f.bo {
copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv])
}
n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n)
if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
} else {
f.fail(err)
}
return err
}
f.bv = f.bv - f.bo + n
f.bo = 0
return nil
} }
// feed retrieves another 16-bit word from the stream and consumes // feed retrieves another 16-bit word from the stream and consumes
// it into f.c. It returns false if there are no more bytes available. // it into f.c. It returns false if there are no more bytes available.
// Otherwise, on error, it sets f.err. // Otherwise, on error, it sets f.err.
func (f *decompressor) feed() bool { func (f *decompressor) feed() bool {
if f.err != nil { err := f.ensureAtLeast(2)
return true
}
var b0, b1 byte
b0, err := f.r.ReadByte()
if err == nil {
b1, err = f.r.ReadByte()
}
if err != nil { if err != nil {
if err == io.EOF { if err == io.ErrUnexpectedEOF {
return false return false
} }
f.err = err
} }
f.c |= (uint32(b1)<<8 | uint32(b0)) << (16 - f.nbits) f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits)
f.nbits += 16 f.nbits += 16
f.bo += 2
return true return true
} }
@@ -101,7 +131,7 @@ func (f *decompressor) feed() bool {
func (f *decompressor) getBits(n byte) uint16 { func (f *decompressor) getBits(n byte) uint16 {
if f.nbits < n { if f.nbits < n {
if !f.feed() { if !f.feed() {
f.err = io.ErrUnexpectedEOF f.fail(io.ErrUnexpectedEOF)
} }
} }
c := uint16(f.c >> (32 - n)) c := uint16(f.c >> (32 - n))
@@ -175,7 +205,7 @@ func buildTable(codelens []byte) *huffman {
func (f *decompressor) getCode(h *huffman) uint16 { func (f *decompressor) getCode(h *huffman) uint16 {
if h.maxbits == 0 { if h.maxbits == 0 {
// This is an empty tree. It should not be used. // This is an empty tree. It should not be used.
f.err = errCorrupt f.fail(errCorrupt)
return 0 return 0
} }
if f.nbits < maxTreePathLen { if f.nbits < maxTreePathLen {
@@ -188,7 +218,7 @@ func (f *decompressor) getCode(h *huffman) uint16 {
c := h.table[f.c>>(32-h.maxbits)] c := h.table[f.c>>(32-h.maxbits)]
n := byte(c >> lenshift) n := byte(c >> lenshift)
if f.nbits < n { if f.nbits < n {
f.err = io.ErrUnexpectedEOF f.fail(io.ErrUnexpectedEOF)
return 0 return 0
} }
// Only consume the length of the code, not the maximum // Only consume the length of the code, not the maximum
@@ -279,13 +309,11 @@ func (f *decompressor) readBlockHeader() (byte, uint16, error) {
// If the previous block was an unaligned uncompressed block, restore // If the previous block was an unaligned uncompressed block, restore
// 2-byte alignment. // 2-byte alignment.
if f.unaligned { if f.unaligned {
_, err := f.r.ReadByte() err := f.ensureAtLeast(1)
if err != nil { if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return 0, 0, err return 0, 0, err
} }
f.bo++
f.unaligned = false f.unaligned = false
} }
@@ -321,19 +349,17 @@ func (f *decompressor) readBlockHeader() (byte, uint16, error) {
} }
f.getBits(n) f.getBits(n)
if f.err != nil {
return 0, 0, f.err
}
// Read the LRU values for the next block. // Read the LRU values for the next block.
var lru [12]byte err := f.ensureAtLeast(12)
_, err := io.ReadFull(f.r, lru[:])
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
} }
f.lru[0] = uint16(binary.LittleEndian.Uint32(lru[0:4]))
f.lru[1] = uint16(binary.LittleEndian.Uint32(lru[4:8])) f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4]))
f.lru[2] = uint16(binary.LittleEndian.Uint32(lru[8:12])) f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8]))
f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12]))
f.bo += 12
default: default:
return 0, 0, errCorrupt return 0, 0, errCorrupt
@@ -476,7 +502,18 @@ func (f *decompressor) readBlock(start uint16) (int, error) {
// Remember to realign the byte stream at the next block. // Remember to realign the byte stream at the next block.
f.unaligned = true f.unaligned = true
} }
return io.ReadFull(f.r, f.window[start:start+size]) copied := 0
if f.bo < f.bv {
copied = int(size)
s := int(start)
if copied > f.bv-f.bo {
copied = f.bv - f.bo
}
copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied])
f.bo += copied
}
n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size])
return copied + n, err
} }
hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock) hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock)
@@ -543,11 +580,8 @@ func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) {
f := &decompressor{ f := &decompressor{
lru: [3]uint16{1, 1, 1}, lru: [3]uint16{1, 1, 1},
uncompressed: uncompressedSize, uncompressed: uncompressedSize,
} b: make([]byte, 4096),
if br, ok := r.(Reader); ok { r: r,
f.r = br
} else {
f.r = bufio.NewReader(r)
} }
return f, nil return f, nil
} }