summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2024-12-27 03:09:16 +0700
committerShulhan <ms@kilabit.info>2025-01-06 00:34:26 +0700
commit8d871363ec5471be9d03c514159c17efec0baff8 (patch)
treef1f60d21f79b2910bec0e02ca52b1ea0a36187a8
parent3a01474bc37e26d79dabd095d79ad0c9b6273af2 (diff)
downloadpakakeh.go-8d871363ec5471be9d03c514159c17efec0baff8.tar.xz
lib/binary: implement buffer for reading/writing in BigEndian
BigEndianBuffer provides backing storage for writing (most of) Go native types into binary in big-endian order. The zero value of BigEndianBuffer is an empty buffer ready to use. The following basic types are supported for Write and Read: bool, byte, int, float, complex, and string. The slice and array are also supported as long as the slice's element type is one of basic types.
-rw-r--r--lib/binary/benchmark_test.go57
-rw-r--r--lib/binary/big_endian_buffer.go152
-rw-r--r--lib/binary/big_endian_buffer_test.go732
-rw-r--r--lib/binary/binary.go453
-rw-r--r--lib/binary/storage.go469
5 files changed, 1862 insertions, 1 deletions
diff --git a/lib/binary/benchmark_test.go b/lib/binary/benchmark_test.go
new file mode 100644
index 00000000..5dd9078d
--- /dev/null
+++ b/lib/binary/benchmark_test.go
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: 2024 M. Shulhan <ms@kilabit.info>
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+package binary
+
+import (
+ "bytes"
+ "encoding/binary"
+ "testing"
+)
+
+// This benchmark is taken from https://github.com/golang/go/issues/27757 .
+func BenchmarkStd_BinaryWrite_SliceByte(b *testing.B) {
+ var data = make([]byte, 2e6)
+ var buf = bytes.NewBuffer([]byte{})
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ binary.Write(buf, binary.BigEndian, data)
+ }
+}
+
+func BenchmarkBigEndianBuffer_Write_SliceByte(b *testing.B) {
+ var data = make([]byte, 2e6)
+ var beb BigEndianBuffer
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ beb.Write(data)
+ }
+}
+
+// This benchmark is taken from https://github.com/golang/go/issues/70503 .
+func BenchmarkStd_BinaryWrite_SliceFloat32(b *testing.B) {
+ var data = make([]float32, 1000)
+ for i := range data {
+ data[i] = float32(i) / 42
+ }
+ var buf bytes.Buffer
+ b.SetBytes(int64(4 * len(data)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ binary.Write(&buf, binary.BigEndian, data)
+ }
+}
+
+func BenchmarkBigEndianBuffer_Write_SliceFloat32(b *testing.B) {
+ data := make([]float32, 1000)
+ for i := range data {
+ data[i] = float32(i) / 42
+ }
+ var beb BigEndianBuffer
+ b.SetBytes(int64(4 * len(data)))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ beb.Write(data)
+ }
+}
diff --git a/lib/binary/big_endian_buffer.go b/lib/binary/big_endian_buffer.go
new file mode 100644
index 00000000..8203e36f
--- /dev/null
+++ b/lib/binary/big_endian_buffer.go
@@ -0,0 +1,152 @@
+// SPDX-FileCopyrightText: 2024 M. Shulhan <ms@kilabit.info>
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+package binary
+
+import (
+ "fmt"
+ "io"
+ "reflect"
+)
+
+// BigEndianBuffer provides backing storage for writing (most of) Go native
+// types into binary in big-endian order.
+// The zero value of BigEndianBuffer is ready to use.
+//
+// The following basic types are supported for Write and Read: bool, byte,
+// int, float, complex, and string.
+// The slice and array are also supported as long as the slice's element type
+// is one of basic types.
+//
+// For the string, slice, and array, the Write operation write the dynamic
+// length first as 4 bytes of uint32 and then followed by its content.
+//
+// For struct, each exported field is written in order.
+// Field with type pointer have single byte flag to indicate whether the value
+// is nil during write or not.
+// If pointer field is nil, the flag will be set to 0, otherwise it will be
+// set to 1.
+type BigEndianBuffer struct {
+ storage
+}
+
+// NewBigEndianBuffer creates and initializes a new [BigEndianBuffer] using
+// bin as its initial contents.
+// The new [BigEndianBuffer] takes control of the bin and the caller should
+// not use bin after this call.
+func NewBigEndianBuffer(bin []byte) *BigEndianBuffer {
+ return &BigEndianBuffer{
+ storage: storage{
+ bin: bin,
+ off: int64(len(bin)),
+ endness: EndianBig,
+ },
+ }
+}
+
+// Bytes return the backing storage.
+func (beb *BigEndianBuffer) Bytes() []byte {
+ return beb.bin
+}
+
+// Read the binary value into data by its type.
+//
+// Like any function that needs to set any type, the instance of parameter
+// data must be passed as pointer, including for slice, excepts as noted
+// below.
+//
+// Slice with length can be read without passing it as pointer,
+//
+// s := make([]int, 5)
+// Read(s) // OK.
+//
+// But array with size cannot be read without passing it as pointer,
+//
+// a := [5]int{}
+// Read(a) // Fail, panic: reflect.Value.Addr of unaddressable value.
+// Read(&a) // OK.
+func (beb *BigEndianBuffer) Read(data any) (n int, err error) {
+ var logp = `Read`
+ var refval = reflect.ValueOf(data)
+ var refkind = refval.Kind()
+ if refkind == reflect.Pointer {
+ refval = reflect.Indirect(refval)
+ refkind = refval.Kind()
+ }
+ if isKindIgnored(refkind) {
+ return 0, nil
+ }
+ if !refval.CanAddr() {
+ if refkind == reflect.Slice && !refval.IsNil() {
+ // Slice that has been created with make can be read,
+ // even if the length is 0.
+ } else {
+ // The passed data must be pointer to variable.
+ return 0, fmt.Errorf(`%s: expecting pointer to %T`,
+ logp, data)
+ }
+ }
+
+ beb.endness = EndianBig
+ n, err = decode(EndianBig, beb.bin[beb.off:], data)
+ if err != nil {
+ return 0, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ beb.off += int64(n)
+
+ return n, nil
+}
+
+// Reset the internal storage, start from empty again.
+func (beb *BigEndianBuffer) Reset() {
+ beb.bin = beb.bin[:0]
+ beb.off = 0
+}
+
+// Seek move the write and read position to the offset off.
+// It will return an error if the offset out of range.
+func (beb *BigEndianBuffer) Seek(off int64, whence int) (ret int64, err error) {
+ var logp = `Seek`
+ var size = int64(len(beb.bin))
+ if whence == io.SeekStart {
+ if off > size {
+ return 0, fmt.Errorf(`%s: offset %d out of range (0-%d)`,
+ logp, off, size)
+ }
+ beb.off = off
+ return beb.off, nil
+ }
+ if whence == io.SeekCurrent {
+ off = beb.off + off
+ if off > size || off < 0 {
+ return 0, fmt.Errorf(`%s: offset %d out of range (max %d)`,
+ logp, off, size)
+ }
+ beb.off = off
+ return beb.off, nil
+ }
+ if whence == io.SeekEnd {
+ off = size - off
+ if off > size || off < 0 {
+ return 0, fmt.Errorf(`%s: offset %d out of range (max %d)`,
+ logp, off, size)
+ }
+ beb.off = off
+ return beb.off, nil
+ }
+ return 0, fmt.Errorf(`%s: invalid whence %d`, logp, whence)
+}
+
+// Write any data to binary format.
+// The following type of data are ignored: Invalid, Uintptr, Chan, Func,
+// Interface, Map; and will return with (0, nil).
+func (beb *BigEndianBuffer) Write(data any) (n int, err error) {
+ beb.storage.endness = EndianBig
+ n, err = beb.storage.encode(data)
+ if err != nil {
+ return 0, fmt.Errorf(`Write: %w`, err)
+ }
+ return n, nil
+}
diff --git a/lib/binary/big_endian_buffer_test.go b/lib/binary/big_endian_buffer_test.go
new file mode 100644
index 00000000..e3151455
--- /dev/null
+++ b/lib/binary/big_endian_buffer_test.go
@@ -0,0 +1,732 @@
+// SPDX-FileCopyrightText: 2024 M. Shulhan <ms@kilabit.info>
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+package binary_test
+
+import (
+ "bytes"
+ "io"
+ "math"
+ "reflect"
+ "testing"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/binary"
+ "git.sr.ht/~shulhan/pakakeh.go/lib/test"
+)
+
+func TestBigEndianBuffer(t *testing.T) {
+ var listCase = []struct {
+ desc string
+ data any
+ expBin []byte
+ expN int
+ }{{
+ desc: `bool=true`,
+ data: bool(true),
+ expBin: []byte{0x01},
+ expN: 1,
+ }, {
+ desc: `bool=false`,
+ data: bool(false),
+ expBin: []byte{0x00},
+ expN: 1,
+ }, {
+ desc: `int8=127`,
+ data: int8(127),
+ expBin: []byte{0x7f},
+ expN: 1,
+ }, {
+ desc: `uint8=255`,
+ data: uint8(255),
+ expBin: []byte{0xff},
+ expN: 1,
+ }, {
+ desc: `int16=32767`,
+ data: int16(math.MaxInt16),
+ expBin: []byte{0x7f, 0xff},
+ expN: 2,
+ }, {
+ desc: `uint16=65535`,
+ data: uint16(math.MaxUint16),
+ expBin: []byte{0xff, 0xff},
+ expN: 2,
+ }, {
+ desc: `int32=2147483647`,
+ data: int32(math.MaxInt32),
+ expBin: []byte{0x7f, 0xff, 0xff, 0xff},
+ expN: 4,
+ }, {
+ desc: `uint32=4294967295`,
+ data: uint32(math.MaxUint32),
+ expBin: []byte{0xff, 0xff, 0xff, 0xff},
+ expN: 4,
+ }, {
+ desc: `int64=math.MaxInt64`,
+ data: int64(math.MaxInt64),
+ expBin: []byte{
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `uint64=math.MaxUint64`,
+ data: uint64(math.MaxUint64),
+ expBin: []byte{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `int=math.MaxInt`,
+ data: int(math.MaxInt),
+ expBin: []byte{
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `uint=math.MaxUint`,
+ data: uint(math.MaxUint),
+ expBin: []byte{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `float32=math.Float32`,
+ data: float32(math.MaxFloat32),
+ expBin: []byte{
+ 0x7f, 0x7f, 0xff, 0xff,
+ },
+ expN: 4,
+ }, {
+ desc: `float64=math.Float64`,
+ data: float64(math.MaxFloat64),
+ expBin: []byte{
+ 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `complex64=complex(1, 2)`,
+ data: complex(float32(1), float32(2)),
+ expBin: []byte{
+ 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ },
+ expN: 8,
+ }, {
+ desc: `complex128=complex(3,4)`,
+ data: complex(float64(3), float64(4)),
+ expBin: []byte{
+ 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ expN: 16,
+ }, {
+ desc: `string="日本語"`,
+ data: string(`日本語`),
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x09,
+ 0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac, 0xe8, 0xaa, 0x9e,
+ },
+ expN: 13,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ n, err := beb.Write(tcase.data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotBin = beb.Bytes()
+ test.Assert(t, tcase.desc+` Write n`, tcase.expN, n)
+ test.Assert(t, tcase.desc+` Write Bytes`, tcase.expBin, gotBin)
+
+ beb.Seek(0, io.SeekStart)
+
+ // Create new(T) based on the type of tcase.data.
+ var gotData = newWithReflect(tcase.data)
+ n, err = beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Change the *T to T back for assert.
+ gotData = reflect.Indirect(reflect.ValueOf(gotData)).
+ Interface()
+ test.Assert(t, tcase.desc+` Read n`, tcase.expN, n)
+ test.Assert(t, tcase.desc+` Read data`, tcase.data, gotData)
+ }
+}
+
+func TestBigEndianBuffer_Array(t *testing.T) {
+ var listCase = []struct {
+ desc string
+ data any
+ expBin []byte
+ expN int
+ }{{
+ desc: `[2]bool`,
+ data: [2]bool{false, true},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x01,
+ },
+ expN: 6,
+ }, {
+ desc: `[3]int32`,
+ data: [3]int32{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ },
+ expN: 16,
+ }, {
+ desc: `[2]uint64`,
+ data: [2]uint64{100, 200},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8,
+ },
+ expN: 20,
+ }, {
+ desc: `empty array`,
+ data: [0]int{},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x00,
+ },
+ expN: 4,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ t.Run(`Write `+tcase.desc, func(t *testing.T) {
+ n, err := beb.Write(tcase.data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotBin = beb.Bytes()
+ test.Assert(t, `n`, tcase.expN, n)
+ test.Assert(t, `Bytes`, tcase.expBin, gotBin)
+ })
+
+ t.Run(`Read `+tcase.desc, func(t *testing.T) {
+ beb.Seek(0, io.SeekStart)
+
+ // Create pointer to array based on the type of
+ // tcase.data.
+ var gotData = newWithReflect(tcase.data)
+ n, err := beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Change the *T to T back for assert.
+ gotData = reflect.Indirect(reflect.ValueOf(gotData)).
+ Interface()
+
+ test.Assert(t, `n`, tcase.expN, n)
+ test.Assert(t, `data`, tcase.data, gotData)
+ })
+ }
+}
+
+func TestBigEndianBuffer_IgnoredType(t *testing.T) {
+ var iface io.Writer = &bytes.Buffer{}
+
+ var listCase = []struct {
+ data any
+ desc string
+ }{{
+ desc: `nil`,
+ }, {
+ desc: `uintptr`,
+ data: uintptr(0xFF),
+ }, {
+ desc: `Chan`,
+ data: make(chan int, 1),
+ }, {
+ desc: `Func`,
+ data: func() {},
+ }, {
+ desc: `Interface`,
+ data: iface,
+ }, {
+ desc: `Map`,
+ data: map[int]int{},
+ }}
+
+ var beb binary.BigEndianBuffer
+ var expBin []byte
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ n, err := beb.Write(tcase.data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var got = beb.Bytes()
+ test.Assert(t, `n`, 0, n)
+ test.Assert(t, `Bytes`, expBin, got)
+ }
+}
+
+func TestBigEndianBuffer_Pointer(t *testing.T) {
+ var listCase = []struct {
+ desc string
+ data func() any
+ expBin []byte
+ expN int
+ }{{
+ desc: `*bool=true`,
+ data: func() any { val := bool(true); return &val },
+ expBin: []byte{0x01},
+ expN: 1,
+ }, {
+ desc: `*bool=false`,
+ data: func() any { val := bool(false); return &val },
+ expBin: []byte{0x00},
+ expN: 1,
+ }, {
+ desc: `*int8=127`,
+ data: func() any { val := int8(127); return &val },
+ expBin: []byte{0x7f},
+ expN: 1,
+ }, {
+ desc: `*uint8=255`,
+ data: func() any { val := uint8(255); return &val },
+ expBin: []byte{0xff},
+ expN: 1,
+ }, {
+ desc: `*int16=32767`,
+ data: func() any { val := int16(math.MaxInt16); return &val },
+ expBin: []byte{0x7f, 0xff},
+ expN: 2,
+ }, {
+ desc: `*uint16=65535`,
+ data: func() any { val := uint16(math.MaxUint16); return &val },
+ expBin: []byte{0xff, 0xff},
+ expN: 2,
+ }, {
+ desc: `*int32=2147483647`,
+ data: func() any { val := int32(math.MaxInt32); return &val },
+ expBin: []byte{0x7f, 0xff, 0xff, 0xff},
+ expN: 4,
+ }, {
+ desc: `*uint32=4294967295`,
+ data: func() any { val := uint32(math.MaxUint32); return &val },
+ expBin: []byte{0xff, 0xff, 0xff, 0xff},
+ expN: 4,
+ }, {
+ desc: `*int64=math.MaxInt64`,
+ data: func() any { val := int64(math.MaxInt64); return &val },
+ expBin: []byte{
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `*uint64=math.MaxUint64`,
+ data: func() any { val := uint64(math.MaxUint64); return &val },
+ expBin: []byte{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `*int=math.MaxInt`,
+ data: func() any { val := int(math.MaxInt); return &val },
+ expBin: []byte{
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `*uint=math.MaxUint`,
+ data: func() any { val := uint(math.MaxUint); return &val },
+ expBin: []byte{
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `*float32=math.Float32`,
+ data: func() any { val := float32(math.MaxFloat32); return &val },
+ expBin: []byte{
+ 0x7f, 0x7f, 0xff, 0xff,
+ },
+ expN: 4,
+ }, {
+ desc: `*float64=math.Float64`,
+ data: func() any { val := float64(math.MaxFloat64); return &val },
+ expBin: []byte{
+ 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ },
+ expN: 8,
+ }, {
+ desc: `*complex64=complex(1, 2)`,
+ data: func() any { val := complex(float32(1), float32(2)); return &val },
+ expBin: []byte{
+ 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ },
+ expN: 8,
+ }, {
+ desc: `*complex128=complex(3,4)`,
+ data: func() any { val := complex(float64(3), float64(4)); return &val },
+ expBin: []byte{
+ 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ },
+ expN: 16,
+ }, {
+ desc: `*string="日本語"`,
+ data: func() any { val := string(`日本語`); return &val },
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x09,
+ 0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac, 0xe8, 0xaa, 0x9e,
+ },
+ expN: 13,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ var data = tcase.data()
+ n, err := beb.Write(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, tcase.desc+` Write n`, tcase.expN, n)
+ var gotBin = beb.Bytes()
+ test.Assert(t, tcase.desc+` Write Bytes`, tcase.expBin, gotBin)
+
+ beb.Seek(0, io.SeekStart)
+
+ // Create new(T) based on the type of tcase.data.
+ var gotData = newWithReflect(data)
+ n, err = beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, tcase.desc+` Read n`, tcase.expN, n)
+ test.Assert(t, tcase.desc+` Read data`, data, gotData)
+ }
+}
+
+func TestBigEndianBuffer_Slices(t *testing.T) {
+ var listCase = []struct {
+ desc string
+ data any
+ expBin []byte
+ expN int
+ }{{
+ desc: `[]bool`,
+ data: []bool{false, true},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x01,
+ },
+ expN: 6,
+ }, {
+ desc: `[]int8`,
+ data: []int8{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x01, 0x02, 0x03,
+ },
+ expN: 7,
+ }, {
+ desc: `[]byte`,
+ data: []byte{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x01, 0x02, 0x03,
+ },
+ expN: 7,
+ }, {
+ desc: `[]int16`,
+ data: []int16{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
+ },
+ expN: 10,
+ }, {
+ desc: `[]uint16`,
+ data: []uint16{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
+ },
+ expN: 10,
+ }, {
+ desc: `[]int32`,
+ data: []int32{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ },
+ expN: 16,
+ }, {
+ desc: `[]uint32`,
+ data: []uint32{1, 2, 3},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ },
+ expN: 16,
+ }, {
+ desc: `[]int64`,
+ data: []int64{100, 200},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8,
+ },
+ expN: 20,
+ }, {
+ desc: `[]uint64`,
+ data: []uint64{100, 200},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8,
+ },
+ expN: 20,
+ }, {
+ desc: `[]int`,
+ data: []int{},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x00,
+ },
+ expN: 4,
+ }, {
+ desc: `[]uint`,
+ data: []uint{100, 200},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8,
+ },
+ expN: 20,
+ }, {
+ desc: `[]float32`,
+ data: []float32{3.45, 6.78},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x40, 0x5c, 0xcc, 0xcd,
+ 0x40, 0xd8, 0xf5, 0xc3,
+ },
+ expN: 12,
+ }, {
+ desc: `[]float64`,
+ data: []float64{3.45, 6.78},
+ expBin: []byte{
+ 0x00, 0x00, 0x00, 0x02,
+ 0x40, 0x0b, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a,
+ 0x40, 0x1b, 0x1e, 0xb8, 0x51, 0xeb, 0x85, 0x1f,
+ },
+ expN: 20,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ t.Run(`Write `+tcase.desc, func(t *testing.T) {
+ n, err := beb.Write(tcase.data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotBin = beb.Bytes()
+ test.Assert(t, `n`, tcase.expN, n)
+ test.Assert(t, `Bytes`, tcase.expBin, gotBin)
+ })
+
+ t.Run(`Read `+tcase.desc, func(t *testing.T) {
+ beb.Seek(0, io.SeekStart)
+
+ // Create pointer to slices based on the type of
+ // tcase.data.
+ var gotData = newWithReflect(tcase.data)
+ n, err := beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Change the *T to T back for assert.
+ gotData = reflect.Indirect(reflect.ValueOf(gotData)).
+ Interface()
+ test.Assert(t, `n`, tcase.expN, n)
+ test.Assert(t, `data`, tcase.data, gotData)
+ })
+
+ t.Run(`Read with length `+tcase.desc, func(t *testing.T) {
+ beb.Seek(0, io.SeekStart)
+
+ // Create slices based on the type of tcase.data.
+
+ var sliceType = reflect.TypeOf(tcase.data)
+ var sliceLen = reflect.ValueOf(tcase.data).Len()
+ var gotData = reflect.
+ MakeSlice(sliceType, sliceLen, sliceLen).
+ Interface()
+
+ n, err := beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, `n`, tcase.expN, n)
+ test.Assert(t, `data`, tcase.data, gotData)
+ })
+ }
+}
+
+func TestBigEndianBuffer_Struct(t *testing.T) {
+ var vfloat32 float32 = 3
+
+ type testStruct struct {
+ PtrFloat32 *float32
+ MapIntInt map[int]int
+ String string
+ SliceByte []byte
+ Int32 int32
+ Bool bool
+ }
+
+ var listCase = []struct {
+ desc string
+ data any
+ expBin []byte
+ expN int
+ }{{
+ desc: `empty struct`,
+ data: testStruct{},
+ expBin: []byte{
+ 0x00, // PtrFloat32
+ 0x00, 0x00, 0x00, 0x00, // String
+ 0x00, 0x00, 0x00, 0x00, // SliceByte
+ 0x00, 0x00, 0x00, 0x00, // Int32
+ 0x00, // Bool
+ },
+ expN: 14,
+ }, {
+ desc: `all set`,
+ data: testStruct{
+ PtrFloat32: &vfloat32,
+ String: `hello`,
+ SliceByte: []byte{4, 5},
+ Int32: 2,
+ Bool: true,
+ },
+ expBin: []byte{
+ 0x01, // PtrFloat32
+ 0x40, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x05, // String
+ 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+ 0x00, 0x00, 0x00, 0x02, // SliceByte
+ 0x04, 0x05,
+ 0x00, 0x00, 0x00, 0x02, // Int32
+ 0x01, // Bool
+ },
+ expN: 25,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ n, err := beb.Write(tcase.data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotBin = beb.Bytes()
+ test.Assert(t, tcase.desc+` Write n`, tcase.expN, n)
+ test.Assert(t, tcase.desc+` Write Bytes`, tcase.expBin, gotBin)
+
+ beb.Seek(0, io.SeekStart)
+
+ // Create new(T) based on the type of tcase.data.
+ var gotData = newWithReflect(tcase.data)
+ n, err = beb.Read(gotData)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Change the *T to T back for assert.
+ gotData = reflect.Indirect(reflect.ValueOf(gotData)).
+ Interface()
+ test.Assert(t, tcase.desc+` Read n`, tcase.expN, n)
+ test.Assert(t, tcase.desc+` Read data`, tcase.data, gotData)
+ }
+}
+
+func TestBigEndianBuffer_ReadFailed(t *testing.T) {
+ var vint int
+
+ var listCase = []struct {
+ desc string
+ dataRead any
+ dataWrite any
+ expErrRead string
+ expErrWrite string
+ }{{
+ desc: `dataRead=nil`,
+ dataWrite: bool(true),
+ expErrRead: `Read: expecting pointer to bool`,
+ }, {
+ desc: `dataRead not pointer`,
+ dataWrite: bool(true),
+ dataRead: vint,
+ expErrRead: `Read: expecting pointer to int`,
+ }, {
+ desc: `[]map[int]int`,
+ dataWrite: []map[int]int{},
+ expErrWrite: `Write: unsupported type []map[int]int`,
+ }}
+
+ var beb binary.BigEndianBuffer
+
+ for _, tcase := range listCase {
+ beb.Reset()
+
+ _, err := beb.Write(tcase.dataWrite)
+ if err != nil {
+ test.Assert(t, `Write error`, tcase.expErrWrite,
+ err.Error())
+ }
+
+ _, err = beb.Seek(0, io.SeekStart)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = beb.Read(tcase.dataRead)
+ if err != nil {
+ test.Assert(t, `Read error`, tcase.expErrRead,
+ err.Error())
+ }
+ }
+}
+
+// newWithReflect create new value with non-pointer type of in.
+func newWithReflect(in any) (out any) {
+ var refval = reflect.Indirect(reflect.ValueOf(in))
+ return reflect.New(refval.Type()).Interface()
+}
diff --git a/lib/binary/binary.go b/lib/binary/binary.go
index 72479d2d..825d9513 100644
--- a/lib/binary/binary.go
+++ b/lib/binary/binary.go
@@ -5,8 +5,459 @@
// Package binary complement the standard [binary] package.
package binary
-import "time"
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "time"
+)
var timeNow = func() time.Time {
return time.Now().UTC()
}
+
+// Endian define the byte order to convert data to binary and vice versa.
+type Endian = byte
+
+const (
+ // EndianBig define big-endian order, where bytes stored in
+ // left-to-right.
+ // For example int16=32767 {0x7F 0xFF} will be stored as is.
+ EndianBig Endian = 1
+
+ // EndianLittle define little-endian order, where byte stored in
+ // right-to-left.
+ // For example int16=32767 {0x7F 0xFF} will be stored as {0xFF 0x7F}
+ EndianLitte = 2
+)
+
+// DecodeString convert binary to string.
+func DecodeString(endness Endian, bin []byte) (val string) {
+ var length = int(DecodeUint32(endness, bin))
+ val = string(bin[4 : 4+length])
+ return val
+}
+
+// DecodeUint16 convert binary to uint16.
+func DecodeUint16(endness Endian, bin []byte) (val uint16) {
+ if endness == EndianBig {
+ val = uint16(bin[1]) | uint16(bin[0])<<8
+ } else {
+ val = uint16(bin[0]) | uint16(bin[1])<<8
+ }
+ return val
+}
+
+// DecodeUint32 convert binary to uint32.
+func DecodeUint32(endness Endian, bin []byte) (val uint32) {
+ if endness == EndianBig {
+ val = uint32(bin[3]) |
+ uint32(bin[2])<<8 |
+ uint32(bin[1])<<16 |
+ uint32(bin[0])<<24
+ } else {
+ val = uint32(bin[0]) |
+ uint32(bin[1])<<8 |
+ uint32(bin[2])<<16 |
+ uint32(bin[3])<<24
+ }
+ return val
+}
+
+// DecodeUint64 convert binary to uint64.
+func DecodeUint64(endness Endian, bin []byte) (val uint64) {
+ if endness == EndianBig {
+ val = uint64(bin[7]) |
+ uint64(bin[6])<<8 |
+ uint64(bin[5])<<16 |
+ uint64(bin[4])<<24 |
+ uint64(bin[3])<<32 |
+ uint64(bin[2])<<40 |
+ uint64(bin[1])<<48 |
+ uint64(bin[0])<<56
+ } else {
+ val = uint64(bin[0]) |
+ uint64(bin[1])<<8 |
+ uint64(bin[2])<<16 |
+ uint64(bin[3])<<24 |
+ uint64(bin[4])<<32 |
+ uint64(bin[5])<<40 |
+ uint64(bin[6])<<48 |
+ uint64(bin[7])<<56
+ }
+ return val
+}
+
+// EncodeString convert string to binary as slice of byte.
+// String is encoded by writing the length of string (number of bytes) and
+// then followed by content of string in bytes.
+//
+// The endian-ness does not affect on how to write the content of string.
+func EncodeString(endness Endian, val string) (bin []byte) {
+ var length = len(val)
+ var binsize = EncodeUint32(endness, uint32(length))
+ bin = make([]byte, 0, 4+length)
+ bin = append(bin, binsize...)
+ bin = append(bin, []byte(val)...)
+ return bin
+}
+
+// EncodeUint16 convert uint16 to binary as slice of byte.
+func EncodeUint16(endness Endian, val uint16) (bin []byte) {
+ bin = make([]byte, 2)
+ if endness == EndianBig {
+ bin[0] = byte(val >> 8)
+ bin[1] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ }
+ return bin
+}
+
+// EncodeUint32 convert uint32 to binary as slice of byte.
+func EncodeUint32(endness Endian, val uint32) (bin []byte) {
+ bin = make([]byte, 4)
+ if endness == EndianBig {
+ bin[0] = byte(val >> 24)
+ bin[1] = byte(val >> 16)
+ bin[2] = byte(val >> 8)
+ bin[3] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ bin[2] = byte(val >> 16)
+ bin[3] = byte(val >> 24)
+ }
+ return bin
+}
+
+// EncodeUint64 convert uint64 to binary as slice of byte.
+func EncodeUint64(endness Endian, val uint64) (bin []byte) {
+ bin = make([]byte, 8)
+ if endness == EndianBig {
+ bin[0] = byte(val >> 56)
+ bin[1] = byte(val >> 48)
+ bin[2] = byte(val >> 40)
+ bin[3] = byte(val >> 32)
+ bin[4] = byte(val >> 24)
+ bin[5] = byte(val >> 16)
+ bin[6] = byte(val >> 8)
+ bin[7] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ bin[2] = byte(val >> 16)
+ bin[3] = byte(val >> 24)
+ bin[4] = byte(val >> 32)
+ bin[5] = byte(val >> 40)
+ bin[6] = byte(val >> 48)
+ bin[7] = byte(val >> 56)
+ }
+ return bin
+}
+
+// SetUint16 write an uint16 value val into bin.
+// This function assume the bin has enought storage, otherwise it would be
+// panic.
+func SetUint16(endness Endian, bin []byte, val uint16) {
+ if endness == EndianBig {
+ bin[0] = byte(val >> 8)
+ bin[1] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ }
+}
+
+// SetUint32 write an uint32 value val into bin.
+// This function assume the bin has enought storage, otherwise it would be
+// panic.
+func SetUint32(endness Endian, bin []byte, val uint32) {
+ if endness == EndianBig {
+ bin[0] = byte(val >> 24)
+ bin[1] = byte(val >> 16)
+ bin[2] = byte(val >> 8)
+ bin[3] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ bin[2] = byte(val >> 16)
+ bin[3] = byte(val >> 24)
+ }
+}
+
+// SetUint64 write an uint64 value val into bin.
+// This function assume the bin has enought storage, otherwise it would be
+// panic.
+func SetUint64(endness Endian, bin []byte, val uint64) {
+ if endness == EndianBig {
+ bin[0] = byte(val >> 56)
+ bin[1] = byte(val >> 48)
+ bin[2] = byte(val >> 40)
+ bin[3] = byte(val >> 32)
+ bin[4] = byte(val >> 24)
+ bin[5] = byte(val >> 16)
+ bin[6] = byte(val >> 8)
+ bin[7] = byte(val)
+ } else {
+ bin[0] = byte(val)
+ bin[1] = byte(val >> 8)
+ bin[2] = byte(val >> 16)
+ bin[3] = byte(val >> 24)
+ bin[4] = byte(val >> 32)
+ bin[5] = byte(val >> 40)
+ bin[6] = byte(val >> 48)
+ bin[7] = byte(val >> 56)
+ }
+}
+
+func decode(endness Endian, bin []byte, val any) (n int, err error) {
+ var refval = reflect.ValueOf(val)
+ refval = reflect.Indirect(refval)
+ var refkind = refval.Kind()
+
+ if isKindIgnored(refkind) {
+ return 0, nil
+ }
+ if refkind == reflect.Array {
+ return decodeArray(endness, bin, refval)
+ }
+ if refkind == reflect.Slice {
+ return decodeSlice(endness, bin, refval)
+ }
+ if refkind == reflect.Struct {
+ return decodeStruct(endness, bin, refval)
+ }
+
+ switch v := val.(type) {
+ case *bool:
+ var b = bin[0]
+ if b == 1 {
+ *v = true
+ }
+ n = 1
+
+ case *int8:
+ *v = int8(bin[0])
+ n = 1
+ case *uint8:
+ *v = uint8(bin[0])
+ n = 1
+
+ case *int16:
+ var ui16 = DecodeUint16(endness, bin[:2])
+ *v = int16(ui16)
+ n = 2
+ case *uint16:
+ var ui16 = DecodeUint16(endness, bin[:2])
+ *v = ui16
+ n = 2
+
+ case *int32:
+ var ui32 = DecodeUint32(endness, bin[:4])
+ *v = int32(ui32)
+ n = 4
+ case *uint32:
+ var ui32 = DecodeUint32(endness, bin[:4])
+ *v = ui32
+ n = 4
+
+ case *int64:
+ var ui64 = DecodeUint64(endness, bin[:8])
+ *v = int64(ui64)
+ n = 8
+ case *uint64:
+ *v = DecodeUint64(endness, bin[:8])
+ n = 8
+
+ case *int:
+ if refval.Type().Size() == 4 {
+ var ui32 = DecodeUint32(endness, bin[:4])
+ *v = int(ui32)
+ n = 4
+ } else {
+ var ui64 = DecodeUint64(endness, bin[:8])
+ *v = int(ui64)
+ n = 8
+ }
+ case *uint:
+ if refval.Type().Size() == 4 {
+ var ui32 = DecodeUint32(endness, bin[:4])
+ *v = uint(ui32)
+ n = 4
+ } else {
+ var ui64 = DecodeUint64(endness, bin[:8])
+ *v = uint(ui64)
+ n = 8
+ }
+
+ case *float32:
+ var ui32 = DecodeUint32(endness, bin[:4])
+ *v = math.Float32frombits(ui32)
+ n = 4
+ case *float64:
+ var ui64 = DecodeUint64(endness, bin[:8])
+ *v = math.Float64frombits(ui64)
+ n = 8
+
+ case *complex64:
+ var ui32 uint32 = DecodeUint32(endness, bin[:4])
+ var re float32 = math.Float32frombits(ui32)
+
+ ui32 = DecodeUint32(endness, bin[4:8])
+ var im float32 = math.Float32frombits(ui32)
+
+ *v = complex(re, im)
+ n = 8
+
+ case *complex128:
+ var ui64 uint64 = DecodeUint64(endness, bin[:8])
+ var re float64 = math.Float64frombits(ui64)
+
+ ui64 = DecodeUint64(endness, bin[8:16])
+ var im float64 = math.Float64frombits(ui64)
+
+ *v = complex(re, im)
+ n = 16
+
+ case *string:
+ *v = DecodeString(endness, bin)
+ n = 4 + len(*v)
+
+ default:
+ return 0, fmt.Errorf(`unsupported type %T`, v)
+ }
+
+ return n, nil
+}
+
+func decodeArray(endness Endian, bin []byte, refval reflect.Value) (
+ n int, err error,
+) {
+ var reftype = refval.Type()
+ var elkind = reftype.Elem().Kind()
+ if isKindIgnored(elkind) {
+ return 0, fmt.Errorf(`unsupported element type %T`,
+ refval.Interface())
+ }
+
+ // Read and compare the length from parameter and stored.
+ var reflen = refval.Len()
+ var storedLen = int(DecodeUint32(endness, bin[:4]))
+
+ if reflen != storedLen {
+ return 0, fmt.Errorf(`expecting slice/array length %d, got %d`,
+ storedLen, reflen)
+ }
+
+ var total = 4
+ bin = bin[4:]
+
+ for x := range storedLen {
+ var elval = refval.Index(x)
+
+ n, err = decode(endness, bin, elval.Addr().Interface())
+ if err != nil {
+ return total, err
+ }
+
+ bin = bin[n:]
+ total += n
+ }
+
+ return total, nil
+}
+
+// decodeSlice read the slice values from bin.
+func decodeSlice(endness Endian, bin []byte, refval reflect.Value) (
+ n int, err error,
+) {
+ var logp = `decodeSlice`
+
+ if !refval.CanAddr() {
+ if refval.IsNil() {
+ return 0, fmt.Errorf(`%s: expecting initialized slice, got nil`, logp)
+ }
+ return decodeArray(endness, bin, refval)
+ }
+
+ var eltype = refval.Type().Elem()
+ var elkind = eltype.Kind()
+ if isKindIgnored(elkind) {
+ // The element of slice is not supported.
+ return 0, nil
+ }
+
+ // Read the length.
+ var sliceLen = int(DecodeUint32(endness, bin[:4]))
+ var slice = reflect.MakeSlice(refval.Type(), 0, sliceLen)
+ var total = 4
+ bin = bin[4:]
+
+ for range sliceLen {
+ // Create new(T) to be filled.
+ var elval = reflect.New(eltype)
+ n, err = decode(endness, bin, elval.Interface())
+ if err != nil {
+ return total, err
+ }
+
+ // Convert *T to T back.
+ elval = reflect.Indirect(elval)
+ slice = reflect.Append(slice, elval)
+ bin = bin[n:]
+ total += n
+ }
+
+ // Finally, set the reflect value to the slice we created.
+ refval.Set(slice)
+
+ return total, nil
+}
+
+func decodeStruct(endness Endian, bin []byte, refval reflect.Value) (
+ total int, err error,
+) {
+ var logp = `decodeStruct`
+ var visibleFields = reflect.VisibleFields(refval.Type())
+ var n int
+ for _, sfield := range visibleFields {
+ if !sfield.IsExported() {
+ continue
+ }
+ var fval = refval.FieldByIndex(sfield.Index)
+ var fkind = fval.Type().Kind()
+ if fkind != reflect.Pointer {
+ if !fval.CanAddr() {
+ return total, fmt.Errorf(`%s: field %q is unsetabble`,
+ logp, sfield.Name)
+ }
+ fval = fval.Addr()
+ } else {
+ var fflag byte
+ n, _ = decode(endness, bin, &fflag)
+ bin = bin[n:]
+ total += n
+ if fflag == 0 {
+ // Skip pointer with nil.
+ continue
+ }
+ var newval = reflect.New(fval.Type().Elem())
+ fval.Set(newval)
+ }
+ n, err = decode(endness, bin, fval.Interface())
+ if err != nil {
+ return total, err
+ }
+ bin = bin[n:]
+ total += n
+ }
+ return total, nil
+}
+
+func isKindIgnored(refkind reflect.Kind) bool {
+ return refkind == reflect.Invalid || refkind == reflect.Uintptr ||
+ refkind == reflect.Chan || refkind == reflect.Func ||
+ refkind == reflect.Interface || refkind == reflect.Map
+}
diff --git a/lib/binary/storage.go b/lib/binary/storage.go
new file mode 100644
index 00000000..2afda66f
--- /dev/null
+++ b/lib/binary/storage.go
@@ -0,0 +1,469 @@
+// SPDX-FileCopyrightText: 2024 M. Shulhan <ms@kilabit.info>
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+package binary
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "slices"
+ "unsafe"
+)
+
+// storage provides the backing storage for BigEndianBuffer.
+type storage struct {
+ bin []byte
+ off int64
+ endness Endian
+}
+
+func (stor *storage) encode(data any) (n int, err error) {
+ switch val := data.(type) {
+ case bool:
+ if val {
+ stor.bin = append(stor.bin[:stor.off], 1)
+ } else {
+ stor.bin = append(stor.bin[:stor.off], 0)
+ }
+ stor.off++
+ return 1, nil
+ case *bool:
+ if *val {
+ stor.bin = append(stor.bin[:stor.off], 1)
+ } else {
+ stor.bin = append(stor.bin[:stor.off], 0)
+ }
+ stor.off++
+ return 1, nil
+
+ case int8:
+ stor.bin = append(stor.bin[:stor.off], byte(val))
+ stor.off++
+ return 1, nil
+ case *int8:
+ stor.bin = append(stor.bin[:stor.off], byte(*val))
+ stor.off++
+ return 1, nil
+ case uint8:
+ stor.bin = append(stor.bin[:stor.off], val)
+ stor.off++
+ return 1, nil
+ case *uint8:
+ stor.bin = append(stor.bin[:stor.off], *val)
+ stor.off++
+ return 1, nil
+
+ case int16:
+ n = stor.encodeUint16(uint16(val))
+ return n, nil
+ case *int16:
+ n = stor.encodeUint16(uint16(*val))
+ return n, nil
+ case uint16:
+ n = stor.encodeUint16(val)
+ return n, nil
+ case *uint16:
+ n = stor.encodeUint16(*val)
+ return n, nil
+
+ case int32:
+ n = stor.encodeUint32(uint32(val))
+ return n, nil
+ case *int32:
+ n = stor.encodeUint32(uint32(*val))
+ return n, nil
+ case uint32:
+ n = stor.encodeUint32(val)
+ return n, nil
+ case *uint32:
+ n = stor.encodeUint32(*val)
+ return n, nil
+
+ case int64:
+ n = stor.encodeUint64(uint64(val))
+ return n, nil
+ case *int64:
+ n = stor.encodeUint64(uint64(*val))
+ return n, nil
+ case uint64:
+ n = stor.encodeUint64(val)
+ return n, nil
+
+ case *uint64:
+ n = stor.encodeUint64(*val)
+ return n, nil
+
+ case int:
+ if unsafe.Sizeof(val) == 4 {
+ n = stor.encodeUint32(uint32(val))
+ } else {
+ n = stor.encodeUint64(uint64(val))
+ }
+ return n, nil
+ case *int:
+ if unsafe.Sizeof(*val) == 4 {
+ n = stor.encodeUint32(uint32(*val))
+ } else {
+ n = stor.encodeUint64(uint64(*val))
+ }
+ return n, nil
+ case uint:
+ if unsafe.Sizeof(val) == 4 {
+ n = stor.encodeUint32(uint32(val))
+ } else {
+ n = stor.encodeUint64(uint64(val))
+ }
+ return n, nil
+ case *uint:
+ if unsafe.Sizeof(*val) == 4 {
+ n = stor.encodeUint32(uint32(*val))
+ } else {
+ n = stor.encodeUint64(uint64(*val))
+ }
+ return n, nil
+
+ case float32:
+ n = stor.encodeUint32(math.Float32bits(val))
+ return n, nil
+
+ case *float32:
+ n = stor.encodeUint32(math.Float32bits(*val))
+ return n, nil
+
+ case float64:
+ n = stor.encodeUint64(math.Float64bits(val))
+ return n, nil
+
+ case *float64:
+ n = stor.encodeUint64(math.Float64bits(*val))
+ return n, nil
+
+ case complex64:
+ n = stor.encodeComplex64(val)
+ return n, nil
+
+ case *complex64:
+ n = stor.encodeComplex64(*val)
+ return n, nil
+
+ case complex128:
+ n = stor.encodeComplex128(val)
+ return n, nil
+ case *complex128:
+ n = stor.encodeComplex128(*val)
+ return n, nil
+
+ case string:
+ n = stor.encodeSliceByte([]byte(val))
+ return n, nil
+
+ case *string:
+ n = stor.encodeSliceByte([]byte(*val))
+ return n, nil
+
+ case []bool:
+ n = 4 + len(val)
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ if v {
+ tmp[x] = 1
+ } else {
+ tmp[x] = 0
+ }
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []int8:
+ n = 4 + len(val)
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ tmp[x] = uint8(v)
+ }
+ stor.off += int64(n)
+ return n, nil
+ case []uint8:
+ n = stor.encodeSliceByte(val)
+ return n, nil
+
+ case []int16:
+ n = 4 + (2 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint16(stor.endness, tmp[x*2:], uint16(v))
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []uint16:
+ n = 4 + (2 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint16(stor.endness, tmp[x*2:], v)
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []int32:
+ n = 4 + (4 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint32(stor.endness, tmp[x*4:], uint32(v))
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []uint32:
+ n = 4 + (4 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint32(stor.endness, tmp[x*4:], v)
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []int64:
+ n = 4 + (8 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint64(stor.endness, tmp[x*8:], uint64(v))
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []uint64:
+ n = 4 + (8 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint64(stor.endness, tmp[x*8:], v)
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []int:
+ if unsafe.Sizeof(int(1)) == 4 {
+ n = 4 + (4 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint32(stor.endness, tmp[x*4:], uint32(v))
+ }
+ } else {
+ n = 4 + (8 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint64(stor.endness, tmp[x*8:], uint64(v))
+ }
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []uint:
+ if unsafe.Sizeof(uint(1)) == 4 {
+ n = 4 + (4 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint32(stor.endness, tmp[x*4:], uint32(v))
+ }
+ } else {
+ n = 4 + (8 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, v := range val {
+ SetUint64(stor.endness, tmp[x*8:], uint64(v))
+ }
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []float32:
+ n = 4 + (4 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, f32 := range val {
+ var ui32 = math.Float32bits(f32)
+ SetUint32(stor.endness, tmp[x*4:], ui32)
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ case []float64:
+ n = 4 + (8 * len(val))
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ var tmp = stor.bin[stor.off+4:]
+ for x, f64 := range val {
+ var ui64 = math.Float64bits(f64)
+ SetUint64(stor.endness, tmp[x*8:], ui64)
+ }
+ stor.off += int64(n)
+ return n, nil
+
+ default:
+ var refval = reflect.ValueOf(data)
+ var refkind = refval.Kind()
+
+ if refkind == reflect.Pointer {
+ refval = reflect.Indirect(refval)
+ refkind = refval.Kind()
+ }
+ if isKindIgnored(refkind) {
+ return 0, nil
+ }
+
+ // We have eliminated most of unused types, and left with Array,
+ // Slice, String, Struct, and basic types.
+
+ switch refkind {
+ case reflect.Array:
+ n, err = stor.encodeSlice(refval)
+ case reflect.Slice:
+ n, err = stor.encodeSlice(refval)
+ case reflect.Struct:
+ n, err = stor.encodeStruct(refval)
+ default:
+ return 0, fmt.Errorf(`unsupported type %T`, data)
+ }
+ }
+ return n, err
+}
+
+func (stor *storage) encodeComplex64(val complex64) (n int) {
+ n = 8
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:],
+ math.Float32bits(float32(real(val))))
+ SetUint32(stor.endness, stor.bin[stor.off+4:],
+ math.Float32bits(float32(imag(val))))
+ stor.off += int64(n)
+ return n
+}
+
+func (stor *storage) encodeComplex128(val complex128) (n int) {
+ n = 16
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint64(stor.endness, stor.bin[stor.off:],
+ math.Float64bits(float64(real(val))))
+ SetUint64(stor.endness, stor.bin[stor.off+8:],
+ math.Float64bits(float64(imag(val))))
+ stor.off += int64(n)
+ return n
+}
+
+func (stor *storage) encodeSlice(refval reflect.Value) (
+ total int, err error,
+) {
+ var elkind = refval.Type().Elem().Kind()
+ if isKindIgnored(elkind) {
+ // The element of array or slice is not supported.
+ return 0, fmt.Errorf(`unsupported type %T`, refval.Interface())
+ }
+
+ var (
+ size = refval.Len()
+ n int
+ )
+ stor.encodeUint32(uint32(size))
+ total = 4
+ for x := range size {
+ var el reflect.Value = refval.Index(x)
+ n, err = stor.encode(el.Interface())
+ if err != nil {
+ return total, err
+ }
+ total += n
+ }
+ return total, nil
+}
+
+func (stor *storage) encodeStruct(refval reflect.Value) (
+ total int, err error,
+) {
+ var visibleFields = reflect.VisibleFields(refval.Type())
+ var n int
+ for _, sfield := range visibleFields {
+ if !sfield.IsExported() {
+ continue
+ }
+ var fval = refval.FieldByIndex(sfield.Index)
+ if fval.Kind() == reflect.Pointer {
+ // Field with nil value will be written only as single
+ // byte 0.
+ var fflag byte
+ if fval.IsNil() {
+ fflag = 0
+ } else {
+ fflag = 1
+ }
+ n, _ = stor.encode(fflag)
+ total += n
+ if fflag == 0 {
+ continue
+ }
+ }
+ n, err = stor.encode(fval.Interface())
+ if err != nil {
+ return total, err
+ }
+ total += n
+ }
+ return total, nil
+}
+
+func (stor *storage) encodeSliceByte(val []byte) (n int) {
+ n = 4 + len(val)
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], uint32(len(val)))
+ copy(stor.bin[stor.off+4:], val)
+ stor.off += int64(n)
+ return n
+}
+
+func (stor *storage) encodeUint16(val uint16) (n int) {
+ n = 2
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint16(stor.endness, stor.bin[stor.off:], val)
+ stor.off += int64(n)
+ return n
+}
+
+func (stor *storage) encodeUint32(val uint32) (n int) {
+ n = 4
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint32(stor.endness, stor.bin[stor.off:], val)
+ stor.off += int64(n)
+ return n
+}
+
+func (stor *storage) encodeUint64(val uint64) (n int) {
+ n = 8
+ stor.bin = slices.Grow(stor.bin[:stor.off], n)[:int(stor.off)+n]
+ SetUint64(stor.endness, stor.bin[stor.off:], val)
+ stor.off += int64(n)
+ return n
+}