542 lines
14 KiB
Go
542 lines
14 KiB
Go
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
|
|
}
|