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