| // 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 |
| } |