The live tests reached one shared bus and assert, read and remove the mesh's own objects by their fixed names, so packages run in parallel deleted what each other read and the suite passed only one package at a time; a red suite read as noise. internal/testbus starts a server per test, linked in at the nats-server release go.mod pins, and a test holds that pin to the catalogue's bus image and to the facts snapshot's bus when there is one, so the tests never run a bus the mesh does not. The waiter test read a timing (the most connections held at one look) and now reads the state it means (the fewest held across the wait). make check runs the packages in parallel under the race detector, with a timeout.
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 && hasAsm {
|
|
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 && hasAsm {
|
|
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)
|
|
}
|