blob: e0f35e0e4f94ebdb87a05b28f9d094fe698d24ee [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.
// The following enables go generate to generate the doc.go file.
//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go .
package main
import (
"fmt"
"io"
"regexp"
"strings"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/rpc/reserved"
"v.io/v23/vdl"
"v.io/v23/vdlroot/signature"
"v.io/x/lib/cmdline"
"v.io/x/ref/lib/v23cmd"
"v.io/x/ref/lib/vdl/build"
"v.io/x/ref/lib/vdl/codegen/vdlgen"
"v.io/x/ref/lib/vdl/compile"
_ "v.io/x/ref/runtime/factories/generic"
)
var (
flagInsecure bool
flagShowReserved bool
)
func main() {
cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
cmdline.Main(cmdVRPC)
}
func init() {
const (
insecureVal = false
insecureName = "insecure"
insecureDesc = "If true, skip server authentication. This means that the client will reveal its blessings to servers that it may not recognize."
)
cmdSignature.Flags.BoolVar(&flagInsecure, insecureName, insecureVal, insecureDesc)
cmdIdentify.Flags.BoolVar(&flagInsecure, insecureName, insecureVal, insecureDesc)
cmdSignature.Flags.BoolVar(&flagShowReserved, "show-reserved", false, "if true, also show the signatures of reserved methods")
}
var cmdVRPC = &cmdline.Command{
Name: "vrpc",
Short: "sends and receives Vanadium remote procedure calls",
Long: `
Command vrpc sends and receives Vanadium remote procedure calls. It is used as
a generic client to interact with any Vanadium server.
`,
// TODO(toddw): Add cmdServe, which will take an interface as input, and set
// up a server capable of handling the given methods. When a request is
// received, it'll allow the user to respond via stdin.
Children: []*cmdline.Command{cmdSignature, cmdCall, cmdIdentify},
}
const serverDesc = `
<server> identifies a Vanadium server. It can either be the object address of
the server, or an object name that will be resolved to an end-point.
`
var cmdSignature = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runSignature),
Name: "signature",
Short: "Describe the interfaces of a Vanadium server",
Long: `
Signature connects to the Vanadium server identified by <server>.
If no [method] is provided, returns all interfaces implemented by the server.
If a [method] is provided, returns the signature of just that method.
`,
ArgsName: "<server> [method]",
ArgsLong: serverDesc + `
[method] is the optional server method name.
`,
}
var cmdCall = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runCall),
Name: "call",
Short: "Call a method of a Vanadium server",
Long: `
Call connects to the Vanadium server identified by <server> and calls the
<method> with the given positional [args...], returning results on stdout.
TODO(toddw): stdin is read for streaming arguments sent to the server. An EOF
on stdin (e.g. via ^D) causes the send stream to be closed.
Regardless of whether the call is streaming, the main goroutine blocks for
streaming and positional results received from the server.
All input arguments (both positional and streaming) are specified as VDL
expressions, with commas separating multiple expressions. Positional arguments
may also be specified as separate command-line arguments. Streaming arguments
may also be specified as separate newline-terminated expressions.
The method signature is always retrieved from the server as a first step. This
makes it easier to input complex typed arguments, since the top-level type for
each argument is implicit and doesn't need to be specified.
`,
ArgsName: "<server> <method> [args...]",
ArgsLong: serverDesc + `
<method> is the server method to call.
[args...] are the positional input arguments, specified as VDL expressions.
`,
}
var cmdIdentify = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runIdentify),
Name: "identify",
Short: "Reveal blessings presented by a Vanadium server",
Long: `
Identify connects to the Vanadium server identified by <server> and dumps out
the blessings presented by that server (and the subset of those that are
considered valid by the principal running this tool) to standard output.
`,
ArgsName: "<server>",
ArgsLong: serverDesc,
}
func runSignature(ctx *context.T, env *cmdline.Env, args []string) error {
// Error-check args.
var server, method string
switch len(args) {
case 1:
server = args[0]
case 2:
server, method = args[0], args[1]
default:
return env.UsageErrorf("wrong number of arguments")
}
// Get the interface or method signature, and pretty-print. We print the
// named types after the signatures, to aid in readability.
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
var types vdlgen.NamedTypes
var opts []rpc.CallOpt
if flagInsecure {
opts = append(opts, options.SkipServerEndpointAuthorization{})
}
if method != "" {
methodSig, err := reserved.MethodSignature(ctx, server, method, opts...)
if err != nil {
return fmt.Errorf("MethodSignature failed: %v", err)
}
vdlgen.PrintMethod(env.Stdout, methodSig, &types)
fmt.Fprintln(env.Stdout)
types.Print(env.Stdout)
return nil
}
ifacesSig, err := reserved.Signature(ctx, server, opts...)
if err != nil {
return fmt.Errorf("Signature failed: %v", err)
}
for i, iface := range ifacesSig {
if !flagShowReserved && naming.IsReserved(iface.Name) {
continue
}
if i > 0 {
fmt.Fprintln(env.Stdout)
}
vdlgen.PrintInterface(env.Stdout, iface, &types)
fmt.Fprintln(env.Stdout)
}
types.Print(env.Stdout)
return nil
}
func runCall(ctx *context.T, env *cmdline.Env, args []string) error {
// Error-check args, and set up argsdata with a comma-separated list of
// arguments, allowing each individual arg to already be comma-separated.
//
// TODO(toddw): Should we just space-separate the args instead?
if len(args) < 2 {
return env.UsageErrorf("must specify <server> and <method>")
}
server, method := args[0], args[1]
var argsdata string
for _, arg := range args[2:] {
arg := strings.TrimSpace(arg)
if argsdata == "" || strings.HasSuffix(argsdata, ",") || strings.HasPrefix(arg, ",") {
argsdata += arg
} else {
argsdata += "," + arg
}
}
// Get the method signature and parse args.
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
methodSig, err := reserved.MethodSignature(ctx, server, method)
if err != nil {
return fmt.Errorf("MethodSignature failed: %v", err)
}
inargs, err := parseInArgs(argsdata, methodSig)
if err != nil {
// TODO: Print signature and example.
return err
}
// Start the method call.
call, err := v23.GetClient(ctx).StartCall(ctx, server, method, inargs)
if err != nil {
return fmt.Errorf("StartCall failed: %v", err)
}
// TODO(toddw): Fire off a goroutine to handle streaming inputs.
// Handle streaming results.
StreamingResultsLoop:
for {
var item *vdl.Value
switch err := call.Recv(&item); {
case err == io.EOF:
break StreamingResultsLoop
case err != nil:
return fmt.Errorf("call.Recv failed: %v", err)
}
fmt.Fprintf(env.Stdout, "<< %v\n", vdlgen.TypedConst(item, "", nil))
}
// Finish the method call.
outargs := make([]*vdl.Value, len(methodSig.OutArgs))
outptrs := make([]interface{}, len(outargs))
for i := range outargs {
outptrs[i] = &outargs[i]
}
if err := call.Finish(outptrs...); err != nil {
return fmt.Errorf("call.Finish failed: %v", err)
}
// Pretty-print results.
for i, arg := range outargs {
if i > 0 {
fmt.Fprint(env.Stdout, " ")
}
fmt.Fprint(env.Stdout, vdlgen.TypedConst(arg, "", nil))
}
fmt.Fprintln(env.Stdout)
return nil
}
func parseInArgs(argsdata string, methodSig signature.Method) ([]interface{}, error) {
if len(methodSig.InArgs) == 0 {
return nil, nil
}
var intypes []*vdl.Type
for _, inarg := range methodSig.InArgs {
intypes = append(intypes, inarg.Type)
}
env := compile.NewEnv(-1)
inargs := build.BuildExprs(argsdata, intypes, env)
if err := env.Errors.ToError(); err != nil {
return nil, fmt.Errorf("can't parse in-args:\n%v", err)
}
if got, want := len(inargs), len(methodSig.InArgs); got != want {
return nil, fmt.Errorf("got %d args, want %d", got, want)
}
// Translate []*vdl.Value to []interface, with each item still *vdl.Value.
var ret []interface{}
for _, arg := range inargs {
ret = append(ret, arg)
}
return ret, nil
}
func runIdentify(ctx *context.T, env *cmdline.Env, args []string) error {
if len(args) != 1 {
return env.UsageErrorf("wrong number of arguments")
}
server := args[0]
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
var opts []rpc.CallOpt
if flagInsecure {
opts = append(opts, options.SkipServerEndpointAuthorization{})
}
// The method name does not matter - only interested in authentication,
// not in actually making an RPC.
call, err := v23.GetClient(ctx).StartCall(ctx, server, "", nil, opts...)
if err != nil {
return fmt.Errorf(`client.StartCall(%q, "", nil) failed with %v`, server, err)
}
valid, presented := call.RemoteBlessings()
fmt.Fprintf(env.Stdout, "PRESENTED: %v\nVALID: %v\nPUBLICKEY: %v\n", presented, valid, presented.PublicKey())
return nil
}