blob: ec7a0d510f17f43460c1b2e918b627f89ccf150f [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"
)
var testCtx = func() MarshalerContext {
var hdr MessageHeader
ctx := hdr.NewCtx()
// The write wire format and read wire format will be the same within all
// tests, so set the decode field based on the encode field instead of the
// MessageHeader
ctx.DecodeUnionsFromXUnionBytes = ctx.EncodeUnionsAsXUnionBytes
return ctx
}()
var isV1 = testCtx.EncodeUnionsAsXUnionBytes
var v1OnlyList = map[string]bool{
"union1": true,
"union1-bis": true,
"union2": true,
"union1-A-uninitialized": true,
"union1-A-tag-out-of-bounds": true,
}
func shouldSkipTestUntilV1(testName string) bool {
_, ok := v1OnlyList[testName]
return ok
}
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 Test64bitOrdinal(t *testing.T) {
input := []byte{
0x21, 0xEB, 0x7E, 0x76, 0x55, 0x55, 0x55, 0x55, // 64bit ordinal
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() failed: %s", err)
}
}
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,
}, 56}, // A=24(xunion header), B=8(ptr), out-of-line A=24
{"union1-bis", &TestUnion1{
A: u1,
B: &u1,
}, 64}, // A=24(xunion header), B=24(xunion header), out-of-line A=8, out-of-line B=8
{"union2", &TestUnion2{
A: []Union1{u1, u1, u1},
B: []*Union1{&u1, nil, nil},
}, 208}, // A=8(size)+8(ptr), B=8(size)+8(ptr), OOL A=3*(24+8), OOL B=3*24+8
// 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 := MarshalWithContext(testCtx, 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 := UnmarshalWithContext(testCtx, 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() {
if shouldSkipTestUntilV1(ex.name) && !isV1 {
continue
}
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",
context: MarshalerContext{
DecodeUnionsFromXUnionBytes: false,
EncodeUnionsAsXUnionBytes: false,
},
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",
context: MarshalerContext{
DecodeUnionsFromXUnionBytes: false,
EncodeUnionsAsXUnionBytes: false,
},
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"`
A *SimpleTable `fidl:"0" fidl_offset_v1:"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,
}, ErrInvalidXUnionTag},
{"union1-A-tag-out-of-bounds", &TestUnion1{
A: Union1{I_union1Tag: Union1D + 1},
B: nil,
}, ErrInvalidXUnionTag},
}
for _, ex := range cases {
if shouldSkipTestUntilV1(ex.name) && !isV1 {
continue
}
t.Run(ex.name, func(t *testing.T) {
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
_, _, err := MarshalWithContext(testCtx, 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 TestMarshalUnionToXUnionUsingHeaderFlag(t *testing.T) {
const expectedUnionTag = XUnion1AsUnionD
const expectedFieldValue = 1.2
var inHeader MessageHeader
inHeader.SetUnionFromXunionBytes()
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 marshaling: %v\n", err)
}
var union XUnion1AsUnionStruct
var outHeader MessageHeader
if err := UnmarshalHeaderThenMessage(respb[:], nil, &outHeader, &union); err != nil {
t.Fatalf("error unmarshaling: %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)
}
}
type ordinalSubstitution struct {
unionOrdinal uint32
position int
}
type unionXUnionMigrationTestCase struct {
name string
xunionInput Message
unionInput Message
ordinalSubstitutions []ordinalSubstitution
}
var unionXUnionMigrationTestCases = []unionXUnionMigrationTestCase{
{
name: "XUnion1AsUnion",
xunionInput: &XUnion1Struct{Xu: XUnion1WithD(2)},
unionInput: &XUnion1AsUnionStruct{Xuau: XUnion1AsUnionWithD(2)},
},
{
name: "UnionInsideUnion",
xunionInput: &XUnionInsideXUnionStruct{U: XUnionInsideXUnionWithB(XUnion1WithD(2))},
unionInput: &UnionInsideUnionStruct{U: UnionInsideUnionWithB(XUnion1AsUnionWithD(2))},
},
}
func TestMarshalUnionAsXUnion(t *testing.T) {
for _, tc := range unionXUnionMigrationTestCases {
t.Run(tc.name, func(t *testing.T) {
var xunionRespb [zx.ChannelMaxMessageBytes]byte
var xunionResph [zx.ChannelMaxMessageHandles]zx.Handle
xun, _, err := Marshal(tc.xunionInput, xunionRespb[:], xunionResph[:])
if err != nil {
t.Fatalf("error marshaling: %v\n", err)
}
var unionRespb [zx.ChannelMaxMessageBytes]byte
var unionResph [zx.ChannelMaxMessageHandles]zx.Handle
ctx := MarshalerContext{EncodeUnionsAsXUnionBytes: true}
un, _, err := MarshalWithContext(ctx, tc.unionInput, unionRespb[:], unionResph[:])
if err != nil {
t.Fatalf("error marshaling: %v\n", err)
}
if unionRespb != xunionRespb {
t.Fatalf("got %v, want %v", unionRespb[:un], xunionRespb[:xun])
}
})
}
}
func TestUnmarshalUnionFromXUnion(t *testing.T) {
for _, tc := range unionXUnionMigrationTestCases {
t.Run(tc.name, func(t *testing.T) {
var respb [zx.ChannelMaxMessageBytes]byte
var resph [zx.ChannelMaxMessageHandles]zx.Handle
_, _, err := Marshal(tc.xunionInput, respb[:], resph[:])
if err != nil {
t.Fatalf("error marshaling: %v\n", err)
}
ctx := MarshalerContext{DecodeUnionsFromXUnionBytes: true}
result := reflect.New(reflect.TypeOf(tc.unionInput).Elem()).Interface().(Message)
_, _, err = UnmarshalWithContext(ctx, respb[:], nil, result)
if err != nil {
t.Fatalf("error unmarshaling: %v", err)
}
if !reflect.DeepEqual(tc.unionInput, result) {
t.Errorf("unexpected difference. got: %#v, want %#v", result, tc.unionInput)
}
})
}
}
// TODO(pascallouis): Move to GIDL
func TestMarshalUnmarshalUcharEnumVec(t *testing.T) {
m := TestUcharEnumVec{Values: []TestUcharEnum{TestUcharEnumUcharEnum1, TestUcharEnumUcharEnum2}}
var mbytes [zx.ChannelMaxMessageBytes]byte
nb, _, err := MarshalWithContext(testCtx, &m, mbytes[:], nil)
if err != nil {
t.Fatalf("Marshal vec failed: %v", err)
}
m = TestUcharEnumVec{}
_, _, err = UnmarshalWithContext(testCtx, mbytes[:nb], nil, &m)
if err != nil {
t.Fatalf("Unmarshal vec failed: %v", err)
}
if len(m.Values) != 2 {
t.Fatalf("Unmarshalled values do not match")
}
if m.Values[0] != TestUcharEnumUcharEnum1 {
t.Errorf("First unmarshalled value doesn't match")
}
if m.Values[1] != TestUcharEnumUcharEnum2 {
t.Errorf("Second unmarshalled value doesn't match")
}
}
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)
}
}