@agoric/network 0.1.1-orchestration-dev-096c4e8.0 → 0.1.1-other-dev-fbe72e7.0.fbe72e7

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,13 +1,23 @@
1
1
  // @ts-check
2
2
 
3
+ /// <reference types="@agoric/store/exported.js" />
4
+
5
+ import { Fail } from '@endo/errors';
3
6
  import { E } from '@endo/far';
4
7
  import { M } from '@endo/patterns';
5
- import { Fail } from '@agoric/assert';
6
- import { whileTrue } from '@agoric/internal';
7
8
  import { toBytes } from './bytes.js';
9
+ import { Shape } from './shapes.js';
8
10
 
9
- import '@agoric/store/exported.js';
10
11
  /// <reference path="./types.js" />
12
+ /**
13
+ * @import {AttemptDescription, Bytes, CloseReason, Closable, Connection, ConnectionHandler, Endpoint, ListenHandler, Port, Protocol, ProtocolHandler, ProtocolImpl, SendOptions} from './types.js';
14
+ * @import {PromiseVow, Remote, VowTools} from '@agoric/vow';
15
+ */
16
+
17
+ /** @typedef {VowTools & { finalizer: Finalizer }} Powers */
18
+
19
+ const sink = () => {};
20
+ harden(sink);
11
21
 
12
22
  /**
13
23
  * Compatibility note: this must match what our peers use, so don't change it
@@ -15,128 +25,8 @@ import '@agoric/store/exported.js';
15
25
  */
16
26
  export const ENDPOINT_SEPARATOR = '/';
17
27
 
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
- });
28
+ // Mark the finalizer close reason.
29
+ export const CLOSE_REASON_FINALIZER = 'closed-by-finalizer';
140
30
 
141
31
  /** @param {unknown} err */
