blob: 1f3c0e1bd092e10830bb19df3afbe3b9d77ff79e [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build fuchsia
package fidl_test
import (
"bytes"
"fmt"
"math/rand"
"reflect"
"strings"
"syscall/zx"
"testing"
. "syscall/zx/fidl"
. "syscall/zx/fidl/bindingstest"
)
type marshalFunc struct {
name string
fn func(Message, []byte, []zx.Handle) (int, int, error)
}
var marshalFuncs = []marshalFunc{
{"Marshal", func(input Message, respb []byte, resph []zx.Handle) (int, int, error) {
return Marshal(input, respb, resph)
}},
{"MarshalNew", MarshalNew},
}
type unmarshalFunc struct {
name string
fn func([]byte, []zx.Handle, Message) error
}
var unmarshalFuncs = []unmarshalFunc{
{"Unmarshal", func(respb []byte, resph []zx.Handle, output Message) error {
return Unmarshal(respb, resph, output)
}},
{"UnmarshalNew", UnmarshalNew},
}
type example struct {
name string
input Message
expectSize int
}
// general provides test cases used to verify correctness, and
// benchmark against.
//
// Keep these as a slice to preserve consistent ordering when running.
func general() []example {
var (
s = "bye"
s256 = strings.Repeat("hello!!!", 64)
s8192 = strings.Repeat("hello!!!", 1024)
v1 = []int64{-1}
v2 = []string{"x", "hello"}
v3 = []int64{101010}
u1 = Union1{}
)
u1.SetB(TestSimple{X: 555})
vmo, err := zx.NewVMO(10, 0)
if err != nil {
panic(fmt.Sprintf("failed to create vmo: %v", err))
}
defer vmo.Close()
h0, h1, err := zx.NewChannel(0)
defer h0.Close()
defer h1.Close()
if err != nil {
panic(fmt.Sprintf("failed to create vmo: %v", err))
}
st1 := SimpleTable{}
st1.SetX(42)
st1.SetY(67)
return []example{
// simple (to get started)
{"simple", &TestSimple{X: 124}, 8},
{"simplebool", &TestSimpleBool{X: true}, 8},
// alignement
{"align1", &TestAlignment1{X: -36, Y: -10, Z: 51}, 8},
{"align2", &TestAlignment2{
A: 1212141,
B: 908935,
C: -1,
D: 125,
E: -22,
F: 111,
G: 1515,
H: 65535,
I: 1515,
}, 24},
// floats
{"float1", &TestFloat1{A: -36.0}, 8},
{"float2", &TestFloat2{A: -1254918271.0}, 8},
{"float3", &TestFloat3{A: 1241.1, B: 0.2141, C: 20, D: 0.0}, 32},
// arrays
{"array1", &TestArray1{A: [5]int8{1, 77, 2, 4, 89}}, 8},
{"array2", &TestArray2{A: -1.0, B: [1]float32{0.2}}, 16},
{"array3", &TestArray3{
A: -999,
B: [3]uint16{11, 12, 13},
C: 1021,
}, 24},
{"array4", &TestArray4{
A: [9]bool{true, false, false, true, false, true, true, true, true},
}, 16},
// strings
{"string1", &TestString1{A: "str", B: nil}, 40},
{"string1-longer256", &TestString1{A: s256, B: &s256}, 1056},
{"string1-longer8192", &TestString1{A: s8192, B: &s8192}, 16416},
{"string2", &TestString2{A: [2]string{"hello", "g"}}, 48},
{"string3", &TestString3{
A: [2]string{"boop", "g"},
B: [2]*string{&s, nil},
}, 88},
{"string-with-bound", &TestStringWithBound{A: "str"}, 24},
{"opt-string-with-bound", &TestOptStringWithBound{A: &s}, 24},
// vectors
{"vector1", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v1,
}, 88},
{"vector2", &TestVector2{
A: [2][]int8{{9, -1}, {}},
B: [][]int8{{-111, 41}, {-1, -1, -1, -1}},
C: []*[]string{nil, &v2},
}, 200},
// structs
{"struct1", &TestStruct1{
A: TestSimple{
X: -9999,
},
B: &TestSimple{
X: 1254125,
},
}, 24},
{"struct2", &TestStruct2{
A: TestArray1{
A: [5]int8{1, 77, 2, 4, 5},
},
B: TestFloat1{
A: 2.81212,
},
C: TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v3,
},
D: &TestString1{
A: "str",
B: nil,
},
}, 152},
// unions
{"union1", &TestUnion1{
A: u1,
B: nil,
}, 24},
{"union1-bis", &TestUnion1{
A: u1,
B: &u1,
}, 40},
{"union2", &TestUnion2{
A: []Union1{u1, u1, u1},
B: []*Union1{&u1, nil, nil},
}, 120},
// handles
{"handle1", &TestHandle1{
A: zx.Handle(22),
B: zx.HandleInvalid,
C: vmo,
D: zx.VMO(zx.HandleInvalid),
}, 16},
{"handle2", &TestHandle2{
A: []zx.Handle{zx.Handle(vmo)},
B: []zx.VMO{zx.VMO(zx.HandleInvalid)},
}, 48},
// interfaces
{"interface1", &TestInterface1{
A: Test1Interface(ChannelProxy{Channel: h0}),
B: Test1Interface(ChannelProxy{Channel: zx.Channel(zx.HandleInvalid)}),
C: Test1InterfaceRequest(InterfaceRequest{Channel: h1}),
D: Test1InterfaceRequest(InterfaceRequest{
Channel: zx.Channel(zx.HandleInvalid),
}),
}, 16},
// tables
{"table1", &TestSimpleTable{
Table: st1,
}, 112},
}
}
// Using a fixed seed for the randomizer for deterministic behavior of the
// tests, all the while using 'random looking' data.
var r = rand.New(rand.NewSource(524287))
func randBytes(num int) []byte {
bytes := make([]byte, num, num)
if _, err := r.Read(bytes); err != nil {
panic(err)
}
return bytes
}
// fuchsia provides test cases from fuchsia verify correctness, and
// benchmark against.
//
// Keep these as a slice to preserve consistent ordering when running.
func fuchsia() []example {
var (
bytes16 = randBytes(16)
bytes64 = randBytes(64)
bytes256 = randBytes(256)
bytes1024 = randBytes(1024)
bytes4096 = randBytes(4096)
bytes8192 = randBytes(8192)
)
return []example{
// fuchsia.io, ReadAt response
{"fuchsia.io-readAt-response0", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: []byte{},
}, 24 + 0},
{"fuchsia.io-readAt-response16", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes16,
}, 24 + 16},
{"fuchsia.io-readAt-response64", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes64,
}, 24 + 64},
{"fuchsia.io-readAt-response256", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes256,
}, 24 + 256},
{"fuchsia.io-readAt-response1024", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes1024,
}, 24 + 1024},
{"fuchsia.io-readAt-response4096", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes4096,
}, 24 + 4096},
{"fuchsia.io-readAt-response8192", &TestFuchsiaIoReadAtResponse{
S: 5,
Data: bytes8192,
}, 24 + 8192},
// fuchsia.io, WriteAt request
{"fuchsia.io-writeAt-request0", &TestFuchsiaIoWriteAtRequest{
Data: []byte{},
Offset: 5,
}, 24 + 0},
{"fuchsia.io-writeAt-request16", &TestFuchsiaIoWriteAtRequest{
Data: bytes16,
Offset: 5,
}, 24 + 16},
{"fuchsia.io-writeAt-request64", &TestFuchsiaIoWriteAtRequest{
Data: bytes64,
Offset: 5,
}, 24 + 64},
{"fuchsia.io-writeAt-request256", &TestFuchsiaIoWriteAtRequest{
Data: bytes256,
Offset: 5,
}, 24 + 256},
{"fuchsia.io-writeAt-request1024", &TestFuchsiaIoWriteAtRequest{
Data: bytes1024,
Offset: 5,
}, 24 + 1024},
{"fuchsia.io-writeAt-request4096", &TestFuchsiaIoWriteAtRequest{
Data: bytes4096,
Offset: 5,
}, 24 + 4096},
{"fuchsia.io-writeAt-request8192", &TestFuchsiaIoWriteAtRequest{
Data: bytes8192,
Offset: 5,
}, 24 + 8192},
}
}
type checker struct {
marshalFunc
unmarshalFunc
}
func (c checker) check(t *testing.T, input Message, expectSize int) {
t.Helper()
defer func() {
if r := recover(); r != nil {
// When running tests on device, this bubbles up the error
// on the console launching the tests, rather than having
// to look at the device's kernel logs.
t.Fatalf("panic: %s", r)
panic(r)
}
}()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := c.marshalFunc.fn(input, respb[:], resph[:])
if err != nil {
t.Fatalf("marshal: failed: %s", err)
}
if nb != expectSize {
t.Fatalf("marshal: expected size %d but got %d: %v", expectSize, nb, respb[:nb])
}
output := makeDefault(input)
if err := c.unmarshalFunc.fn(respb[:nb], resph[:nh], output); err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", input, output)
}
}
func TestCorrectness(t *testing.T) {
for _, m := range marshalFuncs {
for _, u := range unmarshalFuncs {
c := checker{m, u}
for _, ex := range general() {
t.Run(fmt.Sprintf("%s-%s-%s", m.name, u.name, ex.name), func(t *testing.T) {
c.check(t, ex.input, ex.expectSize)
})
}
for _, ex := range fuchsia() {
t.Run(fmt.Sprintf("%s-%s-%s", m.name, u.name, ex.name), func(t *testing.T) {
c.check(t, ex.input, ex.expectSize)
})
}
}
}
}
func makeDefault(msg Message) Message {
typ := reflect.TypeOf(msg)
if typ.Kind() != reflect.Ptr && typ.Elem().Kind() != reflect.Struct {
panic("expecting *struct")
}
return reflect.New(typ.Elem()).Interface().(Message)
}
// successCase represents a golden test for a success case, where encoding
// and decoding should succceed.
type successCase struct {
name string
input Message
bytes []byte
handles []zx.Handle
}
func (ex successCase) check(t *testing.T) {
for _, mFn := range marshalFuncs {
for _, uFn := range unmarshalFuncs {
t.Run(ex.name+"_"+mFn.name+"_"+uFn.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
// When running tests on device, this bubbles up the error
// on the console launching the tests, rather than having
// to look at the device's kernel logs.
t.Fatalf("panic: %s", r)
panic(r)
}
}()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := mFn.fn(ex.input, respb[:], resph[:])
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(ex.bytes, respb[:nb]) {
t.Fatalf("expected %x, got %x", ex.bytes, respb[:nb])
}
if len(ex.handles) == 0 {
if nh != 0 {
t.Fatalf("no handles expected, got %d", nh)
}
} else {
if !reflect.DeepEqual(ex.handles, resph[:nh]) {
t.Fatalf("expected %v, got %v", ex.handles, resph[:nh])
}
}
output := makeDefault(ex.input)
if err := uFn.fn(respb[:nb], resph[:nh], output); err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(ex.input, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", ex.input, output)
}
})
}
}
}
type errorCaseUnmarshal struct {
name string
message Message
input []byte
errorCode ErrorCode
}
var baseErrorCasesUnmarshal = []errorCaseUnmarshal{
{"empty-array-TestSimple", &TestSimple{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestSimple", &TestSimple{}, nil, ErrPayloadTooSmall},
{"small-array-TestSimple", &TestSimple{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"empty-array-TestSimpleBool", &TestSimpleBool{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestSimpleBool", &TestSimpleBool{}, nil, ErrPayloadTooSmall},
{"empty-array-TestAlignment1", &TestAlignment1{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestAlignment1", &TestAlignment1{}, nil, ErrPayloadTooSmall},
{"small-array-TestAlignment1", &TestAlignment1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"empty-array-TestAlignment2", &TestAlignment2{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestAlignment2", &TestAlignment2{}, nil, ErrPayloadTooSmall},
{"small-array-TestAlignment2", &TestAlignment2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"small-array-TestAlignment2-2", &TestAlignment2{}, make([]byte, 10), ErrPayloadTooSmall},
{"empty-array-TestFloat1", &TestFloat1{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestFloat1", &TestFloat1{}, nil, ErrPayloadTooSmall},
{"small-array-TestFloat1", &TestFloat1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"empty-array-TestFloat2", &TestFloat2{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestFloat2", &TestFloat2{}, nil, ErrPayloadTooSmall},
{"small-array-TestFloat2", &TestFloat2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"empty-array-TestFloat3", &TestFloat3{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestFloat3", &TestFloat3{}, nil, ErrPayloadTooSmall},
{"small-array-TestFloat3", &TestFloat3{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"small-array-TestFloat3-2", &TestFloat3{}, make([]byte, 6), ErrPayloadTooSmall},
{"nil-array-TestArray1", &TestArray1{}, nil, ErrPayloadTooSmall},
{"empty-array-TestArray1", &TestArray1{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestArray1", &TestArray1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"nil-array-TestArray2", &TestArray2{}, nil, ErrPayloadTooSmall},
{"empty-array-TestArray2", &TestArray2{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestArray2", &TestArray2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"six-bytes-array-TestArray2", &TestArray2{}, make([]byte, 6), ErrPayloadTooSmall},
{"nil-array-TestArray3", &TestArray3{}, nil, ErrPayloadTooSmall},
{"empty-array-TestArray3", &TestArray3{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestArray3", &TestArray3{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"six-bytes-array-TestArray3", &TestArray3{}, make([]byte, 6), ErrPayloadTooSmall},
{"thirteen-bytes-array-TestArray3", &TestArray3{}, make([]byte, 13), ErrPayloadTooSmall},
{"nil-array-TestArray4", &TestArray4{}, nil, ErrPayloadTooSmall},
{"empty-array-TestArray4", &TestArray4{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestArray4", &TestArray4{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"nil-array-TestString1", &TestString1{}, nil, ErrPayloadTooSmall},
{"empty-array-TestString1", &TestString1{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestString1", &TestString1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"nil-array-TestString2", &TestString2{}, nil, ErrPayloadTooSmall},
{"empty-array-TestString2", &TestString2{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestString2", &TestString2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"nil-array-TestStruct2", &TestStruct2{}, nil, ErrPayloadTooSmall},
{"empty-array-TestStruct2", &TestStruct2{}, []byte{}, ErrPayloadTooSmall},
{"two-bytes-array-TestStruct2", &TestStruct2{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"six-bytes-array-TestStruct2", &TestStruct2{}, make([]byte, 6), ErrPayloadTooSmall},
{"thirteen-bytes-array-TestStruct2", &TestStruct2{}, make([]byte, 13), ErrPayloadTooSmall},
{"empty-array-TestUnion1", &TestUnion1{}, []byte{}, ErrPayloadTooSmall},
{"nil-array-TestUnion1", &TestUnion1{}, nil, ErrPayloadTooSmall},
{"small-array-TestUnion1", &TestUnion1{}, []byte{0x00, 0x0}, ErrPayloadTooSmall},
{"non-zero-or-one-bool", &TestSimpleBool{}, []byte{2}, ErrInvalidBoolValue},
{"wrong-tag", &TestUnion1{}, []byte{0, 0, 0, 4}, ErrInvalidUnionTag},
{"empty-struct-non-zero", &TestEmptyStructSandwich{}, emptyStructSandwichWithOneInsteadOfZero, ErrInvalidEmptyStruct},
}
var allErrorCasesUnmarshal = append(baseErrorCasesUnmarshal, []errorCaseUnmarshal{
{"string-wrong-ptr-no-alloc", &TestStringWithBound{}, []byte{
3, 0, 0, 0, 0, 0, 0, 0, // length
0, 0, 0, 0, 0, 0, 0, 0, // ptr (no alloc)
// no data, unmarshal should fail before
}, ErrUnexpectedNullRef},
{"string-wrong-ptr-incorrect", &TestStringWithBound{}, []byte{
3, 0, 0, 0, 0, 0, 0, 0, // length
0, 0, 0, 0, 0, 0, 0, 1, // ptr (no alloc)
// no data, unmarshal should fail before
}, ErrBadRefEncoding},
{"string-too-long", &TestStringWithBound{}, []byte{
9, 0, 0, 0, 0, 0, 0, 0, // length (too long)
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr
// no data, unmarshal should fail before
}, ErrStringTooLong},
{"opt-string-wrong-ptr-incorrect", &TestOptStringWithBound{}, []byte{
3, 0, 0, 0, 0, 0, 0, 0, // length
0, 0, 0, 0, 0, 0, 0, 1, // ptr (no alloc)
// no data, unmarshal should fail before
}, ErrBadRefEncoding},
{"opt-string-too-long", &TestOptStringWithBound{}, []byte{
9, 0, 0, 0, 0, 0, 0, 0, // length (too long)
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr
// no data, unmarshal should fail before
}, ErrStringTooLong},
}...)
var simpleTableWithXY = []byte{
5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
8, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
0, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 3: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 4: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
8, 0, 0, 0, 0, 0, 0, 0, // envelope 5: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
42, 0, 0, 0, 0, 0, 0, 0, // field X
67, 0, 0, 0, 0, 0, 0, 0, // field Y
}
var simpleTableWithY = []byte{
5, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
0, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 3: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
0, 0, 0, 0, 0, 0, 0, 0, // envelope 4: num bytes / num handles
0, 0, 0, 0, 0, 0, 0, 0, // no alloc
8, 0, 0, 0, 0, 0, 0, 0, // envelope 5: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
67, 0, 0, 0, 0, 0, 0, 0, // field Y
}
// TableWithStringAndVector: {
// foo: "hello",
// bar: 27,
// }
var tableWithStringAndVectorHello27 = []byte{
2, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
24, 0, 0, 0, 0, 0, 0, 0, // envelope 1: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // envelope 1: alloc present
8, 0, 0, 0, 0, 0, 0, 0, // envelope 2: num bytes / num handles
255, 255, 255, 255, 255, 255, 255, 255, // envelope 2: alloc present
5, 0, 0, 0, 0, 0, 0, 0, // element 1: length
255, 255, 255, 255, 255, 255, 255, 255, // element 1: alloc present
104, 101, 108, 108, 111, 0, 0, 0, // element 1: hello
27, 0, 0, 0, 0, 0, 0, 0, // element 2: value
}
var emptyTable = []byte{
0, 0, 0, 0, 0, 0, 0, 0, // max ordinal
255, 255, 255, 255, 255, 255, 255, 255, // alloc present
}
var emptyStructSandwich = []byte{
6, 0, 0, 0, 0, 0, 0, 0, // length of "before"
255, 255, 255, 255, 255, 255, 255, 255, // "before" is present
0, // empty struct zero field
0, 0, 0, 0, 0, 0, 0, // 7 bytes of padding after empty struct, to align to 64 bits
5, 0, 0, 0, 0, 0, 0, 0, // length of "world"
255, 255, 255, 255, 255, 255, 255, 255, // "after" is present
'b', 'e', 'f', 'o', 'r', 'e',
0, 0, // 2 bytes of padding after "hello", to align to 64 bits
'a', 'f', 't', 'e', 'r', // "world" string
0, 0, 0, // 3 bytes of padding after "world", to align to 64 bits
}
var emptyStructSandwichWithOneInsteadOfZero = []byte{
6, 0, 0, 0, 0, 0, 0, 0, // length of "before"
255, 255, 255, 255, 255, 255, 255, 255, // "before" is present
1, // empty struct with invalid value of 1 (instead of zero)
0, 0, 0, 0, 0, 0, 0, // 7 bytes of padding after empty struct, to align to 64 bits
5, 0, 0, 0, 0, 0, 0, 0, // length of "world"
255, 255, 255, 255, 255, 255, 255, 255, // "after" is present
'b', 'e', 'f', 'o', 'r', 'e',
0, 0, // 2 bytes of padding after "hello", to align to 64 bits
'a', 'f', 't', 'e', 'r', // "world" string
0, 0, 0, // 3 bytes of padding after "world", to align to 64 bits
}
func TestAllSuccessCases(t *testing.T) {
successCase{
name: "empty-struct-sandwich",
input: &TestEmptyStructSandwich{
Before: "before",
Es: EmptyStruct{},
After: "after",
},
bytes: emptyStructSandwich,
}.check(t)
st1 := SimpleTable{}
st1.SetX(42)
st1.SetY(67)
successCase{
name: "simpletable-x-and-y",
input: &TestSimpleTable{
Table: st1,
},
bytes: simpleTableWithXY,
}.check(t)
st2 := SimpleTable{}
st2.SetY(67)
successCase{
name: "simpletable-just-y",
input: &TestSimpleTable{
Table: st2,
},
bytes: simpleTableWithY,
}.check(t)
successCase{
name: "table-with-string-and-vector-1",
input: &TestTableWithStringAndVector{
Table: TableWithStringAndVector{
Foo: "hello",
FooPresent: true,
Bar: 27,
BarPresent: true,
},
},
bytes: tableWithStringAndVectorHello27,
}.check(t)
successCase{
name: "empty-table",
input: &TestSimpleTable{},
bytes: emptyTable,
}.check(t)
var xu SampleXUnion
xu.SetU(0xdeadbeef)
successCase{
name: "inline-xunion-in-struct",
input: &TestInlineXUnionInStruct{
Before: "before",
Xu: xu,
After: "after",
},
bytes: []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
// secondary object 1: "before"
'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00,
// secondary object 2: xunion content
0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // xunion envelope content (0xdeadbeef) + padding
// secondary object 3: "after"
'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00,
},
}.check(t)
successCase{
name: "optional-xunion-in-struct-absent",
input: &TestOptionalXUnionInStruct{
Before: "before",
// No xunion.
After: "after",
},
bytes: []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // envelope data is absent
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
// secondary object 1: "before"
'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00,
// secondary object 2: "after"
'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00,
},
}.check(t)
successCase{
name: "optional-xunion-in-struct-present",
input: &TestOptionalXUnionInStruct{
Before: "before",
Xu: &xu,
After: "after",
},
bytes: []byte{
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" presence
// secondary object 1: "before"
'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00,
// secondary object 2: xunion content
0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // xunion envelope content (0xdeadbeef) + padding
// secondary object 3: "after"
'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00,
},
}.check(t)
var xitAbsent XUnionInTable
xitAbsent.SetBefore("before")
xitAbsent.SetAfter("after")
successCase{
name: "xunion-in-table-xunion-absent",
input: &TestXUnionInTable{
Value: xitAbsent,
},
bytes: []byte{
// primary object
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // vector<envelope> element count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // vector<envelope> present
// secondary object 1: vector data
// vector[0]: envelope<string before>
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" is present
// vector[1]: envelope<SampleXUnion xu>
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // xunion is absent
// vector[2]: envelope<string after>
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" is present
// secondary object 2: "before" length + pointer
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" present
// secondary object 3: "before"
'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, // "before"
// secondary object 4: "after" length + pointer
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" present
// secondary object 5: "before"
'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, // "after"
},
}.check(t)
var xitPresent XUnionInTable
xitPresent.SetBefore("before")
xitPresent.SetXu(xu)
xitPresent.SetAfter("after")
successCase{
name: "xunion-in-table-xunion-present",
input: &TestXUnionInTable{
Value: xitPresent,
},
bytes: []byte{
// primary object
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // vector<envelope> element count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // vector<envelope> present
// secondary object 1: vector data
// vector[0]: envelope<string before>
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" is present
// vector[1]: envelope<SampleXUnion xu>
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // xunion is present
// vector[2]: envelope<string after>
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" is present
// secondary object 2: "before" length + pointer
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "before" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "before" present
// secondary object 3: "before"
'b', 'e', 'f', 'o', 'r', 'e', 0x00, 0x00, // "before"
// secondary object 4: xunion
0x39, 0xdb, 0x88, 0x79, 0x00, 0x00, 0x00, 0x00, // xunion discriminator + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // num bytes + num handles
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope data is present
// secondary object 5: xunion content
0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00, // 0xdeadbeef + padding
// secondary object 6: "after" length + pointer
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // "after" length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // "after" present
// secondary object 7: "before"
'a', 'f', 't', 'e', 'r', 0x00, 0x00, 0x00, // "after"
},
}.check(t)
var ipAddressConfig IpAddressConfig
ipAddressConfig.SetDhcp(true)
successCase{
name: "add-ethernet-device-request",
input: &TestAddEthernetDeviceRequest{
TopologicalPath: "@/dev/sys/pci/00:03.0/e1000/ethernet",
Config: InterfaceConfig{
Name: "ethp0003",
IpAddressConfig: ipAddressConfig,
},
Device: EthernetDeviceInterface{zx.Channel(0xdeadbeef)},
},
bytes: []byte{
0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // topological_path
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // topological_path
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // name
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // name
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // subnet (dhcp variant)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // padding
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // device (handle present)
0x40, 0x2f, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x79, // @/dev/sy
0x73, 0x2f, 0x70, 0x63, 0x69, 0x2f, 0x30, 0x30, // s/pci/00
0x3a, 0x30, 0x33, 0x2e, 0x30, 0x2f, 0x65, 0x31, // :03.0/e1
0x30, 0x30, 0x30, 0x2f, 0x65, 0x74, 0x68, 0x65, // 000/ethe
0x72, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, // rnet
0x65, 0x74, 0x68, 0x70, 0x30, 0x30, 0x30, 0x33, // ethp0003
},
handles: []zx.Handle{
zx.Handle(0xdeadbeef),
},
}.check(t)
}
func TestTableCompatibilityWithXY(t *testing.T) {
ost := OlderSimpleTable{}
ost.SetX(42)
st := SimpleTable{}
st.SetX(42)
st.SetY(67)
nst := NewerSimpleTable{}
nst.SetX(42)
nst.SetY(67)
cases := []Message{
&TestOlderSimpleTable{Table: ost},
&TestSimpleTable{Table: st},
&TestNewerSimpleTable{Table: nst},
}
for _, expected := range cases {
for _, u := range unmarshalFuncs {
output := makeDefault(expected)
u.fn(simpleTableWithXY, nil, output)
if !reflect.DeepEqual(expected, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
}
}
}
}
func TestTableCompatibilityWithY(t *testing.T) {
ost := OlderSimpleTable{}
st := SimpleTable{}
st.SetY(67)
nst := NewerSimpleTable{}
nst.SetY(67)
cases := []Message{
&TestOlderSimpleTable{Table: ost},
&TestSimpleTable{Table: st},
&TestNewerSimpleTable{Table: nst},
}
for _, expected := range cases {
for _, u := range unmarshalFuncs {
output := makeDefault(expected)
u.fn(simpleTableWithY, nil, output)
if !reflect.DeepEqual(expected, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
}
}
}
}
func TestFailuresMarshal(t *testing.T) {
v1 := []int64{1, 2, 3}
cases := []struct {
name string
input Message
errorCode ErrorCode
}{
{"string3-string-too-long", &TestString3{
A: [2]string{
"too long!", // limit is 4, provided is longer(tm)
"g",
},
B: [2]*string{nil, nil},
}, ErrStringTooLong},
{"vector1-C-vector-too-long", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99, 100, 101}, // limit is 2, provided is 3
D: nil,
}, ErrVectorTooLong},
{"vector1-D-vector-too-long", &TestVector1{
A: []int8{1, 2, 3, 4},
B: nil,
C: []int32{99},
D: &v1, // limit is 2, provided is 3
}, ErrVectorTooLong},
{"union1-A-uninitialized", &TestUnion1{
A: Union1{}, // Intentionally don't set any members of the union.
B: nil,
}, ErrInvalidUnionTag},
{"union1-A-tag-out-of-bounds", &TestUnion1{
A: Union1{Union1Tag: Union1D + 1},
B: nil,
}, ErrInvalidUnionTag},
}
for _, ex := range cases {
t.Run(ex.name, func(t *testing.T) {
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
_, _, err := MarshalNew(ex.input, respb[:], resph[:])
validationErr, ok := err.(ValidationError)
if !ok {
t.Fatalf("expected ValidationError, was %v", err)
}
if validationErr.Code() != ex.errorCode {
t.Fatalf("expected %s, was %s", ex.errorCode, validationErr.Code())
}
})
}
}
func TestFailuresUnmarshalNoHandles(t *testing.T) {
for _, ex := range allErrorCasesUnmarshal {
t.Run(ex.name, func(t *testing.T) {
err := UnmarshalNew(ex.input, nil, ex.message)
validationErr, ok := err.(ValidationError)
if !ok {
t.Fatalf("expected ValidationError, was %v", err)
}
if validationErr.Code() != ex.errorCode {
t.Fatalf("expected %s, was %s", ex.errorCode, validationErr.Code())
}
})
}
}
func benchmarkMarshal(b *testing.B, ex example) {
for _, m := range marshalFuncs {
b.Run(fmt.Sprintf("%s-%s", m.name, ex.name), func(b *testing.B) {
b.StopTimer()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
b.StartTimer()
for n := 0; n < b.N; n++ {
if _, _, err := m.fn(ex.input, respb[:], resph[:]); err != nil {
b.Fail()
}
}
})
}
}
func BenchmarkMarshal(b *testing.B) {
for _, ex := range general() {
benchmarkMarshal(b, ex)
}
for _, ex := range fuchsia() {
benchmarkMarshal(b, ex)
}
}
func benchmarkUnmarshal(b *testing.B, ex example) {
for _, u := range unmarshalFuncs {
b.Run(fmt.Sprintf("%s-%s", u.name, ex.name), func(b *testing.B) {
b.StopTimer()
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
nb, nh, err := Marshal(ex.input, respb[:], resph[:])
if err != nil {
b.Fail()
}
output := makeDefault(ex.input)
b.StartTimer()
for n := 0; n < b.N; n++ {
if err := u.fn(respb[:nb], resph[:nh], output); err != nil {
b.Fail()
}
}
})
}
}
func BenchmarkUnmarshal(b *testing.B) {
for _, ex := range general() {
benchmarkUnmarshal(b, ex)
}
for _, ex := range fuchsia() {
benchmarkUnmarshal(b, ex)
}
}