diff options
| author | Shulhan <ms@kilabit.info> | 2024-12-27 03:09:16 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2025-01-06 00:34:26 +0700 |
| commit | 8d871363ec5471be9d03c514159c17efec0baff8 (patch) | |
| tree | f1f60d21f79b2910bec0e02ca52b1ea0a36187a8 | |
| parent | 3a01474bc37e26d79dabd095d79ad0c9b6273af2 (diff) | |
| download | pakakeh.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.go | 57 | ||||
| -rw-r--r-- | lib/binary/big_endian_buffer.go | 152 | ||||
| -rw-r--r-- | lib/binary/big_endian_buffer_test.go | 732 | ||||
| -rw-r--r-- | lib/binary/binary.go | 453 | ||||
| -rw-r--r-- | lib/binary/storage.go | 469 |
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 +} |
