@agoric/network 0.1.1-dev-7ffae88.0 → 0.1.1-orchestration-dev-096c4e8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -10
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/network.d.ts +130 -28
- package/src/network.d.ts.map +1 -1
- package/src/network.js +869 -426
- package/src/router.d.ts +42 -22
- package/src/router.d.ts.map +1 -1
- package/src/router.js +147 -77
- package/src/types.d.ts +29 -28
- package/src/types.d.ts.map +1 -1
- package/src/types.js +36 -29
- package/tsconfig.json +1 -0
- package/typedoc.json +9 -0
package/src/network.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { E } from '@endo/far';
|
|
4
|
+
import { M } from '@endo/patterns';
|
|
4
5
|
import { Fail } from '@agoric/assert';
|
|
5
6
|
import { whileTrue } from '@agoric/internal';
|
|
6
7
|
import { toBytes } from './bytes.js';
|
|
@@ -14,6 +15,130 @@ import '@agoric/store/exported.js';
|
|
|
14
15
|
*/
|
|
15
16
|
export const ENDPOINT_SEPARATOR = '/';
|
|
16
17
|
|
|
18
|
+
const Shape1 = /** @type {const} */ ({
|
|
19
|
+
/**
|
|
20
|
+
* Data is string | Buffer | ArrayBuffer
|
|
21
|
+
* but only string is passable
|
|
22
|
+
*/
|
|
23
|
+
Data: M.string(),
|
|
24
|
+
Bytes: M.string(),
|
|
25
|
+
Endpoint: M.string(),
|
|
26
|
+
// TODO: match on "Vow" tag
|
|
27
|
+
// @endo/patterns supports it as of
|
|
28
|
+
// https://github.com/endojs/endo/pull/2091
|
|
29
|
+
// but that's not in agoric-sdk yet.
|
|
30
|
+
// For now, use M.any() to avoid:
|
|
31
|
+
// cannot check unrecognized tag "Vow": "[Vow]"
|
|
32
|
+
Vow: M.any(),
|
|
33
|
+
|
|
34
|
+
ConnectionHandler: M.remotable('ConnectionHandler'),
|
|
35
|
+
Connection: M.remotable('Connection'),
|
|
36
|
+
InboundAttempt: M.remotable('InboundAttempt'),
|
|
37
|
+
Listener: M.remotable('Listener'),
|
|
38
|
+
ListenHandler: M.remotable('ListenHandler'),
|
|
39
|
+
Port: M.remotable('Port'),
|
|
40
|
+
ProtocolHandler: M.remotable('ProtocolHandler'),
|
|
41
|
+
ProtocolImpl: M.remotable('ProtocolImpl'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const Shape2 = /** @type {const} */ ({
|
|
45
|
+
...Shape1,
|
|
46
|
+
Vow$: shape => M.or(shape, Shape1.Vow),
|
|
47
|
+
AttemptDescription: M.splitRecord(
|
|
48
|
+
{ handler: Shape1.ConnectionHandler },
|
|
49
|
+
{ remoteAddress: Shape1.Endpoint, localAddress: Shape1.Endpoint },
|
|
50
|
+
),
|
|
51
|
+
Opts: M.recordOf(M.string(), M.any()),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const Shape = /** @type {const} */ harden({
|
|
55
|
+
...Shape2,
|
|
56
|
+
ConnectionI: M.interface('Connection', {
|
|
57
|
+
send: M.callWhen(Shape2.Data)
|
|
58
|
+
.optional(Shape2.Opts)
|
|
59
|
+
.returns(Shape2.Vow$(Shape2.Bytes)),
|
|
60
|
+
close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
|
|
61
|
+
getLocalAddress: M.call().returns(Shape2.Endpoint),
|
|
62
|
+
getRemoteAddress: M.call().returns(Shape2.Endpoint),
|
|
63
|
+
}),
|
|
64
|
+
InboundAttemptI: M.interface('InboundAttempt', {
|
|
65
|
+
accept: M.callWhen(Shape2.AttemptDescription).returns(
|
|
66
|
+
Shape2.Vow$(Shape2.Connection),
|
|
67
|
+
),
|
|
68
|
+
getLocalAddress: M.call().returns(Shape2.Endpoint),
|
|
69
|
+
getRemoteAddress: M.call().returns(Shape2.Endpoint),
|
|
70
|
+
close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
|
|
71
|
+
}),
|
|
72
|
+
PortI: M.interface('Port', {
|
|
73
|
+
getLocalAddress: M.call().returns(Shape2.Endpoint),
|
|
74
|
+
addListener: M.callWhen(Shape2.Listener).returns(
|
|
75
|
+
Shape2.Vow$(M.undefined()),
|
|
76
|
+
),
|
|
77
|
+
connect: M.callWhen(Shape2.Endpoint)
|
|
78
|
+
.optional(Shape2.ConnectionHandler)
|
|
79
|
+
.returns(Shape2.Vow$(Shape2.Connection)),
|
|
80
|
+
removeListener: M.callWhen(Shape2.Listener).returns(
|
|
81
|
+
Shape2.Vow$(M.undefined()),
|
|
82
|
+
),
|
|
83
|
+
revoke: M.callWhen().returns(M.undefined()),
|
|
84
|
+
}),
|
|
85
|
+
ProtocolHandlerI: M.interface('ProtocolHandler', {
|
|
86
|
+
onCreate: M.callWhen(M.remotable(), Shape2.ProtocolHandler).returns(
|
|
87
|
+
Shape2.Vow$(M.undefined()),
|
|
88
|
+
),
|
|
89
|
+
generatePortID: M.callWhen(Shape2.Endpoint, Shape2.ProtocolHandler).returns(
|
|
90
|
+
Shape2.Vow$(M.string()),
|
|
91
|
+
),
|
|
92
|
+
onBind: M.callWhen(
|
|
93
|
+
Shape2.Port,
|
|
94
|
+
Shape2.Endpoint,
|
|
95
|
+
Shape2.ProtocolHandler,
|
|
96
|
+
).returns(Shape2.Vow$(M.undefined())),
|
|
97
|
+
onListen: M.callWhen(
|
|
98
|
+
Shape2.Port,
|
|
99
|
+
Shape2.Endpoint,
|
|
100
|
+
Shape2.ListenHandler,
|
|
101
|
+
Shape2.ProtocolHandler,
|
|
102
|
+
).returns(Shape2.Vow$(M.undefined())),
|
|
103
|
+
onListenRemove: M.callWhen(
|
|
104
|
+
Shape2.Port,
|
|
105
|
+
Shape2.Endpoint,
|
|
106
|
+
Shape2.ListenHandler,
|
|
107
|
+
Shape2.ProtocolHandler,
|
|
108
|
+
).returns(Shape2.Vow$(M.undefined())),
|
|
109
|
+
onInstantiate: M.callWhen(
|
|
110
|
+
Shape2.Port,
|
|
111
|
+
Shape2.Endpoint,
|
|
112
|
+
Shape2.Endpoint,
|
|
113
|
+
Shape2.ProtocolHandler,
|
|
114
|
+
).returns(Shape2.Vow$(Shape2.Endpoint)),
|
|
115
|
+
onConnect: M.callWhen(
|
|
116
|
+
Shape2.Port,
|
|
117
|
+
Shape2.Endpoint,
|
|
118
|
+
Shape2.Endpoint,
|
|
119
|
+
Shape2.ConnectionHandler,
|
|
120
|
+
Shape2.ProtocolHandler,
|
|
121
|
+
).returns(Shape2.Vow$(Shape2.AttemptDescription)),
|
|
122
|
+
onRevoke: M.callWhen(
|
|
123
|
+
Shape2.Port,
|
|
124
|
+
Shape2.Endpoint,
|
|
125
|
+
Shape2.ProtocolHandler,
|
|
126
|
+
).returns(Shape2.Vow$(M.undefined())),
|
|
127
|
+
}),
|
|
128
|
+
ProtocolImplI: M.interface('ProtocolImpl', {
|
|
129
|
+
bind: M.callWhen(Shape2.Endpoint).returns(Shape2.Vow$(Shape2.Port)),
|
|
130
|
+
inbound: M.callWhen(Shape2.Endpoint, Shape2.Endpoint).returns(
|
|
131
|
+
Shape2.Vow$(Shape2.InboundAttempt),
|
|
132
|
+
),
|
|
133
|
+
outbound: M.callWhen(
|
|
134
|
+
Shape2.Port,
|
|
135
|
+
Shape2.Endpoint,
|
|
136
|
+
Shape2.ConnectionHandler,
|
|
137
|
+
).returns(Shape2.Vow$(Shape2.Connection)),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/** @param {unknown} err */
|
|
17
142
|
export const rethrowUnlessMissing = err => {
|
|
18
143
|
// Ugly hack rather than being able to determine if the function
|
|
19
144
|
// exists.
|
|
@@ -27,279 +152,389 @@ export const rethrowUnlessMissing = err => {
|
|
|
27
152
|
};
|
|
28
153
|
|
|
29
154
|
/**
|
|
30
|
-
*
|
|
155
|
+
* Get the list of prefixes from longest to shortest.
|
|
31
156
|
*
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {Endpoint} localAddr
|
|
34
|
-
* @param {Endpoint} remoteAddr
|
|
35
|
-
* @param {Set<Closable>} [current]
|
|
36
|
-
* @returns {Connection}
|
|
157
|
+
* @param {string} addr
|
|
37
158
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
localAddr,
|
|
41
|
-
remoteAddr,
|
|
42
|
-
current = new Set(),
|
|
43
|
-
) => {
|
|
44
|
-
let closed;
|
|
45
|
-
/** @type {Set<import('@endo/promise-kit').PromiseKit<Bytes>>} */
|
|
46
|
-
const pendingAcks = new Set();
|
|
47
|
-
/** @type {Connection} */
|
|
48
|
-
const connection = Far('Connection', {
|
|
49
|
-
getLocalAddress() {
|
|
50
|
-
return localAddr;
|
|
51
|
-
},
|
|
52
|
-
getRemoteAddress() {
|
|
53
|
-
return remoteAddr;
|
|
54
|
-
},
|
|
55
|
-
async close() {
|
|
56
|
-
if (closed) {
|
|
57
|
-
throw closed;
|
|
58
|
-
}
|
|
59
|
-
current.delete(connection);
|
|
60
|
-
closed = Error('Connection closed');
|
|
61
|
-
for (const ackDeferred of [...pendingAcks.values()]) {
|
|
62
|
-
pendingAcks.delete(ackDeferred);
|
|
63
|
-
ackDeferred.reject(closed);
|
|
64
|
-
}
|
|
65
|
-
await E(handler)
|
|
66
|
-
.onClose(connection, undefined, handler)
|
|
67
|
-
.catch(rethrowUnlessMissing);
|
|
68
|
-
},
|
|
69
|
-
async send(data, opts) {
|
|
70
|
-
// console.log('send', data, local === srcHandler);
|
|
71
|
-
if (closed) {
|
|
72
|
-
throw closed;
|
|
73
|
-
}
|
|
74
|
-
const bytes = toBytes(data);
|
|
75
|
-
const ackDeferred = makePromiseKit();
|
|
76
|
-
pendingAcks.add(ackDeferred);
|
|
77
|
-
E(handler)
|
|
78
|
-
.onReceive(connection, bytes, handler, opts)
|
|
79
|
-
.catch(err => {
|
|
80
|
-
rethrowUnlessMissing(err);
|
|
81
|
-
return '';
|
|
82
|
-
})
|
|
83
|
-
.then(
|
|
84
|
-
ack => {
|
|
85
|
-
pendingAcks.delete(ackDeferred);
|
|
86
|
-
ackDeferred.resolve(toBytes(ack));
|
|
87
|
-
},
|
|
88
|
-
err => {
|
|
89
|
-
pendingAcks.delete(ackDeferred);
|
|
90
|
-
ackDeferred.reject(err);
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
return ackDeferred.promise;
|
|
94
|
-
},
|
|
95
|
-
});
|
|
159
|
+
export function getPrefixes(addr) {
|
|
160
|
+
const parts = addr.split(ENDPOINT_SEPARATOR);
|
|
96
161
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
162
|
+
/** @type {string[]} */
|
|
163
|
+
const ret = [];
|
|
164
|
+
for (let i = parts.length; i > 0; i -= 1) {
|
|
165
|
+
// Try most specific match.
|
|
166
|
+
const prefix = parts.slice(0, i).join(ENDPOINT_SEPARATOR);
|
|
167
|
+
ret.push(prefix);
|
|
168
|
+
}
|
|
169
|
+
return ret;
|
|
170
|
+
}
|
|
103
171
|
|
|
104
172
|
/**
|
|
105
|
-
* @
|
|
106
|
-
* @
|
|
107
|
-
* @
|
|
108
|
-
* @
|
|
109
|
-
* @
|
|
110
|
-
* @
|
|
173
|
+
* @typedef {object} ConnectionOpts
|
|
174
|
+
* @property {Endpoint[]} addrs
|
|
175
|
+
* @property {ConnectionHandler[]} handlers
|
|
176
|
+
* @property {MapStore<number, Connection>} conns
|
|
177
|
+
* @property {WeakSetStore<Closable>} current
|
|
178
|
+
* @property {0|1} l
|
|
179
|
+
* @property {0|1} r
|
|
111
180
|
*/
|
|
112
|
-
export function crossoverConnection(
|
|
113
|
-
handler0,
|
|
114
|
-
addr0,
|
|
115
|
-
handler1,
|
|
116
|
-
addr1,
|
|
117
|
-
current = new WeakSet(),
|
|
118
|
-
) {
|
|
119
|
-
/** @type {Connection[]} */
|
|
120
|
-
const conns = [];
|
|
121
|
-
/** @type {ConnectionHandler[]} */
|
|
122
|
-
const handlers = [handler0, handler1];
|
|
123
|
-
/** @type {Endpoint[]} */
|
|
124
|
-
const addrs = [addr0, addr1];
|
|
125
181
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
182
|
+
/**
|
|
183
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
184
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
185
|
+
*/
|
|
186
|
+
const prepareHalfConnection = (zone, { when }) => {
|
|
187
|
+
const makeHalfConnection = zone.exoClass(
|
|
188
|
+
'Connection',
|
|
189
|
+
Shape.ConnectionI,
|
|
190
|
+
/** @param {ConnectionOpts} opts */
|
|
191
|
+
({ addrs, handlers, conns, current, l, r }) => {
|
|
192
|
+
/** @type {string | undefined} */
|
|
193
|
+
let closed;
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
addrs,
|
|
197
|
+
handlers,
|
|
198
|
+
conns,
|
|
199
|
+
current,
|
|
200
|
+
l,
|
|
201
|
+
r,
|
|
202
|
+
closed,
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
{
|
|
129
206
|
getLocalAddress() {
|
|
207
|
+
const { addrs, l } = this.state;
|
|
130
208
|
return addrs[l];
|
|
131
209
|
},
|
|
132
210
|
getRemoteAddress() {
|
|
211
|
+
const { addrs, r } = this.state;
|
|
133
212
|
return addrs[r];
|
|
134
213
|
},
|
|
214
|
+
/** @param {Data} packetBytes */
|
|
135
215
|
async send(packetBytes) {
|
|
216
|
+
const { closed, handlers, r, conns } = this.state;
|
|
136
217
|
if (closed) {
|
|
137
218
|
throw closed;
|
|
138
219
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
220
|
+
|
|
221
|
+
const ack = await when(
|
|
222
|
+
E(handlers[r])
|
|
223
|
+
.onReceive(conns.get(r), toBytes(packetBytes), handlers[r])
|
|
224
|
+
.catch(rethrowUnlessMissing),
|
|
225
|
+
);
|
|
226
|
+
|
|
142
227
|
return toBytes(ack || '');
|
|
143
228
|
},
|
|
144
229
|
async close() {
|
|
230
|
+
const { closed, current, conns, l, handlers } = this.state;
|
|
145
231
|
if (closed) {
|
|
146
|
-
throw closed;
|
|
232
|
+
throw Error(closed);
|
|
147
233
|
}
|
|
148
|
-
closed =
|
|
149
|
-
current.delete(conns
|
|
150
|
-
await
|
|
151
|
-
.
|
|
152
|
-
|
|
234
|
+
this.state.closed = 'Connection closed';
|
|
235
|
+
current.delete(conns.get(l));
|
|
236
|
+
await when(
|
|
237
|
+
E(this.state.handlers[l]).onClose(
|
|
238
|
+
conns.get(l),
|
|
239
|
+
undefined,
|
|
240
|
+
handlers[l],
|
|
241
|
+
),
|
|
242
|
+
).catch(rethrowUnlessMissing);
|
|
153
243
|
},
|
|
154
|
-
}
|
|
155
|
-
|
|
244
|
+
},
|
|
245
|
+
);
|
|
156
246
|
|
|
157
|
-
makeHalfConnection
|
|
158
|
-
|
|
247
|
+
return makeHalfConnection;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {import('@agoric/zone').Zone} zone
|
|
252
|
+
* @param {ConnectionHandler} handler0
|
|
253
|
+
* @param {Endpoint} addr0
|
|
254
|
+
* @param {ConnectionHandler} handler1
|
|
255
|
+
* @param {Endpoint} addr1
|
|
256
|
+
* @param {(opts: ConnectionOpts) => Connection} makeConnection
|
|
257
|
+
* @param {WeakSetStore<Closable>} current
|
|
258
|
+
*/
|
|
259
|
+
export const crossoverConnection = (
|
|
260
|
+
zone,
|
|
261
|
+
handler0,
|
|
262
|
+
addr0,
|
|
263
|
+
handler1,
|
|
264
|
+
addr1,
|
|
265
|
+
makeConnection,
|
|
266
|
+
current = zone.detached().weakSetStore('crossoverCurrentConnections'),
|
|
267
|
+
) => {
|
|
268
|
+
const detached = zone.detached();
|
|
269
|
+
|
|
270
|
+
/** @type {MapStore<number, Connection>} */
|
|
271
|
+
const conns = detached.mapStore('addrToConnections');
|
|
272
|
+
|
|
273
|
+
/** @type {ConnectionHandler[]} */
|
|
274
|
+
const handlers = harden([handler0, handler1]);
|
|
275
|
+
/** @type {Endpoint[]} */
|
|
276
|
+
const addrs = harden([addr0, addr1]);
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {0|1} l
|
|
280
|
+
* @param {0|1} r
|
|
281
|
+
*/
|
|
282
|
+
const makeHalfConnection = (l, r) => {
|
|
283
|
+
conns.init(l, makeConnection({ addrs, handlers, conns, current, l, r }));
|
|
284
|
+
};
|
|
159
285
|
|
|
160
286
|
/**
|
|
161
287
|
* @param {number} l local side of the connection
|
|
162
288
|
* @param {number} r remote side of the connection
|
|
163
289
|
*/
|
|
164
|
-
|
|
165
|
-
current.add(conns
|
|
290
|
+
const openHalfConnection = (l, r) => {
|
|
291
|
+
current.add(conns.get(l));
|
|
166
292
|
E(handlers[l])
|
|
167
|
-
.onOpen(conns
|
|
293
|
+
.onOpen(conns.get(l), addrs[l], addrs[r], handlers[l])
|
|
168
294
|
.catch(rethrowUnlessMissing);
|
|
169
|
-
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
makeHalfConnection(0, 1);
|
|
298
|
+
makeHalfConnection(1, 0);
|
|
170
299
|
|
|
171
300
|
openHalfConnection(0, 1);
|
|
172
301
|
openHalfConnection(1, 0);
|
|
173
302
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
303
|
+
return [conns.get(0), conns.get(1)];
|
|
304
|
+
};
|
|
177
305
|
|
|
178
306
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* @param {
|
|
307
|
+
* @param {import('@agoric/zone').Zone} zone
|
|
308
|
+
* @param {(opts: ConnectionOpts) => Connection} makeConnection
|
|
309
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
182
310
|
*/
|
|
183
|
-
|
|
184
|
-
const
|
|
311
|
+
const prepareInboundAttempt = (zone, makeConnection, { when }) => {
|
|
312
|
+
const makeInboundAttempt = zone.exoClass(
|
|
313
|
+
'InboundAttempt',
|
|
314
|
+
Shape.InboundAttemptI,
|
|
315
|
+
/**
|
|
316
|
+
* @param {object} opts
|
|
317
|
+
* @param {string} opts.localAddr
|
|
318
|
+
* @param {string} opts.remoteAddr
|
|
319
|
+
* @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
|
|
320
|
+
* @param {string} opts.listenPrefix
|
|
321
|
+
* @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
|
|
322
|
+
*/
|
|
323
|
+
({
|
|
324
|
+
localAddr,
|
|
325
|
+
remoteAddr,
|
|
326
|
+
currentConnections,
|
|
327
|
+
listenPrefix,
|
|
328
|
+
listening,
|
|
329
|
+
}) => {
|
|
330
|
+
/** @type {String | undefined} */
|
|
331
|
+
let consummated;
|
|
185
332
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
333
|
+
return {
|
|
334
|
+
localAddr,
|
|
335
|
+
remoteAddr,
|
|
336
|
+
consummated,
|
|
337
|
+
currentConnections,
|
|
338
|
+
listenPrefix,
|
|
339
|
+
listening,
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
getLocalAddress() {
|
|
344
|
+
// Return address metadata.
|
|
345
|
+
return this.state.localAddr;
|
|
346
|
+
},
|
|
347
|
+
getRemoteAddress() {
|
|
348
|
+
return this.state.remoteAddr;
|
|
349
|
+
},
|
|
350
|
+
async close() {
|
|
351
|
+
const { consummated, localAddr, remoteAddr } = this.state;
|
|
352
|
+
const { listening, listenPrefix, currentConnections } = this.state;
|
|
195
353
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* @returns {Protocol} the local capability for connecting and listening
|
|
201
|
-
*/
|
|
202
|
-
export function makeNetworkProtocol(protocolHandler) {
|
|
203
|
-
/** @type {LegacyMap<Port, Set<Closable>>} */
|
|
204
|
-
// Legacy because we're storing a JS Set
|
|
205
|
-
const currentConnections = makeLegacyMap('port');
|
|
354
|
+
if (consummated) {
|
|
355
|
+
throw Error(consummated);
|
|
356
|
+
}
|
|
357
|
+
this.state.consummated = 'Already closed';
|
|
206
358
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
359
|
+
const [port, listener] = listening.get(listenPrefix);
|
|
360
|
+
|
|
361
|
+
const current = currentConnections.get(port);
|
|
362
|
+
current.delete(this.self);
|
|
363
|
+
|
|
364
|
+
await when(
|
|
365
|
+
E(listener).onReject(port, localAddr, remoteAddr, listener),
|
|
366
|
+
).catch(rethrowUnlessMissing);
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* @param {object} opts
|
|
370
|
+
* @param {string} [opts.localAddress]
|
|
371
|
+
* @param {string} [opts.remoteAddress]
|
|
372
|
+
* @param {ConnectionHandler} opts.handler
|
|
373
|
+
*/
|
|
374
|
+
async accept({ localAddress, remoteAddress, handler: rchandler }) {
|
|
375
|
+
const { consummated, localAddr, remoteAddr } = this.state;
|
|
376
|
+
const { listening, listenPrefix, currentConnections } = this.state;
|
|
377
|
+
if (consummated) {
|
|
378
|
+
throw Error(consummated);
|
|
379
|
+
}
|
|
380
|
+
this.state.consummated = 'Already accepted';
|
|
381
|
+
|
|
382
|
+
if (localAddress === undefined) {
|
|
383
|
+
localAddress = localAddr;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (remoteAddress === undefined) {
|
|
387
|
+
remoteAddress = remoteAddr;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const [port, listener] = listening.get(listenPrefix);
|
|
391
|
+
const current = currentConnections.get(port);
|
|
214
392
|
|
|
215
|
-
|
|
216
|
-
const boundPorts = makeScalarMapStore('localAddr');
|
|
393
|
+
current.delete(this.self);
|
|
217
394
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
395
|
+
const lchandler = await when(
|
|
396
|
+
E(listener).onAccept(port, localAddress, remoteAddress, listener),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return crossoverConnection(
|
|
400
|
+
zone,
|
|
401
|
+
lchandler,
|
|
402
|
+
localAddress,
|
|
403
|
+
rchandler,
|
|
404
|
+
remoteAddress,
|
|
405
|
+
makeConnection,
|
|
406
|
+
current,
|
|
407
|
+
)[1];
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return makeInboundAttempt;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/** @enum {number} */
|
|
416
|
+
const RevokeState = /** @type {const} */ ({
|
|
417
|
+
NOT_REVOKED: 0,
|
|
418
|
+
REVOKING: 1,
|
|
419
|
+
REVOKED: 2,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @param {import('@agoric/zone').Zone} zone
|
|
424
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
425
|
+
*/
|
|
426
|
+
const preparePort = (zone, { when }) => {
|
|
427
|
+
const makeIncapable = zone.exoClass('Incapable', undefined, () => ({}), {});
|
|
428
|
+
|
|
429
|
+
const makePort = zone.exoClass(
|
|
430
|
+
'Port',
|
|
431
|
+
Shape.PortI,
|
|
432
|
+
/**
|
|
433
|
+
* @param {object} opts
|
|
434
|
+
* @param {Endpoint} opts.localAddr
|
|
435
|
+
* @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
|
|
436
|
+
* @param {SetStore<Connection>} opts.openConnections
|
|
437
|
+
* @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
|
|
438
|
+
* @param {MapStore<string, Port>} opts.boundPorts
|
|
439
|
+
* @param {ProtocolHandler} opts.protocolHandler
|
|
440
|
+
* @param {ProtocolImpl} opts.protocolImpl
|
|
441
|
+
*/
|
|
442
|
+
({
|
|
443
|
+
localAddr,
|
|
444
|
+
listening,
|
|
445
|
+
openConnections,
|
|
446
|
+
currentConnections,
|
|
447
|
+
boundPorts,
|
|
448
|
+
protocolHandler,
|
|
449
|
+
protocolImpl,
|
|
450
|
+
}) => {
|
|
451
|
+
return {
|
|
452
|
+
listening,
|
|
453
|
+
openConnections,
|
|
454
|
+
currentConnections,
|
|
455
|
+
boundPorts,
|
|
224
456
|
localAddr,
|
|
225
457
|
protocolHandler,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (boundPorts.has(localAddr)) {
|
|
235
|
-
return boundPorts.get(localAddr);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/** @enum {number} */
|
|
239
|
-
const RevokeState = {
|
|
240
|
-
NOT_REVOKED: 0,
|
|
241
|
-
REVOKING: 1,
|
|
242
|
-
REVOKED: 2,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
/** @type {RevokeState} */
|
|
246
|
-
let revoked = RevokeState.NOT_REVOKED;
|
|
247
|
-
const openConnections = new Set();
|
|
248
|
-
|
|
249
|
-
/** @type {Port} */
|
|
250
|
-
const port = Far('Port', {
|
|
458
|
+
protocolImpl,
|
|
459
|
+
/** @type {RevokeState | undefined} */
|
|
460
|
+
revoked: undefined,
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
{
|
|
251
464
|
getLocalAddress() {
|
|
252
465
|
// Works even after revoke().
|
|
253
|
-
return localAddr;
|
|
466
|
+
return this.state.localAddr;
|
|
254
467
|
},
|
|
468
|
+
/** @param {ListenHandler} listenHandler */
|
|
255
469
|
async addListener(listenHandler) {
|
|
256
|
-
|
|
470
|
+
const { revoked, listening, localAddr, protocolHandler } = this.state;
|
|
471
|
+
|
|
472
|
+
!revoked || Fail`Port ${this.state.localAddr} is revoked`;
|
|
257
473
|
listenHandler || Fail`listenHandler is not defined`;
|
|
474
|
+
|
|
258
475
|
if (listening.has(localAddr)) {
|
|
259
476
|
// Last one wins.
|
|
260
477
|
const [lport, lhandler] = listening.get(localAddr);
|
|
261
478
|
if (lhandler === listenHandler) {
|
|
262
479
|
return;
|
|
263
480
|
}
|
|
264
|
-
listening.set(localAddr, [
|
|
481
|
+
listening.set(localAddr, [this.self, listenHandler]);
|
|
265
482
|
E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
|
|
266
483
|
} else {
|
|
267
|
-
listening.init(localAddr, [
|
|
484
|
+
listening.init(localAddr, harden([this.self, listenHandler]));
|
|
268
485
|
}
|
|
269
486
|
|
|
270
|
-
//
|
|
487
|
+
// ASSUME: that the listener defines onAccept.
|
|
271
488
|
|
|
272
|
-
await
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
489
|
+
await when(
|
|
490
|
+
E(protocolHandler).onListen(
|
|
491
|
+
this.self,
|
|
492
|
+
localAddr,
|
|
493
|
+
listenHandler,
|
|
494
|
+
protocolHandler,
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
await when(E(listenHandler).onListen(this.self, listenHandler)).catch(
|
|
498
|
+
rethrowUnlessMissing,
|
|
277
499
|
);
|
|
278
|
-
await E(listenHandler)
|
|
279
|
-
.onListen(port, listenHandler)
|
|
280
|
-
.catch(rethrowUnlessMissing);
|
|
281
500
|
},
|
|
501
|
+
/** @param {ListenHandler} listenHandler */
|
|
282
502
|
async removeListener(listenHandler) {
|
|
503
|
+
const { listening, localAddr, protocolHandler } = this.state;
|
|
283
504
|
listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
|
|
284
505
|
listening.get(localAddr)[1] === listenHandler ||
|
|
285
506
|
Fail`Port ${localAddr} handler to remove is not listening`;
|
|
286
507
|
listening.delete(localAddr);
|
|
287
|
-
await
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
508
|
+
await when(
|
|
509
|
+
E(protocolHandler).onListenRemove(
|
|
510
|
+
this.self,
|
|
511
|
+
localAddr,
|
|
512
|
+
listenHandler,
|
|
513
|
+
protocolHandler,
|
|
514
|
+
),
|
|
515
|
+
);
|
|
516
|
+
await when(E(listenHandler).onRemove(this.self, listenHandler)).catch(
|
|
517
|
+
rethrowUnlessMissing,
|
|
292
518
|
);
|
|
293
|
-
await E(listenHandler)
|
|
294
|
-
.onRemove(port, listenHandler)
|
|
295
|
-
.catch(rethrowUnlessMissing);
|
|
296
519
|
},
|
|
297
|
-
|
|
520
|
+
/**
|
|
521
|
+
* @param {Endpoint} remotePort
|
|
522
|
+
* @param {ConnectionHandler} connectionHandler
|
|
523
|
+
*/
|
|
524
|
+
async connect(
|
|
525
|
+
remotePort,
|
|
526
|
+
connectionHandler = /** @type {any} */ (makeIncapable()),
|
|
527
|
+
) {
|
|
528
|
+
const { revoked, localAddr, protocolImpl, openConnections } =
|
|
529
|
+
this.state;
|
|
530
|
+
|
|
298
531
|
!revoked || Fail`Port ${localAddr} is revoked`;
|
|
299
532
|
/** @type {Endpoint} */
|
|
300
533
|
const dst = harden(remotePort);
|
|
301
534
|
// eslint-disable-next-line no-use-before-define
|
|
302
|
-
const conn = await
|
|
535
|
+
const conn = await when(
|
|
536
|
+
protocolImpl.outbound(this.self, dst, connectionHandler),
|
|
537
|
+
);
|
|
303
538
|
if (revoked) {
|
|
304
539
|
void E(conn).close();
|
|
305
540
|
} else {
|
|
@@ -308,273 +543,481 @@ export function makeNetworkProtocol(protocolHandler) {
|
|
|
308
543
|
return conn;
|
|
309
544
|
},
|
|
310
545
|
async revoke() {
|
|
546
|
+
const { revoked, localAddr } = this.state;
|
|
547
|
+
const { protocolHandler, currentConnections, listening, boundPorts } =
|
|
548
|
+
this.state;
|
|
549
|
+
|
|
311
550
|
revoked !== RevokeState.REVOKED ||
|
|
312
551
|
Fail`Port ${localAddr} is already revoked`;
|
|
313
|
-
revoked = RevokeState.REVOKING;
|
|
314
|
-
await
|
|
315
|
-
|
|
552
|
+
this.state.revoked = RevokeState.REVOKING;
|
|
553
|
+
await when(
|
|
554
|
+
E(protocolHandler).onRevoke(this.self, localAddr, protocolHandler),
|
|
555
|
+
);
|
|
556
|
+
this.state.revoked = RevokeState.REVOKED;
|
|
316
557
|
|
|
317
558
|
// Clean up everything we did.
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
.close()
|
|
321
|
-
.catch(_ => {}),
|
|
322
|
-
);
|
|
559
|
+
const values = [...currentConnections.get(this.self).values()];
|
|
560
|
+
const ps = values.map(conn => when(E(conn).close()).catch(_ => {}));
|
|
323
561
|
if (listening.has(localAddr)) {
|
|
324
562
|
const listener = listening.get(localAddr)[1];
|
|
325
|
-
ps.push(
|
|
563
|
+
ps.push(this.self.removeListener(listener));
|
|
326
564
|
}
|
|
327
565
|
await Promise.all(ps);
|
|
328
|
-
currentConnections.delete(
|
|
566
|
+
currentConnections.delete(this.self);
|
|
329
567
|
boundPorts.delete(localAddr);
|
|
330
|
-
return `Port ${localAddr} revoked`;
|
|
331
568
|
},
|
|
332
|
-
}
|
|
569
|
+
},
|
|
570
|
+
);
|
|
333
571
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
currentConnections.init(port, new Set());
|
|
337
|
-
return port;
|
|
338
|
-
};
|
|
572
|
+
return makePort;
|
|
573
|
+
};
|
|
339
574
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
575
|
+
/**
|
|
576
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
577
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
578
|
+
*/
|
|
579
|
+
const prepareBinder = (zone, powers) => {
|
|
580
|
+
const makeConnection = prepareHalfConnection(zone, powers);
|
|
581
|
+
const { when } = powers;
|
|
582
|
+
const makeInboundAttempt = prepareInboundAttempt(
|
|
583
|
+
zone,
|
|
584
|
+
makeConnection,
|
|
585
|
+
powers,
|
|
586
|
+
);
|
|
587
|
+
const makePort = preparePort(zone, powers);
|
|
588
|
+
const detached = zone.detached();
|
|
589
|
+
|
|
590
|
+
const makeBinderKit = zone.exoClassKit(
|
|
591
|
+
'binder',
|
|
592
|
+
{
|
|
593
|
+
protocolImpl: Shape.ProtocolImplI,
|
|
594
|
+
binder: M.interface('Binder', {
|
|
595
|
+
bind: M.callWhen(Shape.Endpoint).returns(Shape.Port),
|
|
596
|
+
}),
|
|
597
|
+
},
|
|
598
|
+
/**
|
|
599
|
+
* @param {object} opts
|
|
600
|
+
* @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
|
|
601
|
+
* @param {MapStore<string, Port>} opts.boundPorts
|
|
602
|
+
* @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
|
|
603
|
+
* @param {ProtocolHandler} opts.protocolHandler
|
|
604
|
+
*/
|
|
605
|
+
({ currentConnections, boundPorts, listening, protocolHandler }) => {
|
|
606
|
+
/** @type {SetStore<Connection>} */
|
|
607
|
+
const openConnections = detached.setStore('openConnections');
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
currentConnections,
|
|
611
|
+
boundPorts,
|
|
612
|
+
listening,
|
|
613
|
+
revoked: RevokeState.NOT_REVOKED,
|
|
614
|
+
openConnections,
|
|
615
|
+
protocolHandler,
|
|
616
|
+
/** @type {Endpoint | undefined} */
|
|
617
|
+
localAddr: undefined,
|
|
618
|
+
};
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
protocolImpl: {
|
|
622
|
+
/**
|
|
623
|
+
* @param {Endpoint} listenAddr
|
|
624
|
+
* @param {Endpoint} remoteAddr
|
|
625
|
+
*/
|
|
626
|
+
async inbound(listenAddr, remoteAddr) {
|
|
627
|
+
const { listening, protocolHandler, currentConnections } = this.state;
|
|
628
|
+
|
|
629
|
+
let lastFailure = Error(`No listeners for ${listenAddr}`);
|
|
630
|
+
for await (const listenPrefix of getPrefixes(listenAddr)) {
|
|
631
|
+
if (!listening.has(listenPrefix)) {
|
|
632
|
+
continue;
|
|
379
633
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
634
|
+
const [port, _] = listening.get(listenPrefix);
|
|
635
|
+
let localAddr;
|
|
636
|
+
|
|
637
|
+
await (async () => {
|
|
638
|
+
// See if our protocol is willing to receive this connection.
|
|
639
|
+
const localInstance = await when(
|
|
640
|
+
E(protocolHandler).onInstantiate(
|
|
641
|
+
port,
|
|
642
|
+
listenPrefix,
|
|
643
|
+
remoteAddr,
|
|
644
|
+
protocolHandler,
|
|
645
|
+
),
|
|
646
|
+
).catch(rethrowUnlessMissing);
|
|
647
|
+
localAddr = localInstance
|
|
648
|
+
? `${listenAddr}/${localInstance}`
|
|
649
|
+
: listenAddr;
|
|
650
|
+
})().catch(e => {
|
|
651
|
+
lastFailure = e;
|
|
652
|
+
});
|
|
653
|
+
if (!localAddr) {
|
|
654
|
+
continue;
|
|
393
655
|
}
|
|
394
|
-
|
|
395
|
-
current.
|
|
656
|
+
// We have a legitimate inbound attempt.
|
|
657
|
+
const current = currentConnections.get(port);
|
|
658
|
+
const inboundAttempt = makeInboundAttempt({
|
|
659
|
+
localAddr,
|
|
660
|
+
remoteAddr,
|
|
661
|
+
currentConnections,
|
|
662
|
+
listenPrefix,
|
|
663
|
+
listening,
|
|
664
|
+
});
|
|
396
665
|
|
|
397
|
-
|
|
666
|
+
current.add(inboundAttempt);
|
|
667
|
+
return inboundAttempt;
|
|
668
|
+
}
|
|
669
|
+
throw lastFailure;
|
|
670
|
+
},
|
|
671
|
+
/**
|
|
672
|
+
* @param {Port} port
|
|
673
|
+
* @param {Endpoint} remoteAddr
|
|
674
|
+
* @param {ConnectionHandler} lchandler
|
|
675
|
+
*/
|
|
676
|
+
async outbound(port, remoteAddr, lchandler) {
|
|
677
|
+
const { protocolHandler, currentConnections } = this.state;
|
|
678
|
+
|
|
679
|
+
const localAddr = await E(port).getLocalAddress();
|
|
680
|
+
|
|
681
|
+
// Allocate a local address.
|
|
682
|
+
const initialLocalInstance = await when(
|
|
683
|
+
E(protocolHandler).onInstantiate(
|
|
398
684
|
port,
|
|
399
685
|
localAddr,
|
|
400
686
|
remoteAddr,
|
|
401
|
-
|
|
687
|
+
protocolHandler,
|
|
688
|
+
),
|
|
689
|
+
).catch(rethrowUnlessMissing);
|
|
690
|
+
const initialLocalAddr = initialLocalInstance
|
|
691
|
+
? `${localAddr}/${initialLocalInstance}`
|
|
692
|
+
: localAddr;
|
|
693
|
+
|
|
694
|
+
let lastFailure;
|
|
695
|
+
let accepted;
|
|
696
|
+
await (async () => {
|
|
697
|
+
// Attempt the loopback connection.
|
|
698
|
+
const attempt = await when(
|
|
699
|
+
this.facets.protocolImpl.inbound(remoteAddr, initialLocalAddr),
|
|
402
700
|
);
|
|
701
|
+
accepted = await when(attempt.accept({ handler: lchandler }));
|
|
702
|
+
})().catch(e => {
|
|
703
|
+
lastFailure = e;
|
|
704
|
+
});
|
|
705
|
+
if (accepted) {
|
|
706
|
+
return accepted;
|
|
707
|
+
}
|
|
403
708
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
: localAddr;
|
|
430
|
-
|
|
431
|
-
let lastFailure;
|
|
432
|
-
let accepted;
|
|
433
|
-
await (async () => {
|
|
434
|
-
// Attempt the loopback connection.
|
|
435
|
-
const attempt = await protocolImpl.inbound(
|
|
436
|
-
remoteAddr,
|
|
437
|
-
initialLocalAddr,
|
|
438
|
-
);
|
|
439
|
-
accepted = await attempt.accept({ handler: lchandler });
|
|
440
|
-
})().catch(e => {
|
|
441
|
-
lastFailure = e;
|
|
442
|
-
});
|
|
443
|
-
if (accepted) {
|
|
444
|
-
return accepted;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const {
|
|
448
|
-
remoteAddress = remoteAddr,
|
|
449
|
-
handler: rchandler,
|
|
450
|
-
localAddress = localAddr,
|
|
451
|
-
} =
|
|
452
|
-
/** @type {Partial<AttemptDescription>} */
|
|
453
|
-
(
|
|
454
|
-
await E(protocolHandler).onConnect(
|
|
455
|
-
port,
|
|
456
|
-
initialLocalAddr,
|
|
457
|
-
remoteAddr,
|
|
709
|
+
const {
|
|
710
|
+
remoteAddress = remoteAddr,
|
|
711
|
+
handler: rchandler,
|
|
712
|
+
localAddress = localAddr,
|
|
713
|
+
} =
|
|
714
|
+
/** @type {Partial<AttemptDescription>} */
|
|
715
|
+
(
|
|
716
|
+
await when(
|
|
717
|
+
E(protocolHandler).onConnect(
|
|
718
|
+
port,
|
|
719
|
+
initialLocalAddr,
|
|
720
|
+
remoteAddr,
|
|
721
|
+
lchandler,
|
|
722
|
+
protocolHandler,
|
|
723
|
+
),
|
|
724
|
+
)
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
if (!rchandler) {
|
|
728
|
+
throw lastFailure;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const current = currentConnections.get(port);
|
|
732
|
+
return crossoverConnection(
|
|
733
|
+
zone,
|
|
458
734
|
lchandler,
|
|
735
|
+
localAddress,
|
|
736
|
+
rchandler,
|
|
737
|
+
remoteAddress,
|
|
738
|
+
makeConnection,
|
|
739
|
+
current,
|
|
740
|
+
)[0];
|
|
741
|
+
},
|
|
742
|
+
async bind(localAddr) {
|
|
743
|
+
return this.facets.binder.bind(localAddr);
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
binder: {
|
|
747
|
+
/** @param {string} localAddr */
|
|
748
|
+
async bind(localAddr) {
|
|
749
|
+
const {
|
|
459
750
|
protocolHandler,
|
|
460
|
-
|
|
461
|
-
|
|
751
|
+
boundPorts,
|
|
752
|
+
listening,
|
|
753
|
+
openConnections,
|
|
754
|
+
currentConnections,
|
|
755
|
+
} = this.state;
|
|
756
|
+
|
|
757
|
+
// Check if we are underspecified (ends in slash)
|
|
758
|
+
const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
|
|
759
|
+
for await (const _ of whileTrue(() => underspecified)) {
|
|
760
|
+
const portID = await when(
|
|
761
|
+
E(protocolHandler).generatePortID(localAddr, protocolHandler),
|
|
762
|
+
);
|
|
763
|
+
const newAddr = `${localAddr}${portID}`;
|
|
764
|
+
if (!boundPorts.has(newAddr)) {
|
|
765
|
+
localAddr = newAddr;
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
462
769
|
|
|
463
|
-
|
|
464
|
-
throw lastFailure;
|
|
465
|
-
}
|
|
770
|
+
this.state.localAddr = localAddr;
|
|
466
771
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
772
|
+
if (boundPorts.has(localAddr)) {
|
|
773
|
+
return boundPorts.get(localAddr);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const port = makePort({
|
|
777
|
+
localAddr,
|
|
778
|
+
listening,
|
|
779
|
+
openConnections,
|
|
780
|
+
currentConnections,
|
|
781
|
+
boundPorts,
|
|
782
|
+
protocolHandler,
|
|
783
|
+
protocolImpl: this.facets.protocolImpl,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
await when(
|
|
787
|
+
E(protocolHandler).onBind(port, localAddr, protocolHandler),
|
|
788
|
+
);
|
|
789
|
+
boundPorts.init(localAddr, harden(port));
|
|
790
|
+
currentConnections.init(port, detached.setStore('connections'));
|
|
791
|
+
return port;
|
|
792
|
+
},
|
|
793
|
+
},
|
|
475
794
|
},
|
|
476
|
-
|
|
795
|
+
);
|
|
477
796
|
|
|
478
|
-
|
|
479
|
-
|
|
797
|
+
return makeBinderKit;
|
|
798
|
+
};
|
|
480
799
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
800
|
+
/**
|
|
801
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
802
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
803
|
+
*/
|
|
804
|
+
export const prepareNetworkProtocol = (zone, powers) => {
|
|
805
|
+
const makeBinderKit = prepareBinder(zone, powers);
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* @param {ProtocolHandler} protocolHandler
|
|
809
|
+
* @returns {Protocol}
|
|
810
|
+
*/
|
|
811
|
+
const makeNetworkProtocol = protocolHandler => {
|
|
812
|
+
const detached = zone.detached();
|
|
813
|
+
|
|
814
|
+
/** @type {MapStore<Port, SetStore<Closable>>} */
|
|
815
|
+
const currentConnections = detached.mapStore('portToCurrentConnections');
|
|
816
|
+
|
|
817
|
+
/** @type {MapStore<string, Port>} */
|
|
818
|
+
const boundPorts = detached.mapStore('addrToPort');
|
|
819
|
+
|
|
820
|
+
/** @type {MapStore<Endpoint, [Port, ListenHandler]>} */
|
|
821
|
+
const listening = detached.mapStore('listening');
|
|
822
|
+
|
|
823
|
+
const { binder, protocolImpl } = makeBinderKit({
|
|
824
|
+
currentConnections,
|
|
825
|
+
boundPorts,
|
|
826
|
+
listening,
|
|
827
|
+
protocolHandler,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// Wire up the local protocol to the handler.
|
|
831
|
+
void E(protocolHandler).onCreate(protocolImpl, protocolHandler);
|
|
832
|
+
return binder;
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
return makeNetworkProtocol;
|
|
836
|
+
};
|
|
484
837
|
|
|
485
838
|
/**
|
|
486
839
|
* Create a ConnectionHandler that just echoes its packets.
|
|
487
840
|
*
|
|
488
|
-
* @
|
|
841
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
489
842
|
*/
|
|
490
|
-
export
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
843
|
+
export const prepareEchoConnectionKit = zone => {
|
|
844
|
+
const makeEchoConnectionKit = zone.exoClassKit(
|
|
845
|
+
'EchoConnectionKit',
|
|
846
|
+
{
|
|
847
|
+
handler: M.interface('ConnectionHandler', {
|
|
848
|
+
onReceive: M.callWhen(
|
|
849
|
+
Shape2.Connection,
|
|
850
|
+
Shape2.Bytes,
|
|
851
|
+
Shape2.ConnectionHandler,
|
|
852
|
+
)
|
|
853
|
+
.optional(Shape2.Opts)
|
|
854
|
+
.returns(Shape2.Data),
|
|
855
|
+
onClose: M.callWhen(Shape2.Connection)
|
|
856
|
+
.optional(M.any(), Shape2.ConnectionHandler)
|
|
857
|
+
.returns(M.undefined()),
|
|
858
|
+
}),
|
|
859
|
+
listener: M.interface('Listener', {
|
|
860
|
+
onListen: M.callWhen(Shape.Port, Shape.ListenHandler).returns(
|
|
861
|
+
Shape.Vow$(M.undefined()),
|
|
862
|
+
),
|
|
863
|
+
onAccept: M.callWhen(
|
|
864
|
+
Shape.Port,
|
|
865
|
+
Shape.Endpoint,
|
|
866
|
+
Shape.Endpoint,
|
|
867
|
+
Shape.ListenHandler,
|
|
868
|
+
).returns(Shape.Vow$(Shape.ConnectionHandler)),
|
|
869
|
+
}),
|
|
499
870
|
},
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
871
|
+
() => {
|
|
872
|
+
/** @type {string | undefined} */
|
|
873
|
+
let closed;
|
|
874
|
+
return {
|
|
875
|
+
closed,
|
|
876
|
+
};
|
|
505
877
|
},
|
|
506
|
-
|
|
507
|
-
|
|
878
|
+
{
|
|
879
|
+
handler: {
|
|
880
|
+
/**
|
|
881
|
+
* @param {Connection} _connection
|
|
882
|
+
* @param {Bytes} bytes
|
|
883
|
+
* @param {ConnectionHandler} _connectionHandler
|
|
884
|
+
*/
|
|
885
|
+
async onReceive(_connection, bytes, _connectionHandler) {
|
|
886
|
+
const { closed } = this.state;
|
|
887
|
+
|
|
888
|
+
if (closed) {
|
|
889
|
+
throw closed;
|
|
890
|
+
}
|
|
891
|
+
return bytes;
|
|
892
|
+
},
|
|
893
|
+
/**
|
|
894
|
+
* @param {Connection} _connection
|
|
895
|
+
* @param {CloseReason} [_reason]
|
|
896
|
+
* @param {ConnectionHandler} [_connectionHandler]
|
|
897
|
+
*/
|
|
898
|
+
async onClose(_connection, _reason, _connectionHandler) {
|
|
899
|
+
const { closed } = this.state;
|
|
900
|
+
|
|
901
|
+
if (closed) {
|
|
902
|
+
throw Error(closed);
|
|
903
|
+
}
|
|
508
904
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
905
|
+
this.state.closed = 'Connection closed';
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
listener: {
|
|
909
|
+
async onAccept(_port, _localAddr, _remoteAddr, _listenHandler) {
|
|
910
|
+
return harden(this.facets.handler);
|
|
911
|
+
},
|
|
912
|
+
async onListen(port, _listenHandler) {
|
|
913
|
+
console.debug(`listening on echo port: ${port}`);
|
|
914
|
+
},
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
return makeEchoConnectionKit;
|
|
920
|
+
};
|
|
516
921
|
|
|
517
922
|
/**
|
|
518
923
|
* Create a protocol handler that just connects to itself.
|
|
519
924
|
*
|
|
520
|
-
* @param {
|
|
521
|
-
* @
|
|
925
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
926
|
+
* @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
|
|
522
927
|
*/
|
|
523
|
-
export function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
// TODO
|
|
535
|
-
},
|
|
536
|
-
async generatePortID(_protocolHandler) {
|
|
537
|
-
return makePortID();
|
|
538
|
-
},
|
|
539
|
-
async onBind(_port, _localAddr, _protocolHandler) {
|
|
540
|
-
// TODO: Maybe handle a bind?
|
|
541
|
-
},
|
|
542
|
-
async onConnect(_port, localAddr, remoteAddr, _chandler, protocolHandler) {
|
|
543
|
-
const [lport, lhandler] = listeners.get(remoteAddr);
|
|
544
|
-
const rchandler =
|
|
545
|
-
/** @type {ConnectionHandler} */
|
|
546
|
-
(await E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler));
|
|
547
|
-
// console.log(`rchandler is`, rchandler);
|
|
548
|
-
const remoteInstance = await E(protocolHandler)
|
|
549
|
-
.onInstantiate(lport, remoteAddr, localAddr, protocolHandler)
|
|
550
|
-
.catch(rethrowUnlessMissing);
|
|
928
|
+
export function prepareLoopbackProtocolHandler(zone, { when }) {
|
|
929
|
+
const detached = zone.detached();
|
|
930
|
+
|
|
931
|
+
const makeLoopbackProtocolHandler = zone.exoClass(
|
|
932
|
+
'ProtocolHandler',
|
|
933
|
+
Shape.ProtocolHandlerI,
|
|
934
|
+
/** @param {string} [instancePrefix] */
|
|
935
|
+
(instancePrefix = 'nonce/') => {
|
|
936
|
+
/** @type {MapStore<string, [Port, ListenHandler]>} */
|
|
937
|
+
const listeners = detached.mapStore('localAddr');
|
|
938
|
+
|
|
551
939
|
return {
|
|
552
|
-
|
|
553
|
-
|
|
940
|
+
listeners,
|
|
941
|
+
portNonce: 0n,
|
|
942
|
+
instancePrefix,
|
|
943
|
+
instanceNonce: 0n,
|
|
554
944
|
};
|
|
555
945
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
946
|
+
{
|
|
947
|
+
async onCreate(_impl, _protocolHandler) {
|
|
948
|
+
// noop
|
|
949
|
+
},
|
|
950
|
+
async generatePortID(_localAddr, _protocolHandler) {
|
|
951
|
+
this.state.portNonce += 1n;
|
|
952
|
+
return `port${this.state.portNonce}`;
|
|
953
|
+
},
|
|
954
|
+
async onBind(_port, _localAddr, _protocolHandler) {
|
|
955
|
+
// noop, for now; Maybe handle a bind?
|
|
956
|
+
},
|
|
957
|
+
async onConnect(
|
|
958
|
+
_port,
|
|
959
|
+
localAddr,
|
|
960
|
+
remoteAddr,
|
|
961
|
+
_chandler,
|
|
962
|
+
protocolHandler,
|
|
963
|
+
) {
|
|
964
|
+
const { listeners } = this.state;
|
|
965
|
+
const [lport, lhandler] = listeners.get(remoteAddr);
|
|
966
|
+
const rchandler = await when(
|
|
967
|
+
E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler),
|
|
968
|
+
);
|
|
969
|
+
// console.log(`rchandler is`, rchandler);
|
|
970
|
+
const remoteInstance = await when(
|
|
971
|
+
E(protocolHandler).onInstantiate(
|
|
972
|
+
lport,
|
|
973
|
+
remoteAddr,
|
|
974
|
+
localAddr,
|
|
975
|
+
protocolHandler,
|
|
976
|
+
),
|
|
977
|
+
).catch(rethrowUnlessMissing);
|
|
978
|
+
return {
|
|
979
|
+
remoteInstance,
|
|
980
|
+
handler: rchandler,
|
|
981
|
+
};
|
|
982
|
+
},
|
|
983
|
+
async onInstantiate(_port, _localAddr, _remote, _protocol) {
|
|
984
|
+
const { instancePrefix } = this.state;
|
|
985
|
+
this.state.instanceNonce += 1n;
|
|
986
|
+
return `${instancePrefix}${this.state.instanceNonce}`;
|
|
987
|
+
},
|
|
988
|
+
async onListen(port, localAddr, listenHandler, _protocolHandler) {
|
|
989
|
+
const { listeners } = this.state;
|
|
990
|
+
|
|
991
|
+
// This implementation has a simple last-one-wins replacement policy.
|
|
992
|
+
// Other handlers might use different policies.
|
|
993
|
+
if (listeners.has(localAddr)) {
|
|
994
|
+
const lhandler = listeners.get(localAddr)[1];
|
|
995
|
+
if (lhandler !== listenHandler) {
|
|
996
|
+
listeners.set(localAddr, [port, listenHandler]);
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
listeners.init(localAddr, harden([port, listenHandler]));
|
|
564
1000
|
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
1001
|
+
},
|
|
1002
|
+
/**
|
|
1003
|
+
* @param {Port} port
|
|
1004
|
+
* @param {Endpoint} localAddr
|
|
1005
|
+
* @param {ListenHandler} listenHandler
|
|
1006
|
+
* @param {*} _protocolHandler
|
|
1007
|
+
*/
|
|
1008
|
+
async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
|
|
1009
|
+
const { listeners } = this.state;
|
|
1010
|
+
const [lport, lhandler] = listeners.get(localAddr);
|
|
1011
|
+
lport === port || Fail`Port does not match listener on ${localAddr}`;
|
|
1012
|
+
lhandler === listenHandler ||
|
|
1013
|
+
Fail`Listen handler does not match listener on ${localAddr}`;
|
|
1014
|
+
listeners.delete(localAddr);
|
|
1015
|
+
},
|
|
1016
|
+
async onRevoke(_port, _localAddr, _protocolHandler) {
|
|
1017
|
+
// This is an opportunity to clean up resources.
|
|
1018
|
+
},
|
|
578
1019
|
},
|
|
579
|
-
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
return makeLoopbackProtocolHandler;
|
|
580
1023
|
}
|