Skip to content

Commit d491d2f

Browse files
authored
Merge pull request #594 from Semisol/feature/3ph-base
3PH part 1: Prepare for 3PH support
2 parents 46ccd63 + a1ef2e9 commit d491d2f

22 files changed

+11932
-556
lines changed

‎rpc/network.go‎

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,25 @@ type PeerID struct {
2727
// attached to the message via SCM_RIGHTS. This file descriptor would be one
2828
// end of a newly-created socketpair, with the other end having been sent to the
2929
// process hosting the capability in RecipientId.
30-
type ThirdPartyCapID capnp.Ptr
30+
//
31+
// Some networks, as an optimization, may permit ThirdPartyToContact to be
32+
// forwarded across multiple vats. For example, imagine Alice sends a capability
33+
// to Bob, who passes it along to Carol, who further pass it to Dave. Bob will send
34+
// a `Provide` message to Alice telling her to expect the capability to be picked
35+
// up by Carol, and then will pass Carol a `ThirdPartyToContact` pointing to Alice.
36+
// If `ThirdPartyToContact` is non-forwardable, then Carol must form a connection
37+
// to Alice, send an `Accept` to receive the capability, and then immediately send
38+
// a `Provide` to provide it to Dave, before then being able to give a
39+
// `ThirdPartyToContact` to Dave which points to Alice. This is a bit of a waste.
40+
// If `ThirdPartyToContact` is forwardable, then Carol can simply pass it along to
41+
// Dave without making any connection to Alice. Some VatNetwork implementations may
42+
// require that Carol add a signature to the `ThirdPartyToContact` authenticating
43+
// that she really did forward it to Dave, which Dave will then present back to
44+
// Alice. Other implementations may simply pass along an unguessable token and
45+
// instruct Alice that whoever presents the token should receive the capability.
46+
// A VatNetwork may choose not to allow forwarding if it doesn't want its security
47+
// to be dependent on secret bearer tokens nor cryptographic signatures.
48+
type ThirdPartyToContact capnp.Ptr
3149

3250
// The information that must be sent in a `Provide` message to identify the
3351
// recipient of the capability.
@@ -41,7 +59,7 @@ type ThirdPartyCapID capnp.Ptr
4159
// attached to the message via SCM_RIGHTS. This file descriptor would be one
4260
// end of a newly-created socketpair, with the other end having been sent to the
4361
// capability's recipient in ThirdPartyCapId.
44-
type RecipientID capnp.Ptr
62+
type ThirdPartyToAwait capnp.Ptr
4563

4664
// The information that must be sent in an `Accept` message to identify the
4765
// object being accepted.
@@ -50,46 +68,72 @@ type RecipientID capnp.Ptr
5068
// be the public key fingerprint of the provider vat along with a nonce matching
5169
// the one in the `RecipientId` used in the `Provide` message sent from that
5270
// provider.
53-
type ProvisionID capnp.Ptr
71+
type ThirdPartyCompletion capnp.Ptr
5472

55-
// Data needed to perform a third-party handoff, returned by
56-
// Newtork.Introduce.
73+
// Data needed to perform a third-party handoff.
5774
type IntroductionInfo struct {
58-
SendToRecipient ThirdPartyCapID
59-
SendToProvider RecipientID
75+
SendToProvider ThirdPartyToAwait
76+
SendToRecipient ThirdPartyToContact
6077
}
6178

6279
// A Network is a reference to a multi-party (generally >= 3) network
6380
// of Cap'n Proto peers. Use this instead of NewConn when establishing
6481
// connections outside a point-to-point setting.
82+
//
83+
// In addition to satisfying the method set, a correct implementation
84+
// of Network must be comparable.
6585
type Network interface {
6686
// Return the identifier for caller on this network.
6787
LocalID() PeerID
6888

69-
// Connect to another peer by ID. The supplied Options are used
70-
// for the connection, with the values for RemotePeerID and Network
71-
// overridden by the Network.
72-
Dial(PeerID, *Options) (*Conn, error)
89+
// Connect to another peer by ID. Re-uses any existing connection
90+
// to the peer.
91+
Dial(PeerID) (*Conn, error)
92+
93+
// Accept and handle incoming connections on the network until
94+
// the context is canceled.
95+
Serve(context.Context) error
96+
}
7397

74-
// Accept the next incoming connection on the network, using the
75-
// supplied Options for the connection. Generally, callers will
76-
// want to invoke this in a loop when launching a server.
77-
Accept(context.Context, *Options) (*Conn, error)
98+
// A Network3PH is a Network which supports three-party handoff of capabilities.
99+
// TODO(before merge): could this interface be named better?
100+
type Network3PH interface {
101+
// Introduces both connections for a three-party handoff. After this,
102+
// the `ThirdPartyToAwait` will be sent to the `provider` and the
103+
// `ThirdPartyToContact` will be sent to the `recipient`.
104+
//
105+
// An error indicates introduction is not possible between the two `Conn`s.
106+
Introduce(provider *Conn, recipient *Conn) (IntroductionInfo, error)
78107

79-
// Introduce the two connections, in preparation for a third party
80-
// handoff. Afterwards, a Provide messsage should be sent to
81-
// provider, and a ThirdPartyCapId should be sent to recipient.
82-
Introduce(provider, recipient *Conn) (IntroductionInfo, error)
108+
// Attempts forwarding of a `ThirdPartyToContact` received from `from` to
109+
// `destination`, with both vats being in this Network. This method
110+
// return a `ThirdPartyToContact` to send to `destination`.
111+
//
112+
// An error indicates forwarding is not possible.
113+
Forward(from *Conn, destination *Conn, info ThirdPartyToContact) (ThirdPartyToContact, error)
83114

84-
// Given a ThirdPartyCapID, received from introducedBy, connect
85-
// to the third party. The caller should then send an Accept
86-
// message over the returned Connection.
87-
DialIntroduced(capID ThirdPartyCapID, introducedBy *Conn) (*Conn, ProvisionID, error)
115+
// Completes a three-party handoff.
116+
//
117+
// The provided `completion` has been received from `conn` in an `Accept`.
118+
//
119+
// This method blocks until there is a matching `AwaitThirdParty`, if there is
120+
// none currently, and returns the `value` passed to it.
121+
//
122+
// An error indicates that this completion can never succeed, for example due
123+
// to a `completion` that is malformed. The error will be sent in response to the
124+
// `Accept`.
125+
CompleteThirdParty(ctx context.Context, conn *Conn, completion ThirdPartyCompletion) (any, error)
88126

89-
// Given a RecipientID received in a Provide message via
90-
// introducedBy, wait for the recipient to connect, and
91-
// return the connection formed. If there is already an
92-
// established connection to the relevant Peer, this
93-
// SHOULD return the existing connection immediately.
94-
AcceptIntroduced(recipientID RecipientID, introducedBy *Conn) (*Conn, error)
127+
// Awaits for completion of a three-party handoff.
128+
//
129+
// The provided `await` has been received from `conn`.
130+
//
131+
// While the context is valid, any `CompleteThirdParty` calls that match
132+
// the provided `await` should return `value`.
133+
//
134+
// After the context is canceled, future calls to `CompleteThirdParty` are
135+
// not required to return the provided `value`.
136+
//
137+
// This method SHOULD not block.
138+
AwaitThirdParty(ctx context.Context, conn *Conn, await ThirdPartyToAwait, value any)
95139
}

‎rpc/rpc.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,7 +1303,7 @@ func (c *lockedConn) parseReturn(dq *deferred.Queue, ret rpccp.Return, called []
13031303
return parsedReturn{err: rpcerr.WrapFailed("parse return", err), parseFailed: true}
13041304
}
13051305
return parsedReturn{err: exc.New(exc.Type(e.Type()), "", reason)}
1306-
case rpccp.Return_Which_acceptFromThirdParty:
1306+
case rpccp.Return_Which_awaitFromThirdParty:
13071307
// TODO: 3PH. Can wait until after the MVP, because we can keep
13081308
// setting allowThirdPartyTailCall = false
13091309
fallthrough
@@ -1742,7 +1742,7 @@ func (c *Conn) handleDisembargo(ctx context.Context, in transport.IncomingMessag
17421742
})
17431743
})
17441744

