blob: 25257a38b6927b178d83d5c8bf4db29e8b16be05 [file] [log] [blame]
// 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"
"mojo/public/go/application"
"mojo/public/go/bindings"
"mojo/public/go/system"
"mojo/public/interfaces/bindings/mojom_types"
"mojo/public/interfaces/bindings/service_describer"
"mojom/v23serverproxy"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/vdl"
"v.io/v23/vdlroot/signature"
"v.io/v23/vom"
"v.io/x/mojo/proxy/util"
"v.io/x/mojo/transcoder"
"v.io/x/ref/runtime/factories/roaming"
)
//#include "mojo/public/c/system/handle.h"
import "C"
// 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([]*vom.RawBytes, 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 *vom.RawBytes).
// 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) {
// 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) callRemoteWithResponse(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
}
type mojoService struct {
delegate *delegate
}
func (r *mojoService) Endpoints() (endpoints []string, err error) {
endpointObjs := r.delegate.v23Server.Status().Endpoints
endpoints = make([]string, len(endpointObjs))
for i, endpointObj := range endpointObjs {
endpoints[i] = endpointObj.String()
}
return endpoints, nil
}
// callRemoteMethod calls the method remotely in a generic way.
// Produces []*vom.RawBytes 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{}) ([]*vom.RawBytes, 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.
header := bindings.MessageHeader{
Type: ordinal,
Flags: bindings.MessageExpectsResponseFlag,
RequestId: fs.ids.Count(),
}
// Now produce the *bindings.Message that we will send to the other side.
inType, err := transcoder.MojomStructToVDLType(mm.Parameters, desc)
if err != nil {
return nil, err
}
message, err := encodeMessageFromVom(header, argptrs, inType)
if err != nil {
return nil, err
}
// Otherwise, make a generic call with the message.
outMessage, err := fs.callRemoteWithResponse(ctx, message)
if err != nil {
return nil, err
}
// Decode the *vom.RawBytes from the mojom bytes and mojom type.
outType, err := transcoder.MojomStructToVDLType(*mm.ResponseParams, desc)
if err != nil {
return nil, err
}
target := util.StructSplitTarget()
if err := transcoder.FromMojo(target, outMessage.Payload, outType); err != nil {
return nil, fmt.Errorf("transcoder.FromMojo failed: %v", err)
}
return target.Fields(), nil
}
func encodeMessageFromVom(header bindings.MessageHeader, argptrs []interface{}, t *vdl.Type) (*bindings.Message, error) {
// Convert argptrs into their true form: []*vom.RawBytes
inargs := make([]*vom.RawBytes, len(argptrs))
for i := range argptrs {
inargs[i] = *argptrs[i].(**vom.RawBytes)
}
encoder := bindings.NewEncoder()
if err := header.Encode(encoder); err != nil {
return nil, err
}
if bytes, handles, err := encoder.Data(); err != nil {
return nil, err
} else {
target := transcoder.ToMojomTarget()
if err := util.JoinRawBytesAsStruct(target, t, inargs); err != nil {
return nil, err
}
moreBytes := target.Bytes()
// Append the encoded "payload" to the end of the slice.
bytes = append(bytes, moreBytes...)
return &bindings.Message{
Header: header,
Bytes: bytes,
Handles: handles,
Payload: moreBytes,
}, 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
}
type dispatcher struct {
appctx application.Context
}
func (v23pd *dispatcher) Lookup(ctx *context.T, suffix string) (interface{}, security.Authorizer, error) {
ctx.Infof("dispatcher.Lookup for suffix: %s", suffix)
return fakeService{
appctx: v23pd.appctx,
suffix: suffix,
ids: bindings.NewCounter(),
}, security.AllowEveryone(), nil
}
type delegate struct {
ctx *context.T
shutdown v23.Shutdown
stubs []*bindings.Stub
v23Server rpc.Server
}
func (delegate *delegate) Initialize(context application.Context) {
// Start up v23 whenever a v23proxy is begun.
// This is done regardless of whether we are initializing this v23proxy for use
// as a client or as a server.
roaming.SetArgs(context)
ctx, shutdown := v23.Init()
delegate.ctx = ctx
delegate.shutdown = shutdown
ctx.Infof("delegate.Initialize...")
// TODO(alexfandrianto): Does Mojo stop us from creating too many v23proxy?
// Is it 1 per shell? Ideally, each device will only serve 1 of these v23proxy,
// but it is not problematic to have extra.
_, s, err := v23.WithNewDispatchingServer(ctx, "", &dispatcher{
appctx: context,
})
if err != nil {
ctx.Fatal("Error serving service: ", err)
}
delegate.v23Server = s
fmt.Println("Listening at:", s.Status().Endpoints[0].Name())
}
func (delegate *delegate) Create(request v23serverproxy.V23ServerProxy_Request) {
svc := &mojoService{delegate: delegate}
v23Stub := v23serverproxy.NewV23ServerProxyStub(request, svc, bindings.GetAsyncWaiter())
delegate.stubs = append(delegate.stubs, v23Stub)
go func() {
// Read header message
if err := v23Stub.ServeRequest(); err != nil {
connectionError, ok := err.(*bindings.ConnectionError)
if !ok || !connectionError.Closed() {
delegate.ctx.Errorf("%v", err)
}
return
}
}()
}
func (delegate *delegate) AcceptConnection(connection *application.Connection) {
delegate.ctx.Infof("delegate.AcceptConnection...")
connection.ProvideServices(&v23serverproxy.V23ServerProxy_ServiceFactory{delegate})
}
func (delegate *delegate) Quit() {
delegate.ctx.Infof("delegate.Quit...")
for _, stub := range delegate.stubs {
stub.Close()
}
delegate.shutdown()
}
//export MojoMain
func MojoMain(handle C.MojoHandle) C.MojoResult {
application.Run(&delegate{}, system.MojoHandle(handle))
return C.MOJO_RESULT_OK
}
func main() {
}