357 lines
7.8 KiB
Go
357 lines
7.8 KiB
Go
package struc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
// Custom Type
|
|
type Int3 uint32
|
|
|
|
// newInt3 returns a pointer to an Int3
|
|
func newInt3(in int) *Int3 {
|
|
i := Int3(in)
|
|
return &i
|
|
}
|
|
|
|
type Int3Struct struct {
|
|
I Int3
|
|
}
|
|
|
|
func (i *Int3) Pack(p []byte, opt *Options) (int, error) {
|
|
var tmp [4]byte
|
|
binary.BigEndian.PutUint32(tmp[:], uint32(*i))
|
|
copy(p, tmp[1:])
|
|
return 3, nil
|
|
}
|
|
func (i *Int3) Unpack(r io.Reader, length int, opt *Options) error {
|
|
var tmp [4]byte
|
|
if _, err := r.Read(tmp[1:]); err != nil {
|
|
return err
|
|
}
|
|
*i = Int3(binary.BigEndian.Uint32(tmp[:]))
|
|
return nil
|
|
}
|
|
func (i *Int3) Size(opt *Options) int {
|
|
return 3
|
|
}
|
|
func (i *Int3) String() string {
|
|
return strconv.FormatUint(uint64(*i), 10)
|
|
}
|
|
|
|
// Array of custom type
|
|
type ArrayInt3Struct struct {
|
|
I [2]Int3
|
|
}
|
|
|
|
// Custom type of array of standard type
|
|
type DoubleUInt8 [2]uint8
|
|
|
|
type DoubleUInt8Struct struct {
|
|
I DoubleUInt8
|
|
}
|
|
|
|
func (di *DoubleUInt8) Pack(p []byte, opt *Options) (int, error) {
|
|
copy(p, (*di)[:])
|
|
|
|
return 2, nil
|
|
}
|
|
|
|
func (di *DoubleUInt8) Unpack(r io.Reader, length int, opt *Options) error {
|
|
for i := 0; i < 2; i++ {
|
|
var value uint8
|
|
if err := binary.Read(r, binary.LittleEndian, &value); err != nil {
|
|
if err == io.EOF {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
return err
|
|
}
|
|
di[i] = value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (di *DoubleUInt8) Size(opt *Options) int {
|
|
return 2
|
|
}
|
|
|
|
func (di *DoubleUInt8) String() string {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// Custom type of array of custom type
|
|
type DoubleInt3 [2]Int3
|
|
|
|
type DoubleInt3Struct struct {
|
|
D DoubleInt3
|
|
}
|
|
|
|
func (di *DoubleInt3) Pack(p []byte, opt *Options) (int, error) {
|
|
var out []byte
|
|
for _, value := range *di {
|
|
tmp := make([]byte, 3)
|
|
if _, err := value.Pack(tmp, opt); err != nil {
|
|
return 0, err
|
|
}
|
|
out = append(out, tmp...)
|
|
}
|
|
copy(p, out)
|
|
|
|
return 6, nil
|
|
}
|
|
|
|
func (di *DoubleInt3) Unpack(r io.Reader, length int, opt *Options) error {
|
|
for i := 0; i < 2; i++ {
|
|
di[i].Unpack(r, 0, opt)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (di *DoubleInt3) Size(opt *Options) int {
|
|
return 6
|
|
}
|
|
|
|
func (di *DoubleInt3) String() string {
|
|
panic("not implemented")
|
|
}
|
|
|
|
// Custom type of slice of standard type
|
|
// Slice of uint8, stored in a zero terminated list.
|
|
type SliceUInt8 []uint8
|
|
|
|
type SliceUInt8Struct struct {
|
|
I SliceUInt8
|
|
N uint8 // A field after to ensure the length is correct.
|
|
}
|
|
|
|
func (ia *SliceUInt8) Pack(p []byte, opt *Options) (int, error) {
|
|
copy(p, *ia)
|
|
|
|
return len(*ia) + 1, nil
|
|
}
|
|
|
|
func (ia *SliceUInt8) Unpack(r io.Reader, length int, opt *Options) error {
|
|
for {
|
|
var value uint8
|
|
if err := binary.Read(r, binary.LittleEndian, &value); err != nil {
|
|
if err == io.EOF {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
return err
|
|
}
|
|
if value == 0 {
|
|
break
|
|
}
|
|
*ia = append(*ia, value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ia *SliceUInt8) Size(opt *Options) int {
|
|
return len(*ia) + 1
|
|
}
|
|
|
|
func (ia *SliceUInt8) String() string {
|
|
panic("not implemented")
|
|
}
|
|
|
|
type ArrayOfUInt8Struct struct {
|
|
I [2]uint8
|
|
}
|
|
|
|
// Custom 4-character fixed string, similar to CHAR(4) in SQL.
|
|
type Char4 string
|
|
|
|
func (*Char4) Size(opt *Options) int {
|
|
return 4
|
|
}
|
|
|
|
func (c *Char4) Pack(p []byte, opt *Options) (int, error) {
|
|
buf := []byte(*c)
|
|
buf = append(buf, make([]byte, c.Size(nil)-len(buf))...)
|
|
|
|
copy(p, buf)
|
|
return len(buf), nil
|
|
}
|
|
|
|
func (c *Char4) Unpack(r io.Reader, length int, opt *Options) error {
|
|
buf := bytes.Buffer{}
|
|
if _, err := buf.ReadFrom(r); err != nil {
|
|
if err == io.EOF {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
return err
|
|
}
|
|
|
|
*c = Char4(buf.String())
|
|
return nil
|
|
}
|
|
|
|
func (c *Char4) String() string {
|
|
return string(*c)
|
|
}
|
|
|
|
type Char4Struct struct {
|
|
C Char4
|
|
}
|
|
|
|
func TestCustomTypes(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
packObj interface{}
|
|
emptyObj interface{}
|
|
expectBytes []byte
|
|
skip bool // Skip the test, because it fails.
|
|
// Switch to expectFail when possible:
|
|
// https://github.com/golang/go/issues/25951
|
|
}{
|
|
// Start tests with unimplemented non-custom types.
|
|
{
|
|
name: "ArrayOfUInt8",
|
|
packObj: [2]uint8{32, 64},
|
|
emptyObj: [2]uint8{0, 0},
|
|
expectBytes: []byte{32, 64},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "PointerToArrayOfUInt8",
|
|
packObj: &[2]uint8{32, 64},
|
|
emptyObj: &[2]uint8{0, 0},
|
|
expectBytes: []byte{32, 64},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "ArrayOfUInt8Struct",
|
|
packObj: &ArrayOfUInt8Struct{I: [2]uint8{32, 64}},
|
|
emptyObj: &ArrayOfUInt8Struct{},
|
|
expectBytes: []byte{32, 64},
|
|
},
|
|
{
|
|
name: "CustomType",
|
|
packObj: newInt3(3),
|
|
emptyObj: newInt3(0),
|
|
expectBytes: []byte{0, 0, 3},
|
|
},
|
|
{
|
|
name: "CustomType-Big",
|
|
packObj: newInt3(4000),
|
|
emptyObj: newInt3(0),
|
|
expectBytes: []byte{0, 15, 160},
|
|
},
|
|
{
|
|
name: "CustomTypeStruct",
|
|
packObj: &Int3Struct{3},
|
|
emptyObj: &Int3Struct{},
|
|
expectBytes: []byte{0, 0, 3},
|
|
},
|
|
{
|
|
name: "ArrayOfCustomType",
|
|
packObj: [2]Int3{3, 4},
|
|
emptyObj: [2]Int3{},
|
|
expectBytes: []byte{0, 0, 3, 0, 0, 4},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "PointerToArrayOfCustomType",
|
|
packObj: &[2]Int3{3, 4},
|
|
emptyObj: &[2]Int3{},
|
|
expectBytes: []byte{0, 0, 3, 0, 0, 4},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "ArrayOfCustomTypeStruct",
|
|
packObj: &ArrayInt3Struct{[2]Int3{3, 4}},
|
|
emptyObj: &ArrayInt3Struct{},
|
|
expectBytes: []byte{0, 0, 3, 0, 0, 4},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "CustomTypeOfArrayOfUInt8",
|
|
packObj: &DoubleUInt8{32, 64},
|
|
emptyObj: &DoubleUInt8{},
|
|
expectBytes: []byte{32, 64},
|
|
},
|
|
{
|
|
name: "CustomTypeOfArrayOfUInt8Struct",
|
|
packObj: &DoubleUInt8Struct{I: DoubleUInt8{32, 64}},
|
|
emptyObj: &DoubleUInt8Struct{},
|
|
expectBytes: []byte{32, 64},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "CustomTypeOfArrayOfCustomType",
|
|
packObj: &DoubleInt3{Int3(128), Int3(256)},
|
|
emptyObj: &DoubleInt3{},
|
|
expectBytes: []byte{0, 0, 128, 0, 1, 0},
|
|
},
|
|
{
|
|
name: "CustomTypeOfArrayOfCustomTypeStruct",
|
|
packObj: &DoubleInt3Struct{D: DoubleInt3{Int3(128), Int3(256)}},
|
|
emptyObj: &DoubleInt3Struct{},
|
|
expectBytes: []byte{0, 0, 128, 0, 1, 0},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "CustomTypeOfSliceOfUInt8",
|
|
packObj: &SliceUInt8{128, 64, 32},
|
|
emptyObj: &SliceUInt8{},
|
|
expectBytes: []byte{128, 64, 32, 0},
|
|
},
|
|
{
|
|
name: "CustomTypeOfSliceOfUInt8-Empty",
|
|
packObj: &SliceUInt8{},
|
|
emptyObj: &SliceUInt8{},
|
|
expectBytes: []byte{0},
|
|
},
|
|
{
|
|
name: "CustomTypeOfSliceOfUInt8Struct",
|
|
packObj: &SliceUInt8Struct{I: SliceUInt8{128, 64, 32}, N: 192},
|
|
emptyObj: &SliceUInt8Struct{},
|
|
expectBytes: []byte{128, 64, 32, 0, 192},
|
|
skip: true, // Not implemented.
|
|
},
|
|
{
|
|
name: "CustomTypeOfChar4Struct",
|
|
packObj: &Char4Struct{C: Char4("foo\x00")},
|
|
emptyObj: &Char4Struct{},
|
|
expectBytes: []byte{102, 111, 111, 0},
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
// TODO: Switch to t.Run() when Go 1.7 is the minimum supported version.
|
|
t.Log("RUN ", test.name)
|
|
runner := func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Log("unexpected panic:", r)
|
|
t.Error(r)
|
|
}
|
|
}()
|
|
if test.skip {
|
|
// TODO: Switch to t.Skip() when Go 1.7 is supported
|
|
t.Log("skipped unimplemented")
|
|
return
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := Pack(&buf, test.packObj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(buf.Bytes(), test.expectBytes) {
|
|
t.Fatal("error packing, expect:", test.expectBytes, "found:", buf.Bytes())
|
|
}
|
|
if err := Unpack(&buf, test.emptyObj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(test.packObj, test.emptyObj) {
|
|
t.Fatal("error unpacking, expect:", test.packObj, "found:", test.emptyObj)
|
|
}
|
|
}
|
|
runner(t)
|
|
}
|
|
}
|