142
32
  export const rethrowUnlessMissing = err => {
@@ -144,7 +34,7 @@ export const rethrowUnlessMissing = err => {
144
34
  // exists.
145
35
  if (
146
36
  !(err instanceof TypeError) ||
147
- !err.message.match(/target has no method|is not a function$/)
37
+ !String(err.message).match(/target has no method|is not a function$/)
148
38
  ) {
149
39
  throw err;
150
40
  }
@@ -169,10 +59,24 @@ export function getPrefixes(addr) {
169
59
  return ret;
170
60
  }
171
61
 
62
+ /**
63
+ * Validate IBC port name
64
+ * @param {string} specifiedName
65
+ */
66
+ function throwIfInvalidPortName(specifiedName) {
67
+ // Contains between 2 and 128 characters
68
+ // Can contain alphanumeric characters
69
+ // Valid symbols: ., ,, _, +, -, #, [, ], <, >
70
+ const portNameRegex = new RegExp('^[a-zA-Z0-9.,_+\\-#<>\\[\\]]{2,128}$');
71
+ if (!portNameRegex.test(specifiedName)) {
72
+ throw Error(`Invalid IBC port name: ${specifiedName}`);
73
+ }
74
+ }
75
+
172
76
  /**
173
77
  * @typedef {object} ConnectionOpts
174
78
  * @property {Endpoint[]} addrs
175
- * @property {ConnectionHandler[]} handlers
79
+ * @property {Remote<Required<ConnectionHandler>>[]} handlers
176
80
  * @property {MapStore<number, Connection>} conns
177
81
  * @property {WeakSetStore<Closable>} current
178
82
  * @property {0|1} l
@@ -181,17 +85,14 @@ export function getPrefixes(addr) {
181
85
 
182
86
  /**
183
87
  * @param {import('@agoric/base-zone').Zone} zone
184
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
88
+ * @param {Powers} powers
185
89
  */
186
- const prepareHalfConnection = (zone, { when }) => {
187
- const makeHalfConnection = zone.exoClass(
90
+ const prepareHalfConnection = (zone, { watch, allVows, finalizer }) => {
91
+ const makeHalfConnectionKit = zone.exoClassKit(
188
92
  'Connection',
189
93
  Shape.ConnectionI,
190
94
  /** @param {ConnectionOpts} opts */
191
95
  ({ addrs, handlers, conns, current, l, r }) => {
192
- /** @type {string | undefined} */
193
- let closed;
194
-
195
96
  return {
196
97
  addrs,
197
98
  handlers,
@@ -199,62 +100,104 @@ const prepareHalfConnection = (zone, { when }) => {
199
100
  current,
200
101
  l,
201
102
  r,
202
- closed,
103
+ /** @type {string | undefined} */
104
+ closed: undefined,
203
105
  };
204
106
  },
205
107
  {
206
- getLocalAddress() {
207
- const { addrs, l } = this.state;
208
- return addrs[l];
209
- },
210
- getRemoteAddress() {
211
- const { addrs, r } = this.state;
212
- return addrs[r];
213
- },
214
- /** @param {Data} packetBytes */
215
- async send(packetBytes) {
216
- const { closed, handlers, r, conns } = this.state;
217
- if (closed) {
218
- throw closed;
219
- }
108
+ connection: {
109
+ getLocalAddress() {
110
+ const { addrs, l } = this.state;
111
+ return addrs[l];
112
+ },
113
+ getRemoteAddress() {
114
+ const { addrs, r } = this.state;
115
+ return addrs[r];
116
+ },
117
+ /**
118
+ * @param {Bytes} packetBytes
119
+ * @param {SendOptions} [opts]
120
+ */
121
+ async send(packetBytes, opts) {
122
+ const { closed, handlers, r, conns } = this.state;
123
+ if (closed) {
124
+ throw Error(closed);
125
+ }
220
126
 
221
- const ack = await when(
222
- E(handlers[r])
223
- .onReceive(conns.get(r), toBytes(packetBytes), handlers[r])
224
- .catch(rethrowUnlessMissing),
225
- );
127
+ const innerVow = watch(
128
+ E(handlers[r]).onReceive(
129
+ conns.get(r),
130
+ toBytes(packetBytes),
131
+ handlers[r],
132
+ opts,
133
+ ),
134
+ this.facets.openConnectionAckWatcher,
135
+ );
136
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
137
+ },
138
+ async close() {
139
+ const { closed, current, conns, l, r } = this.state;
140
+ if (closed) {
141
+ throw Error(closed);
142
+ }
143
+ this.state.closed = 'Connection closed';
144
+
145
+ // Tear down both sides.
146
+ const lconn = conns.get(l);
147
+ const rconn = conns.get(r);
148
+ current.delete(lconn);
149
+ current.delete(rconn);
226
150
 
227
- return toBytes(ack || '');
151
+ const innerVow = watch(
152
+ allVows([finalizer.finalize(lconn), finalizer.finalize(rconn)]),
153
+ this.facets.sinkWatcher,
154
+ );
155
+
156
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
157
+ },
228
158
  },
229
- async close() {
230
- const { closed, current, conns, l, handlers } = this.state;
231
- if (closed) {
232
- throw Error(closed);
233
- }
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);
159
+ openConnectionAckWatcher: {
160
+ onFulfilled(ack) {
161
+ return toBytes(ack || '');
162
+ },
163
+ },
164
+ rethrowUnlessMissingWatcher: {
165
+ onRejected(e) {
166
+ rethrowUnlessMissing(e);
167
+ },
168
+ },
169
+ sinkWatcher: {
170
+ onFulfilled(_value) {
171
+ return undefined;
172
+ },
243
173
  },
244
174
  },
245
175
  );
246
176
 
177
+ const makeHalfConnection = ({ addrs, handlers, conns, current, l, r }) => {
178
+ const { connection } = makeHalfConnectionKit({
179
+ addrs,
180
+ handlers,
181
+ conns,
182
+ current,
183
+ l,
184
+ r,
185
+ });
186
+ return harden(connection);
187
+ };
188
+
247
189
  return makeHalfConnection;
248
190
  };
249
191
 
250
192
  /**
251
193
  * @param {import('@agoric/zone').Zone} zone
252
- * @param {ConnectionHandler} handler0
194
+ * @param {Remote<Required<ConnectionHandler>>} handler0
253
195
  * @param {Endpoint} addr0
254
- * @param {ConnectionHandler} handler1
196
+ * @param {Remote<Required<ConnectionHandler>>} handler1
255
197
  * @param {Endpoint} addr1
256
198
  * @param {(opts: ConnectionOpts) => Connection} makeConnection
257
- * @param {WeakSetStore<Closable>} current
199
+ * @param {Finalizer} finalizer
200
+ * @param {WeakSetStore<Closable>} [current]
258
201
  */
259
202
  export const crossoverConnection = (
260
203
  zone,
@@ -263,6 +206,7 @@ export const crossoverConnection = (
263
206
  handler1,
264
207
  addr1,
265
208
  makeConnection,
209
+ finalizer,
266
210
  current = zone.detached().weakSetStore('crossoverCurrentConnections'),
267
211
  ) => {
268
212
  const detached = zone.detached();
@@ -270,7 +214,7 @@ export const crossoverConnection = (
270
214
  /** @type {MapStore<number, Connection>} */
271
215
  const conns = detached.mapStore('addrToConnections');
272
216
 
273
- /** @type {ConnectionHandler[]} */
217
+ /** @type {Remote<Required<ConnectionHandler>>[]} */
274
218
  const handlers = harden([handler0, handler1]);
275
219
  /** @type {Endpoint[]} */
276
220
  const addrs = harden([addr0, addr1]);
@@ -288,9 +232,13 @@ export const crossoverConnection = (
288
232
  * @param {number} r remote side of the connection
289
233
  */
290
234
  const openHalfConnection = (l, r) => {
291
- current.add(conns.get(l));
235
+ const lconn = conns.get(l);
236
+ current.add(lconn);
237
+ if (!finalizer.has(lconn)) {
238
+ finalizer.initConnection(lconn, handlers[l]);
239
+ }
292
240
  E(handlers[l])
293
- .onOpen(conns.get(l), addrs[l], addrs[r], handlers[l])
241
+ .onOpen(lconn, addrs[l], addrs[r], handlers[l])
294
242
  .catch(rethrowUnlessMissing);
295
243
  };
296
244
 
@@ -306,19 +254,19 @@ export const crossoverConnection = (
306
254
  /**
307
255
  * @param {import('@agoric/zone').Zone} zone
308
256
  * @param {(opts: ConnectionOpts) => Connection} makeConnection
309
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
257
+ * @param {Powers} powers
310
258
  */
311
- const prepareInboundAttempt = (zone, makeConnection, { when }) => {
312
- const makeInboundAttempt = zone.exoClass(
259
+ const prepareInboundAttempt = (zone, makeConnection, { watch, finalizer }) => {
260
+ const makeInboundAttemptKit = zone.exoClassKit(
313
261
  'InboundAttempt',
314
262
  Shape.InboundAttemptI,
315
263
  /**
316
264
  * @param {object} opts
317
265
  * @param {string} opts.localAddr
318
266
  * @param {string} opts.remoteAddr
319
- * @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
267
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
320
268
  * @param {string} opts.listenPrefix
321
- * @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
269
+ * @param {MapStore<Endpoint, [Port, Remote<Required<ListenHandler>>]>} opts.listening
322
270
  */
323
271
  ({
324
272
  localAddr,
@@ -327,7 +275,7 @@ const prepareInboundAttempt = (zone, makeConnection, { when }) => {
327
275
  listenPrefix,
328
276
  listening,
329
277
  }) => {
330
- /** @type {String | undefined} */
278
+ /** @type {string | undefined} */
331
279
  let consummated;
332
280
 
333
281
  return {
@@ -340,132 +288,182 @@ const prepareInboundAttempt = (zone, makeConnection, { when }) => {
340
288
  };
341
289
  },
342
290
  {
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;
291
+ inboundAttempt: {
292
+ getLocalAddress() {
293
+ // Return address metadata.
294
+ return this.state.localAddr;
295
+ },
296
+ getRemoteAddress() {
297
+ return this.state.remoteAddr;
298
+ },
299
+ async close() {
300
+ const { consummated, localAddr, remoteAddr } = this.state;
301
+ const { listening, listenPrefix, currentConnections } = this.state;
353
302
 
354
- if (consummated) {
355
- throw Error(consummated);
356
- }
357
- this.state.consummated = 'Already closed';
303
+ if (consummated) {
304
+ throw Error(consummated);
305
+ }
306
+ this.state.consummated = 'Already closed';
358
307
 
359
- const [port, listener] = listening.get(listenPrefix);
308
+ const [port, listener] = listening.get(listenPrefix);
360
309
 
361
- const current = currentConnections.get(port);
362
- current.delete(this.self);
310
+ const current = currentConnections.get(port);
311
+ current.delete(this.facets.inboundAttempt);
312
+ finalizer.unpin(this.facets.inboundAttempt);
363
313
 
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';
314
+ const innerVow = watch(
315
+ E(listener).onReject(port, localAddr, remoteAddr, listener),
316
+ this.facets.sinkWatcher,
317
+ );
381
318
 
382
- if (localAddress === undefined) {
383
- localAddress = localAddr;
384
- }
319
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
320
+ },
321
+ /**
322
+ * @param {object} opts
323
+ * @param {string} [opts.localAddress]
324
+ * @param {string} [opts.remoteAddress]
325
+ * @param {Remote<ConnectionHandler>} opts.handler
326
+ */
327
+ async accept({ localAddress, remoteAddress, handler: rchandler }) {
328
+ const { consummated, localAddr, remoteAddr } = this.state;
329
+ const { listening, listenPrefix, currentConnections } = this.state;
330
+ if (consummated) {
331
+ throw Error(consummated);
332
+ }
385
333
 
386
- if (remoteAddress === undefined) {
387
- remoteAddress = remoteAddr;
388
- }
334
+ if (localAddress === undefined) {
335
+ localAddress = localAddr;
336
+ }
337
+ this.state.consummated = `${localAddress} Already accepted`;
389
338
 
390
- const [port, listener] = listening.get(listenPrefix);
391
- const current = currentConnections.get(port);
339
+ if (remoteAddress === undefined) {
340
+ remoteAddress = remoteAddr;
341
+ }
392
342
 
393
- current.delete(this.self);
343
+ const [port, listener] = listening.get(listenPrefix);
344
+ const current = currentConnections.get(port);
394
345
 
395
- const lchandler = await when(
396
- E(listener).onAccept(port, localAddress, remoteAddress, listener),
397
- );
346
+ current.delete(this.facets.inboundAttempt);
347
+
348
+ return watch(
349
+ E(listener).onAccept(port, localAddress, remoteAddress, listener),
350
+ this.facets.inboundAttemptAcceptWatcher,
351
+ {
352
+ localAddress,
353
+ rchandler,
354
+ remoteAddress,
355
+ current,
356
+ },
357
+ );
358
+ },
359
+ },
360
+ inboundAttemptAcceptWatcher: {
361
+ onFulfilled(lchandler, watchContext) {
362
+ const { localAddress, rchandler, remoteAddress, current } =
363
+ watchContext;
398
364
 
399
- return crossoverConnection(
400
- zone,
401
- lchandler,
402
- localAddress,
403
- rchandler,
404
- remoteAddress,
405
- makeConnection,
406
- current,
407
- )[1];
365
+ return crossoverConnection(
366
+ zone,
367
+ /** @type {Remote<Required<ConnectionHandler>>} */ (lchandler),
368
+ localAddress,
369
+ /** @type {Remote<Required<ConnectionHandler>>} */ (rchandler),
370
+ remoteAddress,
371
+ makeConnection,
372
+ finalizer,
373
+ current,
374
+ )[1];
375
+ },
376
+ },
377
+ rethrowUnlessMissingWatcher: {
378
+ onRejected(e) {
379
+ rethrowUnlessMissing(e);
380
+ },
381
+ },
382
+ sinkWatcher: {
383
+ onFulfilled(_value) {
384
+ return undefined;
385
+ },
408
386
  },
409
387
  },
410
388
  );
411
389
 
390
+ const makeInboundAttempt = ({
391
+ localAddr,
392
+ remoteAddr,
393
+ currentConnections,
394
+ listenPrefix,
395
+ listening,
396
+ }) => {
397
+ const { inboundAttempt } = makeInboundAttemptKit({
398
+ localAddr,
399
+ remoteAddr,
400
+ currentConnections,
401
+ listenPrefix,
402
+ listening,
403
+ });
404
+
405
+ return harden(inboundAttempt);
406
+ };
407
+
412
408
  return makeInboundAttempt;
413
409
  };
414
410
 
415
- /** @enum {number} */
411
+ /** @enum {typeof RevokeState[keyof typeof RevokeState]} */
416
412
  const RevokeState = /** @type {const} */ ({
417
413
  NOT_REVOKED: 0,
418
414
  REVOKING: 1,
419
415
  REVOKED: 2,
420
416
  });
417
+ harden(RevokeState);
421
418
 
422
419
  /**
423
420
  * @param {import('@agoric/zone').Zone} zone
424
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
421
+ * @param {Powers} powers
425
422
  */
426
- const preparePort = (zone, { when }) => {
423
+ const preparePort = (zone, powers) => {
427
424
  const makeIncapable = zone.exoClass('Incapable', undefined, () => ({}), {});
428
425
 
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,
426
+ const { finalizer, watch, allVows } = powers;
427
+
428
+ /**
429
+ * @param {object} opts
430
+ * @param {Endpoint} opts.localAddr
431
+ * @param {MapStore<Endpoint, [Port, Remote<Required<ListenHandler>>]>} opts.listening
432
+ * @param {SetStore<Remote<Connection>>} opts.openConnections
433
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
434
+ * @param {MapStore<string, Port>} opts.boundPorts
435
+ * @param {Remote<ProtocolHandler>} opts.protocolHandler
436
+ * @param {Remote<ProtocolImpl>} opts.protocolImpl
437
+ */
438
+ const initPort = ({
439
+ localAddr,
440
+ listening,
441
+ openConnections,
442
+ currentConnections,
443
+ boundPorts,
444
+ protocolHandler,
445
+ protocolImpl,
446
+ }) => {
447
+ return {
444
448
  listening,
445
449
  openConnections,
446
450
  currentConnections,
447
451
  boundPorts,
452
+ localAddr,
448
453
  protocolHandler,
449
454
  protocolImpl,
450
- }) => {
451
- return {
452
- listening,
453
- openConnections,
454
- currentConnections,
455
- boundPorts,
456
- localAddr,
457
- protocolHandler,
458
- protocolImpl,
459
- /** @type {RevokeState | undefined} */
460
- revoked: undefined,
461
- };
462
- },
463
- {
455
+ /** @type {RevokeState | undefined} */
456
+ revoked: undefined,
457
+ };
458
+ };
459
+
460
+ const makePortKit = zone.exoClassKit('Port', Shape.PortI, initPort, {
461
+ port: {
464
462
  getLocalAddress() {
465
463
  // Works even after revoke().
466
464
  return this.state.localAddr;
467
465
  },
468
- /** @param {ListenHandler} listenHandler */
466
+ /** @param {Remote<ListenHandler>} listenHandler */
469
467
  async addListener(listenHandler) {
470
468
  const { revoked, listening, localAddr, protocolHandler } = this.state;
471
469
 
@@ -478,129 +476,278 @@ const preparePort = (zone, { when }) => {
478
476
  if (lhandler === listenHandler) {
479
477
  return;
480
478
  }
481
- listening.set(localAddr, [this.self, listenHandler]);
479
+ listening.set(localAddr, [
480
+ this.facets.port,
481
+ /** @type {Remote<Required<ListenHandler>>} */ (listenHandler),
482
+ ]);
482
483
  E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
483
484
  } else {
484
- listening.init(localAddr, harden([this.self, listenHandler]));
485
+ listening.init(
486
+ localAddr,
487
+ harden([
488
+ this.facets.port,
489
+ /** @type {Remote<Required<ListenHandler>>} */ (listenHandler),
490
+ ]),
491
+ );
485
492
  }
486
493
 
487
494
  // ASSUME: that the listener defines onAccept.
488
495
 
489
- await when(
496
+ const innerVow = watch(
490
497
  E(protocolHandler).onListen(
491
- this.self,
498
+ this.facets.port,
492
499
  localAddr,
493
500
  listenHandler,
494
501
  protocolHandler,
495
502
  ),
503
+ this.facets.portAddListenerWatcher,
504
+ { listenHandler },
496
505
  );
497
- await when(E(listenHandler).onListen(this.self, listenHandler)).catch(
498
- rethrowUnlessMissing,
499
- );
506
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
500
507
  },
501
- /** @param {ListenHandler} listenHandler */
508
+ /** @param {Remote<ListenHandler>} listenHandler */
502
509
  async removeListener(listenHandler) {
503
510
  const { listening, localAddr, protocolHandler } = this.state;
504
511
  listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
505
512
  listening.get(localAddr)[1] === listenHandler ||
506
513
  Fail`Port ${localAddr} handler to remove is not listening`;
507
514
  listening.delete(localAddr);
508
- await when(
515
+
516
+ const innerVow = watch(
509
517
  E(protocolHandler).onListenRemove(
510
- this.self,
518
+ this.facets.port,
511
519
  localAddr,
512
520
  listenHandler,
513
521
  protocolHandler,
514
522
  ),
523
+ this.facets.portRemoveListenerWatcher,
524
+ { listenHandler },
515
525
  );
516
- await when(E(listenHandler).onRemove(this.self, listenHandler)).catch(
517
- rethrowUnlessMissing,
518
- );
526
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
519
527
  },
520
528
  /**
521
529
  * @param {Endpoint} remotePort
522
- * @param {ConnectionHandler} connectionHandler
530
+ * @param {Remote<ConnectionHandler>} [connectionHandler]
523
531
  */
524
532
  async connect(
525
533
  remotePort,
526
- connectionHandler = /** @type {any} */ (makeIncapable()),
534
+ connectionHandler = /** @type {Remote<ConnectionHandler>} */ (
535
+ makeIncapable()
536
+ ),
527
537
  ) {
528
- const { revoked, localAddr, protocolImpl, openConnections } =
529
- this.state;
538
+ const { revoked, localAddr, protocolImpl } = this.state;
530
539
 
531
540
  !revoked || Fail`Port ${localAddr} is revoked`;
532
541
  /** @type {Endpoint} */
533
542
  const dst = harden(remotePort);
534
- // eslint-disable-next-line no-use-before-define
535
- const conn = await when(
536
- protocolImpl.outbound(this.self, dst, connectionHandler),
543
+ return watch(
544
+ E(protocolImpl).outbound(this.facets.port, dst, connectionHandler),
545
+ this.facets.portConnectWatcher,
546
+ { chandler: connectionHandler },
537
547
  );
538
- if (revoked) {
539
- void E(conn).close();
540
- } else {
541
- openConnections.add(conn);
542
- }
543
- return conn;
544
548
  },
545
549
  async revoke() {
546
550
  const { revoked, localAddr } = this.state;
547
- const { protocolHandler, currentConnections, listening, boundPorts } =
548
- this.state;
551
+ const { protocolHandler } = this.state;
549
552
 
550
553
  revoked !== RevokeState.REVOKED ||
551
554
  Fail`Port ${localAddr} is already revoked`;
555
+
552
556
  this.state.revoked = RevokeState.REVOKING;
553
- await when(
554
- E(protocolHandler).onRevoke(this.self, localAddr, protocolHandler),
557
+ const revokeVow = watch(
558
+ E(protocolHandler).onRevoke(
559
+ this.facets.port,
560
+ localAddr,
561
+ protocolHandler,
562
+ ),
563
+ this.facets.portRevokeWatcher,
555
564
  );
556
- this.state.revoked = RevokeState.REVOKED;
565
+
566
+ return watch(revokeVow, this.facets.portRevokeCleanupWatcher);
567
+ },
568
+ },
569
+ portAddListenerWatcher: {
570
+ onFulfilled(_value, watcherContext) {
571
+ const { listenHandler } = watcherContext;
572
+ return E(listenHandler).onListen(this.facets.port, listenHandler);
573
+ },
574
+ },
575
+ portRemoveListenerWatcher: {
576
+ onFulfilled(_value, watcherContext) {
577
+ const { listenHandler } = watcherContext;
578
+ return E(listenHandler).onRemove(this.facets.port, listenHandler);
579
+ },
580
+ },
581
+ portConnectWatcher: {
582
+ onFulfilled(conn, { chandler }) {
583
+ const { openConnections, revoked } = this.state;
584
+
585
+ if (!finalizer.has(conn)) {
586
+ finalizer.initConnection(conn, chandler);
587
+ }
588
+ if (revoked) {
589
+ return finalizer.finalize(conn);
590
+ }
591
+ openConnections.add(conn);
592
+ return conn;
593
+ },
594
+ },
595
+ portRevokeWatcher: {
596
+ onFulfilled(_value) {
597
+ const { currentConnections, listening, localAddr } = this.state;
598
+ const port = this.facets.port;
557
599
 
558
600
  // Clean up everything we did.
559
- const values = [...currentConnections.get(this.self).values()];
560
- const ps = values.map(conn => when(E(conn).close()).catch(_ => {}));
601
+ const values = [...currentConnections.get(port).values()];
602
+
603
+ const ps = [];
604
+ ps.push(
605
+ ...values.map(obj =>
606
+ watch(finalizer.finalize(obj), this.facets.sinkWatcher),
607
+ ),
608
+ );
609
+
561
610
  if (listening.has(localAddr)) {
562
611
  const listener = listening.get(localAddr)[1];
563
- ps.push(this.self.removeListener(listener));
612
+ ps.push(port.removeListener(listener));
564
613
  }
565
- await Promise.all(ps);
566
- currentConnections.delete(this.self);
614
+
615
+ return watch(allVows(ps), this.facets.rethrowUnlessMissingWatcher);
616
+ },
617
+ },
618
+ sinkWatcher: {
619
+ onFulfilled() {
620
+ return undefined;
621
+ },
622
+ onRejected() {
623
+ return undefined;
624
+ },
625
+ },
626
+ portRevokeCleanupWatcher: {
627
+ onFulfilled(_value) {
628
+ const { currentConnections, boundPorts, localAddr } = this.state;
629
+
630
+ this.state.revoked = RevokeState.REVOKED;
631
+
632
+ currentConnections.delete(this.facets.port);
567
633
  boundPorts.delete(localAddr);
568
634
  },
569
635
  },
570
- );
636
+ rethrowUnlessMissingWatcher: {
637
+ onRejected(e) {
638
+ rethrowUnlessMissing(e);
639
+ },
640
+ },
641
+ });
642
+
643
+ const makePort = ({
644
+ localAddr,
645
+ listening,
646
+ openConnections,
647
+ currentConnections,
648
+ boundPorts,
649
+ protocolHandler,
650
+ protocolImpl,
651
+ }) => {
652
+ const { port } = makePortKit({
653
+ localAddr,
654
+ listening,
655
+ openConnections,
656
+ currentConnections,
657
+ boundPorts,
658
+ protocolHandler,
659
+ protocolImpl,
660
+ });
661
+ return harden(port);
662
+ };
571
663
 
572
664
  return makePort;
573
665
  };
574
666
 
575
667
  /**
576
668
  * @param {import('@agoric/base-zone').Zone} zone
577
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
669
+ * @param {Powers} powers
578
670
  */
579
671
  const prepareBinder = (zone, powers) => {
580
672
  const makeConnection = prepareHalfConnection(zone, powers);
581
- const { when } = powers;
673
+
674
+ const { watch, finalizer } = powers;
675
+
582
676
  const makeInboundAttempt = prepareInboundAttempt(
583
677
  zone,
584
678
  makeConnection,
585
679
  powers,
586
680
  );
681
+
587
682
  const makePort = preparePort(zone, powers);
683
+
588
684
  const detached = zone.detached();
589
685
 
590
- const makeBinderKit = zone.exoClassKit(
686
+ const makeFullBinderKit = zone.exoClassKit(
591
687
  'binder',
592
688
  {
593
689
  protocolImpl: Shape.ProtocolImplI,
594
690
  binder: M.interface('Binder', {
595
- bind: M.callWhen(Shape.Endpoint).returns(Shape.Port),
691
+ bindPort: M.callWhen(Shape.Endpoint).returns(Shape.Vow$(Shape.Port)),
692
+ }),
693
+ binderInboundInstantiateWatcher: M.interface(
694
+ 'BinderInboundInstantiateWatcher',
695
+ {
696
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
697
+ },
698
+ ),
699
+ binderInboundInstantiateCatchWatcher: M.interface(
700
+ 'BinderInboundInstantiateCatchWatcher',
701
+ {
702
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
703
+ },
704
+ ),
705
+ binderOutboundInstantiateWatcher: M.interface(
706
+ 'BinderOutboundInstantiateWatcher',
707
+ {
708
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
709
+ },
710
+ ),
711
+ binderOutboundConnectWatcher: M.interface(
712
+ 'BinderOutboundConnectWatcher',
713
+ {
714
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
715
+ },
716
+ ),
717
+ binderOutboundCatchWatcher: M.interface('BinderOutboundCatchWatcher', {
718
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
719
+ }),
720
+ binderOutboundInboundWatcher: M.interface(
721
+ 'BinderOutboundInboundWatcher',
722
+ {
723
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
724
+ },
725
+ ),
726
+ binderOutboundAcceptWatcher: M.interface('BinderOutboundAcceptWatcher', {
727
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
728
+ }),
729
+ binderBindGeneratePortWatcher: M.interface(
730
+ 'BinderBindGeneratePortWatcher',
731
+ {
732
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
733
+ },
734
+ ),
735
+ binderPortWatcher: M.interface('BinderPortWatcher', {
736
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
737
+ }),
738
+ binderBindWatcher: M.interface('BinderBindWatcher', {
739
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
740
+ }),
741
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
742
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
596
743
  }),
597
744
  },
598
745
  /**
599
746
  * @param {object} opts
600
- * @param { MapStore<Port, SetStore<Closable>> } opts.currentConnections
747
+ * @param {MapStore<Port, SetStore<Closable>>} opts.currentConnections
601
748
  * @param {MapStore<string, Port>} opts.boundPorts
602
- * @param {MapStore<Endpoint, [Port, ListenHandler]>} opts.listening
603
- * @param {ProtocolHandler} opts.protocolHandler
749
+ * @param {MapStore<Endpoint, [Port, Remote<Required<ListenHandler>>]>} opts.listening
750
+ * @param {Remote<ProtocolHandler>} opts.protocolHandler
604
751
  */
605
752
  ({ currentConnections, boundPorts, listening, protocolHandler }) => {
606
753
  /** @type {SetStore<Connection>} */
@@ -613,8 +760,6 @@ const prepareBinder = (zone, powers) => {
613
760
  revoked: RevokeState.NOT_REVOKED,
614
761
  openConnections,
615
762
  protocolHandler,
616
- /** @type {Endpoint | undefined} */
617
- localAddr: undefined,
618
763
  };
619
764
  },
620
765
  {
@@ -624,49 +769,58 @@ const prepareBinder = (zone, powers) => {
624
769
  * @param {Endpoint} remoteAddr
625
770
  */
626
771
  async inbound(listenAddr, remoteAddr) {
627
- const { listening, protocolHandler, currentConnections } = this.state;
772
+ const { listening, protocolHandler } = this.state;
773
+
774
+ const prefixes = getPrefixes(listenAddr);
775
+ let listenPrefixIndex = 0;
776
+ let listenPrefix;
628
777
 
629
- let lastFailure = Error(`No listeners for ${listenAddr}`);
630
- for await (const listenPrefix of getPrefixes(listenAddr)) {
778
+ while (listenPrefixIndex < prefixes.length) {
779
+ listenPrefix = prefixes[listenPrefixIndex];
631
780
  if (!listening.has(listenPrefix)) {
781
+ listenPrefixIndex += 1;
632
782
  continue;
633
783
  }
634
- const [port, _] = listening.get(listenPrefix);
635
- let localAddr;
636
784
 
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;
655
- }
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
- });
785
+ break;
786
+ }
665
787
 
666
- current.add(inboundAttempt);
667
- return inboundAttempt;
788
+ if (listenPrefixIndex >= prefixes.length) {
789
+ throw Error(`No listeners for ${listenAddr}`);
668
790
  }
669
- throw lastFailure;
791
+
792
+ const [port] = listening.get(/** @type {string} **/ (listenPrefix));
793
+
794
+ const innerVow = watch(
795
+ E(
796
+ /** @type {Remote<Required<ProtocolHandler>>} */ (
797
+ protocolHandler
798
+ ),
799
+ ).onInstantiate(
800
+ /** @type {Port} **/ (port),
801
+ prefixes[listenPrefixIndex],
802
+ remoteAddr,
803
+ protocolHandler,
804
+ ),
805
+ this.facets.binderInboundInstantiateWatcher,
806
+ {
807
+ listenAddr,
808
+ remoteAddr,
809
+ port,
810
+ listenPrefixIndex,
811
+ },
812
+ );
813
+
814
+ return watch(
815
+ innerVow,
816
+ this.facets.binderInboundInstantiateCatchWatcher,
817
+ {
818
+ listenPrefixIndex,
819
+ listenAddr,
820
+ remoteAddr,
821
+ lastFailure: Error(`No listeners for ${listenAddr}`),
822
+ },
823
+ );
670
824
  },
671
825
  /**
672
826
  * @param {Port} port
@@ -674,101 +828,310 @@ const prepareBinder = (zone, powers) => {
674
828
  * @param {ConnectionHandler} lchandler
675
829
  */
676
830
  async outbound(port, remoteAddr, lchandler) {
677
- const { protocolHandler, currentConnections } = this.state;
831
+ const { protocolHandler } = this.state;
678
832
 
679
833
  const localAddr = await E(port).getLocalAddress();
680
834
 
681
835
  // Allocate a local address.
682
- const initialLocalInstance = await when(
683
- E(protocolHandler).onInstantiate(
836
+ const instantiateInnerVow = watch(
837
+ E(
838
+ /** @type {Remote<Required<ProtocolHandler>>} */ (
839
+ protocolHandler
840
+ ),
841
+ ).onInstantiate(port, localAddr, remoteAddr, protocolHandler),
842
+ this.facets.binderOutboundInstantiateWatcher,
843
+ {
684
844
  port,
685
845
  localAddr,
686
846
  remoteAddr,
687
847
  protocolHandler,
688
- ),
689
- ).catch(rethrowUnlessMissing);
690
- const initialLocalAddr = initialLocalInstance
691
- ? `${localAddr}/${initialLocalInstance}`
692
- : localAddr;
848
+ },
849
+ );
693
850
 
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),
700
- );
701
- accepted = await when(attempt.accept({ handler: lchandler }));
702
- })().catch(e => {
703
- lastFailure = e;
851
+ const instantiateVow = watch(
852
+ instantiateInnerVow,
853
+ this.facets.rethrowUnlessMissingWatcher,
854
+ );
855
+
856
+ const attemptVow = watch(
857
+ instantiateVow,
858
+ this.facets.binderOutboundInboundWatcher,
859
+ {
860
+ localAddr,
861
+ remoteAddr,
862
+ },
863
+ );
864
+ const acceptedVow = watch(
865
+ attemptVow,
866
+ this.facets.binderOutboundAcceptWatcher,
867
+ {
868
+ handler: lchandler,
869
+ },
870
+ );
871
+
872
+ return watch(acceptedVow, this.facets.binderOutboundCatchWatcher, {
873
+ port,
874
+ remoteAddr,
875
+ lchandler,
876
+ localAddr,
704
877
  });
705
- if (accepted) {
706
- return accepted;
878
+ },
879
+ async bindPort(localAddr) {
880
+ return this.facets.binder.bindPort(localAddr);
881
+ },
882
+ },
883
+ binder: {
884
+ /** @param {string} localAddr */
885
+ async bindPort(localAddr) {
886
+ const { protocolHandler } = this.state;
887
+
888
+ // Check if we are underspecified (ends in slash)
889
+ const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
890
+
891
+ const localAddrVow = watch(
892
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
893
+ this.facets.binderBindGeneratePortWatcher,
894
+ {
895
+ underspecified,
896
+ localAddr,
897
+ },
898
+ );
899
+
900
+ return watch(localAddrVow, this.facets.binderBindWatcher);
901
+ },
902
+ },
903
+ binderInboundInstantiateWatcher: {
904
+ onFulfilled(localInstance, watchContext) {
905
+ const { listenAddr, remoteAddr, port, listenPrefixIndex } =
906
+ watchContext;
907
+ const { listening, currentConnections } = this.state;
908
+ const prefixes = getPrefixes(listenAddr);
909
+
910
+ const localAddr = localInstance
911
+ ? `${listenAddr}/${localInstance}`
912
+ : listenAddr;
913
+ const current = currentConnections.get(port);
914
+ const inboundAttempt = makeInboundAttempt({
915
+ localAddr,
916
+ remoteAddr,
917
+ currentConnections,
918
+ listenPrefix: prefixes[listenPrefixIndex],
919
+ listening,
920
+ });
921
+
922
+ current.add(inboundAttempt);
923
+ finalizer.initCloser(inboundAttempt);
924
+ return inboundAttempt;
925
+ },
926
+ },
927
+ binderInboundInstantiateCatchWatcher: {
928
+ onRejected(e, watchContext) {
929
+ let { lastFailure, listenPrefixIndex } = watchContext;
930
+
931
+ try {
932
+ rethrowUnlessMissing(e);
933
+ } catch (innerE) {
934
+ lastFailure = innerE;
707
935
  }
708
936
 
709
- const {
710
- remoteAddress = remoteAddr,
937
+ const { listenAddr, remoteAddr } = watchContext;
938
+
939
+ const { listening, protocolHandler } = this.state;
940
+
941
+ const prefixes = getPrefixes(listenAddr);
942
+
943
+ let listenPrefix;
944
+
945
+ listenPrefixIndex += 1;
946
+
947
+ while (listenPrefixIndex < prefixes.length) {
948
+ listenPrefix = prefixes[listenPrefixIndex];
949
+ if (!listening.has(listenPrefix)) {
950
+ listenPrefixIndex += 1;
951
+ continue;
952
+ }
953
+
954
+ break;
955
+ }
956
+
957
+ if (listenPrefixIndex >= prefixes.length) {
958
+ throw lastFailure;
959
+ }
960
+
961
+ const [port] = listening.get(/** @type {string} */ (listenPrefix));
962
+
963
+ const innerVow = watch(
964
+ E(
965
+ /** @type {Remote<Required<ProtocolHandler>>} */ (
966
+ protocolHandler
967
+ ),
968
+ ).onInstantiate(
969
+ port,
970
+ prefixes[listenPrefixIndex],
971
+ remoteAddr,
972
+ protocolHandler,
973
+ ),
974
+ this.facets.binderInboundInstantiateWatcher,
975
+ {
976
+ listenAddr,
977
+ remoteAddr,
978
+ port,
979
+ listenPrefixIndex,
980
+ },
981
+ );
982
+
983
+ return watch(
984
+ innerVow,
985
+ this.facets.binderInboundInstantiateCatchWatcher,
986
+ {
987
+ ...watchContext,
988
+ lastFailure,
989
+ listenPrefixIndex,
990
+ },
991
+ );
992
+ },
993
+ },
994
+ binderOutboundInstantiateWatcher: {
995
+ onFulfilled(localInstance, watchContext) {
996
+ const { localAddr } = watchContext;
997
+
998
+ return localInstance ? `${localAddr}/${localInstance}` : localAddr;
999
+ },
1000
+ },
1001
+ binderOutboundConnectWatcher: {
1002
+ onFulfilled(
1003
+ {
711
1004
  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
- );
1005
+ remoteAddress: negotiatedRemoteAddress,
1006
+ localAddress: negotiatedLocalAddress,
1007
+ },
1008
+ watchContext,
1009
+ ) {
1010
+ const {
1011
+ lastFailure,
1012
+ lchandler,
1013
+ localAddr: requestedLocalAddress,
1014
+ remoteAddr: requestedRemoteAddress,
1015
+ port,
1016
+ } = watchContext;
1017
+
1018
+ const { currentConnections } = this.state;
726
1019
 
727
1020
  if (!rchandler) {
728
1021
  throw lastFailure;
729
1022
  }
730
1023
 
731
1024
  const current = currentConnections.get(port);
1025
+
732
1026
  return crossoverConnection(
733
1027
  zone,
734
- lchandler,
735
- localAddress,
736
- rchandler,
737
- remoteAddress,
1028
+ /** @type {Remote<Required<ConnectionHandler>>} */ (lchandler),
1029
+ negotiatedLocalAddress || requestedLocalAddress,
1030
+ /** @type {Remote<Required<ConnectionHandler>>} */ (rchandler),
1031
+ negotiatedRemoteAddress || requestedRemoteAddress,
738
1032
  makeConnection,
1033
+ finalizer,
739
1034
  current,
740
1035
  )[0];
741
1036
  },
742
- async bind(localAddr) {
743
- return this.facets.binder.bind(localAddr);
1037
+ },
1038
+ binderOutboundCatchWatcher: {
1039
+ onRejected(e, watchContext) {
1040
+ let lastFailure;
1041
+
1042
+ try {
1043
+ rethrowUnlessMissing(e);
1044
+ } catch (innerE) {
1045
+ lastFailure = innerE;
1046
+ }
1047
+
1048
+ const { port, remoteAddr, lchandler, localAddr } = watchContext;
1049
+
1050
+ const { protocolHandler } = this.state;
1051
+
1052
+ const connectVow = watch(
1053
+ E(protocolHandler).onConnect(
1054
+ port,
1055
+ localAddr,
1056
+ remoteAddr,
1057
+ lchandler,
1058
+ protocolHandler,
1059
+ ),
1060
+ );
1061
+
1062
+ return watch(connectVow, this.facets.binderOutboundConnectWatcher, {
1063
+ lastFailure,
1064
+ remoteAddr,
1065
+ localAddr,
1066
+ lchandler,
1067
+ port,
1068
+ });
744
1069
  },
745
1070
  },
746
- binder: {
747
- /** @param {string} localAddr */
748
- async bind(localAddr) {
1071
+ binderOutboundInboundWatcher: {
1072
+ onFulfilled(initialLocalAddress, watchContext) {
1073
+ const { remoteAddr, localAddr } = watchContext;
1074
+
1075
+ if (initialLocalAddress === undefined) {
1076
+ initialLocalAddress = localAddr;
1077
+ }
1078
+
1079
+ // Attempt the loopback connection.
1080
+ return this.facets.protocolImpl.inbound(
1081
+ remoteAddr,
1082
+ initialLocalAddress,
1083
+ );
1084
+ },
1085
+ },
1086
+ binderOutboundAcceptWatcher: {
1087
+ onFulfilled(attempt, watchContext) {
1088
+ const { handler } = watchContext;
1089
+ return E(attempt).accept({ handler });
1090
+ },
1091
+ },
1092
+ binderBindGeneratePortWatcher: {
1093
+ onFulfilled(portID, watchContext) {
1094
+ const { localAddr, underspecified } = watchContext;
1095
+ const { protocolHandler, boundPorts } = this.state;
1096
+
1097
+ if (!underspecified) {
1098
+ return localAddr;
1099
+ }
1100
+
1101
+ const newAddr = `${localAddr}${portID}`;
1102
+ if (!boundPorts.has(newAddr)) {
1103
+ return newAddr;
1104
+ }
1105
+ return watch(
1106
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
1107
+ this.facets.binderBindGeneratePortWatcher,
1108
+ watchContext,
1109
+ );
1110
+ },
1111
+ },
1112
+ binderPortWatcher: {
1113
+ onFulfilled(_value, watchContext) {
1114
+ const { port, localAddr } = watchContext;
1115
+ const { boundPorts, currentConnections } = this.state;
1116
+
1117
+ boundPorts.init(localAddr, port);
1118
+ currentConnections.init(
1119
+ port,
1120
+ zone.detached().setStore('connections'),
1121
+ );
1122
+ return port;
1123
+ },
1124
+ },
1125
+ binderBindWatcher: {
1126
+ onFulfilled(localAddr) {
749
1127
  const {
750
- protocolHandler,
751
1128
  boundPorts,
752
1129
  listening,
753
1130
  openConnections,
754
1131
  currentConnections,
1132
+ protocolHandler,
755
1133
  } = this.state;
756
1134
 
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
- }
769
-
770
- this.state.localAddr = localAddr;
771
-
772
1135
  if (boundPorts.has(localAddr)) {
773
1136
  return boundPorts.get(localAddr);
774
1137
  }
@@ -783,29 +1146,50 @@ const prepareBinder = (zone, powers) => {
783
1146
  protocolImpl: this.facets.protocolImpl,
784
1147
  });
785
1148
 
786
- await when(
1149
+ return watch(
787
1150
  E(protocolHandler).onBind(port, localAddr, protocolHandler),
1151
+ this.facets.binderPortWatcher,
1152
+ {
1153
+ port,
1154
+ localAddr,
1155
+ },
788
1156
  );
789
- boundPorts.init(localAddr, harden(port));
790
- currentConnections.init(port, detached.setStore('connections'));
791
- return port;
1157
+ },
1158
+ },
1159
+ rethrowUnlessMissingWatcher: {
1160
+ onRejected(e) {
1161
+ rethrowUnlessMissing(e);
792
1162
  },
793
1163
  },
794
1164
  },
795
1165
  );
796
1166
 
1167
+ const makeBinderKit = ({
1168
+ currentConnections,
1169
+ boundPorts,
1170
+ listening,
1171
+ protocolHandler,
1172
+ }) => {
1173
+ const { protocolImpl, binder } = makeFullBinderKit({
1174
+ currentConnections,
1175
+ boundPorts,
1176
+ listening,
1177
+ protocolHandler,
1178
+ });
1179
+ return harden({ protocolImpl, binder });
1180
+ };
797
1181
  return makeBinderKit;
798
1182
  };
799
1183
 
800
1184
  /**
801
1185
  * @param {import('@agoric/base-zone').Zone} zone
802
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
1186
+ * @param {Powers} powers
803
1187
  */
804
1188
  export const prepareNetworkProtocol = (zone, powers) => {
805
1189
  const makeBinderKit = prepareBinder(zone, powers);
806
1190
 
807
1191
  /**
808
- * @param {ProtocolHandler} protocolHandler
1192
+ * @param {Remote<ProtocolHandler>} protocolHandler
809
1193
  * @returns {Protocol}
810
1194
  */
811
1195
  const makeNetworkProtocol = protocolHandler => {
@@ -817,7 +1201,7 @@ export const prepareNetworkProtocol = (zone, powers) => {
817
1201
  /** @type {MapStore<string, Port>} */
818
1202
  const boundPorts = detached.mapStore('addrToPort');
819
1203
 
820
- /** @type {MapStore<Endpoint, [Port, ListenHandler]>} */
1204
+ /** @type {MapStore<Endpoint, [Port, Remote<Required<ListenHandler>>]>} */
821
1205
  const listening = detached.mapStore('listening');
822
1206
 
823
1207
  const { binder, protocolImpl } = makeBinderKit({
@@ -846,14 +1230,14 @@ export const prepareEchoConnectionKit = zone => {
846
1230
  {
847
1231
  handler: M.interface('ConnectionHandler', {
848
1232
  onReceive: M.callWhen(
849
- Shape2.Connection,
850
- Shape2.Bytes,
851
- Shape2.ConnectionHandler,
1233
+ Shape.Connection,
1234
+ Shape.Bytes,
1235
+ Shape.ConnectionHandler,
852
1236
  )
853
- .optional(Shape2.Opts)
854
- .returns(Shape2.Data),
855
- onClose: M.callWhen(Shape2.Connection)
856
- .optional(M.any(), Shape2.ConnectionHandler)
1237
+ .optional(Shape.Opts)
1238
+ .returns(Shape.Data),
1239
+ onClose: M.callWhen(Shape.Connection)
1240
+ .optional(M.any(), Shape.ConnectionHandler)
857
1241
  .returns(M.undefined()),
858
1242
  }),
859
1243
  listener: M.interface('Listener', {
@@ -869,10 +1253,9 @@ export const prepareEchoConnectionKit = zone => {
869
1253
  }),
870
1254
  },
871
1255
  () => {
872
- /** @type {string | undefined} */
873
- let closed;
874
1256
  return {
875
- closed,
1257
+ /** @type {string | undefined} */
1258
+ closed: undefined,
876
1259
  };
877
1260
  },
878
1261
  {
@@ -886,28 +1269,28 @@ export const prepareEchoConnectionKit = zone => {
886
1269
  const { closed } = this.state;
887
1270
 
888
1271
  if (closed) {
889
- throw closed;
1272
+ throw Error(closed);
890
1273
  }
891
1274
  return bytes;
892
1275
  },
893
1276
  /**
894
1277
  * @param {Connection} _connection
895
- * @param {CloseReason} [_reason]
1278
+ * @param {CloseReason} [reason]
896
1279
  * @param {ConnectionHandler} [_connectionHandler]
897
1280
  */
898
- async onClose(_connection, _reason, _connectionHandler) {
1281
+ async onClose(_connection, reason, _connectionHandler) {
899
1282
  const { closed } = this.state;
900
1283
 
901
1284
  if (closed) {
902
1285
  throw Error(closed);
903
1286
  }
904
1287
 
905
- this.state.closed = 'Connection closed';
1288
+ this.state.closed = reason || 'Connection closed';
906
1289
  },
907
1290
  },
908
1291
  listener: {
909
1292
  async onAccept(_port, _localAddr, _remoteAddr, _listenHandler) {
910
- return harden(this.facets.handler);
1293
+ return this.facets.handler;
911
1294
  },
912
1295
  async onListen(port, _listenHandler) {
913
1296
  console.debug(`listening on echo port: ${port}`);
@@ -918,106 +1301,313 @@ export const prepareEchoConnectionKit = zone => {
918
1301
 
919
1302
  return makeEchoConnectionKit;
920
1303
  };
1304
+ /** @typedef {ReturnType<typeof prepareEchoConnectionKit>} MakeEchoConnectionKit */
921
1305
 
922
1306
  /**
923
1307
  * Create a protocol handler that just connects to itself.
924
1308
  *
925
1309
  * @param {import('@agoric/base-zone').Zone} zone
926
- * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
1310
+ * @param {VowTools} powers
927
1311
  */
928
- export function prepareLoopbackProtocolHandler(zone, { when }) {
1312
+ export function prepareLoopbackProtocolHandler(zone, { watch, allVows }) {
929
1313
  const detached = zone.detached();
930
1314
 
931
- const makeLoopbackProtocolHandler = zone.exoClass(
1315
+ /** @param {string} [instancePrefix] */
1316
+ const initHandler = (instancePrefix = 'nonce/') => {
1317
+ /** @type {MapStore<string, [Remote<Port>, Remote<Required<ListenHandler>>]>} */
1318
+ const listeners = detached.mapStore('localAddr');
1319
+
1320
+ return {
1321
+ listeners,
1322
+ portNonce: 0n,
1323
+ instancePrefix,
1324
+ instanceNonce: 0n,
1325
+ };
1326
+ };
1327
+
1328
+ const makeLoopbackProtocolHandlerKit = zone.exoClassKit(
932
1329
  'ProtocolHandler',
933
1330
  Shape.ProtocolHandlerI,
934
1331
  /** @param {string} [instancePrefix] */
935
- (instancePrefix = 'nonce/') => {
936
- /** @type {MapStore<string, [Port, ListenHandler]>} */
937
- const listeners = detached.mapStore('localAddr');
938
-
939
- return {
940
- listeners,
941
- portNonce: 0n,
942
- instancePrefix,
943
- instanceNonce: 0n,
944
- };
945
- },
1332
+ initHandler,
946
1333
  {
947
- async onCreate(_impl, _protocolHandler) {
948
- // noop
949
- },
950
- async generatePortID(_localAddr, _protocolHandler) {
951
- this.state.portNonce += 1n;
952
- return `port${this.state.portNonce}`;
1334
+ protocolHandler: {
1335
+ async onCreate(_impl, _protocolHandler) {
1336
+ // noop
1337
+ },
1338
+ async generatePortID(_localAddr, _protocolHandler) {
1339
+ this.state.portNonce += 1n;
1340
+ return `port${this.state.portNonce}`;
1341
+ },
1342
+ async onBind(_port, _localAddr, _protocolHandler) {
1343
+ // noop, for now; Maybe handle a bind?
1344
+ },
1345
+ /**
1346
+ * @param {*} _port
1347
+ * @param {Endpoint} localAddr
1348
+ * @param {Endpoint} remoteAddr
1349
+ * @returns {import('@agoric/vow').PromiseVow<AttemptDescription>}}
1350
+ */
1351
+ async onConnect(_port, localAddr, remoteAddr) {
1352
+ const { listeners } = this.state;
1353
+ const [lport, lhandler] = listeners.get(remoteAddr);
1354
+
1355
+ const acceptVow = watch(
1356
+ E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler),
1357
+ this.facets.protocolHandlerAcceptWatcher,
1358
+ );
1359
+
1360
+ const instantiateInnerVow = watch(
1361
+ E(this.facets.protocolHandler).onInstantiate(
1362
+ lport,
1363
+ remoteAddr,
1364
+ localAddr,
1365
+ this.facets.protocolHandler,
1366
+ ),
1367
+ this.facets.protocolHandlerInstantiateWatcher,
1368
+ );
1369
+
1370
+ const instantiateVow = watch(
1371
+ instantiateInnerVow,
1372
+ this.facets.rethrowUnlessMissingWatcher,
1373
+ );
1374
+ return watch(
1375
+ allVows([acceptVow, instantiateVow]),
1376
+ this.facets.protocolHandlerConnectWatcher,
1377
+ );
1378
+ },
1379
+ async onInstantiate(_port, _localAddr, _remote, _protocol) {
1380
+ const { instancePrefix } = this.state;
1381
+ this.state.instanceNonce += 1n;
1382
+ return `${instancePrefix}${this.state.instanceNonce}`;
1383
+ },
1384
+ async onListen(port, localAddr, listenHandler, _protocolHandler) {
1385
+ const { listeners } = this.state;
1386
+
1387
+ // This implementation has a simple last-one-wins replacement policy.
1388
+ // Other handlers might use different policies.
1389
+ if (listeners.has(localAddr)) {
1390
+ const lhandler = listeners.get(localAddr)[1];
1391
+ if (lhandler !== listenHandler) {
1392
+ listeners.set(
1393
+ localAddr,
1394
+ harden([
1395
+ port,
1396
+ /** @type {Remote<Required<ListenHandler>>} */ (
1397
+ listenHandler
1398
+ ),
1399
+ ]),
1400
+ );
1401
+ }
1402
+ } else {
1403
+ listeners.init(
1404
+ localAddr,
1405
+ harden([
1406
+ port,
1407
+ /** @type {Remote<Required<ListenHandler>>} */ (listenHandler),
1408
+ ]),
1409
+ );
1410
+ }
1411
+ },
1412
+ /**
1413
+ * @param {Remote<Port>} port
1414
+ * @param {Endpoint} localAddr
1415
+ * @param {Remote<ListenHandler>} listenHandler
1416
+ * @param {*} _protocolHandler
1417
+ */
1418
+ async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
1419
+ const { listeners } = this.state;
1420
+ const [lport, lhandler] = listeners.get(localAddr);
1421
+ lport === port || Fail`Port does not match listener on ${localAddr}`;
1422
+ lhandler === listenHandler ||
1423
+ Fail`Listen handler does not match listener on ${localAddr}`;
1424
+ listeners.delete(localAddr);
1425
+ },
1426
+ async onRevoke(_port, _localAddr, _protocolHandler) {
1427
+ // This is an opportunity to clean up resources.
1428
+ },
953
1429
  },
954
- async onBind(_port, _localAddr, _protocolHandler) {
955
- // noop, for now; Maybe handle a bind?
1430
+ protocolHandlerAcceptWatcher: {
1431
+ onFulfilled(rchandler) {
1432
+ return rchandler;
1433
+ },
956
1434
  },
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]));
1000
- }
1435
+ protocolHandlerConnectWatcher: {
1436
+ onFulfilled(results) {
1437
+ return {
1438
+ remoteInstance: results[0],
1439
+ handler: results[1],
1440
+ };
1441
+ },
1001
1442
  },
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);
1443
+ protocolHandlerInstantiateWatcher: {
1444
+ onFulfilled(remoteInstance) {
1445
+ return remoteInstance;
1446
+ },
1015
1447
  },
1016
- async onRevoke(_port, _localAddr, _protocolHandler) {
1017
- // This is an opportunity to clean up resources.
1448
+ rethrowUnlessMissingWatcher: {
1449
+ onRejected(e) {
1450
+ rethrowUnlessMissing(e);
1451
+ },
1018
1452
  },
1019
1453
  },
1020
1454
  );
1021
1455
 
1456
+ /** @param {string} [instancePrefix] */
1457
+ const makeLoopbackProtocolHandler = instancePrefix => {
1458
+ const { protocolHandler } = makeLoopbackProtocolHandlerKit(instancePrefix);
1459
+ return harden(protocolHandler);
1460
+ };
1461
+
1022
1462
  return makeLoopbackProtocolHandler;
1023
1463
  }
1464
+
1465
+ /**
1466
+ *
1467
+ * @param {import('@agoric/base-zone').Zone} zone
1468
+ * @param {Powers} powers
1469
+ */
1470
+ export const preparePortAllocator = (zone, { watch }) =>
1471
+ zone.exoClass(
1472
+ 'PortAllocator',
1473
+ M.interface('PortAllocator', {
1474
+ allocateCustomIBCPort: M.callWhen()
1475
+ .optional(M.string())
1476
+ .returns(Shape.Vow$(Shape.Port)),
1477
+ allocateICAControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)),
1478
+ allocateICQControllerPort: M.callWhen().returns(Shape.Vow$(Shape.Port)),
1479
+ allocateCustomLocalPort: M.callWhen()
1480
+ .optional(M.string())
1481
+ .returns(Shape.Vow$(Shape.Port)),
1482
+ }),
1483
+ /**
1484
+ *
1485
+ * @param {object} opts
1486
+ * @param {Protocol} opts.protocol
1487
+ */
1488
+ ({ protocol }) => ({ protocol, lastICAPortNum: 0n, lastICQPortNum: 0n }),
1489
+ {
1490
+ async allocateCustomIBCPort(specifiedName = '') {
1491
+ const { state } = this;
1492
+ let localAddr = `/ibc-port/`;
1493
+
1494
+ if (specifiedName) {
1495
+ throwIfInvalidPortName(specifiedName);
1496
+
1497
+ localAddr = `/ibc-port/custom-${specifiedName}`;
1498
+ }
1499
+
1500
+ // Allocate an IBC port with a unique generated name.
1501
+ return watch(E(state.protocol).bindPort(localAddr));
1502
+ },
1503
+ async allocateICAControllerPort() {
1504
+ const { state } = this;
1505
+ state.lastICAPortNum += 1n;
1506
+ return watch(
1507
+ E(state.protocol).bindPort(
1508
+ `/ibc-port/icacontroller-${state.lastICAPortNum}`,
1509
+ ),
1510
+ );
1511
+ },
1512
+ async allocateICQControllerPort() {
1513
+ const { state } = this;
1514
+ state.lastICQPortNum += 1n;
1515
+ return watch(
1516
+ E(state.protocol).bindPort(
1517
+ `/ibc-port/icqcontroller-${state.lastICQPortNum}`,
1518
+ ),
1519
+ );
1520
+ },
1521
+ async allocateCustomLocalPort(specifiedName = '') {
1522
+ const { state } = this;
1523
+
1524
+ let localAddr = `/local/`;
1525
+
1526
+ if (specifiedName) {
1527
+ throwIfInvalidPortName(specifiedName);
1528
+
1529
+ localAddr = `/local/custom-${specifiedName}`;
1530
+ }
1531
+
1532
+ // Allocate a local port with a unique generated name.
1533
+ return watch(E(state.protocol).bindPort(localAddr));
1534
+ },
1535
+ },
1536
+ );
1537
+ /** @typedef {ReturnType<ReturnType<typeof preparePortAllocator>>} PortAllocator */
1538
+
1539
+ /**
1540
+ * Return a package-specific singleton that pins objects until they are
1541
+ * explicitly unpinned or finalized. It needs to pin objects only because they
1542
+ * are resources that need to be released.
1543
+ *
1544
+ * The reason this functionality wasn't just baked into the other network exos
1545
+ * is to maintain upgrade-compatible with minimal additional changes.
1546
+ *
1547
+ * @param {import('@agoric/base-zone').Zone} zone
1548
+ * @param {VowTools} vowTools
1549
+ */
1550
+ const prepareFinalizer = (zone, { watch }) => {
1551
+ /**
1552
+ * @type {MapStore<{},
1553
+ * { conn: Remote<Connection>, handler: Remote<Required<ConnectionHandler>>} |
1554
+ * { closer: Remote<{ close(): PromiseVow<any> }> }
1555
+ * >}
1556
+ */
1557
+ const objToFinalizerInfo = zone.mapStore('objToFinalizerInfo');
1558
+ return zone.exo('NetworkFinalizer', undefined, {
1559
+ has(obj) {
1560
+ return objToFinalizerInfo.has(obj);
1561
+ },
1562
+ /**
1563
+ * Add a connection and handler for an `onClose` method to be called upon
1564
+ * finalization.
1565
+ * @param {Remote<Connection>} conn
1566
+ * @param {Remote<Required<ConnectionHandler>>} handler
1567
+ */
1568
+ initConnection(conn, handler) {
1569
+ objToFinalizerInfo.init(conn, harden({ conn, handler }));
1570
+ },
1571
+ /**
1572
+ * Add an object with a `close` method to be called (such as an
1573
+ * `inboundAttempt`) upon finalization.
1574
+ * @param {Remote<{ close(): PromiseVow<any> }>} closer
1575
+ */
1576
+ initCloser(closer) {
1577
+ objToFinalizerInfo.init(closer, harden({ closer }));
1578
+ },
1579
+ finalize(obj) {
1580
+ if (!objToFinalizerInfo.has(obj)) {
1581
+ return;
1582
+ }
1583
+ const disposeInfo = objToFinalizerInfo.get(obj);
1584
+ if ('conn' in disposeInfo) {
1585
+ // A connection+handler.
1586
+ const { conn, handler } = disposeInfo;
1587
+ objToFinalizerInfo.delete(obj);
1588
+ return watch(E(handler).onClose(conn, CLOSE_REASON_FINALIZER, handler));
1589
+ } else if ('closer' in disposeInfo) {
1590
+ // Just something with a `close` method.
1591
+ const { closer } = disposeInfo;
1592
+ objToFinalizerInfo.delete(obj);
1593
+ return watch(E(closer).close());
1594
+ }
1595
+ },
1596
+ unpin(obj) {
1597
+ objToFinalizerInfo.delete(obj);
1598
+ },
1599
+ });
1600
+ };
1601
+ harden(prepareFinalizer);
1602
+
1603
+ /**
1604
+ * @param {import('@agoric/base-zone').Zone} zone
1605
+ * @param {VowTools} vowTools
1606
+ * @returns {Powers}
1607
+ */
1608
+ export const prepareNetworkPowers = (zone, vowTools) => {
1609
+ const finalizer = prepareFinalizer(zone, vowTools);
1610
+ return harden({ ...vowTools, finalizer });
1611
+ };
1612
+
1613
+ /** @typedef {ReturnType<typeof prepareFinalizer>} Finalizer */