| // 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 rpc |
| |
| import ( |
| "strings" |
| |
| "v.io/v23/context" |
| "v.io/v23/naming" |
| "v.io/v23/rpc" |
| "v.io/v23/rpc/reserved" |
| "v.io/v23/security" |
| "v.io/v23/security/access" |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/signature" |
| "v.io/v23/verror" |
| "v.io/x/lib/vlog" |
| |
| "v.io/x/ref/lib/apilog" |
| "v.io/x/ref/lib/glob" |
| ) |
| |
| // reservedInvoker returns a special invoker for reserved methods. This invoker |
| // has access to the internal dispatchers, which allows it to perform special |
| // handling for methods like Glob and Signature. |
| func reservedInvoker(dispNormal, dispReserved rpc.Dispatcher) rpc.Invoker { |
| methods := &reservedMethods{dispNormal: dispNormal, dispReserved: dispReserved} |
| invoker := rpc.ReflectInvokerOrDie(methods) |
| methods.selfInvoker = invoker |
| return invoker |
| } |
| |
| // reservedMethods is a regular server implementation object, which is passed to |
| // the regular ReflectInvoker in order to implement reserved methods. The |
| // leading reserved "__" prefix is stripped before any methods are called. |
| // |
| // To add a new reserved method, simply add a method below, along with a |
| // description of the method. |
| type reservedMethods struct { |
| dispNormal rpc.Dispatcher |
| dispReserved rpc.Dispatcher |
| selfInvoker rpc.Invoker |
| } |
| |
| func (r *reservedMethods) Describe__() []rpc.InterfaceDesc { |
| defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| return []rpc.InterfaceDesc{{ |
| Name: "__Reserved", |
| Doc: `Reserved methods implemented by the RPC framework. Each method name is prefixed with a double underscore "__".`, |
| Methods: []rpc.MethodDesc{ |
| { |
| Name: "Glob", |
| Doc: "Glob returns all entries matching the pattern.", |
| InArgs: []rpc.ArgDesc{{Name: "pattern", Doc: ""}}, |
| OutStream: rpc.ArgDesc{Doc: "Streams matching entries back to the client."}, |
| }, |
| { |
| Name: "MethodSignature", |
| Doc: "MethodSignature returns the signature for the given method.", |
| InArgs: []rpc.ArgDesc{{ |
| Name: "method", |
| Doc: "Method name whose signature will be returned.", |
| }}, |
| OutArgs: []rpc.ArgDesc{{ |
| Doc: "Method signature for the given method.", |
| }}, |
| }, |
| { |
| Name: "Signature", |
| Doc: "Signature returns all interface signatures implemented by the object.", |
| OutArgs: []rpc.ArgDesc{{ |
| Doc: "All interface signatures implemented by the object.", |
| }}, |
| }, |
| }, |
| }} |
| } |
| |
| func (r *reservedMethods) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) { |
| suffix := call.Suffix() |
| disp := r.dispNormal |
| if naming.IsReserved(suffix) { |
| disp = r.dispReserved |
| } |
| if disp == nil { |
| return nil, verror.New(verror.ErrUnknownSuffix, ctx, suffix) |
| } |
| obj, _, err := disp.Lookup(suffix) |
| switch { |
| case err != nil: |
| return nil, err |
| case obj == nil: |
| return nil, verror.New(verror.ErrUnknownSuffix, ctx, suffix) |
| } |
| invoker, err := objectToInvoker(obj) |
| if err != nil { |
| return nil, err |
| } |
| sig, err := invoker.Signature(ctx, call) |
| if err != nil { |
| return nil, err |
| } |
| // Append the reserved methods. We wait until now to add the "__" prefix to |
| // each method, so that we can use the regular ReflectInvoker.Signature logic. |
| rsig, err := r.selfInvoker.Signature(ctx, call) |
| if err != nil { |
| return nil, err |
| } |
| for i := range rsig { |
| for j := range rsig[i].Methods { |
| rsig[i].Methods[j].Name = "__" + rsig[i].Methods[j].Name |
| } |
| } |
| return signature.CleanInterfaces(append(sig, rsig...)), nil |
| } |
| |
| func (r *reservedMethods) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) { |
| // Reserved methods use our self invoker, to describe our own methods, |
| if naming.IsReserved(method) { |
| return r.selfInvoker.MethodSignature(ctx, call, naming.StripReserved(method)) |
| } |
| |
| suffix := call.Suffix() |
| disp := r.dispNormal |
| if naming.IsReserved(suffix) { |
| disp = r.dispReserved |
| } |
| if disp == nil { |
| return signature.Method{}, verror.New(verror.ErrUnknownMethod, ctx, rpc.ReservedMethodSignature) |
| } |
| obj, _, err := disp.Lookup(suffix) |
| switch { |
| case err != nil: |
| return signature.Method{}, err |
| case obj == nil: |
| return signature.Method{}, verror.New(verror.ErrUnknownMethod, ctx, rpc.ReservedMethodSignature) |
| } |
| invoker, err := objectToInvoker(obj) |
| if err != nil { |
| return signature.Method{}, err |
| } |
| // TODO(toddw): Decide if we should hide the method signature if the |
| // caller doesn't have access to call it. |
| return invoker.MethodSignature(ctx, call, method) |
| } |
| |
| func (r *reservedMethods) Glob(ctx *context.T, call rpc.StreamServerCall, pattern string) error { |
| // Copy the original call to shield ourselves from changes the flowServer makes. |
| glob := globInternal{r.dispNormal, r.dispReserved, call.Suffix()} |
| return glob.Glob(ctx, call, pattern) |
| } |
| |
| // globInternal handles ALL the Glob requests received by a server and |
| // constructs a response from the state of internal server objects and the |
| // service objects. |
| // |
| // Internal objects exist only at the root of the server and have a name that |
| // starts with a double underscore ("__"). They are only visible in the Glob |
| // response if the double underscore is explicitly part of the pattern, e.g. |
| // "".Glob("__*/*"), or "".Glob("__debug/..."). |
| // |
| // Service objects may choose to implement either AllGlobber or ChildrenGlobber. |
| // AllGlobber is more flexible, but ChildrenGlobber is simpler to implement and |
| // less prone to errors. |
| // |
| // If objects implement AllGlobber, it must be able to handle recursive pattern |
| // for the entire namespace below the receiver object, i.e. "a/b".Glob("...") |
| // must return the name of all the objects under "a/b". |
| // |
| // If they implement ChildrenGlobber, it provides a list of the receiver's |
| // immediate children names, or a non-nil error if the receiver doesn't exist. |
| // |
| // globInternal constructs the Glob response by internally accessing the |
| // AllGlobber or ChildrenGlobber interface of objects as many times as needed. |
| // |
| // Before accessing an object, globInternal ensures that the requester is |
| // authorized to access it. Internal objects require access.Debug. Service |
| // objects require access.Resolve. |
| type globInternal struct { |
| dispNormal rpc.Dispatcher |
| dispReserved rpc.Dispatcher |
| receiver string |
| } |
| |
| // The maximum depth of recursion in Glob. We only count recursion levels |
| // associated with a recursive glob pattern, e.g. a pattern like "..." will be |
| // allowed to recurse up to 10 levels, but "*/*/*/*/..." will go up to 14 |
| // levels. |
| const maxRecursiveGlobDepth = 10 |
| |
| func (i *globInternal) Glob(ctx *context.T, call rpc.StreamServerCall, pattern string) error { |
| vlog.VI(3).Infof("rpc Glob: Incoming request: %q.Glob(%q)", i.receiver, pattern) |
| g, err := glob.Parse(pattern) |
| if err != nil { |
| return err |
| } |
| disp := i.dispNormal |
| tags := []*vdl.Value{vdl.ValueOf(access.Resolve)} |
| if naming.IsReserved(i.receiver) || (i.receiver == "" && naming.IsReserved(pattern)) { |
| disp = i.dispReserved |
| tags = []*vdl.Value{vdl.ValueOf(access.Debug)} |
| } |
| if disp == nil { |
| return reserved.NewErrGlobNotImplemented(ctx) |
| } |
| call = callWithMethodTags(ctx, call, tags) |
| |
| type gState struct { |
| name string |
| glob *glob.Glob |
| depth int |
| } |
| queue := []gState{gState{glob: g}} |
| |
| someMatchesOmitted := false |
| for len(queue) != 0 { |
| select { |
| case <-ctx.Done(): |
| // RPC timed out or was canceled. |
| return nil |
| default: |
| } |
| state := queue[0] |
| queue = queue[1:] |
| |
| subcall := callWithSuffix(ctx, call, naming.Join(i.receiver, state.name)) |
| suffix := subcall.Suffix() |
| if state.depth > maxRecursiveGlobDepth { |
| vlog.Errorf("rpc Glob: exceeded recursion limit (%d): %q", maxRecursiveGlobDepth, suffix) |
| call.Send(naming.GlobReplyError{ |
| naming.GlobError{Name: state.name, Error: reserved.NewErrGlobMaxRecursionReached(ctx)}, |
| }) |
| continue |
| } |
| obj, auth, err := disp.Lookup(suffix) |
| if err != nil { |
| vlog.VI(3).Infof("rpc Glob: Lookup failed for %q: %v", suffix, err) |
| call.Send(naming.GlobReplyError{ |
| naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrNoExist, ctx, err)}, |
| }) |
| continue |
| } |
| if obj == nil { |
| vlog.VI(3).Infof("rpc Glob: object not found for %q", suffix) |
| call.Send(naming.GlobReplyError{ |
| naming.GlobError{Name: state.name, Error: verror.New(verror.ErrNoExist, ctx, "nil object")}, |
| }) |
| continue |
| } |
| |
| // Verify that that requester is authorized for the current object. |
| if err := authorize(ctx, call.Security(), auth); err != nil { |
| someMatchesOmitted = true |
| vlog.VI(3).Infof("rpc Glob: client is not authorized for %q: %v", suffix, err) |
| continue |
| } |
| |
| // If the object implements both AllGlobber and ChildrenGlobber, we'll |
| // use AllGlobber. |
| invoker, err := objectToInvoker(obj) |
| if err != nil { |
| vlog.VI(3).Infof("rpc Glob: object for %q cannot be converted to invoker: %v", suffix, err) |
| call.Send(naming.GlobReplyError{ |
| naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)}, |
| }) |
| continue |
| } |
| gs := invoker.Globber() |
| if gs == nil || (gs.AllGlobber == nil && gs.ChildrenGlobber == nil) { |
| if state.glob.Len() == 0 { |
| subcall.Send(naming.GlobReplyEntry{naming.MountEntry{Name: state.name, IsLeaf: true}}) |
| } else { |
| subcall.Send(naming.GlobReplyError{ |
| naming.GlobError{Name: state.name, Error: reserved.NewErrGlobNotImplemented(ctx)}, |
| }) |
| } |
| continue |
| } |
| if gs.AllGlobber != nil { |
| vlog.VI(3).Infof("rpc Glob: %q implements AllGlobber", suffix) |
| ch, err := gs.AllGlobber.Glob__(ctx, subcall, state.glob.String()) |
| if err != nil { |
| vlog.VI(3).Infof("rpc Glob: %q.Glob(%q) failed: %v", suffix, state.glob, err) |
| subcall.Send(naming.GlobReplyError{naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)}}) |
| continue |
| } |
| if ch == nil { |
| continue |
| } |
| for gr := range ch { |
| switch v := gr.(type) { |
| case naming.GlobReplyEntry: |
| v.Value.Name = naming.Join(state.name, v.Value.Name) |
| subcall.Send(v) |
| case naming.GlobReplyError: |
| v.Value.Name = naming.Join(state.name, v.Value.Name) |
| subcall.Send(v) |
| } |
| } |
| continue |
| } |
| vlog.VI(3).Infof("rpc Glob: %q implements ChildrenGlobber", suffix) |
| children, err := gs.ChildrenGlobber.GlobChildren__(ctx, subcall) |
| // The requested object doesn't exist. |
| if err != nil { |
| subcall.Send(naming.GlobReplyError{naming.GlobError{Name: state.name, Error: verror.Convert(verror.ErrInternal, ctx, err)}}) |
| continue |
| } |
| // The glob pattern matches the current object. |
| if state.glob.Len() == 0 { |
| subcall.Send(naming.GlobReplyEntry{naming.MountEntry{Name: state.name}}) |
| } |
| // The current object has no children. |
| if children == nil { |
| continue |
| } |
| depth := state.depth |
| // This is a recursive pattern. Make sure we don't recurse forever. |
| if state.glob.Len() == 0 { |
| depth++ |
| } |
| for child := range children { |
| if len(child) == 0 || strings.Contains(child, "/") { |
| vlog.Errorf("rpc Glob: %q.GlobChildren__() sent an invalid child name: %q", suffix, child) |
| continue |
| } |
| if ok, _, left := state.glob.MatchInitialSegment(child); ok { |
| next := naming.Join(state.name, child) |
| queue = append(queue, gState{next, left, depth}) |
| } |
| } |
| } |
| if someMatchesOmitted { |
| call.Send(naming.GlobReplyError{naming.GlobError{Error: reserved.NewErrGlobMatchesOmitted(ctx)}}) |
| } |
| return nil |
| } |
| |
| // derivedServerCall allows us to derive calls with slightly different properties, |
| // useful for our various special-cased reserved methods. |
| type derivedServerCall struct { |
| rpc.StreamServerCall |
| suffix string |
| security security.Call |
| } |
| |
| func callWithSuffix(ctx *context.T, src rpc.StreamServerCall, suffix string) rpc.StreamServerCall { |
| sec := securityCallWithSuffix(src.Security(), suffix) |
| return &derivedServerCall{src, suffix, sec} |
| } |
| |
| func callWithMethodTags(ctx *context.T, src rpc.StreamServerCall, tags []*vdl.Value) rpc.StreamServerCall { |
| sec := securityCallWithMethodTags(src.Security(), tags) |
| return &derivedServerCall{src, src.Suffix(), sec} |
| } |
| |
| func (c *derivedServerCall) Suffix() string { |
| defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| return c.suffix |
| } |
| func (c *derivedServerCall) Security() security.Call { |
| defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| return c.security |
| } |
| |
| type derivedSecurityCall struct { |
| security.Call |
| suffix string |
| methodTags []*vdl.Value |
| } |
| |
| func securityCallWithSuffix(src security.Call, suffix string) security.Call { |
| return &derivedSecurityCall{src, suffix, src.MethodTags()} |
| } |
| |
| func securityCallWithMethodTags(src security.Call, tags []*vdl.Value) security.Call { |
| return &derivedSecurityCall{src, src.Suffix(), tags} |
| } |
| |
| func (c *derivedSecurityCall) Suffix() string { |
| defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| return c.suffix |
| } |
| func (c *derivedSecurityCall) MethodTags() []*vdl.Value { |
| defer apilog.LogCall(nil)(nil) // gologcop: DO NOT EDIT, MUST BE FIRST STATEMENT |
| return c.methodTags |
| } |