mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-27 13:13:10 +00:00
586 lines
13 KiB
Go
586 lines
13 KiB
Go
|
// Copyright (c) 2022 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.
|
||
|
|
||
|
package s2
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
// LZ4Converter provides conversion from LZ4 blocks as defined here:
|
||
|
// https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
|
||
|
type LZ4Converter struct {
|
||
|
}
|
||
|
|
||
|
// ErrDstTooSmall is returned when provided destination is too small.
|
||
|
var ErrDstTooSmall = errors.New("s2: destination too small")
|
||
|
|
||
|
// ConvertBlock will convert an LZ4 block and append it as an S2
|
||
|
// block without block length to dst.
|
||
|
// The uncompressed size is returned as well.
|
||
|
// dst must have capacity to contain the entire compressed block.
|
||
|
func (l *LZ4Converter) ConvertBlock(dst, src []byte) ([]byte, int, error) {
|
||
|
if len(src) == 0 {
|
||
|
return dst, 0, nil
|
||
|
}
|
||
|
const debug = false
|
||
|
const inline = true
|
||
|
const lz4MinMatch = 4
|
||
|
|
||
|
s, d := 0, len(dst)
|
||
|
dst = dst[:cap(dst)]
|
||
|
if !debug && hasAmd64Asm {
|
||
|
res, sz := cvtLZ4BlockAsm(dst[d:], src)
|
||
|
if res < 0 {
|
||
|
const (
|
||
|
errCorrupt = -1
|
||
|
errDstTooSmall = -2
|
||
|
)
|
||
|
switch res {
|
||
|
case errCorrupt:
|
||
|
return nil, 0, ErrCorrupt
|
||
|
case errDstTooSmall:
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
default:
|
||
|
return nil, 0, fmt.Errorf("unexpected result: %d", res)
|
||
|
}
|
||
|
}
|
||
|
if d+sz > len(dst) {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
return dst[:d+sz], res, nil
|
||
|
}
|
||
|
|
||
|
dLimit := len(dst) - 10
|
||
|
var lastOffset uint16
|
||
|
var uncompressed int
|
||
|
if debug {
|
||
|
fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst))
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
if s >= len(src) {
|
||
|
return dst[:d], 0, ErrCorrupt
|
||
|
}
|
||
|
// Read literal info
|
||
|
token := src[s]
|
||
|
ll := int(token >> 4)
|
||
|
ml := int(lz4MinMatch + (token & 0xf))
|
||
|
|
||
|
// If upper nibble is 15, literal length is extended
|
||
|
if token >= 0xf0 {
|
||
|
for {
|
||
|
s++
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return dst[:d], 0, ErrCorrupt
|
||
|
}
|
||
|
val := src[s]
|
||
|
ll += int(val)
|
||
|
if val != 255 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Skip past token
|
||
|
if s+ll >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
s++
|
||
|
if ll > 0 {
|
||
|
if d+ll > dLimit {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
if debug {
|
||
|
fmt.Printf("emit %d literals\n", ll)
|
||
|
}
|
||
|
d += emitLiteralGo(dst[d:], src[s:s+ll])
|
||
|
s += ll
|
||
|
uncompressed += ll
|
||
|
}
|
||
|
|
||
|
// Check if we are done...
|
||
|
if s == len(src) && ml == lz4MinMatch {
|
||
|
break
|
||
|
}
|
||
|
// 2 byte offset
|
||
|
if s >= len(src)-2 {
|
||
|
if debug {
|
||
|
fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
offset := binary.LittleEndian.Uint16(src[s:])
|
||
|
s += 2
|
||
|
if offset == 0 {
|
||
|
if debug {
|
||
|
fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
if int(offset) > uncompressed {
|
||
|
if debug {
|
||
|
fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
|
||
|
if ml == lz4MinMatch+15 {
|
||
|
for {
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
val := src[s]
|
||
|
s++
|
||
|
ml += int(val)
|
||
|
if val != 255 {
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if offset == lastOffset {
|
||
|
if debug {
|
||
|
fmt.Printf("emit repeat, length: %d, offset: %d\n", ml, offset)
|
||
|
}
|
||
|
if !inline {
|
||
|
d += emitRepeat16(dst[d:], offset, ml)
|
||
|
} else {
|
||
|
length := ml
|
||
|
dst := dst[d:]
|
||
|
for len(dst) > 5 {
|
||
|
// Repeat offset, make length cheaper
|
||
|
length -= 4
|
||
|
if length <= 4 {
|
||
|
dst[0] = uint8(length)<<2 | tagCopy1
|
||
|
dst[1] = 0
|
||
|
d += 2
|
||
|
break
|
||
|
}
|
||
|
if length < 8 && offset < 2048 {
|
||
|
// Encode WITH offset
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1
|
||
|
d += 2
|
||
|
break
|
||
|
}
|
||
|
if length < (1<<8)+4 {
|
||
|
length -= 4
|
||
|
dst[2] = uint8(length)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 5<<2 | tagCopy1
|
||
|
d += 3
|
||
|
break
|
||
|
}
|
||
|
if length < (1<<16)+(1<<8) {
|
||
|
length -= 1 << 8
|
||
|
dst[3] = uint8(length >> 8)
|
||
|
dst[2] = uint8(length >> 0)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 6<<2 | tagCopy1
|
||
|
d += 4
|
||
|
break
|
||
|
}
|
||
|
const maxRepeat = (1 << 24) - 1
|
||
|
length -= 1 << 16
|
||
|
left := 0
|
||
|
if length > maxRepeat {
|
||
|
left = length - maxRepeat + 4
|
||
|
length = maxRepeat - 4
|
||
|
}
|
||
|
dst[4] = uint8(length >> 16)
|
||
|
dst[3] = uint8(length >> 8)
|
||
|
dst[2] = uint8(length >> 0)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 7<<2 | tagCopy1
|
||
|
if left > 0 {
|
||
|
d += 5 + emitRepeat16(dst[5:], offset, left)
|
||
|
break
|
||
|
}
|
||
|
d += 5
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if debug {
|
||
|
fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset)
|
||
|
}
|
||
|
if !inline {
|
||
|
d += emitCopy16(dst[d:], offset, ml)
|
||
|
} else {
|
||
|
length := ml
|
||
|
dst := dst[d:]
|
||
|
for len(dst) > 5 {
|
||
|
// Offset no more than 2 bytes.
|
||
|
if length > 64 {
|
||
|
off := 3
|
||
|
if offset < 2048 {
|
||
|
// emit 8 bytes as tagCopy1, rest as repeats.
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1
|
||
|
length -= 8
|
||
|
off = 2
|
||
|
} else {
|
||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||
|
// Emit remaining as repeat value (minimum 4 bytes).
|
||
|
dst[2] = uint8(offset >> 8)
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = 59<<2 | tagCopy2
|
||
|
length -= 60
|
||
|
}
|
||
|
// Emit remaining as repeats, at least 4 bytes remain.
|
||
|
d += off + emitRepeat16(dst[off:], offset, length)
|
||
|
break
|
||
|
}
|
||
|
if length >= 12 || offset >= 2048 {
|
||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||
|
dst[2] = uint8(offset >> 8)
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(length-1)<<2 | tagCopy2
|
||
|
d += 3
|
||
|
break
|
||
|
}
|
||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||
|
d += 2
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
lastOffset = offset
|
||
|
}
|
||
|
uncompressed += ml
|
||
|
if d > dLimit {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dst[:d], uncompressed, nil
|
||
|
}
|
||
|
|
||
|
// ConvertBlockSnappy will convert an LZ4 block and append it
|
||
|
// as a Snappy block without block length to dst.
|
||
|
// The uncompressed size is returned as well.
|
||
|
// dst must have capacity to contain the entire compressed block.
|
||
|
func (l *LZ4Converter) ConvertBlockSnappy(dst, src []byte) ([]byte, int, error) {
|
||
|
if len(src) == 0 {
|
||
|
return dst, 0, nil
|
||
|
}
|
||
|
const debug = false
|
||
|
const lz4MinMatch = 4
|
||
|
|
||
|
s, d := 0, len(dst)
|
||
|
dst = dst[:cap(dst)]
|
||
|
// Use assembly when possible
|
||
|
if !debug && hasAmd64Asm {
|
||
|
res, sz := cvtLZ4BlockSnappyAsm(dst[d:], src)
|
||
|
if res < 0 {
|
||
|
const (
|
||
|
errCorrupt = -1
|
||
|
errDstTooSmall = -2
|
||
|
)
|
||
|
switch res {
|
||
|
case errCorrupt:
|
||
|
return nil, 0, ErrCorrupt
|
||
|
case errDstTooSmall:
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
default:
|
||
|
return nil, 0, fmt.Errorf("unexpected result: %d", res)
|
||
|
}
|
||
|
}
|
||
|
if d+sz > len(dst) {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
return dst[:d+sz], res, nil
|
||
|
}
|
||
|
|
||
|
dLimit := len(dst) - 10
|
||
|
var uncompressed int
|
||
|
if debug {
|
||
|
fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst))
|
||
|
}
|
||
|
|
||
|
for {
|
||
|
if s >= len(src) {
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
// Read literal info
|
||
|
token := src[s]
|
||
|
ll := int(token >> 4)
|
||
|
ml := int(lz4MinMatch + (token & 0xf))
|
||
|
|
||
|
// If upper nibble is 15, literal length is extended
|
||
|
if token >= 0xf0 {
|
||
|
for {
|
||
|
s++
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
val := src[s]
|
||
|
ll += int(val)
|
||
|
if val != 255 {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Skip past token
|
||
|
if s+ll >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
s++
|
||
|
if ll > 0 {
|
||
|
if d+ll > dLimit {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
if debug {
|
||
|
fmt.Printf("emit %d literals\n", ll)
|
||
|
}
|
||
|
d += emitLiteralGo(dst[d:], src[s:s+ll])
|
||
|
s += ll
|
||
|
uncompressed += ll
|
||
|
}
|
||
|
|
||
|
// Check if we are done...
|
||
|
if s == len(src) && ml == lz4MinMatch {
|
||
|
break
|
||
|
}
|
||
|
// 2 byte offset
|
||
|
if s >= len(src)-2 {
|
||
|
if debug {
|
||
|
fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
offset := binary.LittleEndian.Uint16(src[s:])
|
||
|
s += 2
|
||
|
if offset == 0 {
|
||
|
if debug {
|
||
|
fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
if int(offset) > uncompressed {
|
||
|
if debug {
|
||
|
fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed)
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
|
||
|
if ml == lz4MinMatch+15 {
|
||
|
for {
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
val := src[s]
|
||
|
s++
|
||
|
ml += int(val)
|
||
|
if val != 255 {
|
||
|
if s >= len(src) {
|
||
|
if debug {
|
||
|
fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src))
|
||
|
}
|
||
|
return nil, 0, ErrCorrupt
|
||
|
}
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if debug {
|
||
|
fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset)
|
||
|
}
|
||
|
length := ml
|
||
|
// d += emitCopyNoRepeat(dst[d:], int(offset), ml)
|
||
|
for length > 0 {
|
||
|
if d >= dLimit {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
|
||
|
// Offset no more than 2 bytes.
|
||
|
if length > 64 {
|
||
|
// Emit a length 64 copy, encoded as 3 bytes.
|
||
|
dst[d+2] = uint8(offset >> 8)
|
||
|
dst[d+1] = uint8(offset)
|
||
|
dst[d+0] = 63<<2 | tagCopy2
|
||
|
length -= 64
|
||
|
d += 3
|
||
|
continue
|
||
|
}
|
||
|
if length >= 12 || offset >= 2048 || length < 4 {
|
||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||
|
dst[d+2] = uint8(offset >> 8)
|
||
|
dst[d+1] = uint8(offset)
|
||
|
dst[d+0] = uint8(length-1)<<2 | tagCopy2
|
||
|
d += 3
|
||
|
break
|
||
|
}
|
||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||
|
dst[d+1] = uint8(offset)
|
||
|
dst[d+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||
|
d += 2
|
||
|
break
|
||
|
}
|
||
|
uncompressed += ml
|
||
|
if d > dLimit {
|
||
|
return nil, 0, ErrDstTooSmall
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return dst[:d], uncompressed, nil
|
||
|
}
|
||
|
|
||
|
// emitRepeat writes a repeat chunk and returns the number of bytes written.
|
||
|
// Length must be at least 4 and < 1<<24
|
||
|
func emitRepeat16(dst []byte, offset uint16, length int) int {
|
||
|
// Repeat offset, make length cheaper
|
||
|
length -= 4
|
||
|
if length <= 4 {
|
||
|
dst[0] = uint8(length)<<2 | tagCopy1
|
||
|
dst[1] = 0
|
||
|
return 2
|
||
|
}
|
||
|
if length < 8 && offset < 2048 {
|
||
|
// Encode WITH offset
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1
|
||
|
return 2
|
||
|
}
|
||
|
if length < (1<<8)+4 {
|
||
|
length -= 4
|
||
|
dst[2] = uint8(length)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 5<<2 | tagCopy1
|
||
|
return 3
|
||
|
}
|
||
|
if length < (1<<16)+(1<<8) {
|
||
|
length -= 1 << 8
|
||
|
dst[3] = uint8(length >> 8)
|
||
|
dst[2] = uint8(length >> 0)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 6<<2 | tagCopy1
|
||
|
return 4
|
||
|
}
|
||
|
const maxRepeat = (1 << 24) - 1
|
||
|
length -= 1 << 16
|
||
|
left := 0
|
||
|
if length > maxRepeat {
|
||
|
left = length - maxRepeat + 4
|
||
|
length = maxRepeat - 4
|
||
|
}
|
||
|
dst[4] = uint8(length >> 16)
|
||
|
dst[3] = uint8(length >> 8)
|
||
|
dst[2] = uint8(length >> 0)
|
||
|
dst[1] = 0
|
||
|
dst[0] = 7<<2 | tagCopy1
|
||
|
if left > 0 {
|
||
|
return 5 + emitRepeat16(dst[5:], offset, left)
|
||
|
}
|
||
|
return 5
|
||
|
}
|
||
|
|
||
|
// emitCopy writes a copy chunk and returns the number of bytes written.
|
||
|
//
|
||
|
// It assumes that:
|
||
|
//
|
||
|
// dst is long enough to hold the encoded bytes
|
||
|
// 1 <= offset && offset <= math.MaxUint16
|
||
|
// 4 <= length && length <= math.MaxUint32
|
||
|
func emitCopy16(dst []byte, offset uint16, length int) int {
|
||
|
// Offset no more than 2 bytes.
|
||
|
if length > 64 {
|
||
|
off := 3
|
||
|
if offset < 2048 {
|
||
|
// emit 8 bytes as tagCopy1, rest as repeats.
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1
|
||
|
length -= 8
|
||
|
off = 2
|
||
|
} else {
|
||
|
// Emit a length 60 copy, encoded as 3 bytes.
|
||
|
// Emit remaining as repeat value (minimum 4 bytes).
|
||
|
dst[2] = uint8(offset >> 8)
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = 59<<2 | tagCopy2
|
||
|
length -= 60
|
||
|
}
|
||
|
// Emit remaining as repeats, at least 4 bytes remain.
|
||
|
return off + emitRepeat16(dst[off:], offset, length)
|
||
|
}
|
||
|
if length >= 12 || offset >= 2048 {
|
||
|
// Emit the remaining copy, encoded as 3 bytes.
|
||
|
dst[2] = uint8(offset >> 8)
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(length-1)<<2 | tagCopy2
|
||
|
return 3
|
||
|
}
|
||
|
// Emit the remaining copy, encoded as 2 bytes.
|
||
|
dst[1] = uint8(offset)
|
||
|
dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
// emitLiteral writes a literal chunk and returns the number of bytes written.
|
||
|
//
|
||
|
// It assumes that:
|
||
|
//
|
||
|
// dst is long enough to hold the encoded bytes
|
||
|
// 0 <= len(lit) && len(lit) <= math.MaxUint32
|
||
|
func emitLiteralGo(dst, lit []byte) int {
|
||
|
if len(lit) == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
i, n := 0, uint(len(lit)-1)
|
||
|
switch {
|
||
|
case n < 60:
|
||
|
dst[0] = uint8(n)<<2 | tagLiteral
|
||
|
i = 1
|
||
|
case n < 1<<8:
|
||
|
dst[1] = uint8(n)
|
||
|
dst[0] = 60<<2 | tagLiteral
|
||
|
i = 2
|
||
|
case n < 1<<16:
|
||
|
dst[2] = uint8(n >> 8)
|
||
|
dst[1] = uint8(n)
|
||
|
dst[0] = 61<<2 | tagLiteral
|
||
|
i = 3
|
||
|
case n < 1<<24:
|
||
|
dst[3] = uint8(n >> 16)
|
||
|
dst[2] = uint8(n >> 8)
|
||
|
dst[1] = uint8(n)
|
||
|
dst[0] = 62<<2 | tagLiteral
|
||
|
i = 4
|
||
|
default:
|
||
|
dst[4] = uint8(n >> 24)
|
||
|
dst[3] = uint8(n >> 16)
|
||
|
dst[2] = uint8(n >> 8)
|
||
|
dst[1] = uint8(n)
|
||
|
dst[0] = 63<<2 | tagLiteral
|
||
|
i = 5
|
||
|
}
|
||
|
return i + copy(dst[i:], lit)
|
||
|
}
|