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