@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/src/network.js CHANGED
@@ -1,6 +1,7 @@
1
- import { makeScalarMapStore, makeLegacyMap } from '@agoric/store';
2
- import { Far, E } from '@endo/far';
3
- import { makePromiseKit } from '@endo/promise-kit';
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
- * Create a handled Connection.
155
+ * Get the list of prefixes from longest to shortest.
31
156
  *
32
- * @param {ConnectionHandler} handler
33
- * @param {Endpoint} localAddr
34
- * @param {Endpoint} remoteAddr
35
- * @param {Set<Closable>} [current]
36
- * @returns {Connection}
157
+ * @param {string} addr
37
158
  */
38
- export const makeConnection = (
39
- handler,
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
- current.add(connection);
98
- E(handler)
99
- .onOpen(connection, localAddr, remoteAddr, handler)
100
- .catch(rethrowUnlessMissing);
101
- return connection;
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
- * @param {ConnectionHandler} handler0
106
- * @param {Endpoint} addr0
107
- * @param {ConnectionHandler} handler1
108
- * @param {Endpoint} addr1
109
- * @param {WeakSet<Connection>} [current]
110
- * @returns {[Connection, Connection]}
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
- function makeHalfConnection(l, r) {
127
- let closed;
128
- conns[l] = Far('Connection', {
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
- const ack = await E(handlers[r])
140
- .onReceive(conns[r], toBytes(packetBytes), handlers[r])
141
- .catch(rethrowUnlessMissing);
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 = Error('Connection closed');
149
- current.delete(conns[l]);
150
- await E(handlers[l])
151
- .onClose(conns[l], undefined, handlers[l])
152
- .catch(rethrowUnlessMissing);
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(0, 1);
158
- makeHalfConnection(1, 0);
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
- function openHalfConnection(l, r) {
165
- current.add(conns[l]);
290
+ const openHalfConnection = (l, r) => {
291
+ current.add(conns.get(l));
166
292
  E(handlers[l])
167
- .onOpen(conns[l], addrs[l], addrs[r], handlers[l])
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
- const [conn0, conn1] = conns;
175
- return [conn0, conn1];
176
- }
303
+ return [conns.get(0), conns.get(1)];
304
+ };
177
305
 
178
306
  /**
179
- * Get the list of prefixes from longest to shortest.
180
- *
181
- * @param {string} addr
307
+ * @param {import('@agoric/zone').Zone} zone
308
+ * @param {(opts: ConnectionOpts) => Connection} makeConnection
309
+ * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
182
310
  */
183
- export function getPrefixes(addr) {
184
- const parts = addr.split(ENDPOINT_SEPARATOR);
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
- /** @type {string[]} */
187
- const ret = [];
188
- for (let i = parts.length; i > 0; i -= 1) {
189
- // Try most specific match.
190
- const prefix = parts.slice(0, i).join(ENDPOINT_SEPARATOR);
191
- ret.push(prefix);
192
- }
193
- return ret;
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
- * Create a protocol that has a handler.
198
- *
199
- * @param {ProtocolHandler} protocolHandler
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
- * Currently must be a single listenHandler. TODO: Do something sensible with
209
- * multiple handlers?
210
- *
211
- * @type {MapStore<Endpoint, [Port, ListenHandler]>}
212
- */
213
- const listening = makeScalarMapStore('localAddr');
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
- /** @type {MapStore<string, Port>} */
216
- const boundPorts = makeScalarMapStore('localAddr');
393
+ current.delete(this.self);
217
394
 
218
- /** @param {Endpoint} localAddr */
219
- const bind = async localAddr => {
220
- // Check if we are underspecified (ends in slash)
221
- const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
222
- for await (const _ of whileTrue(() => underspecified)) {
223
- const portID = await E(protocolHandler).generatePortID(
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
- const newAddr = `${localAddr}${portID}`;
228
- if (!boundPorts.has(newAddr)) {
229
- localAddr = newAddr;
230
- break;
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
- !revoked || Fail`Port ${localAddr} is revoked`;
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, [port, listenHandler]);
481
+ listening.set(localAddr, [this.self, listenHandler]);
265
482
  E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
266
483
  } else {
267
- listening.init(localAddr, [port, listenHandler]);
484
+ listening.init(localAddr, harden([this.self, listenHandler]));
268
485
  }
269
486
 
270
- // TODO: Check that the listener defines onAccept.
487
+ // ASSUME: that the listener defines onAccept.
271
488
 
272
- await E(protocolHandler).onListen(
273
- port,
274
- localAddr,
275
- listenHandler,
276
- protocolHandler,
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 E(protocolHandler).onListenRemove(
288
- port,
289
- localAddr,
290
- listenHandler,
291
- protocolHandler,
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
- async connect(remotePort, connectionHandler = {}) {
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 protocolImpl.outbound(port, dst, connectionHandler);
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 E(protocolHandler).onRevoke(port, localAddr, protocolHandler);
315
- revoked = RevokeState.REVOKED;
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 ps = [...currentConnections.get(port)].map(conn =>
319
- E(conn)
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(port.removeListener(listener));
563
+ ps.push(this.self.removeListener(listener));
326
564
  }
327
565
  await Promise.all(ps);
328
- currentConnections.delete(port);
566
+ currentConnections.delete(this.self);
329
567
  boundPorts.delete(localAddr);
330
- return `Port ${localAddr} revoked`;
331
568
  },
332
- });
569
+ },
570
+ );
333
571
 
334
- await E(protocolHandler).onBind(port, localAddr, protocolHandler);
335
- boundPorts.init(localAddr, port);
336
- currentConnections.init(port, new Set());
337
- return port;
338
- };
572
+ return makePort;
573
+ };
339
574
 
340
- /** @type {ProtocolImpl} */
341
- const protocolImpl = Far('ProtocolImpl', {
342
- bind,
343
- async inbound(listenAddr, remoteAddr) {
344
- let lastFailure = Error(`No listeners for ${listenAddr}`);
345
- for await (const listenPrefix of getPrefixes(listenAddr)) {
346
- if (!listening.has(listenPrefix)) {
347
- continue;
348
- }
349
- const [port, listener] = listening.get(listenPrefix);
350
- let localAddr;
351
- await (async () => {
352
- // See if our protocol is willing to receive this connection.
353
- const localInstance = await E(protocolHandler)
354
- .onInstantiate(port, listenPrefix, remoteAddr, protocolHandler)
355
- .catch(rethrowUnlessMissing);
356
- localAddr = localInstance
357
- ? `${listenAddr}/${localInstance}`
358
- : listenAddr;
359
- })().catch(e => {
360
- lastFailure = e;
361
- });
362
- if (!localAddr) {
363
- continue;
364
- }
365
- // We have a legitimate inbound attempt.
366
- let consummated;
367
- const current = currentConnections.get(port);
368
- const inboundAttempt = Far('InboundAttempt', {
369
- getLocalAddress() {
370
- // Return address metadata.
371
- return localAddr;
372
- },
373
- getRemoteAddress() {
374
- return remoteAddr;
375
- },
376
- async close() {
377
- if (consummated) {
378
- throw consummated;
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
- consummated = Error(`Already closed`);
381
- current.delete(inboundAttempt);
382
- await E(listener)
383
- .onReject(port, localAddr, remoteAddr, listener)
384
- .catch(rethrowUnlessMissing);
385
- },
386
- async accept({
387
- localAddress = localAddr,
388
- remoteAddress = remoteAddr,
389
- handler: rchandler,
390
- }) {
391
- if (consummated) {
392
- throw consummated;
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
- consummated = Error(`Already accepted`);
395
- current.delete(inboundAttempt);
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
- const lchandler = await E(listener).onAccept(
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
- listener,
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
- return crossoverConnection(
405
- lchandler,
406
- localAddress,
407
- rchandler,
408
- remoteAddress,
409
- current,
410
- )[1];
411
- },
412
- });
413
- current.add(inboundAttempt);
414
- return inboundAttempt;
415
- }
416
- throw lastFailure;
417
- },
418
- async outbound(port, remoteAddr, lchandler) {
419
- const localAddr =
420
- /** @type {string} */
421
- (await E(port).getLocalAddress());
422
-
423
- // Allocate a local address.
424
- const initialLocalInstance = await E(protocolHandler)
425
- .onInstantiate(port, localAddr, remoteAddr, protocolHandler)
426
- .catch(rethrowUnlessMissing);
427
- const initialLocalAddr = initialLocalInstance
428
- ? `${localAddr}/${initialLocalInstance}`
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
- if (!rchandler) {
464
- throw lastFailure;
465
- }
770
+ this.state.localAddr = localAddr;
466
771
 
467
- const current = currentConnections.get(port);
468
- return crossoverConnection(
469
- lchandler,
470
- localAddress,
471
- rchandler,
472
- remoteAddress,
473
- current,
474
- )[0];
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
- // Wire up the local protocol to the handler.
479
- void E(protocolHandler).onCreate(protocolImpl, protocolHandler);
797
+ return makeBinderKit;
798
+ };
480
799
 
481
- // Return the user-facing protocol.
482
- return Far('binder', { bind });
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
- * @returns {ConnectionHandler}
841
+ * @param {import('@agoric/base-zone').Zone} zone
489
842
  */
490
- export function makeEchoConnectionHandler() {
491
- let closed;
492
- /** @type {Connection} */
493
- return Far('ConnectionHandler', {
494
- async onReceive(_connection, bytes, _connectionHandler) {
495
- if (closed) {
496
- throw closed;
497
- }
498
- return bytes;
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
- async onClose(_connection, _reason, _connectionHandler) {
501
- if (closed) {
502
- throw closed;
503
- }
504
- closed = Error('Connection closed');
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
- export function makeNonceMaker(prefix = '', suffix = '') {
510
- let nonce = 0;
511
- return async () => {
512
- nonce += 1;
513
- return `${prefix}${nonce}${suffix}`;
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 {ProtocolHandler['onInstantiate']} [onInstantiate]
521
- * @returns {ProtocolHandler} The localhost handler
925
+ * @param {import('@agoric/base-zone').Zone} zone
926
+ * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
522
927
  */
523
- export function makeLoopbackProtocolHandler(
524
- onInstantiate = makeNonceMaker('nonce/'),
525
- ) {
526
- /** @type {MapStore<string, [Port, ListenHandler]>} */
527
- const listeners = makeScalarMapStore('localAddr');
528
-
529
- const makePortID = makeNonceMaker('port');
530
-
531
- return Far('ProtocolHandler', {
532
- // eslint-disable-next-line no-empty-function
533
- async onCreate(_impl, _protocolHandler) {
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
- remoteInstance,
553
- handler: rchandler,
940
+ listeners,
941
+ portNonce: 0n,
942
+ instancePrefix,
943
+ instanceNonce: 0n,
554
944
  };
555
945
  },
556
- onInstantiate,
557
- async onListen(port, localAddr, listenHandler, _protocolHandler) {
558
- // TODO: Implement other listener replacement policies.
559
- if (listeners.has(localAddr)) {
560
- const lhandler = listeners.get(localAddr)[1];
561
- if (lhandler !== listenHandler) {
562
- // Last-one-wins.
563
- listeners.set(localAddr, [port, listenHandler]);
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
- } else {
566
- listeners.init(localAddr, [port, listenHandler]);
567
- }
568
- },
569
- async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
570
- const [lport, lhandler] = listeners.get(localAddr);
571
- lport === port || Fail`Port does not match listener on ${localAddr}`;
572
- lhandler === listenHandler ||
573
- Fail`Listen handler does not match listener on ${localAddr}`;
574
- listeners.delete(localAddr);
575
- },
576
- async onRevoke(_port, _localAddr, _protocolHandler) {
577
- // TODO: maybe clean up?
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
  }