diff --git a/tape/array.go b/tape/array.go new file mode 100644 index 0000000..1d933e3 --- /dev/null +++ b/tape/array.go @@ -0,0 +1,206 @@ +package tape + +import "fmt" +import "iter" +import "slices" + +// encoding and decoding functions must not make any allocations + +func DecodeArray(data []byte, itemLength int) iter.Seq[[]byte] { + return slices.Chunk(data, itemLength) +} + +func EncodeArray(data []byte, items ...[]byte) (n int, err error) { + for _, item := range items { + if n >= len(data) { return n, ErrWrongBufferLength } + copy(data[n:], item) + n += len(item) + } + return n, nil +} + +func ArraySize(length, itemLength int) int { + return length * itemLength +} + +// DecodeStringArray decodes a packed string array from the given data. +func DecodeStringArray[T String](data []byte) (result []T, n int, err error) { + for len(data) > 0 { + if len(data) < 2 { return nil, n, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) } + itemSize16, nn, _ := DecodeI16[uint16](data[:2]) + itemSize := int(itemSize16) + n += nn + data = data[nn:] + if len(data) < itemSize { return nil, n, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) } + result = append(result, T(data[:itemSize])) + data = data[itemSize:] + n += itemSize + } + return result, n, nil +} + +// EncodeStringArray encodes a packed string array into the given buffer. +func EncodeStringArray[T String](buffer []byte, value []T) (n int, err error) { + for _, item := range value { + length, err := StringSize(item) + if err != nil { return n, err } + if len(buffer) < 2 + length { return n, fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) } + EncodeI16(buffer[:2], uint16(length)) + buffer = buffer[2:] + copy(buffer, item) + buffer = buffer[length:] + n += 2 + length + } + if len(buffer) > 0 { return n, fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) } + return n, 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) (result []T, n int, err error) { + result = make([]T, len(data)) + for index, item := range data { + result[index] = T(item) + } + return result, len(data), nil +} + +// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer. +func EncodeI8Array[T Int8](buffer []byte, value []T) (n int, err error) { + if len(buffer) != len(value) { return 0, fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) } + for index, item := range value { + buffer[index] = byte(item) + } + return len(buffer), 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) (value []T, n int, err error) { + if len(data) % 2 != 0 { return nil, 0, 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, len(data) / 2, nil +} + +// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer. +func EncodeI16Array[T Int16](buffer []byte, value []T) (n int, err error) { + if len(buffer) != len(value) * 2 { return 0, fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) } + for _, item := range value { + buffer[0] = byte(item >> 8) + buffer[1] = byte(item) + buffer = buffer[2:] + } + return len(value) * 2, 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) (value []T, n int, err error) { + if len(data) % 4 != 0 { return nil, 0, 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, len(data) / 4, nil +} + +// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer. +func EncodeI32Array[T Int32](buffer []byte, value []T) (n int, err error) { + if len(buffer) != len(value) * 4 { return 0, 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 len(value) * 4, 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) (value []T, n int, err error) { + if len(data) % 8 != 0 { return nil, 0, 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, len(data) / 8, nil +} + +// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer. +func EncodeI64Array[T Int64](buffer []byte, value []T) (n int, err error) { + if len(buffer) != len(value) * 8 { return 0, 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 len(value) * 8, 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 +} diff --git a/tape/array_test.go b/tape/array_test.go new file mode 100644 index 0000000..004cf85 --- /dev/null +++ b/tape/array_test.go @@ -0,0 +1,131 @@ +package tape + +// import "fmt" +import "slices" +// import "errors" +import "testing" +import "math/rand" + +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) + } + } +} + diff --git a/tape/types.go b/tape/types.go index 527f214..8a2c9d6 100644 --- a/tape/types.go +++ b/tape/types.go @@ -169,188 +169,6 @@ func StringSize[T String](value T) (int, error) { return len(value), nil } -// DecodeStringArray decodes a packed string array from the given data. -func DecodeStringArray[T String](data []byte) (result []T, n int, err error) { - for len(data) > 0 { - if len(data) < 2 { return nil, n, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) } - itemSize16, nn, _ := DecodeI16[uint16](data[:2]) - itemSize := int(itemSize16) - n += nn - data = data[nn:] - if len(data) < itemSize { return nil, n, fmt.Errorf("decoding []string: %w", ErrWrongBufferLength) } - result = append(result, T(data[:itemSize])) - data = data[itemSize:] - n += itemSize - } - return result, n, nil -} - -// EncodeStringArray encodes a packed string array into the given buffer. -func EncodeStringArray[T String](buffer []byte, value []T) (n int, err error) { - for _, item := range value { - length, err := StringSize(item) - if err != nil { return n, err } - if len(buffer) < 2 + length { return n, fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) } - EncodeI16(buffer[:2], uint16(length)) - buffer = buffer[2:] - copy(buffer, item) - buffer = buffer[length:] - n += 2 + length - } - if len(buffer) > 0 { return n, fmt.Errorf("encoding []string: %w", ErrWrongBufferLength) } - return n, 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) (result []T, n int, err error) { - result = make([]T, len(data)) - for index, item := range data { - result[index] = T(item) - } - return result, len(data), nil -} - -// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer. -func EncodeI8Array[T Int8](buffer []byte, value []T) (n int, err error) { - if len(buffer) != len(value) { return 0, fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) } - for index, item := range value { - buffer[index] = byte(item) - } - return len(buffer), 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) (value []T, n int, err error) { - if len(data) % 2 != 0 { return nil, 0, 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, len(data) / 2, nil -} - -// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer. -func EncodeI16Array[T Int16](buffer []byte, value []T) (n int, err error) { - if len(buffer) != len(value) * 2 { return 0, fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) } - for _, item := range value { - buffer[0] = byte(item >> 8) - buffer[1] = byte(item) - buffer = buffer[2:] - } - return len(value) * 2, 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) (value []T, n int, err error) { - if len(data) % 4 != 0 { return nil, 0, 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, len(data) / 4, nil -} - -// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer. -func EncodeI32Array[T Int32](buffer []byte, value []T) (n int, err error) { - if len(buffer) != len(value) * 4 { return 0, 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 len(value) * 4, 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) (value []T, n int, err error) { - if len(data) % 8 != 0 { return nil, 0, 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, len(data) / 8, nil -} - -// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer. -func EncodeI64Array[T Int64](buffer []byte, value []T) (n int, err error) { - if len(buffer) != len(value) * 8 { return 0, 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 len(value) * 8, 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) {