blob: 6523df46d664551e51ae47a14cab911f57ed9def [file] [log] [blame]
// Copyright 2019 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.
package fidl_test
import (
"bytes"
"fmt"
"reflect"
"testing"
"syscall/zx"
"syscall/zx/fidl"
)
func makeDefault(typ reflect.Type) fidl.Message {
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
panic(fmt.Sprintf("expecting *struct, actual %s", typ))
}
return reflect.New(typ.Elem()).Interface().(fidl.Message)
}
// Note: fidl.Marshal uses the provided buffers to encode and works positionally to allow
// repeated encoding in the same buffer. This function doesn't do that and always starts
// with an empty buffer so tests do not cover the repeated encoding cases.
func encode(context fidl.MarshalerContext, input fidl.Message) ([]byte, []zx.HandleDisposition, error) {
var respb [zx.ChannelMaxMessageBytes]byte
for i := range respb {
respb[i] = 0xaa
}
var resphd [zx.ChannelMaxMessageHandles]zx.HandleDisposition
nb, nh, err := fidl.Marshal(context, input, respb[:], resphd[:])
if err != nil {
return nil, nil, err
}
return respb[:nb], resphd[:nh], nil
}
func decode(context fidl.MarshalerContext, typ reflect.Type, b []byte, hi []zx.HandleInfo) (msg fidl.Message, err error) {
msg = makeDefault(typ)
err = fidl.Unmarshal(context, b, hi, msg)
return msg, err
}
type encodeSuccessCase struct {
name string
context fidl.MarshalerContext
input fidl.Message
bytes []byte
handleDispositions []zx.HandleDisposition
// This parameter is used so that encodeSuccessCases generated from GIDL
// ignore handle rights, but manual test cases can still opt to check them.
// Setting it to true also enables stricter checking of the subtype (see
// comment below)
checkRights bool
}
func (ex encodeSuccessCase) check(t *testing.T) {
t.Run(ex.name+" encode", func(t *testing.T) {
t.Cleanup(func() {
for _, hd := range ex.handleDispositions {
if err := handleClose(&hd.Handle); err != nil {
t.Error(err)
}
}
})
b, hd, err := encode(ex.context, ex.input)
if err != nil {
t.Fatalf("failed: %s", err)
}
if !bytes.Equal(ex.bytes, b) {
t.Fatalf("expected %x, got %x", ex.bytes, b)
}
if len(ex.handleDispositions) == 0 {
if len(hd) != 0 {
t.Fatalf("no handles expected, got %d", len(hd))
}
} else if len(ex.handleDispositions) != len(hd) {
t.Fatalf("expected %d handles, but encode outputed %d handles", len(ex.handleDispositions), len(hd))
} else {
if ex.checkRights {
if !reflect.DeepEqual(ex.handleDispositions, hd) {
t.Errorf("expected %#v, got %#v", ex.handleDispositions, hd)
}
}
}
})
}
type decodeSuccessCase struct {
name string
context fidl.MarshalerContext
typ reflect.Type
bytes []byte
handleInfos []zx.HandleInfo
equalsExpected func(t *testing.T, value interface{})
}
func (ex decodeSuccessCase) check(t *testing.T) {
t.Run(ex.name+" decode", func(t *testing.T) {
t.Cleanup(func() {
for _, handleInfo := range ex.handleInfos {
if err := handleClose(&handleInfo.Handle); err != nil {
t.Error(err)
}
}
})
output, err := decode(ex.context, ex.typ, ex.bytes, ex.handleInfos)
if err != nil {
t.Fatalf("failed: %s", err)
}
ex.equalsExpected(t, output)
})
}
// Represents a success case where both encode and decode should succeed.
type successCase struct {
name string
context fidl.MarshalerContext
input fidl.Message
bytes []byte
handleInfos []zx.HandleInfo
checkRights bool
}
func (ex successCase) check(t *testing.T) {
var handleDispositions []zx.HandleDisposition
for _, handleInfo := range ex.handleInfos {
handleDispositions = append(handleDispositions, zx.HandleDisposition{
Operation: zx.HandleOpMove,
Handle: handleInfo.Handle,
Type: handleInfo.Type,
Rights: handleInfo.Rights,
Result: zx.ErrOk,
})
}
encodeSuccessCase{
name: ex.name,
context: ex.context,
input: ex.input,
bytes: ex.bytes,
handleDispositions: handleDispositions,
checkRights: ex.checkRights,
}.check(t)
decodeSuccessCase{
name: ex.name,
context: ex.context,
typ: reflect.TypeOf(ex.input),
bytes: ex.bytes,
handleInfos: ex.handleInfos,
equalsExpected: func(t *testing.T, input interface{}) {
if !reflect.DeepEqual(ex.input, input) {
t.Fatalf("expected: %#v, got: %#v", ex.input, input)
}
},
}.check(t)
}
type encodeFailureCase struct {
name string
context fidl.MarshalerContext
input fidl.Message
code fidl.ErrorCode
handles []zx.Handle
}
func (ex encodeFailureCase) check(t *testing.T) {
t.Run(ex.name, func(t *testing.T) {
_, _, err := encode(ex.context, ex.input)
if err == nil {
t.Fatalf("encoding failure %s expected, but no errors were found", ex.code.String())
}
fidlError, ok := err.(fidl.ValidationError)
if !ok {
t.Fatalf("wrong error type: got %T, want fidl.ValidationError", err)
}
if fidlError.Code() != ex.code {
t.Fatalf("wrong error code: got %s, want %s", fidlError.Code().String(), ex.code.String())
}
for i, handle := range ex.handles {
if isHandleValid(&handle) {
t.Errorf("failed to close handle at index #%d: %d", i, handle)
}
}
})
}
type decodeFailureCase struct {
name string
context fidl.MarshalerContext
valTyp reflect.Type
bytes []byte
code fidl.ErrorCode
handleInfos []zx.HandleInfo
}
func (ex decodeFailureCase) check(t *testing.T) {
t.Run(ex.name, func(t *testing.T) {
_, err := decode(ex.context, ex.valTyp, ex.bytes, ex.handleInfos)
if err == nil {
t.Fatalf("decoding failure %s expected, but no errors were found", ex.code.String())
}
fidlError, ok := err.(fidl.ValidationError)
if !ok {
t.Fatalf("wrong error type: got %T, want fidl.ValidationError", err)
}
if fidlError.Code() != ex.code {
t.Fatalf("wrong error code: got %s, want %s", fidlError.Code().String(), ex.code.String())
}
for i, info := range ex.handleInfos {
if isHandleValid(&info.Handle) {
t.Fatalf("failed to close handle at index #%d: %v", i, info)
}
}
})
}
type handleDef struct {
subtype zx.ObjType
rights zx.Rights
}
// createHandlesFromHandleDef returns a list handles where subtype of result[i] == subtypes[i]
func createHandlesFromHandleDef(defs []handleDef) []zx.Handle {
// calculate the number of handles of each subtype that is needed
handleCounts := make(map[zx.ObjType]int)
for _, def := range defs {
handleCounts[def.subtype]++
}
// batch create the handles for each subtype. this makes it possible to
// use both handles of a pair whenever possible
handles := make(map[zx.ObjType][]zx.Handle)
for subtype, count := range handleCounts {
switch subtype {
case zx.ObjTypeChannel:
handles[subtype] = makeChannels(count)
case zx.ObjTypeEvent:
handles[subtype] = makeEvents(count)
default:
panic(fmt.Sprintf("Unsupported handle subtype %d", subtype))
}
}
// rearrange the created handles into the order specified by the input
result := make([]zx.Handle, len(defs))
for i, def := range defs {
subtype := def.subtype
handleCounts[subtype]--
var err error
handle := handles[subtype][handleCounts[subtype]]
result[i], err = handleReplace(handle, def.rights)
if err != nil {
panic(fmt.Sprintf("Error in handle replace: %v", err))
}
}
return result
}
func makeChannels(count int) []zx.Handle {
channels := make([]zx.Handle, count)
for i := 0; i < count/2; i++ {
c1, c2, err := newChannel(0)
if err != nil {
panic(err)
}
channels[i*2] = zx.Handle(c1)
channels[i*2+1] = zx.Handle(c2)
}
if count%2 == 1 {
c1, c2, err := newChannel(0)
if err != nil {
panic(err)
}
channels[count-1] = zx.Handle(c1)
if err := channelClose(&c2); err != nil {
panic(err)
}
}
return channels
}
func makeEvents(count int) []zx.Handle {
events := make([]zx.Handle, count)
for i := 0; i < count; i++ {
event, err := newEvent(0)
if err != nil {
panic(err)
}
events[i] = zx.Handle(event)
}
return events
}