194 lines
3.9 KiB
Go
194 lines
3.9 KiB
Go
package transport
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
var (
|
|
errQlzDataTooShort = errors.New("data too short")
|
|
errQlzUnsupportedLevel = errors.New("only QuickLZ level 1 is supported")
|
|
errQlzDataTooShortForHeader = errors.New("data too short for header")
|
|
)
|
|
|
|
// TableSize is the QuickLZ level-1 hash table size.
|
|
const TableSize = 4096
|
|
|
|
type Qlz struct {
|
|
hashtable [TableSize]int
|
|
}
|
|
|
|
type qlzState struct {
|
|
control uint32
|
|
sourcePos int
|
|
destPos int
|
|
nextHashed int
|
|
}
|
|
|
|
func NewQlz() *Qlz {
|
|
return &Qlz{}
|
|
}
|
|
|
|
func getDecompressedSize(data []byte) int {
|
|
if (data[0] & 0x02) != 0 {
|
|
return int(binary.LittleEndian.Uint32(data[5:9]))
|
|
}
|
|
|
|
return int(data[2])
|
|
}
|
|
|
|
func (q *Qlz) Decompress(data []byte) ([]byte, error) {
|
|
headerLen, decompressedSize, flags, err := parseQlzHeader(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dest := make([]byte, decompressedSize)
|
|
|
|
if (flags & 0x01) == 0 {
|
|
copy(dest, data[headerLen:headerLen+decompressedSize])
|
|
|
|
return dest, nil
|
|
}
|
|
|
|
for i := range q.hashtable {
|
|
q.hashtable[i] = 0
|
|
}
|
|
|
|
state := qlzState{
|
|
control: 1,
|
|
sourcePos: headerLen,
|
|
}
|
|
|
|
for q.ensureControl(data, &state) {
|
|
if (state.control & 1) != 0 {
|
|
if !q.processReference(data, dest, &state) {
|
|
break
|
|
}
|
|
} else {
|
|
if q.processLiteral(data, dest, decompressedSize, &state) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return dest, nil
|
|
}
|
|
|
|
func parseQlzHeader(data []byte) (int, int, byte, error) {
|
|
if len(data) < 3 {
|
|
return 0, 0, 0, errQlzDataTooShort
|
|
}
|
|
flags := data[0]
|
|
level := (flags >> 2) & 0x03
|
|
if level != 1 {
|
|
return 0, 0, 0, errQlzUnsupportedLevel
|
|
}
|
|
headerLen := 3
|
|
if (flags & 0x02) != 0 {
|
|
headerLen = 9
|
|
}
|
|
if len(data) < headerLen {
|
|
return 0, 0, 0, errQlzDataTooShortForHeader
|
|
}
|
|
|
|
return headerLen, getDecompressedSize(data), flags, nil
|
|
}
|
|
|
|
func (q *Qlz) ensureControl(data []byte, st *qlzState) bool {
|
|
if st.control != 1 {
|
|
return true
|
|
}
|
|
if st.sourcePos+4 > len(data) {
|
|
return false
|
|
}
|
|
st.control = binary.LittleEndian.Uint32(data[st.sourcePos : st.sourcePos+4])
|
|
st.sourcePos += 4
|
|
|
|
return true
|
|
}
|
|
|
|
func (q *Qlz) processReference(data, dest []byte, st *qlzState) bool {
|
|
st.control >>= 1
|
|
if st.sourcePos+2 > len(data) {
|
|
return false
|
|
}
|
|
b1 := data[st.sourcePos]
|
|
b2 := data[st.sourcePos+1]
|
|
st.sourcePos += 2
|
|
|
|
hash := int(b1>>4) | (int(b2) << 4)
|
|
matchlen := int(b1 & 0x0F)
|
|
if matchlen != 0 {
|
|
matchlen += 2
|
|
} else {
|
|
if st.sourcePos >= len(data) {
|
|
return false
|
|
}
|
|
matchlen = int(data[st.sourcePos])
|
|
st.sourcePos++
|
|
}
|
|
|
|
offset := q.hashtable[hash]
|
|
for i := range matchlen {
|
|
if st.destPos < len(dest) && offset+i < st.destPos {
|
|
dest[st.destPos] = dest[offset+i]
|
|
st.destPos++
|
|
}
|
|
}
|
|
|
|
end := st.destPos + 1 - matchlen
|
|
q.updateHashtable(dest, &st.nextHashed, end)
|
|
st.nextHashed = st.destPos
|
|
|
|
return true
|
|
}
|
|
|
|
func (q *Qlz) processLiteral(data, dest []byte, decompressedSize int, st *qlzState) bool {
|
|
if st.destPos >= max(decompressedSize, 10)-10 {
|
|
for st.destPos < decompressedSize {
|
|
if st.control == 1 {
|
|
st.sourcePos += 4
|
|
if st.sourcePos > len(data) {
|
|
break
|
|
}
|
|
st.control = binary.LittleEndian.Uint32(data[st.sourcePos-4 : st.sourcePos])
|
|
}
|
|
if st.sourcePos >= len(data) {
|
|
break
|
|
}
|
|
dest[st.destPos] = data[st.sourcePos]
|
|
st.destPos++
|
|
st.sourcePos++
|
|
st.control >>= 1
|
|
}
|
|
|
|
return true
|
|
}
|
|
if st.sourcePos >= len(data) || st.destPos >= len(dest) {
|
|
return true
|
|
}
|
|
dest[st.destPos] = data[st.sourcePos]
|
|
st.destPos++
|
|
st.sourcePos++
|
|
st.control >>= 1
|
|
end := max(st.destPos-2, 0)
|
|
q.updateHashtable(dest, &st.nextHashed, end)
|
|
if st.nextHashed < end {
|
|
st.nextHashed = end
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (q *Qlz) updateHashtable(dest []byte, nextHashed *int, end int) {
|
|
for *nextHashed < end {
|
|
if *nextHashed+3 > len(dest) {
|
|
break
|
|
}
|
|
v := uint32(dest[*nextHashed]) | (uint32(dest[*nextHashed+1]) << 8) | (uint32(dest[*nextHashed+2]) << 16)
|
|
hash := ((v >> 12) ^ v) & 0xFFF
|
|
q.hashtable[hash] = *nextHashed
|
|
*nextHashed++
|
|
}
|
|
}
|