summaryrefslogtreecommitdiffhomepage
path: root/pkg/binary
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/binary')
-rw-r--r--pkg/binary/BUILD17
-rw-r--r--pkg/binary/binary.go263
-rw-r--r--pkg/binary/binary_test.go265
3 files changed, 545 insertions, 0 deletions
diff --git a/pkg/binary/BUILD b/pkg/binary/BUILD
new file mode 100644
index 000000000..16f08b13f
--- /dev/null
+++ b/pkg/binary/BUILD
@@ -0,0 +1,17 @@
+package(licenses = ["notice"]) # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "binary",
+ srcs = ["binary.go"],
+ importpath = "gvisor.googlesource.com/gvisor/pkg/binary",
+ visibility = ["//:sandbox"],
+)
+
+go_test(
+ name = "binary_test",
+ size = "small",
+ srcs = ["binary_test.go"],
+ embed = [":binary"],
+)
diff --git a/pkg/binary/binary.go b/pkg/binary/binary.go
new file mode 100644
index 000000000..df421bdb6
--- /dev/null
+++ b/pkg/binary/binary.go
@@ -0,0 +1,263 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package binary translates between select fixed-sized types and a binary
+// representation.
+package binary
+
+import (
+ "encoding/binary"
+ "fmt"
+ "io"
+ "reflect"
+)
+
+// LittleEndian is the same as encoding/binary.LittleEndian.
+//
+// It is included here as a convenience.
+var LittleEndian = binary.LittleEndian
+
+// BigEndian is the same as encoding/binary.BigEndian.
+//
+// It is included here as a convenience.
+var BigEndian = binary.BigEndian
+
+// AppendUint16 appends the binary representation of a uint16 to buf.
+func AppendUint16(buf []byte, order binary.ByteOrder, num uint16) []byte {
+ buf = extendZero(2, buf)
+ order.PutUint16(buf[len(buf)-2:], num)
+ return buf
+}
+
+// AppendUint32 appends the binary representation of a uint32 to buf.
+func AppendUint32(buf []byte, order binary.ByteOrder, num uint32) []byte {
+ buf = extendZero(4, buf)
+ order.PutUint32(buf[len(buf)-4:], num)
+ return buf
+}
+
+// AppendUint64 appends the binary representation of a uint64 to buf.
+func AppendUint64(buf []byte, order binary.ByteOrder, num uint64) []byte {
+ buf = extendZero(8, buf)
+ order.PutUint64(buf[len(buf)-8:], num)
+ return buf
+}
+
+// Marshal appends a binary representation of data to buf.
+//
+// data must only contain fixed-length signed and unsigned ints, arrays,
+// slices, structs and compositions of said types. data may be a pointer,
+// but cannot contain pointers.
+func Marshal(buf []byte, order binary.ByteOrder, data interface{}) []byte {
+ return marshal(buf, order, reflect.Indirect(reflect.ValueOf(data)))
+}
+
+func marshal(buf []byte, order binary.ByteOrder, data reflect.Value) []byte {
+ switch data.Kind() {
+ case reflect.Int8:
+ buf = append(buf, byte(int8(data.Int())))
+ case reflect.Int16:
+ buf = AppendUint16(buf, order, uint16(int16(data.Int())))
+ case reflect.Int32:
+ buf = AppendUint32(buf, order, uint32(int32(data.Int())))
+ case reflect.Int64:
+ buf = AppendUint64(buf, order, uint64(data.Int()))
+
+ case reflect.Uint8:
+ buf = append(buf, byte(data.Uint()))
+ case reflect.Uint16:
+ buf = AppendUint16(buf, order, uint16(data.Uint()))
+ case reflect.Uint32:
+ buf = AppendUint32(buf, order, uint32(data.Uint()))
+ case reflect.Uint64:
+ buf = AppendUint64(buf, order, data.Uint())
+
+ case reflect.Array, reflect.Slice:
+ for i, l := 0, data.Len(); i < l; i++ {
+ buf = marshal(buf, order, data.Index(i))
+ }
+
+ case reflect.Struct:
+ for i, l := 0, data.NumField(); i < l; i++ {
+ buf = marshal(buf, order, data.Field(i))
+ }
+
+ default:
+ panic("invalid type: " + data.Type().String())
+ }
+ return buf
+}
+
+// Unmarshal unpacks buf into data.
+//
+// data must be a slice or a pointer and buf must have a length of exactly
+// Size(data). data must only contain fixed-length signed and unsigned ints,
+// arrays, slices, structs and compositions of said types.
+func Unmarshal(buf []byte, order binary.ByteOrder, data interface{}) {
+ value := reflect.ValueOf(data)
+ switch value.Kind() {
+ case reflect.Ptr:
+ value = value.Elem()
+ case reflect.Slice:
+ default:
+ panic("invalid type: " + value.Type().String())
+ }
+ buf = unmarshal(buf, order, value)
+ if len(buf) != 0 {
+ panic(fmt.Sprintf("buffer too long by %d bytes", len(buf)))
+ }
+}
+
+func unmarshal(buf []byte, order binary.ByteOrder, data reflect.Value) []byte {
+ switch data.Kind() {
+ case reflect.Int8:
+ data.SetInt(int64(int8(buf[0])))
+ buf = buf[1:]
+ case reflect.Int16:
+ data.SetInt(int64(int16(order.Uint16(buf))))
+ buf = buf[2:]
+ case reflect.Int32:
+ data.SetInt(int64(int32(order.Uint32(buf))))
+ buf = buf[4:]
+ case reflect.Int64:
+ data.SetInt(int64(order.Uint64(buf)))
+ buf = buf[8:]
+
+ case reflect.Uint8:
+ data.SetUint(uint64(buf[0]))
+ buf = buf[1:]
+ case reflect.Uint16:
+ data.SetUint(uint64(order.Uint16(buf)))
+ buf = buf[2:]
+ case reflect.Uint32:
+ data.SetUint(uint64(order.Uint32(buf)))
+ buf = buf[4:]
+ case reflect.Uint64:
+ data.SetUint(order.Uint64(buf))
+ buf = buf[8:]
+
+ case reflect.Array, reflect.Slice:
+ for i, l := 0, data.Len(); i < l; i++ {
+ buf = unmarshal(buf, order, data.Index(i))
+ }
+
+ case reflect.Struct:
+ for i, l := 0, data.NumField(); i < l; i++ {
+ if field := data.Field(i); field.CanSet() {
+ buf = unmarshal(buf, order, field)
+ } else {
+ buf = buf[sizeof(field):]
+ }
+ }
+
+ default:
+ panic("invalid type: " + data.Type().String())
+ }
+ return buf
+}
+
+// Size calculates the buffer sized needed by Marshal or Unmarshal.
+//
+// Size only support the types supported by Marshal.
+func Size(v interface{}) uintptr {
+ return sizeof(reflect.Indirect(reflect.ValueOf(v)))
+}
+
+func sizeof(data reflect.Value) uintptr {
+ switch data.Kind() {
+ case reflect.Int8, reflect.Uint8:
+ return 1
+ case reflect.Int16, reflect.Uint16:
+ return 2
+ case reflect.Int32, reflect.Uint32:
+ return 4
+ case reflect.Int64, reflect.Uint64:
+ return 8
+
+ case reflect.Array, reflect.Slice:
+ var size uintptr
+ for i, l := 0, data.Len(); i < l; i++ {
+ size += sizeof(data.Index(i))
+ }
+ return size
+
+ case reflect.Struct:
+ var size uintptr
+ for i, l := 0, data.NumField(); i < l; i++ {
+ size += sizeof(data.Field(i))
+ }
+ return size
+
+ default:
+ panic("invalid type: " + data.Type().String())
+ }
+}
+
+func extendZero(amount uintptr, buf []byte) []byte {
+ for i := uintptr(0); i < amount; i++ {
+ buf = append(buf, 0)
+ }
+ return buf
+}
+
+// ReadUint16 reads a uint16 from r.
+func ReadUint16(r io.Reader, order binary.ByteOrder) (uint16, error) {
+ buf := make([]byte, 2)
+ if _, err := io.ReadFull(r, buf); err != nil {
+ return 0, err
+ }
+ return order.Uint16(buf), nil
+}
+
+// ReadUint32 reads a uint32 from r.
+func ReadUint32(r io.Reader, order binary.ByteOrder) (uint32, error) {
+ buf := make([]byte, 4)
+ if _, err := io.ReadFull(r, buf); err != nil {
+ return 0, err
+ }
+ return order.Uint32(buf), nil
+}
+
+// ReadUint64 reads a uint64 from r.
+func ReadUint64(r io.Reader, order binary.ByteOrder) (uint64, error) {
+ buf := make([]byte, 8)
+ if _, err := io.ReadFull(r, buf); err != nil {
+ return 0, err
+ }
+ return order.Uint64(buf), nil
+}
+
+// WriteUint16 writes a uint16 to w.
+func WriteUint16(w io.Writer, order binary.ByteOrder, num uint16) error {
+ buf := make([]byte, 2)
+ order.PutUint16(buf, num)
+ _, err := w.Write(buf)
+ return err
+}
+
+// WriteUint32 writes a uint32 to w.
+func WriteUint32(w io.Writer, order binary.ByteOrder, num uint32) error {
+ buf := make([]byte, 4)
+ order.PutUint32(buf, num)
+ _, err := w.Write(buf)
+ return err
+}
+
+// WriteUint64 writes a uint64 to w.
+func WriteUint64(w io.Writer, order binary.ByteOrder, num uint64) error {
+ buf := make([]byte, 8)
+ order.PutUint64(buf, num)
+ _, err := w.Write(buf)
+ return err
+}
diff --git a/pkg/binary/binary_test.go b/pkg/binary/binary_test.go
new file mode 100644
index 000000000..921a0369a
--- /dev/null
+++ b/pkg/binary/binary_test.go
@@ -0,0 +1,265 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package binary
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "reflect"
+ "testing"
+)
+
+func newInt32(i int32) *int32 {
+ return &i
+}
+
+func TestSize(t *testing.T) {
+ if got, want := Size(uint32(10)), uintptr(4); got != want {
+ t.Errorf("Got = %d, want = %d", got, want)
+ }
+}
+
+func TestPanic(t *testing.T) {
+ tests := []struct {
+ name string
+ f func([]byte, binary.ByteOrder, interface{})
+ data interface{}
+ want string
+ }{
+ {"Unmarshal int", Unmarshal, 5, "invalid type: int"},
+ {"Unmarshal []int", Unmarshal, []int{5}, "invalid type: int"},
+ {"Marshal int", func(_ []byte, bo binary.ByteOrder, d interface{}) { Marshal(nil, bo, d) }, 5, "invalid type: int"},
+ {"Marshal int[]", func(_ []byte, bo binary.ByteOrder, d interface{}) { Marshal(nil, bo, d) }, []int{5}, "invalid type: int"},
+ {"Unmarshal short buffer", Unmarshal, newInt32(5), "runtime error: index out of range"},
+ {"Unmarshal long buffer", func(_ []byte, bo binary.ByteOrder, d interface{}) { Unmarshal(make([]byte, 50), bo, d) }, newInt32(5), "buffer too long by 46 bytes"},
+ {"marshal int", func(_ []byte, bo binary.ByteOrder, d interface{}) { marshal(nil, bo, reflect.ValueOf(d)) }, 5, "invalid type: int"},
+ {"Size int", func(_ []byte, _ binary.ByteOrder, d interface{}) { Size(d) }, 5, "invalid type: int"},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ defer func() {
+ r := recover()
+ if got := fmt.Sprint(r); got != test.want {
+ t.Errorf("Got recover() = %q, want = %q", got, test.want)
+ }
+ }()
+
+ test.f(nil, LittleEndian, test.data)
+ })
+ }
+}
+
+type inner struct {
+ Field int32
+}
+
+type outer struct {
+ Int8 int8
+ Int16 int16
+ Int32 int32
+ Int64 int64
+ Uint8 uint8
+ Uint16 uint16
+ Uint32 uint32
+ Uint64 uint64
+
+ Slice []int32
+ Array [5]int32
+ Struct inner
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+ want := outer{
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ []int32{9, 10, 11},
+ [5]int32{12, 13, 14, 15, 16},
+ inner{17},
+ }
+ buf := Marshal(nil, LittleEndian, want)
+ got := outer{Slice: []int32{0, 0, 0}}
+ Unmarshal(buf, LittleEndian, &got)
+ if !reflect.DeepEqual(&got, &want) {
+ t.Errorf("Got = %#v, want = %#v", got, want)
+ }
+}
+
+type outerBenchmark struct {
+ Int8 int8
+ Int16 int16
+ Int32 int32
+ Int64 int64
+ Uint8 uint8
+ Uint16 uint16
+ Uint32 uint32
+ Uint64 uint64
+
+ Array [5]int32
+ Struct inner
+}
+
+func BenchmarkMarshalUnmarshal(b *testing.B) {
+ b.ReportAllocs()
+
+ in := outerBenchmark{
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ [5]int32{9, 10, 11, 12, 13},
+ inner{14},
+ }
+ buf := make([]byte, Size(&in))
+ out := outerBenchmark{}
+
+ for i := 0; i < b.N; i++ {
+ buf := Marshal(buf[:0], LittleEndian, &in)
+ Unmarshal(buf, LittleEndian, &out)
+ }
+}
+
+func BenchmarkReadWrite(b *testing.B) {
+ b.ReportAllocs()
+
+ in := outerBenchmark{
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ [5]int32{9, 10, 11, 12, 13},
+ inner{14},
+ }
+ buf := bytes.NewBuffer(make([]byte, binary.Size(&in)))
+ out := outerBenchmark{}
+
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ if err := binary.Write(buf, LittleEndian, &in); err != nil {
+ b.Error("Write:", err)
+ }
+ if err := binary.Read(buf, LittleEndian, &out); err != nil {
+ b.Error("Read:", err)
+ }
+ }
+}
+
+type outerPadding struct {
+ _ int8
+ _ int16
+ _ int32
+ _ int64
+ _ uint8
+ _ uint16
+ _ uint32
+ _ uint64
+
+ _ []int32
+ _ [5]int32
+ _ inner
+}
+
+func TestMarshalUnmarshalPadding(t *testing.T) {
+ var want outerPadding
+ buf := Marshal(nil, LittleEndian, want)
+ var got outerPadding
+ Unmarshal(buf, LittleEndian, &got)
+ if !reflect.DeepEqual(&got, &want) {
+ t.Errorf("Got = %#v, want = %#v", got, want)
+ }
+}
+
+// Numbers with bits in every byte that distinguishable in big and little endian.
+const (
+ want16 = 64<<8 | 128
+ want32 = 16<<24 | 32<<16 | want16
+ want64 = 1<<56 | 2<<48 | 4<<40 | 8<<32 | want32
+)
+
+func TestReadWriteUint16(t *testing.T) {
+ const want = uint16(want16)
+ var buf bytes.Buffer
+ if err := WriteUint16(&buf, LittleEndian, want); err != nil {
+ t.Error("WriteUint16:", err)
+ }
+ got, err := ReadUint16(&buf, LittleEndian)
+ if err != nil {
+ t.Error("ReadUint16:", err)
+ }
+ if got != want {
+ t.Errorf("got = %d, want = %d", got, want)
+ }
+}
+
+func TestReadWriteUint32(t *testing.T) {
+ const want = uint32(want32)
+ var buf bytes.Buffer
+ if err := WriteUint32(&buf, LittleEndian, want); err != nil {
+ t.Error("WriteUint32:", err)
+ }
+ got, err := ReadUint32(&buf, LittleEndian)
+ if err != nil {
+ t.Error("ReadUint32:", err)
+ }
+ if got != want {
+ t.Errorf("got = %d, want = %d", got, want)
+ }
+}
+
+func TestReadWriteUint64(t *testing.T) {
+ const want = uint64(want64)
+ var buf bytes.Buffer
+ if err := WriteUint64(&buf, LittleEndian, want); err != nil {
+ t.Error("WriteUint64:", err)
+ }
+ got, err := ReadUint64(&buf, LittleEndian)
+ if err != nil {
+ t.Error("ReadUint64:", err)
+ }
+ if got != want {
+ t.Errorf("got = %d, want = %d", got, want)
+ }
+}
+
+type readWriter struct {
+ err error
+}
+
+func (rw *readWriter) Write([]byte) (int, error) {
+ return 0, rw.err
+}
+
+func (rw *readWriter) Read([]byte) (int, error) {
+ return 0, rw.err
+}
+
+func TestReadWriteError(t *testing.T) {
+ tests := []struct {
+ name string
+ f func(rw io.ReadWriter) error
+ }{
+ {"WriteUint16", func(rw io.ReadWriter) error { return WriteUint16(rw, LittleEndian, 0) }},
+ {"ReadUint16", func(rw io.ReadWriter) error { _, err := ReadUint16(rw, LittleEndian); return err }},
+ {"WriteUint32", func(rw io.ReadWriter) error { return WriteUint32(rw, LittleEndian, 0) }},
+ {"ReadUint32", func(rw io.ReadWriter) error { _, err := ReadUint32(rw, LittleEndian); return err }},
+ {"WriteUint64", func(rw io.ReadWriter) error { return WriteUint64(rw, LittleEndian, 0) }},
+ {"ReadUint64", func(rw io.ReadWriter) error { _, err := ReadUint64(rw, LittleEndian); return err }},
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ want := errors.New("want")
+ if got := test.f(&readWriter{want}); got != want {
+ t.Errorf("got = %v, want = %v", got, want)
+ }
+ })
+ }
+}