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 }