From 4f5cd5826fbd4077d1cd160b7cfbd4497cb1b626 Mon Sep 17 00:00:00 2001 From: Joe Williams Date: Sat, 15 Oct 2022 13:04:45 -0600 Subject: [PATCH] add int32 and string types to alignedbuff (#195) --- alignedbuff/alignedbuff.go | 48 ++++++++++++++ alignedbuff/alignedbuff_test.go | 114 ++++++++++++++++++++++++++++++++ binaryutil/binaryutil.go | 21 ++++++ binaryutil/binaryutil_test.go | 33 +++++++++ 4 files changed, 216 insertions(+) diff --git a/alignedbuff/alignedbuff.go b/alignedbuff/alignedbuff.go index 605c0b1..7bb09f8 100644 --- a/alignedbuff/alignedbuff.go +++ b/alignedbuff/alignedbuff.go @@ -118,6 +118,39 @@ func (a *AlignedBuff) Uint64() (uint64, error) { return v, nil } +// Int32 unmarshals an int32 in native endianess and alignment. It returns +// ErrEOF when trying to read beyond the payload. +func (a *AlignedBuff) Int32() (int32, error) { + if err := a.alignCheckedRead(int32AlignMask); err != nil { + return 0, err + } + v := binaryutil.Int32(a.data[a.pos : a.pos+4]) + a.pos += 4 + return v, nil +} + +// String unmarshals a null terminated string +func (a *AlignedBuff) String() (string, error) { + len := 0 + for { + if a.data[a.pos+len] == 0x00 { + break + } + len++ + } + + v := binaryutil.String(a.data[a.pos : a.pos+len]) + a.pos += len + return v, nil +} + +// Unmarshals a string of a given length (for non-null terminated strings) +func (a *AlignedBuff) StringWithLength(len int) (string, error) { + v := binaryutil.String(a.data[a.pos : a.pos+len]) + a.pos += len + return v, nil +} + // Uint unmarshals an uint in native endianess and alignment for the C "unsigned // int" type. It returns ErrEOF when trying to read beyond the payload. Please // note that on 64bit platforms, the size and alignment of C's and Go's unsigned @@ -190,6 +223,19 @@ func (a *AlignedBuff) PutUint64(v uint64) { a.pos += 8 } +// PutInt32 marshals an int32 in native endianess and alignment. +func (a *AlignedBuff) PutInt32(v int32) { + a.alignWrite(int32AlignMask) + a.data = append(a.data, binaryutil.PutInt32(v)...) + a.pos += 4 +} + +// PutString marshals a string. +func (a *AlignedBuff) PutString(v string) { + a.data = append(a.data, binaryutil.PutString(v)...) + a.pos += len(v) +} + // PutUint marshals an uint in native endianess and alignment for the C // "unsigned int" type. Please note that on 64bit platforms, the size and // alignment of C's and Go's unsigned integer data types differ, so we @@ -236,5 +282,7 @@ var uint32AlignMask = int(unsafe.Alignof(uint32(0)) - 1) var uint64AlignMask = int(unsafe.Alignof(uint64(0)) - 1) var padding = bytes.Repeat([]byte{0}, uint64AlignMask) +var int32AlignMask = int(unsafe.Alignof(int32(0)) - 1) + // And this even worse. var uintSize = unsafe.Sizeof(uint32(0)) diff --git a/alignedbuff/alignedbuff_test.go b/alignedbuff/alignedbuff_test.go index dd14a9f..edee6c1 100644 --- a/alignedbuff/alignedbuff_test.go +++ b/alignedbuff/alignedbuff_test.go @@ -20,6 +20,9 @@ func TestAlignmentData(t *testing.T) { if uintSize == 0 { t.Fatal("zero uint size") } + if int32AlignMask == 0 { + t.Fatal("zero uint32 alignment mask") + } } func TestAlignedBuff8(t *testing.T) { @@ -202,3 +205,114 @@ func TestAlignedUint(t *testing.T) { t.Fatalf("sentinel read failed") } } + +func TestAlignedBuffInt32(t *testing.T) { + b0 := New() + b0.PutUint8(0x42) + b0.PutInt32(0x12345678) + b0.PutInt32(0x01cecafe) + + b := NewWithData(b0.data) + + if len(b0.Data()) != 4*4 { + t.Fatalf("alignment padding failed") + } + + v, err := b.Uint8() + if v != 0x42 || err != nil { + t.Fatalf("unaligment read failed") + } + tests := []struct { + name string + v int32 + err error + }{ + { + name: "first read", + v: 0x12345678, + err: nil, + }, + { + name: "second read", + v: 0x01cecafe, + err: nil, + }, + { + name: "end of buffer", + v: 0, + err: ErrEOF, + }, + } + + for _, tt := range tests { + v, err := b.Int32() + if v != tt.v || err != tt.err { + t.Errorf("expected: %#v %#v, got: %#v, %#v", + tt.v, tt.err, v, err) + } + } +} + +func TestAlignedBuffPutNullTerminatedString(t *testing.T) { + b0 := New() + b0.PutUint8(0x42) + b0.PutString("test" + "\x00") + + b := NewWithData(b0.data) + + v, err := b.Uint8() + if v != 0x42 || err != nil { + t.Fatalf("unaligment read failed") + } + tests := []struct { + name string + v string + err error + }{ + { + name: "first read", + v: "test", + err: nil, + }, + } + + for _, tt := range tests { + v, err := b.String() + if v != tt.v || err != tt.err { + t.Errorf("expected: %#v %#v, got: %#v, %#v", + tt.v, tt.err, v, err) + } + } +} + +func TestAlignedBuffPutString(t *testing.T) { + b0 := New() + b0.PutUint8(0x42) + b0.PutString("test") + + b := NewWithData(b0.data) + + v, err := b.Uint8() + if v != 0x42 || err != nil { + t.Fatalf("unaligment read failed") + } + tests := []struct { + name string + v string + err error + }{ + { + name: "first read", + v: "test", + err: nil, + }, + } + + for _, tt := range tests { + v, err := b.StringWithLength(len("test")) + if v != tt.v || err != tt.err { + t.Errorf("expected: %#v %#v, got: %#v, %#v", + tt.v, tt.err, v, err) + } + } +} diff --git a/binaryutil/binaryutil.go b/binaryutil/binaryutil.go index 27755f5..e61973f 100644 --- a/binaryutil/binaryutil.go +++ b/binaryutil/binaryutil.go @@ -16,6 +16,7 @@ package binaryutil import ( + "bytes" "encoding/binary" "unsafe" ) @@ -102,3 +103,23 @@ func (bigEndian) Uint32(b []byte) uint32 { func (bigEndian) Uint64(b []byte) uint64 { return binary.BigEndian.Uint64(b) } + +// For dealing with types not supported by the encoding/binary interface + +func PutInt32(v int32) []byte { + buf := make([]byte, 4) + *(*int32)(unsafe.Pointer(&buf[0])) = v + return buf +} + +func Int32(b []byte) int32 { + return *(*int32)(unsafe.Pointer(&b[0])) +} + +func PutString(s string) []byte { + return []byte(s) +} + +func String(b []byte) string { + return string(bytes.TrimRight(b, "\x00")) +} diff --git a/binaryutil/binaryutil_test.go b/binaryutil/binaryutil_test.go index 78f5f57..8dad105 100644 --- a/binaryutil/binaryutil_test.go +++ b/binaryutil/binaryutil_test.go @@ -107,3 +107,36 @@ func TestBigEndian(t *testing.T) { } } } + +func TestOtherTypes(t *testing.T) { + tests := []struct { + name string + expected []byte + expectedv interface{} + actual []byte + unmarshal func(b []byte) interface{} + }{ + { + name: "Int32", + expected: []byte{0x78, 0x56, 0x34, 0x12}, + expectedv: int32(0x12345678), + actual: PutInt32(0x12345678), + unmarshal: func(b []byte) interface{} { return Int32(b) }, + }, + { + name: "String", + expected: []byte{0x74, 0x65, 0x73, 0x74}, + expectedv: "test", + actual: PutString("test"), + unmarshal: func(b []byte) interface{} { return String(b) }, + }, + } + for _, tt := range tests { + if bytes.Compare(tt.actual, tt.expected) != 0 { + t.Errorf("Put%s failure, expected: %#v, got: %#v", tt.name, tt.expected, tt.actual) + } + if actual := tt.unmarshal(tt.actual); !reflect.DeepEqual(actual, tt.expectedv) { + t.Errorf("%s failure, expected: %#v, got: %#v", tt.name, tt.expectedv, actual) + } + } +}