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) } }