1745-
case rpccp.Disembargo_context_Which_accept, rpccp.Disembargo_context_Which_provide:
1745+
case rpccp.Disembargo_context_Which_accept:
17461746
if c.network != nil {
17471747
panic("TODO: 3PH")
17481748
}

‎std/capnp/c++.capnp‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ annotation allowCancellation(interface, method, file) :Void;
4646
# If your code is not cancellation-safe, then allowing cancellation might give a malicious client
4747
# an easy way to induce use-after-free or other bugs in your server, by requesting cancellation
4848
# when not expected.
49-
5049
using Go = import "/go.capnp";
5150
$Go.package("cxx");
5251
$Go.import("capnproto.org/go/capnp/v3/std/capnp/cxx");

‎std/capnp/compat/byte-stream.capnp‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@0x8f5d14e1c273738d;
2+
3+
using Cxx = import "/capnp/c++.capnp";
4+
$Cxx.namespace("capnp");
5+
$Cxx.allowCancellation;
6+
7+
interface ByteStream {
8+
write @0 (bytes :Data) -> stream;
9+
# Write a chunk.
10+
11+
end @1 ();
12+
# Signals clean EOF. (If the ByteStream is dropped without calling this, then the stream was
13+
# prematurely canceled and so the body should not be considered complete.)
14+
15+
getSubstream @2 (callback :SubstreamCallback,
16+
limit :UInt64 = 0xffffffffffffffff) -> (substream :ByteStream);
17+
# This method is used to implement path shortening optimization. It is designed in particular
18+
# with KJ streams' pumpTo() in mind.
19+
#
20+
# getSubstream() returns a new stream object that can be used to write to the same destination
21+
# as this stream. The substream will operate until it has received `limit` bytes, or its `end()`
22+
# method has been called, whichever occurs first. At that time, it invokes one of the methods of
23+
# `callback` based on the termination condition.
24+
#
25+
# While a substream is active, it is an error to call write() on the original stream. Doing so
26+
# may throw an exception or may arbitrarily interleave bytes with the substream's writes.
27+
28+
startTls @3 (expectedServerHostname :Text) -> stream;
29+
# Client calls this method when it wants to initiate TLS. This ByteStream is not terminated,
30+
# the caller should reuse it.
31+
32+
interface SubstreamCallback {
33+
ended @0 (byteCount :UInt64);
34+
# `end()` was called on the substream after writing `byteCount` bytes. The `end()` call was
35+
# NOT forwarded to the underlying stream, which remains open.
36+
37+
reachedLimit @1 () -> (next :ByteStream);
38+
# The number of bytes specified by the `limit` parameter of `getSubstream()` was reached.
39+
# The substream will "resolve itself" to `next`, so that all future calls to the substream
40+
# are forwarded to `next`.
41+
#
42+
# If the `write()` call which reached the limit included bytes past the limit, then the first
43+
# `write()` call to `next` will be for those leftover bytes.
44+
}
45+
}
46+
using Go = import "/go.capnp";
47+
$Go.package("bytestream");
48+
$Go.import("capnproto.org/go/capnp/v3/std/capnp/compat/bytestream");

0 commit comments

Comments
 (0)