You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
5.9 KiB
290 lines
5.9 KiB
6 years ago
|
package lzo
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"io"
|
||
|
"runtime"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
InputUnderrun = errors.New("input underrun")
|
||
|
LookBehindUnderrun = errors.New("lookbehind underrun")
|
||
|
)
|
||
|
|
||
|
type reader struct {
|
||
|
r io.Reader
|
||
|
len int
|
||
|
buf [4096]byte
|
||
|
cur []byte
|
||
|
Err error
|
||
|
}
|
||
|
|
||
|
func newReader(r io.Reader, inlen int) *reader {
|
||
|
if inlen == 0 {
|
||
|
inlen = -1
|
||
|
}
|
||
|
in := &reader{r: r, len: inlen}
|
||
|
in.Rebuffer()
|
||
|
return in
|
||
|
}
|
||
|
|
||
|
// Read more data from the underlying reader and put it into the buffer.
|
||
|
// Also makes sure there is always at least 32 bytes in the buffer, so that
|
||
|
// in the main loop we can avoid checking for the end of buffer.
|
||
|
func (in *reader) Rebuffer() {
|
||
|
const RBUF_WND = 32
|
||
|
var rbuf [RBUF_WND]byte
|
||
|
|
||
|
if len(in.cur) > RBUF_WND || in.len == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
rb := rbuf[:len(in.cur)]
|
||
|
copy(rb, in.cur)
|
||
|
in.cur = in.buf[:]
|
||
|
copy(in.cur, rb)
|
||
|
|
||
|
cur := in.cur[len(rb):]
|
||
|
if in.len >= 0 && len(cur) > in.len {
|
||
|
cur = cur[:in.len]
|
||
|
}
|
||
|
n, err := in.r.Read(cur)
|
||
|
if err != nil {
|
||
|
// If EOF is returned, treat it as error only if there are no further
|
||
|
// bytes in the window. Otherwise, let's postpone because those bytes
|
||
|
// could contain the terminator.
|
||
|
if err != io.EOF || len(rb) == 0 {
|
||
|
in.Err = err
|
||
|
in.cur = nil
|
||
|
}
|
||
|
}
|
||
|
in.cur = in.cur[:len(rb)+n]
|
||
|
if in.len >= 0 {
|
||
|
in.len -= n
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (in *reader) ReadAppend(out *[]byte, n int) {
|
||
|
for n > 0 {
|
||
|
m := len(in.cur)
|
||
|
if m > n {
|
||
|
m = n
|
||
|
}
|
||
|
*out = append(*out, in.cur[:m]...)
|
||
|
in.cur = in.cur[m:]
|
||
|
n -= m
|
||
|
if len(in.cur) == 0 {
|
||
|
in.Rebuffer()
|
||
|
if len(in.cur) == 0 {
|
||
|
in.Err = io.EOF
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (in *reader) ReadU8() (ch byte) {
|
||
|
ch = in.cur[0]
|
||
|
in.cur = in.cur[1:]
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (in *reader) ReadU16() int {
|
||
|
b0 := in.cur[0]
|
||
|
b1 := in.cur[1]
|
||
|
in.cur = in.cur[2:]
|
||
|
return int(b0) + int(b1)<<8
|
||
|
}
|
||
|
|
||
|
func (in *reader) ReadMulti(base int) (b int) {
|
||
|
for {
|
||
|
for i := 0; i < len(in.cur); i++ {
|
||
|
v := in.cur[i]
|
||
|
if v == 0 {
|
||
|
b += 255
|
||
|
} else {
|
||
|
b += int(v) + base
|
||
|
in.cur = in.cur[i+1:]
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
in.cur = in.cur[0:0]
|
||
|
in.Rebuffer()
|
||
|
if len(in.cur) == 0 {
|
||
|
in.Err = io.EOF
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func copyMatch(out *[]byte, m_pos int, n int) {
|
||
|
if m_pos+n > len(*out) {
|
||
|
// fmt.Println("copy match WITH OVERLAP!")
|
||
|
for i := 0; i < n; i++ {
|
||
|
*out = append(*out, (*out)[m_pos])
|
||
|
m_pos++
|
||
|
}
|
||
|
} else {
|
||
|
// fmt.Println("copy match:", len(*out), m_pos, m_pos+n)
|
||
|
*out = append(*out, (*out)[m_pos:m_pos+n]...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Decompress an input compressed with LZO1X.
|
||
|
//
|
||
|
// LZO1X has a stream terminator marker, so the decompression will always stop
|
||
|
// when this marker is found.
|
||
|
//
|
||
|
// If inLen is not zero, it is expected to match the length of the compressed
|
||
|
// input stream, and it is used to limit reads from the underlying reader; if
|
||
|
// inLen is smaller than the real stream, the decompression will abort with an
|
||
|
// error; if inLen is larger than the real stream, or if it is zero, the
|
||
|
// decompression will succeed but more bytes than necessary might be read
|
||
|
// from the underlying reader. If the reader returns EOF before the termination
|
||
|
// marker is found, the decompression aborts and EOF is returned.
|
||
|
//
|
||
|
// outLen is optional; if it's not zero, it is used as a hint to preallocate the
|
||
|
// output buffer to increase performance of the decompression.
|
||
|
func Decompress1X(r io.Reader, inLen int, outLen int) (out []byte, err error) {
|
||
|
var t, m_pos int
|
||
|
var last2 byte
|
||
|
|
||
|
defer func() {
|
||
|
// To gain performance, we don't do any bounds checking while reading
|
||
|
// the input, so if the decompressor reads past the end of the input
|
||
|
// stream, a runtime error is raised. This saves about 7% of performance
|
||
|
// as the reading functions are very hot in the decompressor.
|
||
|
if r := recover(); r != nil {
|
||
|
if re, ok := r.(runtime.Error); ok {
|
||
|
if re.Error() == "runtime error: index out of range" {
|
||
|
err = io.EOF
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
panic(r)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
out = make([]byte, 0, outLen)
|
||
|
|
||
|
in := newReader(r, inLen)
|
||
|
ip := in.ReadU8()
|
||
|
if ip > 17 {
|
||
|
t = int(ip) - 17
|
||
|
if t < 4 {
|
||
|
goto match_next
|
||
|
}
|
||
|
in.ReadAppend(&out, t)
|
||
|
// fmt.Println("begin:", string(out))
|
||
|
goto first_literal_run
|
||
|
}
|
||
|
|
||
|
begin_loop:
|
||
|
t = int(ip)
|
||
|
if t >= 16 {
|
||
|
goto match
|
||
|
}
|
||
|
if t == 0 {
|
||
|
t = in.ReadMulti(15)
|
||
|
}
|
||
|
in.ReadAppend(&out, t+3)
|
||
|
// fmt.Println("readappend", t+3, string(out[len(out)-t-3:]))
|
||
|
first_literal_run:
|
||
|
ip = in.ReadU8()
|
||
|
last2 = ip
|
||
|
t = int(ip)
|
||
|
if t >= 16 {
|
||
|
goto match
|
||
|
}
|
||
|
m_pos = len(out) - (1 + m2_MAX_OFFSET)
|
||
|
m_pos -= t >> 2
|
||
|
ip = in.ReadU8()
|
||
|
m_pos -= int(ip) << 2
|
||
|
// fmt.Println("m_pos flr", m_pos, len(out), "\n", string(out))
|
||
|
if m_pos < 0 {
|
||
|
err = LookBehindUnderrun
|
||
|
return
|
||
|
}
|
||
|
copyMatch(&out, m_pos, 3)
|
||
|
goto match_done
|
||
|
|
||
|
match:
|
||
|
in.Rebuffer()
|
||
|
if in.Err != nil {
|
||
|
err = in.Err
|
||
|
return
|
||
|
}
|
||
|
t = int(ip)
|
||
|
last2 = ip
|
||
|
if t >= 64 {
|
||
|
m_pos = len(out) - 1
|
||
|
m_pos -= (t >> 2) & 7
|
||
|
ip = in.ReadU8()
|
||
|
m_pos -= int(ip) << 3
|
||
|
// fmt.Println("m_pos t64", m_pos, t, int(ip))
|
||
|
t = (t >> 5) - 1
|
||
|
goto copy_match
|
||
|
} else if t >= 32 {
|
||
|
t &= 31
|
||
|
if t == 0 {
|
||
|
t = in.ReadMulti(31)
|
||
|
}
|
||
|
m_pos = len(out) - 1
|
||
|
v16 := in.ReadU16()
|
||
|
m_pos -= v16 >> 2
|
||
|
last2 = byte(v16 & 0xFF)
|
||
|
// fmt.Println("m_pos t32", m_pos)
|
||
|
} else if t >= 16 {
|
||
|
m_pos = len(out)
|
||
|
m_pos -= (t & 8) << 11
|
||
|
t &= 7
|
||
|
if t == 0 {
|
||
|
t = in.ReadMulti(7)
|
||
|
}
|
||
|
v16 := in.ReadU16()
|
||
|
m_pos -= v16 >> 2
|
||
|
if m_pos == len(out) {
|
||
|
// fmt.Println("END", t, v16, m_pos)
|
||
|
return
|
||
|
}
|
||
|
m_pos -= 0x4000
|
||
|
last2 = byte(v16 & 0xFF)
|
||
|
// fmt.Println("m_pos t16", m_pos)
|
||
|
} else {
|
||
|
m_pos = len(out) - 1
|
||
|
m_pos -= t >> 2
|
||
|
ip = in.ReadU8()
|
||
|
m_pos -= int(ip) << 2
|
||
|
if m_pos < 0 {
|
||
|
err = LookBehindUnderrun
|
||
|
return
|
||
|
}
|
||
|
// fmt.Println("m_pos tX", m_pos)
|
||
|
copyMatch(&out, m_pos, 2)
|
||
|
goto match_done
|
||
|
}
|
||
|
|
||
|
copy_match:
|
||
|
if m_pos < 0 {
|
||
|
err = LookBehindUnderrun
|
||
|
return
|
||
|
}
|
||
|
copyMatch(&out, m_pos, t+2)
|
||
|
|
||
|
match_done:
|
||
|
t = int(last2 & 3)
|
||
|
if t == 0 {
|
||
|
goto match_end
|
||
|
}
|
||
|
match_next:
|
||
|
// fmt.Println("read append finale:", t)
|
||
|
in.ReadAppend(&out, t)
|
||
|
ip = in.ReadU8()
|
||
|
goto match
|
||
|
|
||
|
match_end:
|
||
|
ip = in.ReadU8()
|
||
|
goto begin_loop
|
||
|
}
|