| // Copyright 2015 The Vanadium 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 main |
| |
| import ( |
| "fmt" |
| "log" |
| "strings" |
| |
| "v.io/v23/context" |
| "v.io/v23/rpc" |
| |
| "mojo/public/go/application" |
| "mojo/public/go/bindings" |
| "mojo/public/go/system" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/signature" |
| "v.io/x/mojo/transcoder" |
| |
| "mojo/public/interfaces/bindings/mojom_types" |
| "mojo/public/interfaces/bindings/service_describer" |
| ) |
| |
| // As long as fakeService meets the Invoker interface, it is allowed to pass as |
| // a universal v23 service. |
| // See the function objectToInvoker in v.io/x/ref/runtime/internal/rpc/server.go |
| type fakeService struct { |
| appctx application.Context |
| suffix string |
| router *bindings.Router |
| ids bindings.Counter |
| } |
| |
| // Prepare is used by the Fake Service to prepare the placeholders for the |
| // input data. |
| func (fs fakeService) Prepare(ctx *context.T, method string, numArgs int) (argptrs []interface{}, tags []*vdl.Value, _ error) { |
| inargs := make([]*vdl.Value, numArgs) |
| inptrs := make([]interface{}, len(inargs)) |
| for i := range inargs { |
| inptrs[i] = &inargs[i] |
| } |
| return inptrs, nil, nil |
| } |
| |
| // Wraps the interface request and the name of the requested mojo service. |
| type v23ServiceRequest struct { |
| request bindings.InterfaceRequest |
| name string |
| } |
| |
| func (v *v23ServiceRequest) Name() string { |
| return v.name |
| } |
| |
| func (v *v23ServiceRequest) ServiceDescription() service_describer.ServiceDescription { |
| panic("not supported") |
| } |
| |
| func (v *v23ServiceRequest) PassMessagePipe() system.MessagePipeHandle { |
| return v.request.PassMessagePipe() |
| } |
| |
| // Invoke calls the mojom service based on the suffix and converts the mojom |
| // results (a struct) to Vanadium results (a slice of *vdl.Value). |
| // Note: The argptrs from Prepare are reused here. The vom bytes should have |
| // been decoded into these argptrs, so there are actual values inside now. |
| func (fs fakeService) Invoke(ctx *context.T, call rpc.StreamServerCall, method string, argptrs []interface{}) (results []interface{}, _ error) { |
| // fs.suffix consists of the mojo url and the application/interface name. |
| // The last part should be the name; everything else is the url. |
| parts := strings.Split(fs.suffix, "/") |
| mojourl := strings.Join(parts[:len(parts)-1], "/") // e.g., mojo:go_remote_echo_server. May be defined in a BUILD.gn file. |
| mojoname := parts[len(parts)-1] // e.g., mojo::examples::RemoteEcho. Defined from the interface + module. |
| |
| // Create the generic message pipe. r is a bindings.InterfaceRequest, and |
| // p is a bindings.InterfacePointer. |
| r, p := bindings.CreateMessagePipeForMojoInterface() |
| v := v23ServiceRequest{ |
| request: r, |
| name: mojoname, |
| } // v is an application.ServiceRequest with mojoname |
| |
| // Connect to the mojourl. |
| fs.appctx.ConnectToApplication(mojourl).ConnectToService(&v) |
| |
| // Then assign a new router the FakeService. |
| // This will never conflict because each FakeService is only invoked once. |
| fs.router = bindings.NewRouter(p.PassMessagePipe(), bindings.GetAsyncWaiter()) |
| defer fs.Close_Proxy() |
| |
| ctx.Infof("Fake Service Invoke (Remote Signature: %q -- %q)", mojourl, mojoname) |
| |
| // Vanadium relies on type information, so we will retrieve that first. |
| mojomInterface, desc, err := fs.callRemoteSignature(mojourl, mojoname) |
| if err != nil { |
| return nil, err |
| } |
| |
| ctx.Infof("Fake Service Invoke Signature %v", mojomInterface) |
| ctx.Infof("Fake Service Invoke (Remote Method: %v)", method) |
| |
| // With the type information, we can make the method call to the remote interface. |
| methodResults, err := fs.callRemoteMethod(ctx, method, mojomInterface, desc, argptrs) |
| if err != nil { |
| ctx.Errorf("Method called failed: %v", err) |
| return nil, err |
| } |
| |
| ctx.Infof("Fake Service Invoke Results %v", methodResults) |
| |
| // Convert methodResult to results. |
| results = make([]interface{}, len(methodResults)) |
| for i := range methodResults { |
| results[i] = &methodResults[i] |
| } |
| return results, nil |
| } |
| |
| func (fs fakeService) Close_Proxy() { |
| fs.router.Close() |
| } |
| |
| // callRemoteSignature obtains type and header information from the remote |
| // mojo service. Remote mojo interfaces all define a signature method. |
| func (fs fakeService) callRemoteSignature(mojourl string, mojoname string) (mojomInterface mojom_types.MojomInterface, desc map[string]mojom_types.UserDefinedType, err error) { |
| /*log.Printf("callRemoteSignature: Prepare payload and header") |
| |
| // Prepare the input for the mojo call. |
| // This consists of a payload and a header for the RemoteSignature. |
| payload := mojom_types.SignatureInput{} |
| header := bindings.MessageHeader{ |
| Type: 0xffffffff, // Signature is always type 0xffffffff |
| Flags: bindings.MessageExpectsResponseFlag, // It always has a response. |
| RequestId: fs.ids.Count(), |
| } |
| |
| log.Printf("callRemoteSignature: Encode payload and header") |
| |
| var message *bindings.Message |
| if message, err = bindings.EncodeMessage(header, &payload); err != nil { |
| return response, fmt.Errorf("can't encode request: %v", err.Error()) |
| } |
| |
| log.Printf("callRemoteSignature => callRemoteGeneric") |
| |
| outMessage, err := fs.callRemoteGeneric(message) |
| if err != nil { |
| return response, err |
| } |
| |
| log.Printf("callRemoteSignature: Decode response") |
| |
| if err = outMessage.DecodePayload(&response); err != nil { |
| return |
| } |
| |
| return response, nil*/ |
| |
| // TODO(afandria): The service_describer mojom file defines the constant, but |
| // it is not actually present in the generated code: |
| // https://github.com/domokit/mojo/issues/469 |
| // serviceDescriberInterfaceName := "_ServiceDescriber" |
| |
| r, p := service_describer.CreateMessagePipeForServiceDescriber() |
| fs.appctx.ConnectToApplication(mojourl).ConnectToService(&r) |
| sDescriber := service_describer.NewServiceDescriberProxy(p, bindings.GetAsyncWaiter()) |
| defer sDescriber.Close_Proxy() |
| |
| r2, p2 := service_describer.CreateMessagePipeForServiceDescription() |
| err = sDescriber.DescribeService(mojoname, r2) |
| if err != nil { |
| return |
| } |
| sDescription := service_describer.NewServiceDescriptionProxy(p2, bindings.GetAsyncWaiter()) |
| defer sDescription.Close_Proxy() |
| |
| mojomInterface, err = sDescription.GetTopLevelInterface() |
| if err != nil { |
| return |
| } |
| descPtr, err := sDescription.GetAllTypeDefinitions() |
| if err != nil { |
| return |
| } |
| return mojomInterface, *descPtr, nil |
| } |
| |
| // A helper function that sends a remote message that expects a response. |
| func (fs fakeService) callRemoteGeneric(ctx *context.T, message *bindings.Message) (outMessage *bindings.Message, err error) { |
| ctx.Infof("callRemoteGeneric: Send message along the router") |
| |
| readResult := <-fs.router.AcceptWithResponse(message) |
| if err = readResult.Error; err != nil { |
| return |
| } |
| |
| ctx.Infof("callRemoteGeneric: Audit response message header flag") |
| // The message flag we receive back must be a bindings.MessageIsResponseFlag |
| if readResult.Message.Header.Flags != bindings.MessageIsResponseFlag { |
| err = &bindings.ValidationError{bindings.MessageHeaderInvalidFlags, |
| fmt.Sprintf("invalid message header flag: %v", readResult.Message.Header.Flags), |
| } |
| return |
| } |
| |
| ctx.Infof("callRemoteGeneric: Audit response message header type") |
| // While the mojo service we called into will return a header whose |
| // type must match our outgoing one. |
| if got, want := readResult.Message.Header.Type, message.Header.Type; got != want { |
| err = &bindings.ValidationError{bindings.MessageHeaderUnknownMethod, |
| fmt.Sprintf("invalid method in response: expected %v, got %v", want, got), |
| } |
| return |
| } |
| |
| return readResult.Message, nil |
| } |
| |
| // callRemoteMethod calls the method remotely in a generic way. |
| // Produces []*vdl.Value at the end for the invoker to return. |
| func (fs fakeService) callRemoteMethod(ctx *context.T, method string, mi mojom_types.MojomInterface, desc map[string]mojom_types.UserDefinedType, argptrs []interface{}) ([]*vdl.Value, error) { |
| // We need to parse the signature result to get the method relevant info out. |
| found := false |
| var ordinal uint32 |
| for ord, mm := range mi.Methods { |
| if *mm.DeclData.ShortName == method { |
| ordinal = ord |
| found = true |
| break |
| } |
| } |
| |
| if !found { |
| return nil, fmt.Errorf("callRemoteMethod: method %s does not exist", method) |
| } |
| |
| mm := mi.Methods[ordinal] |
| |
| // A void function must have request id of 0, whereas one with response params |
| // should have a unique request id. |
| var rqId uint64 |
| var flag uint32 |
| if mm.ResponseParams != nil { |
| rqId = fs.ids.Count() |
| flag = bindings.MessageExpectsResponseFlag |
| } else { |
| flag = bindings.MessageNoFlag |
| } |
| |
| header := bindings.MessageHeader{ |
| Type: ordinal, |
| Flags: flag, |
| RequestId: rqId, |
| } |
| |
| // Now produce the *bindings.Message that we will send to the other side. |
| inType := transcoder.MojomStructToVDLType(mm.Parameters, desc) |
| message, err := encodeMessageFromVom(header, argptrs, inType) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Handle the 0 out-arg case first. |
| if mm.ResponseParams == nil { |
| if err = fs.router.Accept(message); err != nil { |
| return nil, err |
| } |
| return make([]*vdl.Value, 0), nil |
| } |
| |
| // Otherwise, make a generic call with the message. |
| outMessage, err := fs.callRemoteGeneric(ctx, message) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Decode the *vdl.Value from the mojom bytes and mojom type. |
| outType := transcoder.MojomStructToVDLType(*mm.ResponseParams, desc) |
| var outVdlValue *vdl.Value |
| if err := transcoder.MojomToVdl(outMessage.Payload, outType, &outVdlValue); err != nil { |
| return nil, fmt.Errorf("transcoder.MojoToVom failed: %v", err) |
| } |
| |
| // Then split the *vdl.Value (struct) into []*vdl.Value |
| response := splitVdlValueByMojomType(outVdlValue, outType) |
| return response, nil |
| } |
| |
| // The fake service has no signature. |
| func (fs fakeService) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) { |
| ctx.Infof("Fake Service Signature???") |
| return nil, nil |
| } |
| |
| // The fake service knows nothing about method signatures. |
| func (fs fakeService) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) { |
| ctx.Infof("Fake Service Method Signature???") |
| return signature.Method{}, nil |
| } |
| |
| // The fake service will never need to glob. |
| func (fs fakeService) Globber() *rpc.GlobState { |
| log.Printf("Fake Service Globber???") |
| return nil |
| } |