blob: 5be8afbec6388d4d07624f4b9aa9732607a9fd38 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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"
"syscall/zx/fidl/conformance"
)
func TestMarshalMessageHeader(t *testing.T) {
data := []byte{
0x12, 0x34, 0x56, 0x78, // txid
0xAB, 0xCD, 0xEF, // flags
0x01, // magic number
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // method ordinal
}
var header MessageHeader
hnb, _, err := Unmarshal(data, nil, &header)
if err != nil {
t.Fatalf("unmarshal failed: %s", err)
}
if hnb != 16 {
t.Fatalf("expected 16 bytes read, was %d", hnb)
}
if header.Magic != 0x01 {
t.Fatalf("expected header txid of 0x01, was %x", header.Magic)
}
}
func TestCheckUnmarshalReadSize(t *testing.T) {
examples := [][]byte{
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, // these will go unread
},
}
for _, data := range examples {
var message conformance.EmptyStruct
hnb, _, err := Unmarshal(data, nil, &message)
if err != nil {
t.Fatalf("unmarshal failed: %s", err)
}
if hnb != 8 {
t.Fatalf("expected 8 bytes read, was %d", hnb)
}
}
}
func TestEnvelopeBadPadding(t *testing.T) {
input := []byte{
0x21, 0xEB, 0x7E, 0x76, 0x55, 0x55, 0x55, 0x55, // ordinal + invalid padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope present
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message XUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err == nil {
t.Fatalf("Unmarshal() returned nil error")
}
errCode := err.(ValidationError).Code()
if errCode != ErrNonZeroPadding {
t.Fatalf("Unmarshal() returned %d", errCode)
}
}
func TestEnvelopeByteCountTooLarge(t *testing.T) {
input := []byte{
0x21, 0xEB, 0x7E, 0x76, 0x00, 0x00, 0x00, 0x00, // ordinal + padding
0xaa, 0xaa, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, // byte + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope present
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message XUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err == nil {
t.Fatalf("Unmarshal() returned nil error")
}
errCode := err.(ValidationError).Code()
if errCode != ErrEnvelopeTooLong {
t.Fatalf("Unmarshal() returned %d", errCode)
}
}
func TestEnvelopeHandleCountTooLarge(t *testing.T) {
input := []byte{
0x21, 0xEB, 0x7E, 0x76, 0x00, 0x00, 0x00, 0x00, // ordinal + padding
0x08, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xaa, // byte + invalid handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope present
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message XUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err == nil {
t.Fatalf("Unmarshal() returned nil error")
}
errCode := err.(ValidationError).Code()
if errCode != ErrTooManyHandles {
t.Fatalf("Unmarshal() returned %d", errCode)
}
}
func TestEnvelopeInvalidPresence(t *testing.T) {
input := []byte{
0x21, 0xEB, 0x7E, 0x76, 0x00, 0x00, 0x00, 0x00, // ordinal + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte + handle count
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, // invalid envelope presence
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message XUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err == nil {
t.Fatalf("Unmarshal() returned nil error")
}
errCode := err.(ValidationError).Code()
if errCode != ErrBadRefEncoding {
t.Fatalf("Unmarshal() returned %d", errCode)
}
}
func TestStrictXUnionWithUnknownData(t *testing.T) {
input := []byte{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ordinal + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope present
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message StrictXUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err == nil {
t.Fatalf("Unmarshal() returned nil error")
}
errCode := err.(ValidationError).Code()
if errCode != ErrInvalidXUnionTag {
t.Fatalf("Unmarshal() returned %d", errCode)
}
}
func TestFlexibleXUnionWithUnknownData(t *testing.T) {
input := []byte{
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ordinal + padding
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte + handle count
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // envelope present
0xef, 0xbe, 0xad, 0xde, 0x11, 0xba, 0x5e, 0xba, // data
}
var message XUnion1Struct
_, _, err := Unmarshal(input, nil, &message)
if err != nil {
t.Fatalf("TestXUnionWithUnknownData failed: %s", err)
}
if int(message.Xu.Ordinal()) != int(input[0]) {
t.Fatalf("XUnion1Tag expected=%d actual=%d", input[0], message.Xu.Ordinal())
}
if !bytes.Equal(message.Xu.I_unknownData, input[24:32]) {
t.Fatalf("I_unknownData expected=%v, actual=%v", input[24:32], message.Xu.I_unknownData)
}
}
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{}
xu1 = XUnion1{}
sxu1 = StrictXUnion1{}
)
u1.SetB(TestSimple{X: 555})
xu1.SetB(TestSimple{X: 555})
sxu1.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},
// alignment
{"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}, // A=8(tag+padding)+8(data), B=8(ptr)
{"union1-bis", &TestUnion1{
A: u1,
B: &u1,
}, 40}, // A=8(tag+padding)+8(data), B=8(ptr), out-of-line A=16
{"union2", &TestUnion2{
A: []Union1{u1, u1, u1},
B: []*Union1{&u1, nil, nil},
}, 120}, // A=8(size)+8(ptr), B=8(size)+8(ptr), OOL A=3*16, OOL B=3*8(ptr), OOL B[0]=16
// xunions
{"xunion1", &TestXUnion1{
A: xu1,
B: nil,
}, 56}, // A=24(xunion_header), B=24(zero_xunion_header), OOL A=8(data)
{"xunion1-bis", &TestXUnion1{
A: xu1,
B: &xu1,
}, 64}, // A=24(xunion_header), B=24(xunion_header), OOL A=8(data), OOL B=8(data)
{"xunion2", &TestXUnion2{
A: []XUnion1{xu1, xu1, xu1},
B: []*XUnion1{&xu1, nil, nil},
}, 208}, // A=8(size)+8(ptr), B=8(size)+8(ptr), OOL A.vector_contents=3*24(xunion_header), OOL B.vector_contents=3*24, OOL A[0,1,2]=3*8, OOL B[0]=8
// strict xunions
{"strict-xunion1", &TestStrictXUnion1{
A: sxu1,
B: nil,
}, 56}, // A=24(xunion_header), B=24(zero_xunion_header), OOL A=8(data)
{"strict-xunion1-bis", &TestStrictXUnion1{
A: sxu1,
B: &sxu1,
}, 64}, // A=24(xunion_header), B=24(xunion_header), OOL A=8(data), OOL B=8(data)
{"strict-xunion2", &TestStrictXUnion2{
A: []StrictXUnion1{sxu1, sxu1, sxu1},
B: []*StrictXUnion1{&sxu1, nil, nil},
}, 208}, // A=8(size)+8(ptr), B=8(size)+8(ptr), OOL A.vector_contents=3*24(xunion_header), OOL B.vector_contents=3*24, OOL A[0,1,2]=3*8, OOL B[0]=8
// 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},
}
}
// TODO(FIDL-508): Convert all these tests to be conformance tests, using GIDL.
func 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 := Marshal(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(reflect.TypeOf(input))
nbActual, nhActual, err := Unmarshal(respb[:nb], resph[:nh], output)
if err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", input, output)
}
if nb != nbActual {
t.Fatalf("unmarshal: num bytes, expected: %d, got: %d", nb, nbActual)
}
if nh != nhActual {
t.Fatalf("unmarshal: num handles, expected: %d, got: %d", nh, nhActual)
}
}
func TestCorrectness(t *testing.T) {
for _, ex := range general() {
t.Run(ex.name, func(t *testing.T) {
check(t, ex.input, ex.expectSize)
})
}
for _, ex := range fuchsia() {
t.Run(ex.name, func(t *testing.T) {
check(t, ex.input, ex.expectSize)
})
}
}
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", &EmptyStruct{}, emptyStructWithOneInsteadOfZero, 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},
{"string-has-truncated-data", &TestStringWithBound{}, []byte{
8, 0, 0, 0, 0, 0, 0, 0, // length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr
0x41, 0x72, 0x67, 0x68, 0x68, 0x68, 0x68, // only 7 bytes
}, ErrMessageTooSmall},
{"string-is-not-valid-utf8", &TestStringWithBound{}, []byte{
2, 0, 0, 0, 0, 0, 0, 0, // length
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ptr
0xc3, 0x28, // invalid 2 octet sequence
}, ErrStringNotUTF8},
{"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
}
var emptyStructWithOneInsteadOfZero = []byte{
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
}
func TestAllManualSuccessCases(t *testing.T) {
// TODO(FIDL-635): Complete conversion.
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)
successCase{
name: "package-resolver-resolve-request",
input: &TestPackageResolverResolveRequest{
PackageUrl: "a",
Selectors: []string{"a"},
UpdatePolicy: UpdatePolicy{
FetchIfAbsent: true,
AllowOldVersions: true,
},
Dir: EthernetDeviceInterfaceRequest{zx.Channel(0xdeadbeef)},
},
bytes: []byte{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // package url size
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // package url ptr
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // selectors size
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // selectors ptr
0x01, 0x01, 0x00, 0x00, // policy struct + padding
0xFF, 0xFF, 0xFF, 0xFF, // request handle
// out of line data
0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
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 {
output := makeDefault(reflect.TypeOf(expected))
if _, _, err := Unmarshal(simpleTableWithXY, nil, output); err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
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 {
output := makeDefault(reflect.TypeOf(expected))
_, _, err := Unmarshal(simpleTableWithY, nil, output)
if err != nil {
t.Fatalf("unmarshal: failed: %s", err)
}
if !reflect.DeepEqual(expected, output) {
t.Fatalf("unmarshal: expected: %v, got: %v", expected, output)
}
}
}
func TestFailureNullableTable(t *testing.T) {
type TestNullableTable struct {
_ struct{} `fidl:"s,123,456" fidl2:"s,123,456"`
A *SimpleTable `fidl:"0"`
}
_, err := CreateMarshaler(TestNullableTable{})
if err == nil {
t.Fatalf("expected error creating marshaler for nullable table")
}
if !strings.Contains(err.Error(), "optional field marshaler") {
t.Fatalf("unexpected error: %s", err.Error())
}
}
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{I_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 := Marshal(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 := Unmarshal(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 TestUnmarshalXunionIntoUnion(t *testing.T) {
const expectedUnionTag = XUnion1AsUnionD
const expectedFieldValue = 1.2
inHeader := MessageHeader{
Flags: [3]byte{
1, // decode unions from xunion bytes flag
0,
0,
},
}
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
input := XUnion1Struct{Xu: XUnion1WithD(expectedFieldValue)}
if _, _, err := MarshalHeaderThenMessage(&inHeader, &input, respb[:], resph[:]); err != nil {
t.Fatalf("error unmarshaling: %v\n", err)
}
// Set the ordinal value to the value in XUnion1AsUnionStruct, since it is
// a different type.
// This value comes from bindingstest/impl.go
const bodyStart = 16
respb[bodyStart+0] = 0x24
respb[bodyStart+1] = 0x94
respb[bodyStart+2] = 0x6a
respb[bodyStart+3] = 0x3e
var union XUnion1AsUnionStruct
var outHeader MessageHeader
if err := UnmarshalHeaderThenMessage(respb[:], nil, &outHeader, &union); err != nil {
t.Fatalf("error marshaling: %v", err)
}
if inHeader != outHeader {
t.Fatalf("want %v, got %v", inHeader, outHeader)
}
if union.Xuau.I_xUnion1AsUnionTag != expectedUnionTag {
t.Fatalf("want %v, got %v", expectedUnionTag, union.Xuau.I_xUnion1AsUnionTag)
}
if union.Xuau.D != expectedFieldValue {
t.Fatalf("want %v, got %v", expectedFieldValue, union.Xuau.D)
}
}
func TestUnmarshalXUnionInsideXUnionIntoUnionInsideUnion(t *testing.T) {
const expectedOuterUnionTag = UnionInsideUnionB
const expectedInnerUnionTag = XUnion1AsUnionD
const expectedFieldValue = 1.2
inHeader := MessageHeader{
Flags: [3]byte{
1, // decode unions from xunion bytes flag
0,
0,
},
}
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
input := XUnionInsideXUnionStruct{U: XUnionInsideXUnionWithB(XUnion1WithD(expectedFieldValue))}
if _, _, err := MarshalHeaderThenMessage(&inHeader, &input, respb[:], resph[:]); err != nil {
t.Fatalf("error unmarshaling: %v\n", err)
}
// Set the ordinal values since they come from a different type.
// This value comes from bindingstest/impl.go
const outerUnionStart = 16
respb[outerUnionStart+0] = 0xd0
respb[outerUnionStart+1] = 0xe8
respb[outerUnionStart+2] = 0x4d
respb[outerUnionStart+3] = 0x14
const innerUnionStart = outerUnionStart + 24
respb[innerUnionStart+0] = 0x24
respb[innerUnionStart+1] = 0x94
respb[innerUnionStart+2] = 0x6a
respb[innerUnionStart+3] = 0x3e
var union UnionInsideUnionStruct
var outHeader MessageHeader
if err := UnmarshalHeaderThenMessage(respb[:], nil, &outHeader, &union); err != nil {
t.Fatalf("error marshaling: %v", err)
}
if inHeader != outHeader {
t.Fatalf("want %v, got %v", inHeader, outHeader)
}
if union.U.I_unionInsideUnionTag != expectedOuterUnionTag {
t.Fatalf("want %v, got %v", expectedOuterUnionTag, union.U.I_unionInsideUnionTag)
}
if union.U.B.I_xUnion1AsUnionTag != expectedInnerUnionTag {
t.Fatalf("want %v, got %v", expectedInnerUnionTag, union.U.B.I_xUnion1AsUnionTag)
}
if union.U.B.D != expectedFieldValue {
t.Fatalf("want %v, got %v", expectedFieldValue, union.U.B.D)
}
}
func TestMarshalUnionIntoXUnion(t *testing.T) {
const expectedUnionTag = XUnion1AsUnionD
const expectedFieldValue = 1.2
var unionRespb [zx.ChannelMaxMessageBytes]byte
var unionResph [zx.ChannelMaxMessageHandles]zx.Handle
unionInput := XUnion1AsUnionStruct{Xuau: XUnion1AsUnionWithD(expectedFieldValue)}
ctx := MarshalerContext{EncodeUnionsAsXUnionBytes: true}
un, _, err := MarshalWithContext(ctx, &unionInput, unionRespb[:], unionResph[:])
if err != nil {
t.Fatalf("error marshaling: %v\n", err)
}
var xunionRespb [zx.ChannelMaxMessageBytes]byte
var xunionResph [zx.ChannelMaxMessageHandles]zx.Handle
xunionInput := XUnion1Struct{Xu: XUnion1WithD(expectedFieldValue)}
xun, _, err := Marshal(&xunionInput, xunionRespb[:], xunionResph[:])
if err != nil {
t.Fatalf("error unmarshaling: %v\n", err)
}
// Set the ordinal value to the value in XUnion1AsUnionStruct, since it is
// a different type.
// This value comes from bindingstest/impl.go
xunionRespb[0] = 0x24
xunionRespb[1] = 0x94
xunionRespb[2] = 0x6a
xunionRespb[3] = 0x3e
if unionRespb != xunionRespb {
t.Fatalf("got %v, want %v", unionRespb[:un], xunionRespb[:xun])
}
}
func TestMarshalUnionInsideUnionIntoXUnionInsideXUnion(t *testing.T) {
const expectedOuterUnionTag = XUnionInsideXUnionB
const expectedInnerUnionTag = XUnion1AsUnionD
const expectedFieldValue = 1.2
var unionRespb [zx.ChannelMaxMessageBytes]byte
var unionResph [zx.ChannelMaxMessageHandles]zx.Handle
unionInput := UnionInsideUnionStruct{U: UnionInsideUnionWithB(XUnion1AsUnionWithD(expectedFieldValue))}
ctx := MarshalerContext{EncodeUnionsAsXUnionBytes: true}
un, _, err := MarshalWithContext(ctx, &unionInput, unionRespb[:], unionResph[:])
if err != nil {
t.Fatalf("error marshaling: %v\n", err)
}
var xunionRespb [zx.ChannelMaxMessageBytes]byte
var xunionResph [zx.ChannelMaxMessageHandles]zx.Handle
xunionInput := XUnionInsideXUnionStruct{U: XUnionInsideXUnionWithB(XUnion1WithD(expectedFieldValue))}
xun, _, err := Marshal(&xunionInput, xunionRespb[:], xunionResph[:])
if err != nil {
t.Fatalf("error unmarshaling: %v\n", err)
}
// The ordinals of the union and equivalent xunion differ.
// Set the inner and outer ordinals on the xunion object.
// These values comes from bindingstest/impl.go
xunionRespb[0] = 0xd0
xunionRespb[1] = 0xe8
xunionRespb[2] = 0x4d
xunionRespb[3] = 0x14
xunionRespb[24] = 0x24
xunionRespb[25] = 0x94
xunionRespb[26] = 0x6a
xunionRespb[27] = 0x3e
if unionRespb != xunionRespb {
t.Fatalf("got %v, want %v", unionRespb[:un], xunionRespb[:xun])
}
}
func benchmarkMarshal(b *testing.B, ex example) {
b.Run(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 := Marshal(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) {
b.Run(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(reflect.TypeOf(ex.input))
b.StartTimer()
for n := 0; n < b.N; n++ {
if _, _, err := Unmarshal(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)
}
}