Initial commit

This commit is contained in:
2025-01-09 02:31:15 -05:00
commit 1bc804190e
18 changed files with 2104 additions and 0 deletions

83
tape/pairs.go Normal file
View File

@@ -0,0 +1,83 @@
package tape
import "iter"
// DecodePairs decodes message tag/value pairs from a byte slice. It returns an
// iterator over all pairs, where the first value is the tag and the second is
// the value. If data yielded by the iterator is retained, it must be copied
// first.
func DecodePairs(data []byte) (iter.Seq2[uint16, []byte], error) {
// determine section bounds
if len(data) < 2 { return nil, ErrDataTooLarge }
length16, _ := DecodeI16[uint16](data[0:2])
data = data[2:]
length := int(length16)
headerSize := length * 4
if len(data) < headerSize { return nil, ErrDataTooLarge }
valuesData := data[headerSize:]
// ensure the value buffer is big enough
var valuesSize int
for index := range length {
offset := index * 4
end, _ := DecodeI16[uint16](data[offset + 2:offset + 4])
valuesSize = int(end)
}
if valuesSize > len(valuesData) {
return nil, ErrDataTooLarge
}
// return iterator
return func(yield func(uint16, []byte) bool) {
start := uint16(0)
for index := range length {
offset := index * 4
key , _ := DecodeI16[uint16](data[offset + 0:offset + 2])
end, _ := DecodeI16[uint16](data[offset + 2:offset + 4])
// if nextValuesOffset < len(valuesData) {
if !yield(key, valuesData[start:end]) {
return
}
// } else {
// if !yield(key, nil) {
// return
// }
// }
start = end
}
}, nil
}
// EncodePairs encodes message tag/value pairs into a byte slice.
func EncodePairs(pairs map[uint16] []byte) ([]byte, error) {
// determine section bounds
headerSize := 2 + len(pairs) * 4
valuesSize := 0
for _, value := range pairs {
valuesSize += len(value)
}
// generate data
buffer := make([]byte, headerSize + valuesSize)
length16, ok := U16CastSafe(len(pairs))
if !ok { return nil, ErrDataTooLarge }
EncodeI16[uint16](buffer[0:2], length16)
index := 0
end := headerSize
for key, value := range pairs {
start := end
end += len(value)
tagOffset := 2 + index * 4
end16, ok := U16CastSafe(end - headerSize)
if !ok { return nil, ErrDataTooLarge }
// write tag and length
EncodeI16[uint16](buffer[tagOffset + 0:tagOffset + 2], key)
EncodeI16[uint16](buffer[tagOffset + 2:tagOffset + 4], end16)
// write value
copy(buffer[start:end], value)
index ++
}
return buffer, nil
}

62
tape/pairs_test.go Normal file
View File

