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 /lib/binary/storage.go | |
| 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.
Diffstat (limited to 'lib/binary/storage.go')
| -rw-r--r-- | lib/binary/storage.go | 469 |
1 files changed, 469 insertions, 0 deletions
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 +} |
