diff --git a/tape/types.go b/tape/types.go index 32a2e59..55a19ec 100644 --- a/tape/types.go +++ b/tape/types.go @@ -10,6 +10,7 @@ const uint16Max = 0xFFFF type Error string; const ( ErrWrongBufferLength Error = "wrong buffer length" ErrDataTooLarge Error = "data too large" + ErrGBEUNotTerminated Error = "GBEU not terminated" ) // Error implements the error interface. @@ -25,6 +26,8 @@ type Int16 interface { ~uint16 | ~int16 } type Int32 interface { ~uint32 | ~int32 } // Int64 is any 64-bit integer. type Int64 interface { ~uint64 | ~int64 } +// UInt is any unsigned integer. +type UInt interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 } // String is any string. type String interface { ~string } @@ -101,6 +104,51 @@ func EncodeI64[T Int64](buffer []byte, value T) error { return nil } +// DecodeGBEU decodes an 8 to 64 bit growing integer into the given buffer. +func DecodeGBEU[T UInt](data []byte) (T, error) { + var value uint64 + for _, chunk := range data { + value *= 0x80 + value += uint64(chunk & 0x7F) + ccb := chunk >> 7 + if ccb == 0 { + return T(value), nil + } + } + return 0, fmt.Errorf("decoding GBEU: %w", ErrGBEUNotTerminated) +} + +// EncodeGBEU encodes an 8 to 64 bit growing integer into a given buffer. +func EncodeGBEU[T UInt] (buffer []byte, value T) (error) { + window := (GBEUSize(value) - 1) * 7 + + index := 0 + for window >= 0 { + if index >= len(buffer) { return fmt.Errorf("encoding GBEU: %w", ErrWrongBufferLength) } + + chunk := uint8(value >> window) & 0x7F + if window > 0 { + chunk |= 0x80 + } else { + } + buffer[index] = chunk + + index += 1 + window -= 7 + } + return nil +} + +// GBEUSize returns the size (in octets) of a GBEU integer. +func GBEUSize[T UInt] (value T) int { + length := 0 + for { + value >>= 7 + length ++ + if value == 0 { return length } + } +} + // DecodeString decodes a string from the given data. func DecodeString[T String](data []byte) (T, error) { return T(data), nil diff --git a/tape/types_test.go b/tape/types_test.go index 994a586..e80b23b 100644 --- a/tape/types_test.go +++ b/tape/types_test.go @@ -1,5 +1,6 @@ package tape +import "fmt" import "slices" import "errors" import "testing" @@ -99,6 +100,115 @@ func TestI64(test *testing.T) { } } +func TestGBEU(test *testing.T) { + var buffer = [16]byte { + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + } + err := EncodeGBEU[uint64](buffer[:0], 5) + if err == nil { test.Fatal("no error") } + if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) } + err = EncodeGBEU[uint64](buffer[:2], 5555555) + if err == nil { test.Fatal("no error") } + if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) } + _, err = DecodeGBEU[uint64](buffer[:0]) + if err == nil { test.Fatal("no error") } + if err.Error() != "decoding GBEU: GBEU not terminated" { test.Fatal(err) } + + err = EncodeGBEU[uint64](buffer[:], 0x97) + if err != nil { test.Fatal(err) } + if correct, got := []byte { 0x81, 0x17 }, buffer[:2]; !slices.Equal(correct, got) { + message := "not equal:" + for _, item := range got { + message = fmt.Sprintf("%s %x", message, item) + } + test.Fatal(message) + } + decoded, err := DecodeGBEU[uint64](buffer[:]) + if err != nil { test.Fatal(err) } + if correct, got := uint64(0x97), decoded; correct != got { + test.Fatalf("not equal: %x", got) + } + + err = EncodeGBEU[uint64](buffer[:], 0x123456) + if err != nil { test.Fatal(err) } + if correct, got := []byte { 0xc8, 0xe8, 0x56 }, buffer[:3]; !slices.Equal(correct, got) { + message := "not equal:" + for _, item := range got { + message = fmt.Sprintf("%s %x", message, item) + } + test.Fatal(message) + } + decoded, err = DecodeGBEU[uint64](buffer[:]) + if err != nil { test.Fatal(err) } + if correct, got := uint64(0x123456), decoded; correct != got { + test.Fatalf("not equal: %x", got) + } + + maxGBEU64 := []byte { 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F } + err = EncodeGBEU[uint64](buffer[:], 0xFFFFFFFFFFFFFFFF) + if err != nil { test.Fatal(err) } + if correct, got := maxGBEU64, buffer[:10]; !slices.Equal(correct, got) { + message := "not equal:" + for _, item := range got { + message = fmt.Sprintf("%s %x", message, item) + } + test.Fatal(message) + } + decoded, err = DecodeGBEU[uint64](buffer[:]) + if err != nil { test.Fatal(err) } + if correct, got := uint64(0xFFFFFFFFFFFFFFFF), decoded; correct != got { + test.Fatalf("not equal: %x", got) + } + + err = EncodeGBEU[uint64](buffer[:], 11) + if err != nil { test.Fatal(err) } + if correct, got := byte(0xb), buffer[0]; correct != got { + test.Fatal("not equal:", got) + } + decoded, err = DecodeGBEU[uint64](buffer[:]) + if err != nil { test.Fatal(err) } + if correct, got := uint64(0xb), decoded; correct != got { + test.Fatalf("not equal: %x", got) + } + + + for _ = range largeNumberNTestRounds { + buffer = [16]byte { } + number := uint64(rand.Int()) + err := EncodeGBEU[uint64](buffer[:], number) + if err != nil { test.Fatal(err) } + decoded, err := DecodeGBEU[uint64](buffer[:]) + if err != nil { test.Fatal(err) } + if decoded != number { + test.Error("not equal:") + test.Errorf("%d != %d", decoded, number) + test.Errorf("%x != %x", decoded, number) + test.Fatal(buffer) + } + } +} + +func TestGBEUSize(test *testing.T) { + if correct, got := 3, GBEUSize(uint(0x100000)); correct != got { + test.Fatal("not equal:", got) + } + if correct, got := 3, GBEUSize(uint(0x123456)); correct != got { + test.Fatal("not equal:", got) + } + if correct, got := 4, GBEUSize(uint(0x223456)); correct != got { + test.Fatal("not equal:", got) + } + if correct, got := 1, GBEUSize(uint(0)); correct != got { + test.Fatal("not equal:", got) + } + if correct, got := 1, GBEUSize(uint(127)); correct != got { + test.Fatal("not equal:", got) + } +} + func TestString(test *testing.T) { var buffer [16]byte err := EncodeString[string](buffer[:], "hello")