@@ -0,0 +1,62 @@
package tape
import "slices"
import "testing"
func TestDecodePairs(test *testing.T) {
pairs := map[uint16] []byte {
3894: []byte("foo"),
7: []byte("br"),
}
got, err := DecodePairs([]byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
if err != nil { test.Fatal(err) }
length := 0
for key, value := range got {
test.Log(key, value)
if !slices.Equal(pairs[key], value) { test.Fatal("not equal") }
length ++
}
test.Log("length")
if length != len(pairs) { test.Fatal("wrong length") }
}
func TestEncodePairs(test *testing.T) {
pairs := map[uint16] []byte {
3894: []byte("foo"),
7: []byte("br"),
}
got, err := EncodePairs(pairs)
if err != nil { test.Fatal(err) }
test.Log(got)
valid := slices.Equal(got, []byte {
0, 2,
15, 54, 0, 3,
0, 7, 0, 5,
102, 111, 111,
98, 114}) ||
slices.Equal(got, []byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
if !valid { test.Fatal("not equal") }
}
func FuzzDecodePairs(fuzz *testing.F) {
fuzz.Add([]byte {
0, 2,
0, 7, 0, 2,
15, 54, 0, 5,
98, 114,
102, 111, 111})
fuzz.Fuzz(func(t *testing.T, buffer []byte) {
// ensure it does not panic :P
DecodePairs(buffer)
})
}

311
tape/types.go Normal file
View File

@@ -0,0 +1,311 @@
// Package tape implements Table Pair Encoding.
package tape
import "fmt"
const dataMaxSize = 0xFFFF
const uint16Max = 0xFFFF
// Error enumerates common errors in this package.
type Error string; const (
ErrWrongBufferLength Error = "wrong buffer length"
ErrDataTooLarge Error = "data too large"
)
// Error implements the error interface.
func (err Error) Error() string {
return string(err)
}
// Int8 is any 8-bit integer.
type Int8 interface { ~uint8 | ~int8 }
// Int16 is any 16-bit integer.
type Int16 interface { ~uint16 | ~int16 }
// Int32 is any 32-bit integer.
type Int32 interface { ~uint32 | ~int32 }
// Int64 is any 64-bit integer.
type Int64 interface { ~uint64 | ~int64 }
// String is any string.
type String interface { ~string }
// DecodeI8 decodes an 8 bit integer from the given data.
func DecodeI8[T Int8](data []byte) (T, error) {
if len(data) != 1 { return 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
return T(data[0]), nil
}
// EncodeI8 encodes an 8 bit integer into the given buffer.
func EncodeI8[T Int8](buffer []byte, value T) error {
if len(buffer) != 1 { return fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
buffer[0] = byte(value)
return nil
}
// DecodeI16 decodes a 16 bit integer from the given data.
func DecodeI16[T Int16](data []byte) (T, error) {
if len(data) != 2 { return 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
return T(data[0]) << 8 | T(data[1]), nil
}
// EncodeI16 encodes a 16 bit integer into the given buffer.
func EncodeI16[T Int16](buffer []byte, value T) error {
if len(buffer) != 2 { return fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 8)
buffer[1] = byte(value)
return nil
}
// DecodeI32 decodes a 32 bit integer from the given data.
func DecodeI32[T Int32](data []byte) (T, error) {
if len(data) != 4 { return 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
return T(data[0]) << 24 |
T(data[1]) << 16 |
T(data[2]) << 8 |
T(data[3]), nil
}
// EncodeI32 encodes a 32 bit integer into the given buffer.
func EncodeI32[T Int32](buffer []byte, value T) error {
if len(buffer) != 4 { return fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 24)
buffer[1] = byte(value >> 16)
buffer[2] = byte(value >> 8)
buffer[3] = byte(value)
return nil
}
// DecodeI64 decodes a 64 bit integer from the given data.
func DecodeI64[T Int64](data []byte) (T, error) {
if len(data) != 8 { return 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
return T(data[0]) << 56 |
T(data[1]) << 48 |
T(data[2]) << 40 |
T(data[3]) << 32 |
T(data[4]) << 24 |
T(data[5]) << 16 |
T(data[6]) << 8 |
T(data[7]), nil
}
// EncodeI64 encodes a 64 bit integer into the given buffer.
func EncodeI64[T Int64](buffer []byte, value T) error {
if len(buffer) != 8 { return fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 56)
buffer[1] = byte(value >> 48)
buffer[2] = byte(value >> 40)
buffer[3] = byte(value >> 32)
buffer[4] = byte(value >> 24)
buffer[5] = byte(value >> 16)
buffer[6] = byte(value >> 8)
buffer[7] = byte(value)
return nil
}
// DecodeString decodes a string from the given data.
func DecodeString[T String](data []byte) (T, error) {
return T(data), nil
}
// EncodeString encodes a string into the given buffer.
func EncodeString[T String](data []byte, value T) error {
if len(data) != len(value) { return fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
copy(data, value)
return nil
}
// StringSize returns the size of a string. Returns 0 and an error if the size
// is too large.
func StringSize[T String](value T) (int, error) {
if len(value) > dataMaxSize { return 0, ErrDataTooLarge }
return len(value), nil
}
// DecodeStringArray decodes a packed string array from the given data.
func DecodeStringArray[T String](data []byte) ([]T, error) {
result := []T { }
for len(data) > 0 {
if len(data) < 2 { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
itemSize16, _ := DecodeI16[uint16](data[:2])
itemSize := int(itemSize16)
data = data[2:]
if len(data) < itemSize { return nil, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) }
result = append(result, T(data[:itemSize]))
data = data[itemSize:]
}
return result, nil
}
// EncodeStringArray encodes a packed string array into the given buffer.
func EncodeStringArray[T String](buffer []byte, value []T) error {
for _, item := range value {
length, err := StringSize(item)
if err != nil { return err }
if len(buffer) < 2 + length { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
EncodeI16(buffer[:2], uint16(length))
buffer = buffer[2:]
copy(buffer, item)
buffer = buffer[length:]
}
if len(buffer) > 0 { return fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) }
return nil
}
// StringArraySize returns the size of a packed string array. Returns 0 and an
// error if the size is too large.
func StringArraySize[T String](value []T) (int, error) {
total := 0
for _, item := range value {
total += 2 + len(item)
}
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI8Array decodes a packed array of 8 bit integers from the given data.
func DecodeI8Array[T Int8](data []byte) ([]T, error) {
result := make([]T, len(data))
for index, item := range data {
result[index] = T(item)
}
return result, nil
}
// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer.
func EncodeI8Array[T Int8](buffer []byte, value []T) error {
if len(buffer) != len(value) { return fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) }
for index, item := range value {
buffer[index] = byte(item)
}
return nil
}
// I8ArraySize returns the size of a packed 8 bit integer array. Returns 0 and
// an error if the size is too large.
func I8ArraySize[T Int8](value []T) (int, error) {
total := len(value)
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI16Array decodes a packed array of 16 bit integers from the given data.
func DecodeI16Array[T Int16](data []byte) ([]T, error) {
if len(data) % 2 != 0 { return nil, fmt.Errorf("decoding []int16: %w", ErrWrongBufferLength) }
length := len(data) / 2
result := make([]T, length)
for index := range length {
offset := index * 2
result[index] = T(data[offset]) << 8 | T(data[offset + 1])
}
return result, nil
}
// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer.
func EncodeI16Array[T Int16](buffer []byte, value []T) error {
if len(buffer) != len(value) * 2 { return fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 8)
buffer[1] = byte(item)
buffer = buffer[2:]
}
return nil
}
// I16ArraySize returns the size of a packed 16 bit integer array. Returns 0 and
// an error if the size is too large.
func I16ArraySize[T Int16](value []T) (int, error) {
total := len(value) * 2
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI32Array decodes a packed array of 32 bit integers from the given data.
func DecodeI32Array[T Int32](data []byte) ([]T, error) {
if len(data) % 4 != 0 { return nil, fmt.Errorf("decoding []int32: %w", ErrWrongBufferLength) }
length := len(data) / 4
result := make([]T, length)
for index := range length {
offset := index * 4
result[index] =
T(data[offset + 0]) << 24 |
T(data[offset + 1]) << 16 |
T(data[offset + 2]) << 8 |
T(data[offset + 3])
}
return result, nil
}
// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer.
func EncodeI32Array[T Int32](buffer []byte, value []T) error {
if len(buffer) != len(value) * 4 { return fmt.Errorf("encoding []int32: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 24)
buffer[1] = byte(item >> 16)
buffer[2] = byte(item >> 8)
buffer[3] = byte(item)
buffer = buffer[4:]
}
return nil
}
// I32ArraySize returns the size of a packed 32 bit integer array. Returns 0 and
// an error if the size is too large.
func I32ArraySize[T Int32](value []T) (int, error) {
total := len(value) * 4
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI64Array decodes a packed array of 32 bit integers from the given data.
func DecodeI64Array[T Int64](data []byte) ([]T, error) {
if len(data) % 8 != 0 { return nil, fmt.Errorf("decoding []int64: %w", ErrWrongBufferLength) }
length := len(data) / 8
result := make([]T, length)
for index := range length {
offset := index * 8
result[index] =
T(data[offset + 0]) << 56 |
T(data[offset + 1]) << 48 |
T(data[offset + 2]) << 40 |
T(data[offset + 3]) << 32 |
T(data[offset + 4]) << 24 |
T(data[offset + 5]) << 16 |
T(data[offset + 6]) << 8 |
T(data[offset + 7])
}
return result, nil
}
// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer.
func EncodeI64Array[T Int64](buffer []byte, value []T) error {
if len(buffer) != len(value) * 8 { return fmt.Errorf("encoding []int64: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 56)
buffer[1] = byte(item >> 48)
buffer[2] = byte(item >> 40)
buffer[3] = byte(item >> 32)
buffer[4] = byte(item >> 24)
buffer[5] = byte(item >> 16)
buffer[6] = byte(item >> 8)
buffer[7] = byte(item)
buffer = buffer[8:]
}
return nil
}
// I64ArraySize returns the size of a packed 64 bit integer array. Returns 0 and
// an error if the size is too large.
func I64ArraySize[T Int64](value []T) (int, error) {
total := len(value) * 8
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// U16CastSafe safely casts an integer to a uint16. If an overflow or underflow
// occurs, it will return (0, false).
func U16CastSafe(n int) (uint16, bool) {
if n < uint16Max && n > 0 {
return uint16(n), true
} else {
return 0, false
}
}

275
tape/types_test.go Normal file
View File

@@ -0,0 +1,275 @@
package tape
import "slices"
import "errors"
import "testing"
import "math/rand"
const largeNumberNTestRounds = 2048
const randStringBytes = "-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func TestI8(test *testing.T) {
var buffer [16]byte
err := EncodeI8[uint8](buffer[:], 5)
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
err = EncodeI8[uint8](buffer[:0], 5)
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI8[uint8](buffer[:])
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI8[uint8](buffer[:0])
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
for number := range uint8(255) {
err := EncodeI8[uint8](buffer[:1], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI8[uint8](buffer[:1])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI16(test *testing.T) {
var buffer [16]byte
err := EncodeI16[uint16](buffer[:], 5)
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
err = EncodeI16[uint16](buffer[:0], 5)
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI16[uint16](buffer[:])
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI16[uint16](buffer[:0])
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint16(rand.Int())
err := EncodeI16[uint16](buffer[:2], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI16[uint16](buffer[:2])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI32(test *testing.T) {
var buffer [16]byte
err := EncodeI32[uint32](buffer[:], 5)
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
err = EncodeI32[uint32](buffer[:0], 5)
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI32[uint32](buffer[:])
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI32[uint32](buffer[:0])
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint32(rand.Int())
err := EncodeI32[uint32](buffer[:4], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI32[uint32](buffer[:4])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI64(test *testing.T) {
var buffer [16]byte
err := EncodeI64[uint64](buffer[:], 5)
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
err = EncodeI64[uint64](buffer[:0], 5)
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI64[uint64](buffer[:])
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
_, err = DecodeI64[uint64](buffer[:0])
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint64(rand.Int())
err := EncodeI64[uint64](buffer[:8], number)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI64[uint64](buffer[:8])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestString(test *testing.T) {
var buffer [16]byte
err := EncodeString[string](buffer[:], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
err = EncodeString[string](buffer[:0], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
_, err = DecodeString[string](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeString[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
length := rand.Intn(16)
str := randString(length)
err := EncodeString[string](buffer[:length], str)
if err != nil { test.Fatal(err) }
decoded, err := DecodeString[string](buffer[:length])
if err != nil { test.Fatal(err) }
if decoded != str {
test.Fatalf("%s != %s", decoded, str)
}
}
}
func TestI8Array(test *testing.T) {
var buffer [64]byte
err := EncodeI8Array[uint8](buffer[:], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
err = EncodeI8Array[uint8](buffer[:0], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI8Array[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI8Array[uint8](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint8](rand.Intn(16))
length, _ := I8ArraySize(array)
if length != len(array) { test.Fatalf("%d != %d", length, len(array)) }
err := EncodeI8Array[uint8](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI8Array[uint8](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI16Array(test *testing.T) {
var buffer [128]byte
err := EncodeI16Array[uint16](buffer[:], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
err = EncodeI16Array[uint16](buffer[:0], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI16Array[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI16Array[uint16](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint16](rand.Intn(16))
length, _ := I16ArraySize(array)
if length != 2 * len(array) { test.Fatalf("%d != %d", length, 2 * len(array)) }
err := EncodeI16Array[uint16](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI16Array[uint16](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI32Array(test *testing.T) {
var buffer [256]byte
err := EncodeI32Array[uint32](buffer[:], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
err = EncodeI32Array[uint32](buffer[:0], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI32Array[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI32Array[uint32](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint32](rand.Intn(16))
length, _ := I32ArraySize(array)
if length != 4 * len(array) { test.Fatalf("%d != %d", length, 4 * len(array)) }
err := EncodeI32Array[uint32](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI32Array[uint32](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI64Array(test *testing.T) {
var buffer [512]byte
err := EncodeI64Array[uint64](buffer[:], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
err = EncodeI64Array[uint64](buffer[:0], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, err = DecodeI64Array[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, err = DecodeI64Array[uint64](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint64](rand.Intn(16))
length, _ := I64ArraySize(array)
if length != 8 * len(array) { test.Fatalf("%d != %d", length, 8 * len(array)) }
err := EncodeI64Array[uint64](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeI64Array[uint64](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestStringArray(test *testing.T) {
var buffer [8192]byte
err := EncodeStringArray[string](buffer[:], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
err = EncodeStringArray[string](buffer[:0], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, err = DecodeStringArray[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randStrings[string](rand.Intn(16), 16)
length, _ := StringArraySize(array)
// TODO test length
err := EncodeStringArray[string](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, err := DecodeStringArray[string](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func randString(length int) string {
buffer := make([]byte, length)
for index := range buffer {
buffer[index] = randStringBytes[rand.Intn(len(randStringBytes))]
}
return string(buffer)
}
func randInts[T interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }] (length int) []T {
buffer := make([]T, length)
for index := range buffer {
buffer[index] = T(rand.Int())
}
return buffer
}
func randStrings[T interface { ~string }] (length, maxItemLength int) []T {
buffer := make([]T, length)
for index := range buffer {
buffer[index] = T(randString(rand.Intn(maxItemLength)))
}
return buffer
}
func errIs(err error, wraps error, description string) bool {
return err != nil && (wraps == nil || errors.Is(err, wraps)) && err.Error() == description
}