diff options
Diffstat (limited to 'pkg/binary')
-rw-r--r-- | pkg/binary/BUILD | 17 | ||||
-rw-r--r-- | pkg/binary/binary.go | 263 | ||||
-rw-r--r-- | pkg/binary/binary_test.go | 265 |
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) + } + }) + } +} |