cloudflared-mirror/vendor/zombiezen.com/go/capnproto2/capability.go

542 lines
14 KiB
Go
Raw Permalink Normal View History

package capnp
import (
"errors"
"strconv"
"golang.org/x/net/context"
)
// An Interface is a reference to a client in a message's capability table.
type Interface struct {
seg *Segment
cap CapabilityID
}
// NewInterface creates a new interface pointer. No allocation is
// performed; s is only used for Segment()'s return value.
func NewInterface(s *Segment, cap CapabilityID) Interface {
return Interface{
seg: s,
cap: cap,
}
}
// ToInterface converts p to an Interface.
//
// Deprecated: Use Ptr.Interface.
func ToInterface(p Pointer) Interface {
if !IsValid(p) {
return Interface{}
}
i, ok := p.underlying().(Interface)
if !ok {
return Interface{}
}
return i
}
// ToPtr converts the interface to a generic pointer.
func (p Interface) ToPtr() Ptr {
return Ptr{
seg: p.seg,
lenOrCap: uint32(p.cap),
flags: interfacePtrFlag,
}
}
// Segment returns the segment this pointer came from.
func (i Interface) Segment() *Segment {
return i.seg
}
// IsValid returns whether the interface is valid.
func (i Interface) IsValid() bool {
return i.seg != nil
}
// HasData is always true.
func (i Interface) HasData() bool {
return true
}
// Capability returns the capability ID of the interface.
func (i Interface) Capability() CapabilityID {
return i.cap
}
// value returns a raw interface pointer with the capability ID.
func (i Interface) value(paddr Address) rawPointer {
if i.seg == nil {
return 0
}
return rawInterfacePointer(i.cap)
}
func (i Interface) underlying() Pointer {
return i
}
// Client returns the client stored in the message's capability table
// or nil if the pointer is invalid.
func (i Interface) Client() Client {
if i.seg == nil {
return nil
}
tab := i.seg.msg.CapTable
if int64(i.cap) >= int64(len(tab)) {
return nil
}
return tab[i.cap]
}
// ErrNullClient is returned from a call made on a null client pointer.
var ErrNullClient = errors.New("capnp: call on null client")
// A CapabilityID is an index into a message's capability table.
type CapabilityID uint32
// A Client represents an Cap'n Proto interface type. It is safe to use
// from multiple goroutines.
//
// Generally, only RPC protocol implementers should provide types that
// implement Client: call ordering guarantees, promises, and
// synchronization are tricky to get right. Prefer creating a server
// that wraps another interface than trying to implement Client.
type Client interface {
// Call starts executing a method and returns an answer that will hold
// the resulting struct. The call's parameters must be placed before
// Call() returns.
//
// Calls are delivered to the capability in the order they are made.
// This guarantee is based on the concept of a capability
// acknowledging delivery of a call: this is specific to an
// implementation of Client. A type that implements Client must
// guarantee that if foo() then bar() is called on a client, that
// acknowledging foo() happens before acknowledging bar().
Call(call *Call) Answer
// Close releases any resources associated with this client.
// No further calls to the client should be made after calling Close.
Close() error
}
// The Call type holds the record for an outgoing interface call.
type Call struct {
// Ctx is the context of the call.
Ctx context.Context
// Method is the interface ID and method ID, along with the optional name,
// of the method to call.
Method Method
// Params is a struct containing parameters for the call.
// This should be set when the RPC system receives a call for an
// exported interface. It is mutually exclusive with ParamsFunc
// and ParamsSize.
Params Struct
// ParamsFunc is a function that populates an allocated struct with
// the parameters for the call. ParamsSize determines the size of the
// struct to allocate. This is used when application code is using a
// client. These settings should be set together; they are mutually
// exclusive with Params.
ParamsFunc func(Struct) error
ParamsSize ObjectSize
// Options passes RPC-specific options for the call.
Options CallOptions
}
// Copy clones a call, ensuring that its Params are placed.
// If Call.ParamsFunc is nil, then the same Call will be returned.
func (call *Call) Copy(s *Segment) (*Call, error) {
if call.ParamsFunc == nil {
return call, nil
}
p, err := call.PlaceParams(s)
if err != nil {
return nil, err
}
return &Call{
Ctx: call.Ctx,
Method: call.Method,
Params: p,
Options: call.Options,
}, nil
}
// PlaceParams returns the parameters struct, allocating it inside
// segment s as necessary. If s is nil, a new single-segment message
// is allocated.
func (call *Call) PlaceParams(s *Segment) (Struct, error) {
if call.ParamsFunc == nil {
return call.Params, nil
}
if s == nil {
var err error
_, s, err = NewMessage(SingleSegment(nil))
if err != nil {
return Struct{}, err
}
}
p, err := NewStruct(s, call.ParamsSize)
if err != nil {
return Struct{}, nil
}
err = call.ParamsFunc(p)
return p, err
}
// CallOptions holds RPC-specific options for an interface call.
// Its usage is similar to the values in context.Context, but is only
// used for a single call: its values are not intended to propagate to
// other callees. An example of an option would be the
// Call.sendResultsTo field in rpc.capnp.
type CallOptions struct {
m map[interface{}]interface{}
}
// NewCallOptions builds a CallOptions value from a list of individual options.
func NewCallOptions(opts []CallOption) CallOptions {
co := CallOptions{make(map[interface{}]interface{})}
for _, o := range opts {
o.f(co)
}
return co
}
// Value retrieves the value associated with the options for this key,
// or nil if no value is associated with this key.
func (co CallOptions) Value(key interface{}) interface{} {
return co.m[key]
}
// With creates a copy of the CallOptions value with other options applied.
func (co CallOptions) With(opts []CallOption) CallOptions {
newopts := CallOptions{make(map[interface{}]interface{})}
for k, v := range co.m {
newopts.m[k] = v
}
for _, o := range opts {
o.f(newopts)
}
return newopts
}
// A CallOption is a function that modifies options on an interface call.
type CallOption struct {
f func(CallOptions)
}
// SetOptionValue returns a call option that associates a value to an
// option key. This can be retrieved later with CallOptions.Value.
func SetOptionValue(key, value interface{}) CallOption {
return CallOption{func(co CallOptions) {
co.m[key] = value
}}
}
// An Answer is the deferred result of a client call, which is usually wrapped by a Pipeline.
type Answer interface {
// Struct waits until the call is finished and returns the result.
Struct() (Struct, error)
// The following methods are the same as in Client except with
// an added transform parameter -- a path to the interface to use.
PipelineCall(transform []PipelineOp, call *Call) Answer
PipelineClose(transform []PipelineOp) error
}
// A Pipeline is a generic wrapper for an answer.
type Pipeline struct {
answer Answer
parent *Pipeline
op PipelineOp
}
// NewPipeline returns a new pipeline based on an answer.
func NewPipeline(ans Answer) *Pipeline {
return &Pipeline{answer: ans}
}
// Answer returns the answer the pipeline is derived from.
func (p *Pipeline) Answer() Answer {
return p.answer
}
// Transform returns the operations needed to transform the root answer
// into the value p represents.
func (p *Pipeline) Transform() []PipelineOp {
n := 0
for q := p; q.parent != nil; q = q.parent {
n++
}
xform := make([]PipelineOp, n)
for i, q := n-1, p; q.parent != nil; i, q = i-1, q.parent {
xform[i] = q.op
}
return xform
}
// Struct waits until the answer is resolved and returns the struct
// this pipeline represents.
func (p *Pipeline) Struct() (Struct, error) {
s, err := p.answer.Struct()
if err != nil {
return Struct{}, err
}
ptr, err := TransformPtr(s.ToPtr(), p.Transform())
if err != nil {
return Struct{}, err
}
return ptr.Struct(), nil
}
// Client returns the client version of p.
func (p *Pipeline) Client() *PipelineClient {
return (*PipelineClient)(p)
}
// GetPipeline returns a derived pipeline which yields the pointer field given.
func (p *Pipeline) GetPipeline(off uint16) *Pipeline {
return p.GetPipelineDefault(off, nil)
}
// GetPipelineDefault returns a derived pipeline which yields the pointer field given,
// defaulting to the value given.
func (p *Pipeline) GetPipelineDefault(off uint16, def []byte) *Pipeline {
return &Pipeline{
answer: p.answer,
parent: p,
op: PipelineOp{
Field: off,
DefaultValue: def,
},
}
}
// PipelineClient implements Client by calling to the pipeline's answer.
type PipelineClient Pipeline
func (pc *PipelineClient) transform() []PipelineOp {
return (*Pipeline)(pc).Transform()
}
// Call calls Answer.PipelineCall with the pipeline's transform.
func (pc *PipelineClient) Call(call *Call) Answer {
return pc.answer.PipelineCall(pc.transform(), call)
}
// Close calls Answer.PipelineClose with the pipeline's transform.
func (pc *PipelineClient) Close() error {
return pc.answer.PipelineClose(pc.transform())
}
// A PipelineOp describes a step in transforming a pipeline.
// It maps closely with the PromisedAnswer.Op struct in rpc.capnp.
type PipelineOp struct {
Field uint16
DefaultValue []byte
}
// String returns a human-readable description of op.
func (op PipelineOp) String() string {
s := make([]byte, 0, 32)
s = append(s, "get field "...)
s = strconv.AppendInt(s, int64(op.Field), 10)
if op.DefaultValue == nil {
return string(s)
}
s = append(s, " with default"...)
return string(s)
}
// A Method identifies a method along with an optional human-readable
// description of the method.
type Method struct {
InterfaceID uint64
MethodID uint16
// Canonical name of the interface. May be empty.
InterfaceName string
// Method name as it appears in the schema. May be empty.
MethodName string
}
// String returns a formatted string containing the interface name or
// the method name if present, otherwise it uses the raw IDs.
// This is suitable for use in error messages and logs.
func (m *Method) String() string {
buf := make([]byte, 0, 128)
if m.InterfaceName == "" {
buf = append(buf, '@', '0', 'x')
buf = strconv.AppendUint(buf, m.InterfaceID, 16)
} else {
buf = append(buf, m.InterfaceName...)
}
buf = append(buf, '.')
if m.MethodName == "" {
buf = append(buf, '@')
buf = strconv.AppendUint(buf, uint64(m.MethodID), 10)
} else {
buf = append(buf, m.MethodName...)
}
return string(buf)
}
// Transform applies a sequence of pipeline operations to a pointer
// and returns the result.
//
// Deprecated: Use TransformPtr.
func Transform(p Pointer, transform []PipelineOp) (Pointer, error) {
pp, err := TransformPtr(toPtr(p), transform)
return pp.toPointer(), err
}
// TransformPtr applies a sequence of pipeline operations to a pointer
// and returns the result.
func TransformPtr(p Ptr, transform []PipelineOp) (Ptr, error) {
n := len(transform)
if n == 0 {
return p, nil
}
s := p.Struct()
for _, op := range transform[:n-1] {
field, err := s.Ptr(op.Field)
if err != nil {
return Ptr{}, err
}
s, err = field.StructDefault(op.DefaultValue)
if err != nil {
return Ptr{}, err
}
}
op := transform[n-1]
p, err := s.Ptr(op.Field)
if err != nil {
return Ptr{}, err
}
if op.DefaultValue != nil {
p, err = p.Default(op.DefaultValue)
}
return p, err
}
type immediateAnswer struct {
s Struct
}
// ImmediateAnswer returns an Answer that accesses s.
func ImmediateAnswer(s Struct) Answer {
return immediateAnswer{s}
}
func (ans immediateAnswer) Struct() (Struct, error) {
return ans.s, nil
}
func (ans immediateAnswer) findClient(transform []PipelineOp) Client {
p, err := TransformPtr(ans.s.ToPtr(), transform)
if err != nil {
return ErrorClient(err)
}
return p.Interface().Client()
}
func (ans immediateAnswer) PipelineCall(transform []PipelineOp, call *Call) Answer {
c := ans.findClient(transform)
if c == nil {
return ErrorAnswer(ErrNullClient)
}
return c.Call(call)
}
func (ans immediateAnswer) PipelineClose(transform []PipelineOp) error {
c := ans.findClient(transform)
if c == nil {
return ErrNullClient
}
return c.Close()
}
type errorAnswer struct {
e error
}
// ErrorAnswer returns a Answer that always returns error e.
func ErrorAnswer(e error) Answer {
return errorAnswer{e}
}
func (ans errorAnswer) Struct() (Struct, error) {
return Struct{}, ans.e
}
func (ans errorAnswer) PipelineCall([]PipelineOp, *Call) Answer {
return ans
}
func (ans errorAnswer) PipelineClose([]PipelineOp) error {
return ans.e
}
// IsFixedAnswer reports whether an answer was created by
// ImmediateAnswer or ErrorAnswer.
func IsFixedAnswer(ans Answer) bool {
switch ans.(type) {
case immediateAnswer:
return true
case errorAnswer:
return true
default:
return false
}
}
type errorClient struct {
e error
}
// ErrorClient returns a Client that always returns error e.
func ErrorClient(e error) Client {
return errorClient{e}
}
func (ec errorClient) Call(*Call) Answer {
return ErrorAnswer(ec.e)
}
func (ec errorClient) Close() error {
return nil
}
// IsErrorClient reports whether c was created with ErrorClient.
func IsErrorClient(c Client) bool {
_, ok := c.(errorClient)
return ok
}
// MethodError is an error on an associated method.
type MethodError struct {
Method *Method
Err error
}
// Error returns the method name concatenated with the error string.
func (e *MethodError) Error() string {
return e.Method.String() + ": " + e.Err.Error()
}
// ErrUnimplemented is the error returned when a method is called on
// a server that does not implement the method.
var ErrUnimplemented = errors.New("capnp: method not implemented")
// IsUnimplemented reports whether e indicates an unimplemented method error.
func IsUnimplemented(e error) bool {
if me, ok := e.(*MethodError); ok {
e = me.Err
}
return e == ErrUnimplemented
}