Update dependencies and replace uuid library (#100)

This commit is contained in:
Joona Hoikkala
2018-08-10 16:51:32 +03:00
committed by GitHub
parent 8aa869b2f8
commit 75d4a30c1f
1771 changed files with 103824 additions and 39288 deletions
+1
View File
@@ -0,0 +1 @@
/huff0-fuzz.zip
+115
View File
@@ -0,0 +1,115 @@
// Copyright 2018 Klaus Post. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on work Copyright (c) 2013, Yann Collet, released under BSD License.
package huff0
import (
"errors"
"io"
)
// bitReader reads a bitstream in reverse.
// The last set bit indicates the start of the stream and is used
// for aligning the input.
type bitReader struct {
in []byte
off uint // next byte to read is at in[off - 1]
value uint64
bitsRead uint8
}
// init initializes and resets the bit reader.
func (b *bitReader) init(in []byte) error {
if len(in) < 1 {
return errors.New("corrupt stream: too short")
}
b.in = in
b.off = uint(len(in))
// The highest bit of the last byte indicates where to start
v := in[len(in)-1]
if v == 0 {
return errors.New("corrupt stream, did not find end of stream")
}
b.bitsRead = 64
b.value = 0
b.fill()
b.fill()
b.bitsRead += 8 - uint8(highBit32(uint32(v)))
return nil
}
// getBits will return n bits. n can be 0.
func (b *bitReader) getBits(n uint8) uint16 {
if n == 0 || b.bitsRead >= 64 {
return 0
}
return b.getBitsFast(n)
}
// getBitsFast requires that at least one bit is requested every time.
// There are no checks if the buffer is filled.
func (b *bitReader) getBitsFast(n uint8) uint16 {
const regMask = 64 - 1
v := uint16((b.value << (b.bitsRead & regMask)) >> ((regMask + 1 - n) & regMask))
b.bitsRead += n
return v
}
// peekBitsFast requires that at least one bit is requested every time.
// There are no checks if the buffer is filled.
func (b *bitReader) peekBitsFast(n uint8) uint16 {
const regMask = 64 - 1
v := uint16((b.value << (b.bitsRead & regMask)) >> ((regMask + 1 - n) & regMask))
return v
}
// fillFast() will make sure at least 32 bits are available.
// There must be at least 4 bytes available.
func (b *bitReader) fillFast() {
if b.bitsRead < 32 {
return
}
// Do single re-slice to avoid bounds checks.
v := b.in[b.off-4 : b.off]
low := (uint32(v[0])) | (uint32(v[1]) << 8) | (uint32(v[2]) << 16) | (uint32(v[3]) << 24)
b.value = (b.value << 32) | uint64(low)
b.bitsRead -= 32
b.off -= 4
}
// fill() will make sure at least 32 bits are available.
func (b *bitReader) fill() {
if b.bitsRead < 32 {
return
}
if b.off > 4 {
v := b.in[b.off-4 : b.off]
low := (uint32(v[0])) | (uint32(v[1]) << 8) | (uint32(v[2]) << 16) | (uint32(v[3]) << 24)
b.value = (b.value << 32) | uint64(low)
b.bitsRead -= 32
b.off -= 4
return
}
for b.off > 0 {
b.value = (b.value << 8) | uint64(b.in[b.off-1])
b.bitsRead -= 8
b.off--
}
}
// finished returns true if all bits have been read from the bit stream.
func (b *bitReader) finished() bool {
return b.off == 0 && b.bitsRead >= 64
}
// close the bitstream and returns an error if out-of-buffer reads occurred.
func (b *bitReader) close() error {
// Release reference.
b.in = nil
if b.bitsRead > 64 {
return io.ErrUnexpectedEOF
}
return nil
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright 2018 Klaus Post. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on work Copyright (c) 2013, Yann Collet, released under BSD License.
package huff0
import "fmt"
// bitWriter will write bits.
// First bit will be LSB of the first byte of output.
type bitWriter struct {
bitContainer uint64
nBits uint8
out []byte
}
// bitMask16 is bitmasks. Has extra to avoid bounds check.
var bitMask16 = [32]uint16{
0, 1, 3, 7, 0xF, 0x1F,
0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF,
0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF} /* up to 16 bits */
// addBits16NC will add up to 16 bits.
// It will not check if there is space for them,
// so the caller must ensure that it has flushed recently.
func (b *bitWriter) addBits16NC(value uint16, bits uint8) {
b.bitContainer |= uint64(value&bitMask16[bits&31]) << (b.nBits & 63)
b.nBits += bits
}
// addBits16Clean will add up to 16 bits. value may not contain more set bits than indicated.
// It will not check if there is space for them, so the caller must ensure that it has flushed recently.
func (b *bitWriter) addBits16Clean(value uint16, bits uint8) {
b.bitContainer |= uint64(value) << (b.nBits & 63)
b.nBits += bits
}
// addBits16Clean will add up to 16 bits. value may not contain more set bits than indicated.
// It will not check if there is space for them, so the caller must ensure that it has flushed recently.
func (b *bitWriter) encSymbol(ct cTable, symbol byte) {
enc := ct[symbol]
b.bitContainer |= uint64(enc.val) << (b.nBits & 63)
b.nBits += enc.nBits
}
// addBits16ZeroNC will add up to 16 bits.
// It will not check if there is space for them,
// so the caller must ensure that it has flushed recently.
// This is fastest if bits can be zero.
func (b *bitWriter) addBits16ZeroNC(value uint16, bits uint8) {
if bits == 0 {
return
}
value <<= (16 - bits) & 15
value >>= (16 - bits) & 15
b.bitContainer |= uint64(value) << (b.nBits & 63)
b.nBits += bits
}
// flush will flush all pending full bytes.
// There will be at least 56 bits available for writing when this has been called.
// Using flush32 is faster, but leaves less space for writing.
func (b *bitWriter) flush() {
v := b.nBits >> 3
switch v {
case 0:
case 1:
b.out = append(b.out,
byte(b.bitContainer),
)
case 2:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
)
case 3:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
)
case 4:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24),
)
case 5:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24),
byte(b.bitContainer>>32),
)
case 6:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24),
byte(b.bitContainer>>32),
byte(b.bitContainer>>40),
)
case 7:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24),
byte(b.bitContainer>>32),
byte(b.bitContainer>>40),
byte(b.bitContainer>>48),
)
case 8:
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24),
byte(b.bitContainer>>32),
byte(b.bitContainer>>40),
byte(b.bitContainer>>48),
byte(b.bitContainer>>56),
)
default:
panic(fmt.Errorf("bits (%d) > 64", b.nBits))
}
b.bitContainer >>= v << 3
b.nBits &= 7
}
// flush32 will flush out, so there are at least 32 bits available for writing.
func (b *bitWriter) flush32() {
if b.nBits < 32 {
return
}
b.out = append(b.out,
byte(b.bitContainer),
byte(b.bitContainer>>8),
byte(b.bitContainer>>16),
byte(b.bitContainer>>24))
b.nBits -= 32
b.bitContainer >>= 32
}
// flushAlign will flush remaining full bytes and align to next byte boundary.
func (b *bitWriter) flushAlign() {
nbBytes := (b.nBits + 7) >> 3
for i := uint8(0); i < nbBytes; i++ {
b.out = append(b.out, byte(b.bitContainer>>(i*8)))
}
b.nBits = 0
b.bitContainer = 0
}
// close will write the alignment bit and write the final byte(s)
// to the output.
func (b *bitWriter) close() error {
// End mark
b.addBits16Clean(1, 1)
// flush until next byte.
b.flushAlign()
return nil
}
// reset and continue writing by appending to out.
func (b *bitWriter) reset(out []byte) {
b.bitContainer = 0
b.nBits = 0
b.out = out
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2018 Klaus Post. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on work Copyright (c) 2013, Yann Collet, released under BSD License.
package huff0
// byteReader provides a byte reader that reads
// little endian values from a byte stream.
// The input stream is manually advanced.
// The reader performs no bounds checks.
type byteReader struct {
b []byte
off int
}
// init will initialize the reader and set the input.
func (b *byteReader) init(in []byte) {
b.b = in
b.off = 0
}
// advance the stream b n bytes.
func (b *byteReader) advance(n uint) {
b.off += int(n)
}
// Int32 returns a little endian int32 starting at current offset.
func (b byteReader) Int32() int32 {
v3 := int32(b.b[b.off+3])
v2 := int32(b.b[b.off+2])
v1 := int32(b.b[b.off+1])
v0 := int32(b.b[b.off])
return (v3 << 24) | (v2 << 16) | (v1 << 8) | v0
}
// Uint32 returns a little endian uint32 starting at current offset.
func (b byteReader) Uint32() uint32 {
v3 := uint32(b.b[b.off+3])
v2 := uint32(b.b[b.off+2])
v1 := uint32(b.b[b.off+1])
v0 := uint32(b.b[b.off])
return (v3 << 24) | (v2 << 16) | (v1 << 8) | v0
}
// unread returns the unread portion of the input.
func (b byteReader) unread() []byte {
return b.b[b.off:]
}
// remain will return the number of bytes remaining.
func (b byteReader) remain() int {
return len(b.b) - b.off
}
+600
View File
@@ -0,0 +1,600 @@
package huff0
import (
"fmt"
"runtime"
"sync"
)
func Compress1X(in []byte, s *Scratch) (out []byte, reUsed bool, err error) {
s, err = s.prepare(in)
if err != nil {
return nil, false, err
}
return compress(in, s, s.compress1X)
}
func Compress4X(in []byte, s *Scratch) (out []byte, reUsed bool, err error) {
s, err = s.prepare(in)
if err != nil {
return nil, false, err
}
if false {
// TODO: compress4Xp only slightly faster.
const parallelThreshold = 8 << 10
if len(in) < parallelThreshold || runtime.GOMAXPROCS(0) == 1 {
return compress(in, s, s.compress4X)
}
return compress(in, s, s.compress4Xp)
}
return compress(in, s, s.compress4X)
}
func compress(in []byte, s *Scratch, compressor func(src []byte) ([]byte, error)) (out []byte, reUsed bool, err error) {
// Nuke previous table if we cannot reuse anyway.
if s.Reuse == ReusePolicyNone {
s.prevTable = s.prevTable[:0]
}
// Create histogram, if none was provided.
maxCount := s.maxCount
var canReuse = false
if maxCount == 0 {
maxCount, canReuse = s.countSimple(in)
} else {
canReuse = s.canUseTable(s.prevTable)
}
// Reset for next run.
s.clearCount = true
s.maxCount = 0
if maxCount >= len(in) {
if maxCount > len(in) {
return nil, false, fmt.Errorf("maxCount (%d) > length (%d)", maxCount, len(in))
}
// One symbol, use RLE
return nil, false, ErrUseRLE
}
if maxCount == 1 || maxCount < (len(in)>>7) {
// Each symbol present maximum once or too well distributed.
return nil, false, ErrIncompressible
}
if s.Reuse == ReusePolicyPrefer && canReuse {
keepTable := s.cTable
s.cTable = s.prevTable
s.Out, err = compressor(in)
s.cTable = keepTable
if err == nil && len(s.Out) < len(in) {
s.OutData = s.Out
return s.Out, true, nil
}
// Do not attempt to re-use later.
s.prevTable = s.prevTable[:0]
}
// Calculate new table.
s.optimalTableLog()
err = s.buildCTable()
if err != nil {
return nil, false, err
}
if !s.canUseTable(s.cTable) {
// TODO: Not needed.
panic("invalid table generated")
}
if s.Reuse == ReusePolicyAllow && canReuse {
hSize := len(s.Out)
oldSize := s.prevTable.estimateSize(s.count[:s.symbolLen])
newSize := s.cTable.estimateSize(s.count[:s.symbolLen])
if oldSize <= hSize+newSize || hSize+12 >= len(in) {
// Retain cTable even if we re-use.
keepTable := s.cTable
s.cTable = s.prevTable
s.Out, err = compressor(in)
s.cTable = keepTable
if len(s.Out) >= len(in) {
return nil, false, ErrIncompressible
}
s.OutData = s.Out
return s.Out, true, nil
}
}
// Use new table
s.cTable.write(s)
s.OutTable = s.Out
// Compress using new table
s.Out, err = compressor(in)
if err != nil {
s.OutTable = nil
return nil, false, err
}
if len(s.Out) >= len(in) {
s.OutTable = nil
return nil, false, ErrIncompressible
}
// Move current table into previous.
s.prevTable, s.cTable = s.cTable, s.prevTable[:0]
s.OutData = s.Out[len(s.OutTable):]
return s.Out, false, nil
}
func (s *Scratch) compress1X(src []byte) ([]byte, error) {
return s.compress1xDo(s.Out, src)
}
func (s *Scratch) compress1xDo(dst, src []byte) ([]byte, error) {
var bw = bitWriter{out: dst}
// N is length divisible by 4.
n := len(src)
n -= n & 3
cTable := s.cTable[:256]
// Encode last bytes.
for i := len(src) & 3; i > 0; i-- {
bw.encSymbol(cTable, src[n+i-1])
}
if s.actualTableLog <= 8 {
n -= 4
for ; n >= 0; n -= 4 {
tmp := src[n : n+4]
// tmp should be len 4
bw.flush32()
bw.encSymbol(cTable, tmp[3])
bw.encSymbol(cTable, tmp[2])
bw.encSymbol(cTable, tmp[1])
bw.encSymbol(cTable, tmp[0])
}
} else {
n -= 4
for ; n >= 0; n -= 4 {
tmp := src[n : n+4]
// tmp should be len 4
bw.flush32()
bw.encSymbol(cTable, tmp[3])
bw.encSymbol(cTable, tmp[2])
bw.flush32()
bw.encSymbol(cTable, tmp[1])
bw.encSymbol(cTable, tmp[0])
}
}
err := bw.close()
return bw.out, err
}
var sixZeros [6]byte
func (s *Scratch) compress4X(src []byte) ([]byte, error) {
if len(src) < 12 {
return nil, ErrIncompressible
}
segmentSize := (len(src) + 3) / 4
// Add placeholder for output length
offsetIdx := len(s.Out)
s.Out = append(s.Out, sixZeros[:]...)
for i := 0; i < 4; i++ {
toDo := src
if len(toDo) > segmentSize {
toDo = toDo[:segmentSize]
}
src = src[len(toDo):]
var err error
idx := len(s.Out)
s.Out, err = s.compress1xDo(s.Out, toDo)
if err != nil {
return nil, err
}
// Write compressed length as little endian before block.
if i < 3 {
// Last length is not written.
length := len(s.Out) - idx
s.Out[i*2+offsetIdx] = byte(length)
s.Out[i*2+offsetIdx+1] = byte(length >> 8)
}
}
return s.Out, nil
}
// compress4Xp will compress 4 streams using separate goroutines.
func (s *Scratch) compress4Xp(src []byte) ([]byte, error) {
if len(src) < 12 {
return nil, ErrIncompressible
}
// Add placeholder for output length
s.Out = s.Out[:6]
segmentSize := (len(src) + 3) / 4
var wg sync.WaitGroup
var errs [4]error
wg.Add(4)
for i := 0; i < 4; i++ {
toDo := src
if len(toDo) > segmentSize {
toDo = toDo[:segmentSize]
}
src = src[len(toDo):]
// Separate goroutine for each block.
go func(i int) {
s.tmpOut[i], errs[i] = s.compress1xDo(s.tmpOut[i][:0], toDo)
wg.Done()
}(i)
}
wg.Wait()
for i := 0; i < 4; i++ {
if errs[i] != nil {
return nil, errs[i]
}
o := s.tmpOut[i]
// Write compressed length as little endian before block.
if i < 3 {
// Last length is not written.
s.Out[i*2] = byte(len(o))
s.Out[i*2+1] = byte(len(o) >> 8)
}
// Write output.
s.Out = append(s.Out, o...)
}
return s.Out, nil
}
// countSimple will create a simple histogram in s.count.
// Returns the biggest count.
// Does not update s.clearCount.
func (s *Scratch) countSimple(in []byte) (max int, reuse bool) {
reuse = true
for _, v := range in {
s.count[v]++
}
m := uint32(0)
if len(s.prevTable) > 0 {
for i, v := range s.count[:] {
if v > m {
m = v
}
if v > 0 {
s.symbolLen = uint16(i) + 1
if i >= len(s.prevTable) {
reuse = false
} else {
if s.prevTable[i].nBits == 0 {
reuse = false
}
}
}
}
return int(m), reuse
}
for i, v := range s.count[:] {
if v > m {
m = v
}
if v > 0 {
s.symbolLen = uint16(i) + 1
}
}
return int(m), false
}
func (s *Scratch) canUseTable(c cTable) bool {
if len(c) < int(s.symbolLen) {
return false
}
for i, v := range s.count[:s.symbolLen] {
if v != 0 && c[i].nBits == 0 {
return false
}
}
return true
}
// minTableLog provides the minimum logSize to safely represent a distribution.
func (s *Scratch) minTableLog() uint8 {
minBitsSrc := highBit32(uint32(s.br.remain()-1)) + 1
minBitsSymbols := highBit32(uint32(s.symbolLen-1)) + 2
if minBitsSrc < minBitsSymbols {
return uint8(minBitsSrc)
}
return uint8(minBitsSymbols)
}
// optimalTableLog calculates and sets the optimal tableLog in s.actualTableLog
func (s *Scratch) optimalTableLog() {
tableLog := s.TableLog
minBits := s.minTableLog()
maxBitsSrc := uint8(highBit32(uint32(s.br.remain()-1))) - 2
if maxBitsSrc < tableLog {
// Accuracy can be reduced
tableLog = maxBitsSrc
}
if minBits > tableLog {
tableLog = minBits
}
// Need a minimum to safely represent all symbol values
if tableLog < minTablelog {
tableLog = minTablelog
}
if tableLog > tableLogMax {
tableLog = tableLogMax
}
s.actualTableLog = tableLog
}
type cTableEntry struct {
val uint16
nBits uint8
// We have 8 bits extra
}
const huffNodesMask = huffNodesLen - 1
func (s *Scratch) buildCTable() error {
if cap(s.cTable) < maxSymbolValue+1 {
s.cTable = make([]cTableEntry, 0, maxSymbolValue+1)
}
s.huffSort()
s.cTable = s.cTable[:s.symbolLen]
var startNode = int16(s.symbolLen)
nonNullRank := s.symbolLen - 1
nodeNb := int16(startNode)
huffNode := s.nodes[1 : huffNodesLen+1]
// This overlays the slice above, but allows "-1" index lookups.
// Different from reference implementation.
huffNode0 := s.nodes[0 : huffNodesLen+1]
for huffNode[nonNullRank].count == 0 {
nonNullRank--
}
lowS := int16(nonNullRank)
nodeRoot := nodeNb + lowS - 1
lowN := nodeNb
huffNode[nodeNb].count = huffNode[lowS].count + huffNode[lowS-1].count
huffNode[lowS].parent, huffNode[lowS-1].parent = uint16(nodeNb), uint16(nodeNb)
nodeNb++
lowS -= 2
for n := nodeNb; n <= nodeRoot; n++ {
huffNode[n].count = 1 << 30
}
// fake entry, strong barrier
huffNode0[0].count = 1 << 31
// create parents
for nodeNb <= nodeRoot {
var n1, n2 int16
if huffNode0[lowS+1].count < huffNode0[lowN+1].count {
n1 = lowS
lowS--
} else {
n1 = lowN
lowN++
}
if huffNode0[lowS+1].count < huffNode0[lowN+1].count {
n2 = lowS
lowS--
} else {
n2 = lowN
lowN++
}
huffNode[nodeNb].count = huffNode0[n1+1].count + huffNode0[n2+1].count
huffNode0[n1+1].parent, huffNode0[n2+1].parent = uint16(nodeNb), uint16(nodeNb)
nodeNb++
}
// distribute weights (unlimited tree height)
huffNode[nodeRoot].nbBits = 0
for n := nodeRoot - 1; n >= startNode; n-- {
huffNode[n].nbBits = huffNode[huffNode[n].parent].nbBits + 1
}
for n := uint16(0); n <= nonNullRank; n++ {
huffNode[n].nbBits = huffNode[huffNode[n].parent].nbBits + 1
}
s.actualTableLog = s.setMaxHeight(int(nonNullRank))
maxNbBits := s.actualTableLog
// fill result into tree (val, nbBits)
if maxNbBits > tableLogMax {
return fmt.Errorf("internal error: maxNbBits (%d) > tableLogMax (%d)", maxNbBits, tableLogMax)
}
var nbPerRank [tableLogMax + 1]uint16
var valPerRank [tableLogMax + 1]uint16
for _, v := range huffNode[:nonNullRank+1] {
nbPerRank[v.nbBits]++
}
// determine stating value per rank
{
min := uint16(0)
for n := maxNbBits; n > 0; n-- {
// get starting value within each rank
valPerRank[n] = min
min += nbPerRank[n]
min >>= 1
}
}
// push nbBits per symbol, symbol order
// TODO: changed `s.symbolLen` -> `nonNullRank+1` (micro-opt)
for _, v := range huffNode[:nonNullRank+1] {
s.cTable[v.symbol].nBits = v.nbBits
}
// assign value within rank, symbol order
for n, val := range s.cTable[:s.symbolLen] {
v := valPerRank[val.nBits]
s.cTable[n].val = v
valPerRank[val.nBits] = v + 1
}
return nil
}
// huffSort will sort symbols, decreasing order.
func (s *Scratch) huffSort() {
type rankPos struct {
base uint32
current uint32
}
// Clear nodes
nodes := s.nodes[:huffNodesLen+1]
s.nodes = nodes
nodes = nodes[1 : huffNodesLen+1]
// Sort into buckets based on length of symbol count.
var rank [32]rankPos
for _, v := range s.count[:s.symbolLen] {
r := highBit32(v+1) & 31
rank[r].base++
}
for n := 30; n > 0; n-- {
rank[n-1].base += rank[n].base
}
for n := range rank[:] {
rank[n].current = rank[n].base
}
for n, c := range s.count[:s.symbolLen] {
r := (highBit32(c+1) + 1) & 31
pos := rank[r].current
rank[r].current++
prev := nodes[(pos-1)&huffNodesMask]
for pos > rank[r].base && c > prev.count {
nodes[pos&huffNodesMask] = prev
pos--
prev = nodes[(pos-1)&huffNodesMask]
}
nodes[pos&huffNodesMask] = nodeElt{count: c, symbol: byte(n)}
}
return
}
func (s *Scratch) setMaxHeight(lastNonNull int) uint8 {
maxNbBits := s.TableLog
huffNode := s.nodes[1 : huffNodesLen+1]
//huffNode = huffNode[: huffNodesLen]
largestBits := huffNode[lastNonNull].nbBits
// early exit : no elt > maxNbBits
if largestBits <= maxNbBits {
return largestBits
}
totalCost := int(0)
baseCost := int(1) << (largestBits - maxNbBits)
n := uint32(lastNonNull)
for huffNode[n].nbBits > maxNbBits {
totalCost += baseCost - (1 << (largestBits - huffNode[n].nbBits))
huffNode[n].nbBits = maxNbBits
n--
}
// n stops at huffNode[n].nbBits <= maxNbBits
for huffNode[n].nbBits == maxNbBits {
n--
}
// n end at index of smallest symbol using < maxNbBits
// renorm totalCost
totalCost >>= largestBits - maxNbBits /* note : totalCost is necessarily a multiple of baseCost */
/* repay normalized cost */
{
const noSymbol = 0xF0F0F0F0
var rankLast [tableLogMax + 2]uint32
for i := range rankLast[:] {
rankLast[i] = noSymbol
}
// Get pos of last (smallest) symbol per rank
{
currentNbBits := uint8(maxNbBits)
for pos := int(n); pos >= 0; pos-- {
if huffNode[pos].nbBits >= currentNbBits {
continue
}
currentNbBits = huffNode[pos].nbBits /* < maxNbBits */
rankLast[maxNbBits-currentNbBits] = uint32(pos)
}
}
for totalCost > 0 {
nBitsToDecrease := uint8(highBit32(uint32(totalCost))) + 1
for ; nBitsToDecrease > 1; nBitsToDecrease-- {
highPos := rankLast[nBitsToDecrease]
lowPos := rankLast[nBitsToDecrease-1]
if highPos == noSymbol {
continue
}
if lowPos == noSymbol {
break
}
highTotal := huffNode[highPos].count
lowTotal := 2 * huffNode[lowPos].count
if highTotal <= lowTotal {
break
}
}
// only triggered when no more rank 1 symbol left => find closest one (note : there is necessarily at least one !)
// HUF_MAX_TABLELOG test just to please gcc 5+; but it should not be necessary
// FIXME: try to remove
for (nBitsToDecrease <= tableLogMax) && (rankLast[nBitsToDecrease] == noSymbol) {
nBitsToDecrease++
}
totalCost -= 1 << (nBitsToDecrease - 1)
if rankLast[nBitsToDecrease-1] == noSymbol {
// this rank is no longer empty
rankLast[nBitsToDecrease-1] = rankLast[nBitsToDecrease]
}
huffNode[rankLast[nBitsToDecrease]].nbBits++
if rankLast[nBitsToDecrease] == 0 {
/* special case, reached largest symbol */
rankLast[nBitsToDecrease] = noSymbol
} else {
rankLast[nBitsToDecrease]--
if huffNode[rankLast[nBitsToDecrease]].nbBits != maxNbBits-nBitsToDecrease {
rankLast[nBitsToDecrease] = noSymbol /* this rank is now empty */
}
}
}
for totalCost < 0 { /* Sometimes, cost correction overshoot */
if rankLast[1] == noSymbol { /* special case : no rank 1 symbol (using maxNbBits-1); let's create one from largest rank 0 (using maxNbBits) */
for huffNode[n].nbBits == maxNbBits {
n--
}
huffNode[n+1].nbBits--
rankLast[1] = n + 1
totalCost++
continue
}
huffNode[rankLast[1]+1].nbBits--
rankLast[1]++
totalCost++
}
}
return maxNbBits
}
type nodeElt struct {
count uint32
parent uint16
symbol byte
nbBits uint8
}
+437
View File
@@ -0,0 +1,437 @@
package huff0
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/klauspost/compress/flate"
)
type inputFn func() ([]byte, error)
var testfiles = []struct {
name string
fn inputFn
err1X error
err4X error
}{
// Digits is the digits of the irrational number e. Its decimal representation
// does not repeat, but there are only 10 possible digits, so it should be
// reasonably compressible.
{name: "digits", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/e.txt") }},
// gettysburg.txt is a small plain text.
{name: "gettysburg", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/gettysburg.txt") }},
// Twain is Project Gutenberg's edition of Mark Twain's classic English novel.
{name: "twain", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/Mark.Twain-Tom.Sawyer.txt") }},
// Random bytes
{name: "random", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/sharnd.out") }, err1X: ErrIncompressible, err4X: ErrIncompressible},
// Low entropy
{name: "low-ent.10k", fn: func() ([]byte, error) { return []byte(strings.Repeat("1221", 10000)), nil }},
// Super Low entropy
{name: "superlow-ent-10k", fn: func() ([]byte, error) { return []byte(strings.Repeat("1", 10000) + strings.Repeat("2", 500)), nil }},
// Zero bytes
{name: "zeroes", fn: func() ([]byte, error) { return make([]byte, 10000), nil }, err1X: ErrUseRLE, err4X: ErrUseRLE},
{name: "crash1", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/crash1.bin") }, err1X: ErrIncompressible, err4X: ErrIncompressible},
{name: "crash2", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/crash2.bin") }, err4X: ErrIncompressible},
{name: "crash3", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/crash3.bin") }, err1X: ErrIncompressible, err4X: ErrIncompressible},
{name: "endzerobits", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/endzerobits.bin") }, err1X: nil, err4X: ErrIncompressible},
{name: "endnonzero", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/endnonzero.bin") }, err4X: ErrIncompressible},
{name: "case1", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/case1.bin") }, err1X: nil},
{name: "case2", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/case2.bin") }, err1X: nil},
{name: "case3", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/case3.bin") }, err1X: nil},
{name: "pngdata.001", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/pngdata.bin") }, err1X: nil},
{name: "normcount2", fn: func() ([]byte, error) { return ioutil.ReadFile("../testdata/normcount2.bin") }, err1X: nil},
}
type fuzzInput struct {
name string
fn inputFn
}
var testfilesExtended []fuzzInput
func init() {
filepath.Walk("./fuzz/compress/corpus", func(path string, info os.FileInfo, err error) error {
if info.Size() == 0 || info.IsDir() {
return nil
}
testfilesExtended = append(testfilesExtended, fuzzInput{
name: filepath.Base(path),
fn: func() ([]byte, error) {
return ioutil.ReadFile(path)
},
})
return nil
})
}
func TestCompress1X(t *testing.T) {
for _, test := range testfiles {
t.Run(test.name, func(t *testing.T) {
var s Scratch
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress1X(buf0, &s)
if err != test.err1X {
t.Errorf("want error %v (%T), got %v (%T)", test.err1X, test.err1X, err, err)
}
if err != nil {
t.Log(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
min := s.minSize(len(buf0))
if len(s.OutData) < min {
t.Errorf("output data length (%d) below shannon limit (%d)", len(s.OutData), min)
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
t.Logf("%s: %d -> %d bytes (%.2f:1) re:%t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
s.Out = nil
bRe, _, err := Compress1X(b, &s)
if err == nil {
t.Log("Could re-compress to", len(bRe))
}
})
}
}
func TestCompress4X(t *testing.T) {
for _, test := range testfiles {
t.Run(test.name, func(t *testing.T) {
var s Scratch
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress4X(buf0, &s)
if err != test.err4X {
t.Errorf("want error %v (%T), got %v (%T)", test.err1X, test.err4X, err, err)
}
if err != nil {
t.Log(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
t.Logf("%s: %d -> %d bytes (%.2f:1) %t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
})
}
}
func TestCompress1XReuse(t *testing.T) {
for _, test := range testfiles {
t.Run(test.name, func(t *testing.T) {
var s Scratch
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress1X(buf0, &s)
if err != test.err1X {
t.Errorf("want error %v (%T), got %v (%T)", test.err1X, test.err1X, err, err)
}
if err != nil {
t.Log(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
firstData := len(s.OutData)
s.Reuse = ReusePolicyAllow
b, re, err = Compress1X(buf0, &s)
if err != nil {
t.Errorf("got secondary error %v (%T)", err, err)
return
}
if !re {
t.Error("Didn't re-use even if data was the same")
}
if len(s.OutTable) != 0 {
t.Error("got table definition, don't want any")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
if len(b) != firstData {
t.Errorf("data length did not match first: %d, second:%d", firstData, len(b))
}
t.Logf("%s: %d -> %d bytes (%.2f:1) %t", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re)
})
}
}
func BenchmarkDeflate(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
dec, err := flate.NewWriter(ioutil.Discard, flate.HuffmanOnly)
if err != nil {
b.Fatal(err)
}
if test.err1X != nil {
b.Skip("skipping")
}
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
dec.Reset(ioutil.Discard)
n, err := dec.Write(buf0)
if err != nil {
b.Fatal(err)
}
if n != len(buf0) {
b.Fatal("mismatch", n, len(buf0))
}
dec.Close()
}
})
}
}
func BenchmarkCompress1XReuseNone(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyNone
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress1X(buf0, &s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress1X(buf0, &s)
if re {
b.Fatal("reused")
}
}
})
}
}
func BenchmarkCompress1XReuseAllow(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyAllow
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress1X(buf0, &s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress1X(buf0, &s)
if !re {
b.Fatal("not reused")
}
}
})
}
}
func BenchmarkCompress1XReusePrefer(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyPrefer
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress1X(buf0, &s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress1X(buf0, &s)
if !re {
b.Fatal("not reused")
}
}
})
}
}
func BenchmarkCompress4XReuseNone(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err4X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyNone
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress4X(buf0, &s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress4X(buf0, &s)
if re {
b.Fatal("reused")
}
}
})
}
}
func BenchmarkCompress4XReuseAllow(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err4X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyAllow
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress4X(buf0, &s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress4X(buf0, &s)
if !re {
b.Fatal("not reused")
}
}
})
}
}
func BenchmarkCompress4XReusePrefer(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err4X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s Scratch
s.Reuse = ReusePolicyPrefer
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
_, re, err := Compress4X(buf0, &s)
if err != test.err4X {
b.Fatal("unexpected error:", err)
}
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, re, _ = Compress4X(buf0, &s)
if !re {
b.Fatal("not reused")
}
}
})
}
}
+392
View File
@@ -0,0 +1,392 @@
package huff0
import (
"errors"
"fmt"
"io"
"github.com/klauspost/compress/fse"
)
type dTable struct {
single []dEntrySingle
double []dEntryDouble
}
// single-symbols decoding
type dEntrySingle struct {
byte uint8
nBits uint8
}
// double-symbols decoding
type dEntryDouble struct {
seq uint16
nBits uint8
len uint8
}
// ReadTable will read a table from the input.
// The size of the input may be larger than the table definition.
// Any content remaining after the table definition will be returned.
// if no Scratch is provided a new one is allocated.
// The returned Scratch can be used for decoding input using this table.
func ReadTable(in []byte, s *Scratch) (s2 *Scratch, remain []byte, err error) {
s, err = s.prepare(in)
if err != nil {
return s, nil, err
}
if len(in) <= 1 {
return s, nil, errors.New("input too small for table")
}
iSize := in[0]
in = in[1:]
if iSize >= 128 {
// Uncompressed
oSize := iSize - 127
iSize = (oSize + 1) / 2
if int(iSize) > len(in) {
return s, nil, errors.New("input too small for table")
}
for n := uint8(0); n < oSize; n += 2 {
v := in[n/2]
s.huffWeight[n] = v >> 4
s.huffWeight[n+1] = v & 15
}
s.symbolLen = uint16(oSize)
in = in[iSize:]
} else {
if len(in) <= int(iSize) {
return s, nil, errors.New("input too small for table")
}
// FSE compressed weights
s.fse.DecompressLimit = 255
hw := s.huffWeight[:]
s.fse.Out = hw
b, err := fse.Decompress(in[:iSize], s.fse)
s.fse.Out = nil
if err != nil {
return s, nil, err
}
if len(b) > 255 {
return s, nil, errors.New("corrupt input: output table too large")
}
s.symbolLen = uint16(len(b))
in = in[iSize:]
}
// collect weight stats
var rankStats [tableLogMax + 1]uint32
weightTotal := uint32(0)
for _, v := range s.huffWeight[:s.symbolLen] {
if v > tableLogMax {
return s, nil, errors.New("corrupt input: weight too large")
}
rankStats[v]++
weightTotal += (1 << (v & 15)) >> 1
}
if weightTotal == 0 {
return s, nil, errors.New("corrupt input: weights zero")
}
// get last non-null symbol weight (implied, total must be 2^n)
{
tableLog := highBit32(weightTotal) + 1
if tableLog > tableLogMax {
return s, nil, errors.New("corrupt input: tableLog too big")
}
s.actualTableLog = uint8(tableLog)
// determine last weight
{
total := uint32(1) << tableLog
rest := total - weightTotal
verif := uint32(1) << highBit32(rest)
lastWeight := highBit32(rest) + 1
if verif != rest {
// last value must be a clean power of 2
return s, nil, errors.New("corrupt input: last value not power of two")
}
s.huffWeight[s.symbolLen] = uint8(lastWeight)
s.symbolLen++
rankStats[lastWeight]++
}
}
if (rankStats[1] < 2) || (rankStats[1]&1 != 0) {
// by construction : at least 2 elts of rank 1, must be even
return s, nil, errors.New("corrupt input: min elt size, even check failed ")
}
// TODO: Choose between single/double symbol decoding
// Calculate starting value for each rank
{
var nextRankStart uint32
for n := uint8(1); n < s.actualTableLog+1; n++ {
current := nextRankStart
nextRankStart += rankStats[n] << (n - 1)
rankStats[n] = current
}
}
// fill DTable (always full size)
tSize := 1 << tableLogMax
if len(s.dt.single) != tSize {
s.dt.single = make([]dEntrySingle, tSize)
}
for n, w := range s.huffWeight[:s.symbolLen] {
length := (uint32(1) << w) >> 1
d := dEntrySingle{
byte: uint8(n),
nBits: s.actualTableLog + 1 - w,
}
for u := rankStats[w]; u < rankStats[w]+length; u++ {
s.dt.single[u] = d
}
rankStats[w] += length
}
return s, in, nil
}
// Decompress1X will decompress a 1X encoded stream.
// The supplied input must match the end of a block exactly.
// Before this is called, the table must be initialized with ReadTable.
func (s *Scratch) Decompress1X(in []byte) (out []byte, err error) {
if len(s.dt.single) == 0 {
return nil, errors.New("no table loaded")
}
var br bitReader
err = br.init(in)
if err != nil {
return nil, err
}
s.Out = s.Out[:0]
decode := func() byte {
val := br.peekBitsFast(s.actualTableLog) /* note : actualTableLog >= 1 */
v := s.dt.single[val]
br.bitsRead += v.nBits
return v.byte
}
hasDec := func(v dEntrySingle) byte {
br.bitsRead += v.nBits
return v.byte
}
// Avoid bounds check by always having full sized table.
const tlSize = 1 << tableLogMax
const tlMask = tlSize - 1
dt := s.dt.single[:tlSize]
// Use temp table to avoid bound checks/append penalty.
var tmp = s.huffWeight[:256]
var off uint8
for br.off >= 8 {
br.fillFast()
tmp[off+0] = hasDec(dt[br.peekBitsFast(s.actualTableLog)&tlMask])
tmp[off+1] = hasDec(dt[br.peekBitsFast(s.actualTableLog)&tlMask])
br.fillFast()
tmp[off+2] = hasDec(dt[br.peekBitsFast(s.actualTableLog)&tlMask])
tmp[off+3] = hasDec(dt[br.peekBitsFast(s.actualTableLog)&tlMask])
off += 4
if off == 0 {
s.Out = append(s.Out, tmp...)
}
}
s.Out = append(s.Out, tmp[:off]...)
for !br.finished() {
br.fill()
s.Out = append(s.Out, decode())
}
return s.Out, br.close()
}
// Decompress4X will decompress a 4X encoded stream.
// Before this is called, the table must be initialized with ReadTable.
// The supplied input must match the end of a block exactly.
// The destination size of the uncompressed data must be known and provided.
func (s *Scratch) Decompress4X(in []byte, dstSize int) (out []byte, err error) {
if len(s.dt.single) == 0 {
return nil, errors.New("no table loaded")
}
if len(in) < 6+(4*1) {
return nil, errors.New("input too small")
}
// TODO: We do not detect when we overrun a buffer, except if the last one does.
var br [4]bitReader
start := 6
for i := 0; i < 3; i++ {
length := int(in[i*2]) | (int(in[i*2+1]) << 8)
if start+length >= len(in) {
return nil, errors.New("truncated input (or invalid offset)")
}
err = br[i].init(in[start : start+length])
if err != nil {
return nil, err
}
start += length
}
err = br[3].init(in[start:])
if err != nil {
return nil, err
}
// Prepare output
if cap(s.Out) < dstSize {
s.Out = make([]byte, 0, dstSize)
}
s.Out = s.Out[:dstSize]
// destination, offset to match first output
dstOut := s.Out
dstEvery := (dstSize + 3) / 4
decode := func(br *bitReader) byte {
val := br.peekBitsFast(s.actualTableLog) /* note : actualTableLog >= 1 */
v := s.dt.single[val]
br.bitsRead += v.nBits
return v.byte
}
// Use temp table to avoid bound checks/append penalty.
var tmp = s.huffWeight[:256]
var off uint8
// Decode 2 values from each decoder/loop.
const bufoff = 256 / 4
bigloop:
for {
for i := range br {
if br[i].off < 4 {
break bigloop
}
br[i].fillFast()
}
tmp[off] = decode(&br[0])
tmp[off+bufoff] = decode(&br[1])
tmp[off+bufoff*2] = decode(&br[2])
tmp[off+bufoff*3] = decode(&br[3])
tmp[off+1] = decode(&br[0])
tmp[off+1+bufoff] = decode(&br[1])
tmp[off+1+bufoff*2] = decode(&br[2])
tmp[off+1+bufoff*3] = decode(&br[3])
off += 2
if off == bufoff {
copy(dstOut, tmp[:bufoff])
copy(dstOut[dstEvery:], tmp[bufoff:bufoff*2])
copy(dstOut[dstEvery*2:], tmp[bufoff*2:bufoff*3])
copy(dstOut[dstEvery*3:], tmp[bufoff*3:bufoff*4])
off = 0
dstOut = dstOut[bufoff:]
// There must at least be 3 buffers left.
if len(dstOut) < dstEvery*3+3 {
return nil, errors.New("corruption detected: stream overrun")
}
}
}
if off > 0 {
ioff := int(off)
if len(dstOut) < dstEvery*3-ioff {
return nil, errors.New("corruption detected: stream overrun")
}
copy(dstOut, tmp[:off])
copy(dstOut[dstEvery:dstEvery+ioff], tmp[bufoff:bufoff*2])
copy(dstOut[dstEvery*2:dstEvery*2+ioff], tmp[bufoff*2:bufoff*3])
copy(dstOut[dstEvery*3:dstEvery*3+ioff], tmp[bufoff*3:bufoff*4])
dstOut = dstOut[off:]
}
for i := range br {
offset := dstEvery * i
br := &br[i]
for !br.finished() {
br.fill()
if offset >= len(dstOut) {
return nil, errors.New("corruption detected: stream overrun")
}
dstOut[offset] = decode(br)
offset++
}
err = br.close()
if err != nil {
return nil, err
}
}
return s.Out, nil
}
// matches will compare a decoding table to a coding table.
// Errors are written to the writer.
// Nothing will be written if table is ok.
func (s *Scratch) matches(ct cTable, w io.Writer) {
if s == nil || len(s.dt.single) == 0 {
return
}
dt := s.dt.single[:1<<s.actualTableLog]
tablelog := s.actualTableLog
ok := 0
broken := 0
for sym, enc := range ct {
errs := 0
broken++
if enc.nBits == 0 {
for _, dec := range dt {
if dec.byte == byte(sym) {
fmt.Fprintf(w, "symbol %x has decoder, but no encoder\n", sym)
errs++
break
}
}
if errs == 0 {
broken--
}
continue
}
// Unused bits in input
ub := tablelog - enc.nBits
top := enc.val << ub
// decoder looks at top bits.
dec := dt[top]
if dec.nBits != enc.nBits {
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", sym, enc.nBits, dec.nBits)
errs++
}
if dec.byte != uint8(sym) {
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", sym, sym, dec.byte)
errs++
}
if errs > 0 {
fmt.Fprintf(w, "%d errros in base, stopping\n", errs)
continue
}
// Ensure that all combinations are covered.
for i := uint16(0); i < (1 << ub); i++ {
vval := top | i
dec := dt[vval]
if dec.nBits != enc.nBits {
fmt.Fprintf(w, "symbol 0x%x bit size mismatch (enc: %d, dec:%d).\n", vval, enc.nBits, dec.nBits)
errs++
}
if dec.byte != uint8(sym) {
fmt.Fprintf(w, "symbol 0x%x decoder output mismatch (enc: %d, dec:%d).\n", vval, sym, dec.byte)
errs++
}
if errs > 20 {
fmt.Fprintf(w, "%d errros, stopping\n", errs)
break
}
}
if errs == 0 {
ok++
broken--
}
}
if broken > 0 {
fmt.Fprintf(w, "%d broken, %d ok\n", broken, ok)
}
}
+482
View File
@@ -0,0 +1,482 @@
package huff0
import (
"bytes"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestDecompress1X(t *testing.T) {
for _, test := range testfiles {
t.Run(test.name, func(t *testing.T) {
var s = &Scratch{}
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress1X(buf0, s)
if err != test.err1X {
t.Errorf("want error %v (%T), got %v (%T)", test.err1X, test.err1X, err, err)
}
if err != nil {
t.Log(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
wantRemain := len(s.OutData)
t.Logf("%s: %d -> %d bytes (%.2f:1) %t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
s.Out = nil
var remain []byte
s, remain, err = ReadTable(b, s)
if err != nil {
t.Error(err)
return
}
var buf bytes.Buffer
if s.matches(s.prevTable, &buf); buf.Len() > 0 {
t.Error(buf.String())
}
if len(remain) != wantRemain {
t.Fatalf("remain mismatch, want %d, got %d bytes", wantRemain, len(remain))
}
t.Logf("remain: %d bytes, ok", len(remain))
dc, err := s.Decompress1X(remain)
if err != nil {
t.Error(err)
return
}
if len(buf0) != len(dc) {
t.Errorf(test.name+"decompressed, want size: %d, got %d", len(buf0), len(dc))
if len(buf0) > len(dc) {
buf0 = buf0[:len(dc)]
} else {
dc = dc[:len(buf0)]
}
if !cmp.Equal(buf0, dc) {
if len(dc) > 1024 {
t.Log(string(dc[:1024]))
t.Errorf(test.name+"decompressed, got delta: \n(in)\t%02x !=\n(out)\t%02x\n", buf0[:1024], dc[:1024])
} else {
t.Log(string(dc))
t.Errorf(test.name+"decompressed, got delta: (in) %v != (out) %v\n", buf0, dc)
}
}
return
}
if !cmp.Equal(buf0, dc) {
if len(buf0) > 1024 {
t.Log(string(dc[:1024]))
} else {
t.Log(string(dc))
}
//t.Errorf(test.name+": decompressed, got delta: \n%s")
t.Errorf(test.name + ": decompressed, got delta")
}
if !t.Failed() {
t.Log("... roundtrip ok!")
}
})
}
}
func TestDecompress4X(t *testing.T) {
for _, test := range testfiles {
t.Run(test.name, func(t *testing.T) {
var s = &Scratch{}
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress4X(buf0, s)
if err != test.err4X {
t.Errorf("want error %v (%T), got %v (%T)", test.err1X, test.err1X, err, err)
}
if err != nil {
t.Log(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
wantRemain := len(s.OutData)
t.Logf("%s: %d -> %d bytes (%.2f:1) %t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
s.Out = nil
var remain []byte
s, remain, err = ReadTable(b, s)
if err != nil {
t.Error(err)
return
}
var buf bytes.Buffer
if s.matches(s.prevTable, &buf); buf.Len() > 0 {
t.Error(buf.String())
}
if len(remain) != wantRemain {
t.Fatalf("remain mismatch, want %d, got %d bytes", wantRemain, len(remain))
}
t.Logf("remain: %d bytes, ok", len(remain))
dc, err := s.Decompress4X(remain, len(buf0))
if err != nil {
t.Error(err)
return
}
if len(buf0) != len(dc) {
t.Errorf(test.name+"decompressed, want size: %d, got %d", len(buf0), len(dc))
if len(buf0) > len(dc) {
buf0 = buf0[:len(dc)]
} else {
dc = dc[:len(buf0)]
}
if !cmp.Equal(buf0, dc) {
if len(dc) > 1024 {
t.Log(string(dc[:1024]))
t.Errorf(test.name+"decompressed, got delta: \n(in)\t%02x !=\n(out)\t%02x\n", buf0[:1024], dc[:1024])
} else {
t.Log(string(dc))
t.Errorf(test.name+"decompressed, got delta: (in) %v != (out) %v\n", buf0, dc)
}
}
return
}
if !cmp.Equal(buf0, dc) {
if len(buf0) > 1024 {
t.Log(string(dc[:1024]))
} else {
t.Log(string(dc))
}
//t.Errorf(test.name+": decompressed, got delta: \n%s")
t.Errorf(test.name + ": decompressed, got delta")
}
if !t.Failed() {
t.Log("... roundtrip ok!")
}
})
}
}
func TestDecompress1XFuzz(t *testing.T) {
for _, test := range testfilesExtended {
t.Run(test.name, func(t *testing.T) {
var s = &Scratch{}
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress1X(buf0, s)
if err != nil {
if err == ErrIncompressible || err == ErrUseRLE || err == ErrTooBig {
t.Log(test.name, err.Error())
return
}
t.Error(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
wantRemain := len(s.OutData)
t.Logf("%s: %d -> %d bytes (%.2f:1) %t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
s.Out = nil
var remain []byte
s, remain, err = ReadTable(b, s)
if err != nil {
t.Error(err)
return
}
var buf bytes.Buffer
if s.matches(s.prevTable, &buf); buf.Len() > 0 {
t.Error(buf.String())
}
if len(remain) != wantRemain {
t.Fatalf("remain mismatch, want %d, got %d bytes", wantRemain, len(remain))
}
t.Logf("remain: %d bytes, ok", len(remain))
dc, err := s.Decompress1X(remain)
if err != nil {
t.Error(err)
return
}
if len(buf0) != len(dc) {
t.Errorf(test.name+"decompressed, want size: %d, got %d", len(buf0), len(dc))
if len(buf0) > len(dc) {
buf0 = buf0[:len(dc)]
} else {
dc = dc[:len(buf0)]
}
if !cmp.Equal(buf0, dc) {
if len(dc) > 1024 {
t.Log(string(dc[:1024]))
t.Errorf(test.name+"decompressed, got delta: \n(in)\t%02x !=\n(out)\t%02x\n", buf0[:1024], dc[:1024])
} else {
t.Log(string(dc))
t.Errorf(test.name+"decompressed, got delta: (in) %v != (out) %v\n", buf0, dc)
}
}
return
}
if !cmp.Equal(buf0, dc) {
if len(buf0) > 1024 {
t.Log(string(dc[:1024]))
} else {
t.Log(string(dc))
}
//t.Errorf(test.name+": decompressed, got delta: \n%s")
t.Errorf(test.name + ": decompressed, got delta")
}
if !t.Failed() {
t.Log("... roundtrip ok!")
}
})
}
}
func TestDecompress4XFuzz(t *testing.T) {
for _, test := range testfilesExtended {
t.Run(test.name, func(t *testing.T) {
var s = &Scratch{}
buf0, err := test.fn()
if err != nil {
t.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
b, re, err := Compress4X(buf0, s)
if err != nil {
if err == ErrIncompressible || err == ErrUseRLE || err == ErrTooBig {
t.Log(test.name, err.Error())
return
}
t.Error(test.name, err.Error())
return
}
if b == nil {
t.Error("got no output")
return
}
if len(s.OutTable) == 0 {
t.Error("got no table definition")
}
if re {
t.Error("claimed to have re-used.")
}
if len(s.OutData) == 0 {
t.Error("got no data output")
}
wantRemain := len(s.OutData)
t.Logf("%s: %d -> %d bytes (%.2f:1) %t (table: %d bytes)", test.name, len(buf0), len(b), float64(len(buf0))/float64(len(b)), re, len(s.OutTable))
s.Out = nil
var remain []byte
s, remain, err = ReadTable(b, s)
if err != nil {
t.Error(err)
return
}
var buf bytes.Buffer
if s.matches(s.prevTable, &buf); buf.Len() > 0 {
t.Error(buf.String())
}
if len(remain) != wantRemain {
t.Fatalf("remain mismatch, want %d, got %d bytes", wantRemain, len(remain))
}
t.Logf("remain: %d bytes, ok", len(remain))
dc, err := s.Decompress4X(remain, len(buf0))
if err != nil {
t.Error(err)
return
}
if len(buf0) != len(dc) {
t.Errorf(test.name+"decompressed, want size: %d, got %d", len(buf0), len(dc))
if len(buf0) > len(dc) {
buf0 = buf0[:len(dc)]
} else {
dc = dc[:len(buf0)]
}
if !cmp.Equal(buf0, dc) {
if len(dc) > 1024 {
t.Log(string(dc[:1024]))
t.Errorf(test.name+"decompressed, got delta: \n(in)\t%02x !=\n(out)\t%02x\n", buf0[:1024], dc[:1024])
} else {
t.Log(string(dc))
t.Errorf(test.name+"decompressed, got delta: (in) %v != (out) %v\n", buf0, dc)
}
}
return
}
if !cmp.Equal(buf0, dc) {
if len(buf0) > 1024 {
t.Log(string(dc[:1024]))
} else {
t.Log(string(dc))
}
//t.Errorf(test.name+": decompressed, got delta: \n%s")
t.Errorf(test.name + ": decompressed, got delta")
}
if !t.Failed() {
t.Log("... roundtrip ok!")
}
})
}
}
func BenchmarkDecompress1XTable(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s = &Scratch{}
s.Reuse = ReusePolicyNone
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
compressed, _, err := Compress1X(buf0, s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
s.Out = nil
s, remain, _ := ReadTable(compressed, s)
s.Decompress1X(remain)
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
s, remain, err := ReadTable(compressed, s)
if err != nil {
b.Fatal(err)
}
_, err = s.Decompress1X(remain)
if err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkDecompress1XNoTable(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err1X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s = &Scratch{}
s.Reuse = ReusePolicyNone
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
compressed, _, err := Compress1X(buf0, s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
s.Out = nil
s, remain, _ := ReadTable(compressed, s)
s.Decompress1X(remain)
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, err = s.Decompress1X(remain)
if err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkDecompress4XNoTable(b *testing.B) {
for _, tt := range testfiles {
test := tt
if test.err4X != nil {
continue
}
b.Run(test.name, func(b *testing.B) {
var s = &Scratch{}
s.Reuse = ReusePolicyNone
buf0, err := test.fn()
if err != nil {
b.Fatal(err)
}
if len(buf0) > BlockSizeMax {
buf0 = buf0[:BlockSizeMax]
}
compressed, _, err := Compress4X(buf0, s)
if err != test.err1X {
b.Fatal("unexpected error:", err)
}
s.Out = nil
s, remain, _ := ReadTable(compressed, s)
s.Decompress4X(remain, len(buf0))
b.ResetTimer()
b.ReportAllocs()
b.SetBytes(int64(len(buf0)))
for i := 0; i < b.N; i++ {
_, err = s.Decompress4X(remain, len(buf0))
if err != nil {
b.Fatal(err)
}
}
})
}
}
@@ -0,0 +1 @@
012939495969798997C2
@@ -0,0 +1 @@
CaDCaDcAecDc7De7777-accD77DeDEEAf_ZohDnpPnXJKmFtxmGZFOVgbxbCaAEEAAfecbD4dc44.x.xAfF-45405050xdbCaDAEEd0EcdDA7AfecbD4dc454057025Vz-04454224054e-0276666767011776664.efZohQpggnpGPTrniXRqJlKmFtmGZFOgVMgwxAAfeB643xbBCaBABE3BEcd3AAfecbD64Bdc664423703235e-07503-0xB66C9fad.-75946554523692857652911-i1V___hoNaqWTStRM9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc1862645149230957031259101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125\B___L7D3PS0hd1_Gxkg_Eg2P_Uo_3yd3gU_a_2__Ct28_l1w16r4_p_Q898Fv_22_6_28282357FECfCFCdBEEaE47fb5Bcý{ý1.0xd-0534420.-8911368683772161602973534420.-89113686<-7301351713.7376291319585350ýý [
@@ -0,0 +1 @@
-08338832110233882812
@@ -0,0 +1 @@
8869fecbD864Bdc186264514923095703125-70989128513186264514923095703125
@@ -0,0 +1 @@
xhy_1P_X_Bers6vz3zZ_95_J_DSW6IkV4_woYf___aC7_p1_1gED_4_Q_28_T73eUL053440.-136868-
@@ -0,0 +1 @@
012756789aefxbcdefx
@@ -0,0 +1 @@
8869-0x0e.-9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125-70989128513186264514923095703125
@@ -0,0 +1 @@
0345678defghijklmnopqrstuvwxyz18872161F029739CFeBEbaE7fb-05340.-13686
@@ -0,0 +1 @@
"�4656612873077392578125
@@ -0,0 +1 @@
186264514923095703125
@@ -0,0 +1 @@
0aaef11119910xC8fFCFCEEaEfb.144-0440.-89116868771616097937988815-9101862645149230957031250xbCaBEBEDAfebDBdc32-xcA3.7475562603-0x0e0862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125|7318e000175420
@@ -0,0 +1,2 @@
*? | "
%`Rw_Y} [QQ.=Rtw
@@ -0,0 +1 @@
1136868377216160798828125
@@ -0,0 +1 @@
GPTrniXRqJlK_mFt____AmGZ__BF_Og__VM_gw.4995325xC2aBABd731BEd3A7Aecb8dc866414295731253.0xDe9D94B9EEaf48EDccB4b5Da0-0x0e-9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125
@@ -0,0 +1 @@
4995325233449559994-09-519341245142309503125
@@ -0,0 +1 @@
92309570312
@@ -0,0 +1 @@
"�465663981283077397778125
@@ -0,0 +1 @@
012@456789@4567x
@@ -0,0 +1 @@
0003<Nv=TiKOMgwDDDyvS<kZQKHwIUmUjWymgXsPoltRZ-ZoISLwOSNjYuumzYRjJv\Vze*.eVzTyLniwrmMYzsIs\.eED(---.--[1111aEEDAfeDd-qdAEl.VZohQpFpGPtgnpGPTrniXRqJlKFtmGZFOgVMgw3Afb35353;af4ba-x___Tr44niX_Rq_JlK_mF__t___4A57_EAxmGZ__BF_Og__VM8gw418451435731250xbBaBEBEd3A7AfbD8Bd18241423095703125!91018245149230957031250xdbB2aBDABEEd73801BEcdD3A7AfecbD84Bdc1862645149230957031253.0742642597915814946756428320630820e-0xf34qj-910162645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc1862645149230957031253Vz-808534522276595512169.4hyPXrsvzzZJSWIkVwoY_aC_pCaEfDc644xfC2.050xDdFae0xAf05AdEF4e2C09e5da6445xC2E3EdD3AcbDc8664118626451492095031250xdbBC2aBDABEd73801BEcdD3A7Afecb7332
@@ -0,0 +1 @@
054175252231364715157010273365424.-0xaC4bc3bBFEE733c17Cb23c7B4E9
@@ -0,0 +1 @@
e11188721616029739CFeBEEbaE7fb-053440.-136868
@@ -0,0 +1 @@
"69128307397778125
@@ -0,0 +1 @@
bDabBef136316160973937988281250xCc8Ed-0534420.-89113686837721616
@@ -0,0 +1 @@
11686837721616029739
@@ -0,0 +1,2 @@
)+~\ <?;ECPFPIE)::
~?
@@ -0,0 +1 @@
-083・883022338828
@@ -0,0 +1 @@
1814923095703125
@@ -0,0 +1 @@
0123456789abcdefx
@@ -0,0 +1 @@
1136868377216160297393798828125
@@ -0,0 +1 @@
exF.-30030xCaEEDA7AfecbDc474499570350xdbCaDABEEd730BEcdD3A7AfecbD84Bdc864549309570325-03753662640774330616234263332315.-09918907192036521631809-0x0eÿ-9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125
@@ -0,0 +1,2 @@
)~\ <_______?;bE1bEC_AcyvP__FPI1bE1d_)+u::]~?
__j_5bu__yU__Dv19WNu_13g_Y1_RYWQvX______1z_L_G_k1Mm[_&_6d48_7V9_hSINhG8r__yj1_8_SWh_pGx_Nk_S1__u08_iZVaayDaTxoo%uj DcH11Q._'riRZU/ h_UO__63v2_G052"B_w34C_K1K.-Afc64ZmsVOt-'tqnf0x8f4E.-07__g.-03255663.-6&OyGOaL_h_Tt2Y1k717n1_7o6vN_KQ6MFXEO_2ANI4__z_l_a_PLn5Hm61_birB_/
@@ -0,0 +1 @@
-0xbafD8aae3Df5b9Bef111368683772161602973937988281250xCc3EBf357FEdaCfCF4CdeBEEbaE47fb5Bc691.-0xd-0Df5b9Bef1113686837721616029739379�Ń7288281250xCc3EBf357FEdaCfCF4CdeBEEbaE47fb5Bc˛ČÁŮ»ľ691.-0xd-0534420.-8911368683772161602973534420.-89113686<37721ľŕÁ [¨616029739379882812
@@ -0,0 +1 @@
-0757e0664060273074361272e0x2cF8a8E.-9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc186264514923095703125-0xcA3.07047556026031-0x0e-9101862645149230957031250xdbBC2aBDABEEd73801BEcdD3A7AfecbD864Bdc1862645149230957031257318e00017542035
@@ -0,0 +1 @@
18695703125
@@ -0,0 +1 @@
3095703125
@@ -0,0 +1 @@
412221647115710273365424.-0xaC4BFE3cCb3c7BE9
@@ -0,0 +1 @@
111%613475767778796029739CFeBEEbaE7fb-053440.-

Some files were not shown because too many files have changed in this diff Show More