@agoric/network 0.1.1-dev-5676146.0 → 0.1.1-dev-e7e7c67.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
@@ -3,7 +3,6 @@
3
3
  import { E } from '@endo/far';
4
4
  import { M } from '@endo/patterns';
5
5
  import { Fail } from '@agoric/assert';
6
- import { whileTrue } from '@agoric/internal';
7
6
  import { toBytes } from './bytes.js';
8
7
 
9
8
  import '@agoric/store/exported.js';
@@ -23,14 +22,12 @@ const Shape1 = /** @type {const} */ ({
23
22
  Data: M.string(),
24
23
  Bytes: M.string(),
25
24
  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
-
25
+ Vow: M.tagged(
26
+ 'Vow',
27
+ harden({
28
+ vowV0: M.remotable('VowV0'),
29
+ }),
30
+ ),
34
31
  ConnectionHandler: M.remotable('ConnectionHandler'),
35
32
  Connection: M.remotable('Connection'),
36
33
  InboundAttempt: M.remotable('InboundAttempt'),
@@ -53,78 +50,146 @@ const Shape2 = /** @type {const} */ ({
53
50
 
54
51
  export const Shape = /** @type {const} */ harden({
55
52
  ...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()),
53
+ ConnectionI: {
54
+ connection: M.interface('Connection', {
55
+ send: M.callWhen(Shape2.Data)
56
+ .optional(Shape2.Opts)
57
+ .returns(Shape2.Vow$(Shape2.Bytes)),
58
+ close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
59
+ getLocalAddress: M.call().returns(Shape2.Endpoint),
60
+ getRemoteAddress: M.call().returns(Shape2.Endpoint),
61
+ }),
62
+ openConnectionAckWatcher: M.interface('OpenConnectionAckWatcher', {
63
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
64
+ }),
65
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
66
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
67
+ }),
68
+ sinkWatcher: M.interface('SinkWatcher', {
69
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
70
+ }),
71
+ },
72
+ InboundAttemptI: {
73
+ inboundAttempt: M.interface('InboundAttempt', {
74
+ accept: M.callWhen(Shape2.AttemptDescription).returns(
75
+ Shape2.Vow$(Shape2.Connection),
76
+ ),
77
+ getLocalAddress: M.call().returns(Shape2.Endpoint),
78
+ getRemoteAddress: M.call().returns(Shape2.Endpoint),
79
+ close: M.callWhen().returns(Shape2.Vow$(M.undefined())),
80
+ }),
81
+ inboundAttemptAcceptWatcher: M.interface('InboundAttemptAcceptWatcher', {
82
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
83
+ }),
84
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
85
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
86
+ }),
87
+ sinkWatcher: M.interface('SinkWatcher', {
88
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
89
+ }),
90
+ },
91
+ PortI: {
92
+ port: M.interface('Port', {
93
+ getLocalAddress: M.call().returns(Shape2.Endpoint),
94
+ addListener: M.callWhen(Shape2.Listener).returns(
95
+ Shape2.Vow$(M.undefined()),
96
+ ),
97
+ connect: M.callWhen(Shape2.Endpoint)
98
+ .optional(Shape2.ConnectionHandler)
99
+ .returns(Shape2.Vow$(Shape2.Connection)),
100
+ removeListener: M.callWhen(Shape2.Listener).returns(
101
+ Shape2.Vow$(M.undefined()),
102
+ ),
103
+ revoke: M.callWhen().returns(Shape2.Vow$(M.undefined())),
104
+ }),
105
+ portAddListenerWatcher: M.interface('PortAddListenerWatcher', {
106
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
107
+ }),
108
+ portRemoveListenerWatcher: M.interface('PortRemoveListenerWatcher', {
109
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
110
+ }),
111
+ portConnectWatcher: M.interface('PortConnectWatcher', {
112
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
113
+ }),
114
+ portRevokeWatcher: M.interface('PortRevokeWatcher', {
115
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
116
+ }),
117
+ portRevokeCleanupWatcher: M.interface('PortRevokeCleanupWatcher', {
118
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
119
+ }),
120
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
121
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
122
+ }),
123
+ sinkWatcher: M.interface('SinkWatcher', {
124
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
125
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
126
+ }),
127
+ },
128
+ ProtocolHandlerI: {
129
+ protocolHandler: M.interface('ProtocolHandler', {
130
+ onCreate: M.callWhen(M.remotable(), Shape2.ProtocolHandler).returns(
131
+ Shape2.Vow$(M.undefined()),
132
+ ),
133
+ generatePortID: M.callWhen(
134
+ Shape2.Endpoint,
135
+ Shape2.ProtocolHandler,
136
+ ).returns(Shape2.Vow$(M.string())),
137
+ onBind: M.callWhen(
138
+ Shape2.Port,
139
+ Shape2.Endpoint,
140
+ Shape2.ProtocolHandler,
141
+ ).returns(Shape2.Vow$(M.undefined())),
142
+ onListen: M.callWhen(
143
+ Shape2.Port,
144
+ Shape2.Endpoint,
145
+ Shape2.ListenHandler,
146
+ Shape2.ProtocolHandler,
147
+ ).returns(Shape2.Vow$(M.undefined())),
148
+ onListenRemove: M.callWhen(
149
+ Shape2.Port,
150
+ Shape2.Endpoint,
151
+ Shape2.ListenHandler,
152
+ Shape2.ProtocolHandler,
153
+ ).returns(Shape2.Vow$(M.undefined())),
154
+ onInstantiate: M.callWhen(
155
+ Shape2.Port,
156
+ Shape2.Endpoint,
157
+ Shape2.Endpoint,
158
+ Shape2.ProtocolHandler,
159
+ ).returns(Shape2.Vow$(Shape2.Endpoint)),
160
+ onConnect: M.callWhen(
161
+ Shape2.Port,
162
+ Shape2.Endpoint,
163
+ Shape2.Endpoint,
164
+ Shape2.ConnectionHandler,
165
+ Shape2.ProtocolHandler,
166
+ ).returns(Shape2.Vow$(Shape2.AttemptDescription)),
167
+ onRevoke: M.callWhen(
168
+ Shape2.Port,
169
+ Shape2.Endpoint,
170
+ Shape2.ProtocolHandler,
171
+ ).returns(Shape2.Vow$(M.undefined())),
172
+ }),
173
+ protocolHandlerAcceptWatcher: M.interface('ProtocolHandlerAcceptWatcher', {
174
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
175
+ }),
176
+ protocolHandlerInstantiateWatcher: M.interface(
177
+ 'ProtocolHandlerInstantiateWatcher',
178
+ {
179
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
180
+ },
88
181
  ),
89
- generatePortID: M.callWhen(Shape2.Endpoint, Shape2.ProtocolHandler).returns(
90
- Shape2.Vow$(M.string()),
182
+ protocolHandlerConnectWatcher: M.interface(
183
+ 'ProtocolHandlerConnectWatcher',
184
+ {
185
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(),
186
+ },
91
187
  ),
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
- }),
188
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
189
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
190
+ }),
191
+ },
192
+
128
193
  ProtocolImplI: M.interface('ProtocolImpl', {
129
194
  bind: M.callWhen(Shape2.Endpoint).returns(Shape2.Vow$(Shape2.Port)),
130
195
  inbound: M.callWhen(Shape2.Endpoint, Shape2.Endpoint).returns(
@@ -183,8 +248,8 @@ export function getPrefixes(addr) {
183
248
  * @param {import('@agoric/base-zone').Zone} zone
184
249
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
185
250
  */
186
- const prepareHalfConnection = (zone, { when }) => {
187
- const makeHalfConnection = zone.exoClass(
251
+ const prepareHalfConnection = (zone, { watch }) => {
252
+ const makeHalfConnectionKit = zone.exoClassKit(
188
253
  'Connection',
189
254
  Shape.ConnectionI,
190
255
  /** @param {ConnectionOpts} opts */
@@ -203,47 +268,81 @@ const prepareHalfConnection = (zone, { when }) => {
203
268
  };
204
269
  },
205
270
  {
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
- }
220
-
221
- const ack = await when(
222
- E(handlers[r])
223
- .onReceive(conns.get(r), toBytes(packetBytes), handlers[r])
224
- .catch(rethrowUnlessMissing),
225
- );
226
-
227
- return toBytes(ack || '');
228
- },
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);
271
+ connection: {
272
+ getLocalAddress() {
273
+ const { addrs, l } = this.state;
274
+ return addrs[l];
275
+ },
276
+ getRemoteAddress() {
277
+ const { addrs, r } = this.state;
278
+ return addrs[r];
279
+ },
280
+ /** @param {Data} packetBytes */
281
+ async send(packetBytes) {
282
+ const { closed, handlers, r, conns } = this.state;
283
+ if (closed) {
284
+ throw closed;
285
+ }
286
+
287
+ const innerVow = watch(
288
+ E(handlers[r]).onReceive(
289
+ conns.get(r),
290
+ toBytes(packetBytes),
291
+ handlers[r],
292
+ ),
293
+ this.facets.openConnectionAckWatcher,
294
+ );
295
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
296
+ },
297
+ async close() {
298
+ const { closed, current, conns, l, handlers } = this.state;
299
+ if (closed) {
300
+ throw Error(closed);
301
+ }
302
+ this.state.closed = 'Connection closed';
303
+ current.delete(conns.get(l));
304
+ const innerVow = watch(
305
+ E(this.state.handlers[l]).onClose(
306
+ conns.get(l),
307
+ undefined,
308
+ handlers[l],
309
+ ),
310
+ this.facets.sinkWatcher,
311
+ );
312
+
313
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
314
+ },
315
+ },
316
+ openConnectionAckWatcher: {
317
+ onFulfilled(ack) {
318
+ return toBytes(ack || '');
319
+ },
320
+ },
321
+ rethrowUnlessMissingWatcher: {
322
+ onRejected(e) {
323
+ rethrowUnlessMissing(e);
324
+ },
325
+ },
326
+ sinkWatcher: {
327
+ onFulfilled(_value) {
328
+ return undefined;
329
+ },
243
330
  },
244
331
  },
245
332
  );
246
333
 
334
+ const makeHalfConnection = ({ addrs, handlers, conns, current, l, r }) => {
335
+ const { connection } = makeHalfConnectionKit({
336
+ addrs,
337
+ handlers,
338
+ conns,
339
+ current,
340
+ l,
341
+ r,
342
+ });
343
+ return harden(connection);
344
+ };
345
+
247
346
  return makeHalfConnection;
248
347
  };
249
348
 
@@ -308,8 +407,8 @@ export const crossoverConnection = (
308
407
  * @param {(opts: ConnectionOpts) => Connection} makeConnection
309
408
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
310
409
  */
311
- const prepareInboundAttempt = (zone, makeConnection, { when }) => {
312
- const makeInboundAttempt = zone.exoClass(
410
+ const prepareInboundAttempt = (zone, makeConnection, { watch }) => {
411
+ const makeInboundAttemptKit = zone.exoClassKit(
313
412
  'InboundAttempt',
314
413
  Shape.InboundAttemptI,
315
414
  /**
@@ -340,75 +439,121 @@ const prepareInboundAttempt = (zone, makeConnection, { when }) => {
340
439
  };
341
440
  },
342
441
  {
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;
353
-
354
- if (consummated) {
355
- throw Error(consummated);
356
- }
357
- this.state.consummated = 'Already closed';
358
-
359
- const [port, listener] = listening.get(listenPrefix);
360
-
361
- const current = currentConnections.get(port);
362
- current.delete(this.self);
363
-
364
- await when(
365
- E(listener).onReject(port, localAddr, remoteAddr, listener),
366
- ).catch(rethrowUnlessMissing);
367
- },
368
- /**
369
- * @param {object} opts
370
- * @param {string} [opts.localAddress]
371
- * @param {string} [opts.remoteAddress]
372
- * @param {ConnectionHandler} opts.handler
373
- */
374
- async accept({ localAddress, remoteAddress, handler: rchandler }) {
375
- const { consummated, localAddr, remoteAddr } = this.state;
376
- const { listening, listenPrefix, currentConnections } = this.state;
377
- if (consummated) {
378
- throw Error(consummated);
379
- }
380
- this.state.consummated = 'Already accepted';
381
-
382
- if (localAddress === undefined) {
383
- localAddress = localAddr;
384
- }
385
-
386
- if (remoteAddress === undefined) {
387
- remoteAddress = remoteAddr;
388
- }
389
-
390
- const [port, listener] = listening.get(listenPrefix);
391
- const current = currentConnections.get(port);
392
-
393
- current.delete(this.self);
394
-
395
- const lchandler = await when(
396
- E(listener).onAccept(port, localAddress, remoteAddress, listener),
397
- );
398
-
399
- return crossoverConnection(
400
- zone,
401
- lchandler,
402
- localAddress,
403
- rchandler,
404
- remoteAddress,
405
- makeConnection,
406
- current,
407
- )[1];
442
+ inboundAttempt: {
443
+ getLocalAddress() {
444
+ // Return address metadata.
445
+ return this.state.localAddr;
446
+ },
447
+ getRemoteAddress() {
448
+ return this.state.remoteAddr;
449
+ },
450
+ async close() {
451
+ const { consummated, localAddr, remoteAddr } = this.state;
452
+ const { listening, listenPrefix, currentConnections } = this.state;
453
+
454
+ if (consummated) {
455
+ throw Error(consummated);
456
+ }
457
+ this.state.consummated = 'Already closed';
458
+
459
+ const [port, listener] = listening.get(listenPrefix);
460
+
461
+ const current = currentConnections.get(port);
462
+ current.delete(this.facets.inboundAttempt);
463
+
464
+ const innerVow = watch(
465
+ E(listener).onReject(port, localAddr, remoteAddr, listener),
466
+ this.facets.sinkWatcher,
467
+ );
468
+
469
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
470
+ },
471
+ /**
472
+ * @param {object} opts
473
+ * @param {string} [opts.localAddress]
474
+ * @param {string} [opts.remoteAddress]
475
+ * @param {ConnectionHandler} opts.handler
476
+ */
477
+ async accept({ localAddress, remoteAddress, handler: rchandler }) {
478
+ const { consummated, localAddr, remoteAddr } = this.state;
479
+ const { listening, listenPrefix, currentConnections } = this.state;
480
+ if (consummated) {
481
+ throw Error(consummated);
482
+ }
483
+
484
+ if (localAddress === undefined) {
485
+ localAddress = localAddr;
486
+ }
487
+ this.state.consummated = `${localAddress} Already accepted`;
488
+
489
+ if (remoteAddress === undefined) {
490
+ remoteAddress = remoteAddr;
491
+ }
492
+
493
+ const [port, listener] = listening.get(listenPrefix);
494
+ const current = currentConnections.get(port);
495
+
496
+ current.delete(this.facets.inboundAttempt);
497
+
498
+ return watch(
499
+ E(listener).onAccept(port, localAddress, remoteAddress, listener),
500
+ this.facets.inboundAttemptAcceptWatcher,
501
+ {
502
+ localAddress,
503
+ rchandler,
504
+ remoteAddress,
505
+ current,
506
+ },
507
+ );
508
+ },
509
+ },
510
+ inboundAttemptAcceptWatcher: {
511
+ onFulfilled(lchandler, watchContext) {
512
+ const { localAddress, rchandler, remoteAddress, current } =
513
+ watchContext;
514
+
515
+ return crossoverConnection(
516
+ zone,
517
+ lchandler,
518
+ localAddress,
519
+ rchandler,
520
+ remoteAddress,
521
+ makeConnection,
522
+ current,
523
+ )[1];
524
+ },
525
+ },
526
+ rethrowUnlessMissingWatcher: {
527
+ onRejected(e) {
528
+ rethrowUnlessMissing(e);
529
+ },
530
+ },
531
+ sinkWatcher: {
532
+ onFulfilled(_value) {
533
+ return undefined;
534
+ },
408
535
  },
409
536
  },
410
537
  );
411
538
 
539
+ const makeInboundAttempt = ({
540
+ localAddr,
541
+ remoteAddr,
542
+ currentConnections,
543
+ listenPrefix,
544
+ listening,
545
+ }) => {
546
+ const { inboundAttempt } = makeInboundAttemptKit({
547
+ localAddr,
548
+ remoteAddr,
549
+ currentConnections,
550
+ listenPrefix,
551
+ listening,
552
+ });
553
+
554
+ return harden(inboundAttempt);
555
+ };
556
+
412
557
  return makeInboundAttempt;
413
558
  };
414
559
 
@@ -423,10 +568,12 @@ const RevokeState = /** @type {const} */ ({
423
568
  * @param {import('@agoric/zone').Zone} zone
424
569
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
425
570
  */
426
- const preparePort = (zone, { when }) => {
571
+ const preparePort = (zone, powers) => {
427
572
  const makeIncapable = zone.exoClass('Incapable', undefined, () => ({}), {});
428
573
 
429
- const makePort = zone.exoClass(
574
+ const { watch, allVows } = powers;
575
+
576
+ const makePortKit = zone.exoClassKit(
430
577
  'Port',
431
578
  Shape.PortI,
432
579
  /**
@@ -461,114 +608,204 @@ const preparePort = (zone, { when }) => {
461
608
  };
462
609
  },
463
610
  {
464
- getLocalAddress() {
465
- // Works even after revoke().
466
- return this.state.localAddr;
467
- },
468
- /** @param {ListenHandler} listenHandler */
469
- async addListener(listenHandler) {
470
- const { revoked, listening, localAddr, protocolHandler } = this.state;
471
-
472
- !revoked || Fail`Port ${this.state.localAddr} is revoked`;
473
- listenHandler || Fail`listenHandler is not defined`;
474
-
475
- if (listening.has(localAddr)) {
476
- // Last one wins.
477
- const [lport, lhandler] = listening.get(localAddr);
478
- if (lhandler === listenHandler) {
479
- return;
611
+ port: {
612
+ getLocalAddress() {
613
+ // Works even after revoke().
614
+ return this.state.localAddr;
615
+ },
616
+ /** @param {ListenHandler} listenHandler */
617
+ async addListener(listenHandler) {
618
+ const { revoked, listening, localAddr, protocolHandler } = this.state;
619
+
620
+ !revoked || Fail`Port ${this.state.localAddr} is revoked`;
621
+ listenHandler || Fail`listenHandler is not defined`;
622
+
623
+ if (listening.has(localAddr)) {
624
+ // Last one wins.
625
+ const [lport, lhandler] = listening.get(localAddr);
626
+ if (lhandler === listenHandler) {
627
+ return;
628
+ }
629
+ listening.set(localAddr, [this.facets.port, listenHandler]);
630
+ E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
631
+ } else {
632
+ listening.init(
633
+ localAddr,
634
+ harden([this.facets.port, listenHandler]),
635
+ );
480
636
  }
481
- listening.set(localAddr, [this.self, listenHandler]);
482
- E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
483
- } else {
484
- listening.init(localAddr, harden([this.self, listenHandler]));
485
- }
486
637
 
487
- // ASSUME: that the listener defines onAccept.
638
+ // ASSUME: that the listener defines onAccept.
488
639
 
489
- await when(
490
- E(protocolHandler).onListen(
491
- this.self,
492
- localAddr,
493
- listenHandler,
494
- protocolHandler,
495
- ),
496
- );
497
- await when(E(listenHandler).onListen(this.self, listenHandler)).catch(
498
- rethrowUnlessMissing,
499
- );
500
- },
501
- /** @param {ListenHandler} listenHandler */
502
- async removeListener(listenHandler) {
503
- const { listening, localAddr, protocolHandler } = this.state;
504
- listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
505
- listening.get(localAddr)[1] === listenHandler ||
506
- Fail`Port ${localAddr} handler to remove is not listening`;
507
- listening.delete(localAddr);
508
- await when(
509
- E(protocolHandler).onListenRemove(
510
- this.self,
511
- localAddr,
512
- listenHandler,
513
- protocolHandler,
514
- ),
515
- );
516
- await when(E(listenHandler).onRemove(this.self, listenHandler)).catch(
517
- rethrowUnlessMissing,
518
- );
519
- },
520
- /**
521
- * @param {Endpoint} remotePort
522
- * @param {ConnectionHandler} connectionHandler
523
- */
524
- async connect(
525
- remotePort,
526
- connectionHandler = /** @type {any} */ (makeIncapable()),
527
- ) {
528
- const { revoked, localAddr, protocolImpl, openConnections } =
529
- this.state;
530
-
531
- !revoked || Fail`Port ${localAddr} is revoked`;
532
- /** @type {Endpoint} */
533
- const dst = harden(remotePort);
534
-
535
- const conn = await when(
536
- protocolImpl.outbound(this.self, dst, connectionHandler),
537
- );
538
- if (revoked) {
539
- void E(conn).close();
540
- } else {
541
- openConnections.add(conn);
542
- }
543
- return conn;
544
- },
545
- async revoke() {
546
- const { revoked, localAddr } = this.state;
547
- const { protocolHandler, currentConnections, listening, boundPorts } =
548
- this.state;
549
-
550
- revoked !== RevokeState.REVOKED ||
551
- Fail`Port ${localAddr} is already revoked`;
552
- this.state.revoked = RevokeState.REVOKING;
553
- await when(
554
- E(protocolHandler).onRevoke(this.self, localAddr, protocolHandler),
555
- );
556
- this.state.revoked = RevokeState.REVOKED;
557
-
558
- // Clean up everything we did.
559
- const values = [...currentConnections.get(this.self).values()];
560
- const ps = values.map(conn => when(E(conn).close()).catch(_ => {}));
561
- if (listening.has(localAddr)) {
562
- const listener = listening.get(localAddr)[1];
563
- ps.push(this.self.removeListener(listener));
564
- }
565
- await Promise.all(ps);
566
- currentConnections.delete(this.self);
567
- boundPorts.delete(localAddr);
640
+ const innerVow = watch(
641
+ E(protocolHandler).onListen(
642
+ this.facets.port,
643
+ localAddr,
644
+ listenHandler,
645
+ protocolHandler,
646
+ ),
647
+ this.facets.portAddListenerWatcher,
648
+ { listenHandler },
649
+ );
650
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
651
+ },
652
+ /** @param {ListenHandler} listenHandler */
653
+ async removeListener(listenHandler) {
654
+ const { listening, localAddr, protocolHandler } = this.state;
655
+ listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
656
+ listening.get(localAddr)[1] === listenHandler ||
657
+ Fail`Port ${localAddr} handler to remove is not listening`;
658
+ listening.delete(localAddr);
659
+
660
+ const innerVow = watch(
661
+ E(protocolHandler).onListenRemove(
662
+ this.facets.port,
663
+ localAddr,
664
+ listenHandler,
665
+ protocolHandler,
666
+ ),
667
+ this.facets.portRemoveListenerWatcher,
668
+ { listenHandler },
669
+ );
670
+ return watch(innerVow, this.facets.rethrowUnlessMissingWatcher);
671
+ },
672
+ /**
673
+ * @param {Endpoint} remotePort
674
+ * @param {ConnectionHandler} connectionHandler
675
+ */
676
+ async connect(
677
+ remotePort,
678
+ connectionHandler = /** @type {any} */ (makeIncapable()),
679
+ ) {
680
+ const { revoked, localAddr, protocolImpl } = this.state;
681
+
682
+ !revoked || Fail`Port ${localAddr} is revoked`;
683
+ /** @type {Endpoint} */
684
+ const dst = harden(remotePort);
685
+ return watch(
686
+ protocolImpl.outbound(this.facets.port, dst, connectionHandler),
687
+ this.facets.portConnectWatcher,
688
+ { revoked },
689
+ );
690
+ },
691
+ async revoke() {
692
+ const { revoked, localAddr } = this.state;
693
+ const { protocolHandler } = this.state;
694
+
695
+ revoked !== RevokeState.REVOKED ||
696
+ Fail`Port ${localAddr} is already revoked`;
697
+
698
+ this.state.revoked = RevokeState.REVOKING;
699
+
700
+ const revokeVow = watch(
701
+ E(protocolHandler).onRevoke(
702
+ this.facets.port,
703
+ localAddr,
704
+ protocolHandler,
705
+ ),
706
+ this.facets.portRevokeWatcher,
707
+ );
708
+
709
+ return watch(revokeVow, this.facets.portRevokeCleanupWatcher);
710
+ },
711
+ },
712
+ portAddListenerWatcher: {
713
+ onFulfilled(_value, watcherContext) {
714
+ const { listenHandler } = watcherContext;
715
+ return E(listenHandler).onListen(this.facets.port, listenHandler);
716
+ },
717
+ },
718
+ portRemoveListenerWatcher: {
719
+ onFulfilled(_value, watcherContext) {
720
+ const { listenHandler } = watcherContext;
721
+ return E(listenHandler).onRemove(this.facets.port, listenHandler);
722
+ },
723
+ },
724
+ portConnectWatcher: {
725
+ onFulfilled(conn, watchContext) {
726
+ const { revoked } = watchContext;
727
+ const { openConnections } = this.state;
728
+
729
+ if (revoked) {
730
+ void E(conn).close();
731
+ } else {
732
+ openConnections.add(conn);
733
+ }
734
+ return conn;
735
+ },
736
+ },
737
+ portRevokeWatcher: {
738
+ onFulfilled(_value) {
739
+ const { currentConnections, listening, localAddr } = this.state;
740
+ const port = this.facets.port;
741
+
742
+ // Clean up everything we did.
743
+ const values = [...currentConnections.get(port).values()];
744
+
745
+ /** @type {import('@agoric/vow').Specimen[]} */
746
+ const ps = [];
747
+
748
+ ps.push(
749
+ ...values.map(conn =>
750
+ watch(E(conn).close(), this.facets.sinkWatcher),
751
+ ),
752
+ );
753
+
754
+ if (listening.has(localAddr)) {
755
+ const listener = listening.get(localAddr)[1];
756
+ ps.push(port.removeListener(listener));
757
+ }
758
+
759
+ return watch(allVows(ps), this.facets.rethrowUnlessMissingWatcher);
760
+ },
761
+ },
762
+ sinkWatcher: {
763
+ onFulfilled() {
764
+ return undefined;
765
+ },
766
+ onRejected() {
767
+ return undefined;
768
+ },
769
+ },
770
+ portRevokeCleanupWatcher: {
771
+ onFulfilled(_value) {
772
+ const { currentConnections, boundPorts, localAddr } = this.state;
773
+
774
+ this.state.revoked = RevokeState.REVOKED;
775
+
776
+ currentConnections.delete(this.facets.port);
777
+ boundPorts.delete(localAddr);
778
+ },
779
+ },
780
+ rethrowUnlessMissingWatcher: {
781
+ onRejected(e) {
782
+ rethrowUnlessMissing(e);
783
+ },
568
784
  },
569
785
  },
570
786
  );
571
787
 
788
+ const makePort = ({
789
+ localAddr,
790
+ listening,
791
+ openConnections,
792
+ currentConnections,
793
+ boundPorts,
794
+ protocolHandler,
795
+ protocolImpl,
796
+ }) => {
797
+ const { port } = makePortKit({
798
+ localAddr,
799
+ listening,
800
+ openConnections,
801
+ currentConnections,
802
+ boundPorts,
803
+ protocolHandler,
804
+ protocolImpl,
805
+ });
806
+ return harden(port);
807
+ };
808
+
572
809
  return makePort;
573
810
  };
574
811
 
@@ -578,21 +815,76 @@ const preparePort = (zone, { when }) => {
578
815
  */
579
816
  const prepareBinder = (zone, powers) => {
580
817
  const makeConnection = prepareHalfConnection(zone, powers);
581
- const { when } = powers;
818
+
819
+ const { watch } = powers;
820
+
582
821
  const makeInboundAttempt = prepareInboundAttempt(
583
822
  zone,
584
823
  makeConnection,
585
824
  powers,
586
825
  );
826
+
587
827
  const makePort = preparePort(zone, powers);
828
+
588
829
  const detached = zone.detached();
589
830
 
590
- const makeBinderKit = zone.exoClassKit(
831
+ const makeFullBinderKit = zone.exoClassKit(
591
832
  'binder',
592
833
  {
593
834
  protocolImpl: Shape.ProtocolImplI,
594
835
  binder: M.interface('Binder', {
595
- bind: M.callWhen(Shape.Endpoint).returns(Shape.Port),
836
+ bind: M.callWhen(Shape.Endpoint).returns(Shape2.Vow$(Shape.Port)),
837
+ }),
838
+ binderInboundInstantiateWatcher: M.interface(
839
+ 'BinderInboundInstantiateWatcher',
840
+ {
841
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
842
+ },
843
+ ),
844
+ binderInboundInstantiateCatchWatcher: M.interface(
845
+ 'BinderInboundInstantiateCatchWatcher',
846
+ {
847
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
848
+ },
849
+ ),
850
+ binderOutboundInstantiateWatcher: M.interface(
851
+ 'BinderOutboundInstantiateWatcher',
852
+ {
853
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
854
+ },
855
+ ),
856
+ binderOutboundConnectWatcher: M.interface(
857
+ 'BinderOutboundConnectWatcher',
858
+ {
859
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
860
+ },
861
+ ),
862
+ binderOutboundCatchWatcher: M.interface('BinderOutboundCatchWatcher', {
863
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
864
+ }),
865
+ binderOutboundInboundWatcher: M.interface(
866
+ 'BinderOutboundInboundWatcher',
867
+ {
868
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
869
+ },
870
+ ),
871
+ binderOutboundAcceptWatcher: M.interface('BinderOutboundAcceptWatcher', {
872
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
873
+ }),
874
+ binderBindGeneratePortWatcher: M.interface(
875
+ 'BinderBindGeneratePortWatcher',
876
+ {
877
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
878
+ },
879
+ ),
880
+ binderPortWatcher: M.interface('BinderPortWatcher', {
881
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
882
+ }),
883
+ binderBindWatcher: M.interface('BinderBindWatcher', {
884
+ onFulfilled: M.call(M.any()).rest(M.any()).returns(M.any()),
885
+ }),
886
+ rethrowUnlessMissingWatcher: M.interface('RethrowUnlessMissingWatcher', {
887
+ onRejected: M.call(M.any()).rest(M.any()).returns(M.any()),
596
888
  }),
597
889
  },
598
890
  /**
@@ -613,8 +905,6 @@ const prepareBinder = (zone, powers) => {
613
905
  revoked: RevokeState.NOT_REVOKED,
614
906
  openConnections,
615
907
  protocolHandler,
616
- /** @type {Endpoint | undefined} */
617
- localAddr: undefined,
618
908
  };
619
909
  },
620
910
  {
@@ -624,49 +914,54 @@ const prepareBinder = (zone, powers) => {
624
914
  * @param {Endpoint} remoteAddr
625
915
  */
626
916
  async inbound(listenAddr, remoteAddr) {
627
- const { listening, protocolHandler, currentConnections } = this.state;
917
+ const { listening, protocolHandler } = this.state;
918
+
919
+ const prefixes = getPrefixes(listenAddr);
920
+ let listenPrefixIndex = 0;
921
+ let listenPrefix;
628
922
 
629
- let lastFailure = Error(`No listeners for ${listenAddr}`);
630
- for await (const listenPrefix of getPrefixes(listenAddr)) {
923
+ while (listenPrefixIndex < prefixes.length) {
924
+ listenPrefix = prefixes[listenPrefixIndex];
631
925
  if (!listening.has(listenPrefix)) {
926
+ listenPrefixIndex += 1;
632
927
  continue;
633
928
  }
634
- const [port, _] = listening.get(listenPrefix);
635
- let localAddr;
636
-
637
- await (async () => {
638
- // See if our protocol is willing to receive this connection.
639
- const localInstance = await when(
640
- E(protocolHandler).onInstantiate(
641
- port,
642
- listenPrefix,
643
- remoteAddr,
644
- protocolHandler,
645
- ),
646
- ).catch(rethrowUnlessMissing);
647
- localAddr = localInstance
648
- ? `${listenAddr}/${localInstance}`
649
- : listenAddr;
650
- })().catch(e => {
651
- lastFailure = e;
652
- });
653
- if (!localAddr) {
654
- continue;
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
- });
665
929
 
666
- current.add(inboundAttempt);
667
- return inboundAttempt;
930
+ break;
668
931
  }
669
- throw lastFailure;
932
+
933
+ if (listenPrefixIndex >= prefixes.length) {
934
+ throw Error(`No listeners for ${listenAddr}`);
935
+ }
936
+
937
+ const [port] = listening.get(/** @type {string} **/ (listenPrefix));
938
+
939
+ const innerVow = watch(
940
+ E(protocolHandler).onInstantiate(
941
+ /** @type {Port} **/ (port),
942
+ prefixes[listenPrefixIndex],
943
+ remoteAddr,
944
+ protocolHandler,
945
+ ),
946
+ this.facets.binderInboundInstantiateWatcher,
947
+ {
948
+ listenAddr,
949
+ remoteAddr,
950
+ port,
951
+ listenPrefixIndex,
952
+ },
953
+ );
954
+
955
+ return watch(
956
+ innerVow,
957
+ this.facets.binderInboundInstantiateCatchWatcher,
958
+ {
959
+ listenPrefixIndex,
960
+ listenAddr,
961
+ remoteAddr,
962
+ lastFailure: Error(`No listeners for ${listenAddr}`),
963
+ },
964
+ );
670
965
  },
671
966
  /**
672
967
  * @param {Port} port
@@ -674,101 +969,293 @@ const prepareBinder = (zone, powers) => {
674
969
  * @param {ConnectionHandler} lchandler
675
970
  */
676
971
  async outbound(port, remoteAddr, lchandler) {
677
- const { protocolHandler, currentConnections } = this.state;
972
+ const { protocolHandler } = this.state;
678
973
 
679
974
  const localAddr = await E(port).getLocalAddress();
680
975
 
681
976
  // Allocate a local address.
682
- const initialLocalInstance = await when(
977
+ const instantiateInnerVow = watch(
683
978
  E(protocolHandler).onInstantiate(
684
979
  port,
685
980
  localAddr,
686
981
  remoteAddr,
687
982
  protocolHandler,
688
983
  ),
689
- ).catch(rethrowUnlessMissing);
690
- const initialLocalAddr = initialLocalInstance
691
- ? `${localAddr}/${initialLocalInstance}`
692
- : localAddr;
984
+ this.facets.binderOutboundInstantiateWatcher,
985
+ {
986
+ port,
987
+ localAddr,
988
+ remoteAddr,
989
+ protocolHandler,
990
+ },
991
+ );
693
992
 
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;
993
+ const instantiateVow = watch(
994
+ instantiateInnerVow,
995
+ this.facets.rethrowUnlessMissingWatcher,
996
+ );
997
+
998
+ const attemptVow = watch(
999
+ instantiateVow,
1000
+ this.facets.binderOutboundInboundWatcher,
1001
+ {
1002
+ localAddr,
1003
+ remoteAddr,
1004
+ },
1005
+ );
1006
+ const acceptedVow = watch(
1007
+ attemptVow,
1008
+ this.facets.binderOutboundAcceptWatcher,
1009
+ {
1010
+ handler: lchandler,
1011
+ },
1012
+ );
1013
+
1014
+ return watch(acceptedVow, this.facets.binderOutboundCatchWatcher, {
1015
+ port,
1016
+ remoteAddr,
1017
+ lchandler,
1018
+ localAddr,
704
1019
  });
705
- if (accepted) {
706
- return accepted;
1020
+ },
1021
+ async bind(localAddr) {
1022
+ return this.facets.binder.bind(localAddr);
1023
+ },
1024
+ },
1025
+ binder: {
1026
+ /** @param {string} localAddr */
1027
+ async bind(localAddr) {
1028
+ const { protocolHandler } = this.state;
1029
+
1030
+ // Check if we are underspecified (ends in slash)
1031
+ const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
1032
+
1033
+ const localAddrVow = watch(
1034
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
1035
+ this.facets.binderBindGeneratePortWatcher,
1036
+ {
1037
+ underspecified,
1038
+ localAddr,
1039
+ },
1040
+ );
1041
+
1042
+ return watch(localAddrVow, this.facets.binderBindWatcher);
1043
+ },
1044
+ },
1045
+ binderInboundInstantiateWatcher: {
1046
+ onFulfilled(localInstance, watchContext) {
1047
+ const { listenAddr, remoteAddr, port, listenPrefixIndex } =
1048
+ watchContext;
1049
+ const { listening, currentConnections } = this.state;
1050
+ const prefixes = getPrefixes(listenAddr);
1051
+
1052
+ const localAddr = localInstance
1053
+ ? `${listenAddr}/${localInstance}`
1054
+ : listenAddr;
1055
+ const current = currentConnections.get(port);
1056
+ const inboundAttempt = makeInboundAttempt({
1057
+ localAddr,
1058
+ remoteAddr,
1059
+ currentConnections,
1060
+ listenPrefix: prefixes[listenPrefixIndex],
1061
+ listening,
1062
+ });
1063
+
1064
+ current.add(inboundAttempt);
1065
+ return inboundAttempt;
1066
+ },
1067
+ },
1068
+ binderInboundInstantiateCatchWatcher: {
1069
+ onRejected(e, watchContext) {
1070
+ let { lastFailure, listenPrefixIndex } = watchContext;
1071
+
1072
+ try {
1073
+ rethrowUnlessMissing(e);
1074
+ } catch (innerE) {
1075
+ lastFailure = innerE;
707
1076
  }
708
1077
 
709
- const {
710
- remoteAddress = remoteAddr,
711
- handler: rchandler,
712
- localAddress = localAddr,
713
- } =
714
- /** @type {Partial<AttemptDescription>} */
715
- (
716
- await when(
717
- E(protocolHandler).onConnect(
718
- port,
719
- initialLocalAddr,
720
- remoteAddr,
721
- lchandler,
722
- protocolHandler,
723
- ),
724
- )
725
- );
1078
+ const { listenAddr, remoteAddr } = watchContext;
1079
+
1080
+ const { listening, protocolHandler } = this.state;
1081
+
1082
+ const prefixes = getPrefixes(listenAddr);
1083
+
1084
+ let listenPrefix;
1085
+
1086
+ listenPrefixIndex += 1;
1087
+
1088
+ while (listenPrefixIndex < prefixes.length) {
1089
+ listenPrefix = prefixes[listenPrefixIndex];
1090
+ if (!listening.has(listenPrefix)) {
1091
+ listenPrefixIndex += 1;
1092
+ continue;
1093
+ }
1094
+
1095
+ break;
1096
+ }
1097
+
1098
+ if (listenPrefixIndex >= prefixes.length) {
1099
+ throw lastFailure;
1100
+ }
1101
+
1102
+ const [port] = listening.get(/** @type {string} */ (listenPrefix));
1103
+
1104
+ const innerVow = watch(
1105
+ E(protocolHandler).onInstantiate(
1106
+ port,
1107
+ prefixes[listenPrefixIndex],
1108
+ remoteAddr,
1109
+ protocolHandler,
1110
+ ),
1111
+ this.facets.binderInboundInstantiateWatcher,
1112
+ {
1113
+ listenAddr,
1114
+ remoteAddr,
1115
+ port,
1116
+ listenPrefixIndex,
1117
+ },
1118
+ );
1119
+
1120
+ return watch(
1121
+ innerVow,
1122
+ this.facets.binderInboundInstantiateCatchWatcher,
1123
+ {
1124
+ ...watchContext,
1125
+ lastFailure,
1126
+ listenPrefixIndex,
1127
+ },
1128
+ );
1129
+ },
1130
+ },
1131
+ binderOutboundInstantiateWatcher: {
1132
+ onFulfilled(localInstance, watchContext) {
1133
+ const { localAddr } = watchContext;
1134
+
1135
+ return localInstance ? `${localAddr}/${localInstance}` : localAddr;
1136
+ },
1137
+ },
1138
+ binderOutboundConnectWatcher: {
1139
+ onFulfilled({ handler: rchandler }, watchContext) {
1140
+ const { lastFailure, remoteAddr, localAddr, lchandler, port } =
1141
+ watchContext;
1142
+
1143
+ const { currentConnections } = this.state;
726
1144
 
727
1145
  if (!rchandler) {
728
1146
  throw lastFailure;
729
1147
  }
730
1148
 
731
1149
  const current = currentConnections.get(port);
1150
+
732
1151
  return crossoverConnection(
733
1152
  zone,
734
1153
  lchandler,
735
- localAddress,
1154
+ localAddr,
736
1155
  rchandler,
737
- remoteAddress,
1156
+ remoteAddr,
738
1157
  makeConnection,
739
1158
  current,
740
1159
  )[0];
741
1160
  },
742
- async bind(localAddr) {
743
- return this.facets.binder.bind(localAddr);
1161
+ },
1162
+ binderOutboundCatchWatcher: {
1163
+ onRejected(e, watchContext) {
1164
+ let lastFailure;
1165
+
1166
+ try {
1167
+ rethrowUnlessMissing(e);
1168
+ } catch (innerE) {
1169
+ lastFailure = innerE;
1170
+ }
1171
+
1172
+ const { port, remoteAddr, lchandler, localAddr } = watchContext;
1173
+
1174
+ const { protocolHandler } = this.state;
1175
+
1176
+ const connectVow = watch(
1177
+ E(protocolHandler).onConnect(
1178
+ port,
1179
+ localAddr,
1180
+ remoteAddr,
1181
+ lchandler,
1182
+ protocolHandler,
1183
+ ),
1184
+ );
1185
+
1186
+ return watch(connectVow, this.facets.binderOutboundConnectWatcher, {
1187
+ lastFailure,
1188
+ remoteAddr,
1189
+ localAddr,
1190
+ lchandler,
1191
+ port,
1192
+ });
744
1193
  },
745
1194
  },
746
- binder: {
747
- /** @param {string} localAddr */
748
- async bind(localAddr) {
1195
+ binderOutboundInboundWatcher: {
1196
+ onFulfilled(initialLocalAddress, watchContext) {
1197
+ const { remoteAddr, localAddr } = watchContext;
1198
+
1199
+ if (initialLocalAddress === undefined) {
1200
+ initialLocalAddress = localAddr;
1201
+ }
1202
+
1203
+ // Attempt the loopback connection.
1204
+ return this.facets.protocolImpl.inbound(
1205
+ remoteAddr,
1206
+ initialLocalAddress,
1207
+ );
1208
+ },
1209
+ },
1210
+ binderOutboundAcceptWatcher: {
1211
+ onFulfilled(attempt, watchContext) {
1212
+ const { handler } = watchContext;
1213
+ return attempt.accept({ handler });
1214
+ },
1215
+ },
1216
+ binderBindGeneratePortWatcher: {
1217
+ onFulfilled(portID, watchContext) {
1218
+ const { localAddr, underspecified } = watchContext;
1219
+ const { protocolHandler, boundPorts } = this.state;
1220
+
1221
+ if (!underspecified) {
1222
+ return localAddr;
1223
+ }
1224
+
1225
+ const newAddr = `${localAddr}${portID}`;
1226
+ if (!boundPorts.has(newAddr)) {
1227
+ return newAddr;
1228
+ }
1229
+ return watch(
1230
+ E(protocolHandler).generatePortID(localAddr, protocolHandler),
1231
+ this.facets.binderBindGeneratePortWatcher,
1232
+ watchContext,
1233
+ );
1234
+ },
1235
+ },
1236
+ binderPortWatcher: {
1237
+ onFulfilled(_value, watchContext) {
1238
+ const { port, localAddr } = watchContext;
1239
+ const { boundPorts, currentConnections } = this.state;
1240
+
1241
+ boundPorts.init(localAddr, harden(port));
1242
+ currentConnections.init(
1243
+ port,
1244
+ zone.detached().setStore('connections'),
1245
+ );
1246
+ return port;
1247
+ },
1248
+ },
1249
+ binderBindWatcher: {
1250
+ onFulfilled(localAddr) {
749
1251
  const {
750
- protocolHandler,
751
1252
  boundPorts,
752
1253
  listening,
753
1254
  openConnections,
754
1255
  currentConnections,
1256
+ protocolHandler,
755
1257
  } = this.state;
756
1258
 
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
1259
  if (boundPorts.has(localAddr)) {
773
1260
  return boundPorts.get(localAddr);
774
1261
  }
@@ -783,17 +1270,38 @@ const prepareBinder = (zone, powers) => {
783
1270
  protocolImpl: this.facets.protocolImpl,
784
1271
  });
785
1272
 
786
- await when(
1273
+ return watch(
787
1274
  E(protocolHandler).onBind(port, localAddr, protocolHandler),
1275
+ this.facets.binderPortWatcher,
1276
+ {
1277
+ port,
1278
+ localAddr,
1279
+ },
788
1280
  );
789
- boundPorts.init(localAddr, harden(port));
790
- currentConnections.init(port, detached.setStore('connections'));
791
- return port;
1281
+ },
1282
+ },
1283
+ rethrowUnlessMissingWatcher: {
1284
+ onRejected(e) {
1285
+ rethrowUnlessMissing(e);
792
1286
  },
793
1287
  },
794
1288
  },
795
1289
  );
796
1290
 
1291
+ const makeBinderKit = ({
1292
+ currentConnections,
1293
+ boundPorts,
1294
+ listening,
1295
+ protocolHandler,
1296
+ }) => {
1297
+ const { protocolImpl, binder } = makeFullBinderKit({
1298
+ currentConnections,
1299
+ boundPorts,
1300
+ listening,
1301
+ protocolHandler,
1302
+ });
1303
+ return harden({ protocolImpl, binder });
1304
+ };
797
1305
  return makeBinderKit;
798
1306
  };
799
1307
 
@@ -925,10 +1433,10 @@ export const prepareEchoConnectionKit = zone => {
925
1433
  * @param {import('@agoric/base-zone').Zone} zone
926
1434
  * @param {ReturnType<import('@agoric/vow').prepareVowTools>} powers
927
1435
  */
928
- export function prepareLoopbackProtocolHandler(zone, { when }) {
1436
+ export function prepareLoopbackProtocolHandler(zone, { watch }) {
929
1437
  const detached = zone.detached();
930
1438
 
931
- const makeLoopbackProtocolHandler = zone.exoClass(
1439
+ const makeLoopbackProtocolHandlerKit = zone.exoClassKit(
932
1440
  'ProtocolHandler',
933
1441
  Shape.ProtocolHandlerI,
934
1442
  /** @param {string} [instancePrefix] */
@@ -944,80 +1452,118 @@ export function prepareLoopbackProtocolHandler(zone, { when }) {
944
1452
  };
945
1453
  },
946
1454
  {
947
- async onCreate(_impl, _protocolHandler) {
948
- // noop
1455
+ protocolHandler: {
1456
+ async onCreate(_impl, _protocolHandler) {
1457
+ // noop
1458
+ },
1459
+ async generatePortID(_localAddr, _protocolHandler) {
1460
+ this.state.portNonce += 1n;
1461
+ return `port${this.state.portNonce}`;
1462
+ },
1463
+ async onBind(_port, _localAddr, _protocolHandler) {
1464
+ // noop, for now; Maybe handle a bind?
1465
+ },
1466
+ async onConnect(
1467
+ _port,
1468
+ localAddr,
1469
+ remoteAddr,
1470
+ _chandler,
1471
+ protocolHandler,
1472
+ ) {
1473
+ const { listeners } = this.state;
1474
+ const [lport, lhandler] = listeners.get(remoteAddr);
1475
+
1476
+ const acceptVow = watch(
1477
+ E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler),
1478
+ this.facets.protocolHandlerAcceptWatcher,
1479
+ );
1480
+
1481
+ const instantiateInnerVow = watch(
1482
+ E(protocolHandler).onInstantiate(
1483
+ lport,
1484
+ remoteAddr,
1485
+ localAddr,
1486
+ protocolHandler,
1487
+ ),
1488
+ this.facets.protocolHandlerInstantiateWatcher,
1489
+ );
1490
+
1491
+ const instantiateVow = watch(
1492
+ instantiateInnerVow,
1493
+ this.facets.rethrowUnlessMissingWatcher,
1494
+ );
1495
+ return watch(
1496
+ Promise.all([acceptVow, instantiateVow]),
1497
+ this.facets.protocolHandlerConnectWatcher,
1498
+ );
1499
+ },
1500
+ async onInstantiate(_port, _localAddr, _remote, _protocol) {
1501
+ const { instancePrefix } = this.state;
1502
+ this.state.instanceNonce += 1n;
1503
+ return `${instancePrefix}${this.state.instanceNonce}`;
1504
+ },
1505
+ async onListen(port, localAddr, listenHandler, _protocolHandler) {
1506
+ const { listeners } = this.state;
1507
+
1508
+ // This implementation has a simple last-one-wins replacement policy.
1509
+ // Other handlers might use different policies.
1510
+ if (listeners.has(localAddr)) {
1511
+ const lhandler = listeners.get(localAddr)[1];
1512
+ if (lhandler !== listenHandler) {
1513
+ listeners.set(localAddr, [port, listenHandler]);
1514
+ }
1515
+ } else {
1516
+ listeners.init(localAddr, harden([port, listenHandler]));
1517
+ }
1518
+ },
1519
+ /**
1520
+ * @param {Port} port
1521
+ * @param {Endpoint} localAddr
1522
+ * @param {ListenHandler} listenHandler
1523
+ * @param {*} _protocolHandler
1524
+ */
1525
+ async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
1526
+ const { listeners } = this.state;
1527
+ const [lport, lhandler] = listeners.get(localAddr);
1528
+ lport === port || Fail`Port does not match listener on ${localAddr}`;
1529
+ lhandler === listenHandler ||
1530
+ Fail`Listen handler does not match listener on ${localAddr}`;
1531
+ listeners.delete(localAddr);
1532
+ },
1533
+ async onRevoke(_port, _localAddr, _protocolHandler) {
1534
+ // This is an opportunity to clean up resources.
1535
+ },
1536
+ },
1537
+ protocolHandlerAcceptWatcher: {
1538
+ onFulfilled(rchandler) {
1539
+ return rchandler;
1540
+ },
949
1541
  },
950
- async generatePortID(_localAddr, _protocolHandler) {
951
- this.state.portNonce += 1n;
952
- return `port${this.state.portNonce}`;
1542
+ protocolHandlerConnectWatcher: {
1543
+ onFulfilled(results) {
1544
+ return {
1545
+ remoteInstance: results[0],
1546
+ handler: results[1],
1547
+ };
1548
+ },
953
1549
  },
954
- async onBind(_port, _localAddr, _protocolHandler) {
955
- // noop, for now; Maybe handle a bind?
1550
+ protocolHandlerInstantiateWatcher: {
1551
+ onFulfilled(remoteInstance) {
1552
+ return remoteInstance;
1553
+ },
956
1554
  },
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
- }
1001
- },
1002
- /**
1003
- * @param {Port} port
1004
- * @param {Endpoint} localAddr
1005
- * @param {ListenHandler} listenHandler
1006
- * @param {*} _protocolHandler
1007
- */
1008
- async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
1009
- const { listeners } = this.state;
1010
- const [lport, lhandler] = listeners.get(localAddr);
1011
- lport === port || Fail`Port does not match listener on ${localAddr}`;
1012
- lhandler === listenHandler ||
1013
- Fail`Listen handler does not match listener on ${localAddr}`;
1014
- listeners.delete(localAddr);
1015
- },
1016
- async onRevoke(_port, _localAddr, _protocolHandler) {
1017
- // This is an opportunity to clean up resources.
1555
+ rethrowUnlessMissingWatcher: {
1556
+ onRejected(e) {
1557
+ rethrowUnlessMissing(e);
1558
+ },
1018
1559
  },
1019
1560
  },
1020
1561
  );
1021
1562
 
1563
+ const makeLoopbackProtocolHandler = instancePrefix => {
1564
+ const { protocolHandler } = makeLoopbackProtocolHandlerKit(instancePrefix);
1565
+ return harden(protocolHandler);
1566
+ };
1567
+
1022
1568
  return makeLoopbackProtocolHandler;
1023
1569
  }