@agoric/network 0.1.1-dev-2f092c3.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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Here is the difference between Textaddr and Multiaddr:
3
+ *
4
+ * - unspecified port on local ibc interface:
5
+ * - /if/ibc0
6
+ * - [['if', 'ibc0']]
7
+ * - specific local port:
8
+ * - /if/ibc0/ordered/transfer
9
+ * - [['if', 'ibc0'], ['ordered', 'transfer']]
10
+ * - remote pointer to chain:
11
+ * - /dnsaddr/ibc.testnet.agoric.com/ordered/transfer
12
+ * - [['dnsaddr', 'ibc.testnet.agoric.com'], ['ordered', 'transfer']]
13
+ * - resolve step to another pointer:
14
+ * - /dnsaddr/rpc.testnet.agoric.com/ibc/testnet-1.19.0/gci/4bc8d.../ordered/transfer
15
+ * - [['dnsaddr', 'rpc.testnet.agoric.com'], ['ibc', 'testnet-1.19.0'], ['gci',
16
+ * '4bc8d...'], ['ordered', 'transfer']]
17
+ * - resolve to the individual interfaces:
18
+ * - /ip4/172.17.0.4/tcp/26657/tendermint/0.33/ibc/testnet-1.19.0/gci/4bc8d.../ordered/transfer
19
+ * - [['ip4', '172.17.0.4'], ['tcp', '26657'], ['tendermint', '0.33'], ['ibc',
20
+ * 'testnet-1.19.0'], ['gci', '4bc8d...'], ['ordered', 'transfer']]
21
+ *
22
+ * @typedef {[string, string][]} Multiaddr
23
+ *
24
+ * @typedef {string} Textaddr An address string formatted as in
25
+ * https://github.com/multiformats/multiaddr
26
+ */
27
+
28
+ /**
29
+ * Transform a text address to a parsed multiaddr
30
+ *
31
+ * @param {Textaddr} ma
32
+ * @returns {Multiaddr}
33
+ */
34
+ export function parse(ma) {
35
+ if (typeof ma !== 'string') {
36
+ return ma;
37
+ }
38
+ let s = ma;
39
+ let m;
40
+ /** @type {[string, string][]} */
41
+ const acc = [];
42
+ // eslint-disable-next-line no-cond-assign
43
+ while ((m = s.match(/^\/([^/]*)(\/([^/]*))?/))) {
44
+ s = s.slice(m[0].length);
45
+ if (m[2]) {
46
+ acc.push([m[1], m[3]]);
47
+ } else {
48
+ // @ts-expect-error '[string]' is not assignable to parameter of type '[string, string]'
49
+ acc.push([m[1]]);
50
+ }
51
+ }
52
+ if (s !== '' || s === ma) {
53
+ throw TypeError(
54
+ `Error parsing Multiaddr ${JSON.stringify(ma)} at ${JSON.stringify(s)}`,
55
+ );
56
+ }
57
+ return acc;
58
+ }
59
+
60
+ /**
61
+ * Transform a parsed multiaddr to a string.
62
+ *
63
+ * @param {Multiaddr | Textaddr} ma
64
+ * @returns {Textaddr}
65
+ */
66
+ export function unparse(ma) {
67
+ if (typeof ma === 'string') {
68
+ return ma;
69
+ }
70
+ const joined = ma.map(kv => kv.join('/')).join('/');
71
+ return `/${joined}`;
72
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @param {ConnectionHandler} handler0
3
+ * @param {Endpoint} addr0
4
+ * @param {ConnectionHandler} handler1
5
+ * @param {Endpoint} addr1
6
+ * @param {WeakSet<Connection>} [current]
7
+ * @returns {[Connection, Connection]}
8
+ */
9
+ export function crossoverConnection(handler0: ConnectionHandler, addr0: Endpoint, handler1: ConnectionHandler, addr1: Endpoint, current?: WeakSet<Connection> | undefined): [Connection, Connection];
10
+ /**
11
+ * Get the list of prefixes from longest to shortest.
12
+ *
13
+ * @param {string} addr
14
+ */
15
+ export function getPrefixes(addr: string): string[];
16
+ /**
17
+ * Create a protocol that has a handler.
18
+ *
19
+ * @param {ProtocolHandler} protocolHandler
20
+ * @returns {Protocol} the local capability for connecting and listening
21
+ */
22
+ export function makeNetworkProtocol(protocolHandler: ProtocolHandler): Protocol;
23
+ /**
24
+ * Create a ConnectionHandler that just echoes its packets.
25
+ *
26
+ * @returns {ConnectionHandler}
27
+ */
28
+ export function makeEchoConnectionHandler(): ConnectionHandler;
29
+ export function makeNonceMaker(prefix?: string, suffix?: string): () => Promise<string>;
30
+ /**
31
+ * Create a protocol handler that just connects to itself.
32
+ *
33
+ * @param {ProtocolHandler['onInstantiate']} [onInstantiate]
34
+ * @returns {ProtocolHandler} The localhost handler
35
+ */
36
+ export function makeLoopbackProtocolHandler(onInstantiate?: ProtocolHandler['onInstantiate']): ProtocolHandler;
37
+ /**
38
+ * Compatibility note: this must match what our peers use, so don't change it
39
+ * casually.
40
+ */
41
+ export const ENDPOINT_SEPARATOR: "/";
42
+ export function rethrowUnlessMissing(err: any): undefined;
43
+ export function makeConnection(handler: ConnectionHandler, localAddr: Endpoint, remoteAddr: Endpoint, current?: Set<Closable> | undefined): Connection;
44
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["network.js"],"names":[],"mappings":"AAuGA;;;;;;;GAOG;AACH,8CAPW,iBAAiB,SACjB,QAAQ,YACR,iBAAiB,SACjB,QAAQ,8CAEN,CAAC,UAAU,EAAE,UAAU,CAAC,CAkEpC;AAED;;;;GAIG;AACH,kCAFW,MAAM,YAahB;AAED;;;;;GAKG;AACH,qDAHW,eAAe,GACb,QAAQ,CA2RpB;AAED;;;;GAIG;AACH,6CAFa,iBAAiB,CAmB7B;AAED,wFAMC;AAED;;;;;GAKG;AACH,4DAHW,eAAe,CAAC,eAAe,CAAC,GAC9B,eAAe,CA2D3B;AAzjBD;;;GAGG;AACH,qCAAsC;AAE/B,0DAUN;AAWM,wCANI,iBAAiB,aACjB,QAAQ,cACR,QAAQ,wCAEN,UAAU,CAkEtB"}
package/src/network.js ADDED
@@ -0,0 +1,580 @@
1
+ import { makeScalarMapStore, makeLegacyMap } from '@agoric/store';
2
+ import { Far, E } from '@endo/far';
3
+ import { makePromiseKit } from '@endo/promise-kit';
4
+ import { Fail } from '@agoric/assert';
5
+ import { whileTrue } from '@agoric/internal';
6
+ import { toBytes } from './bytes.js';
7
+
8
+ import '@agoric/store/exported.js';
9
+ import './types.js';
10
+
11
+ /**
12
+ * Compatibility note: this must match what our peers use, so don't change it
13
+ * casually.
14
+ */
15
+ export const ENDPOINT_SEPARATOR = '/';
16
+
17
+ export const rethrowUnlessMissing = err => {
18
+ // Ugly hack rather than being able to determine if the function
19
+ // exists.
20
+ if (
21
+ !(err instanceof TypeError) ||
22
+ !err.message.match(/target has no method|is not a function$/)
23
+ ) {
24
+ throw err;
25
+ }
26
+ return undefined;
27
+ };
28
+
29
+ /**
30
+ * Create a handled Connection.
31
+ *
32
+ * @param {ConnectionHandler} handler
33
+ * @param {Endpoint} localAddr
34
+ * @param {Endpoint} remoteAddr
35
+ * @param {Set<Closable>} [current]
36
+ * @returns {Connection}
37
+ */
38
+ export const makeConnection = (
39
+ handler,
40
+ localAddr,
41
+ remoteAddr,
42
+ current = new Set(),
43
+ ) => {
44
+ let closed;
45
+ /** @type {Set<import('@endo/promise-kit').PromiseKit<Bytes>>} */
46
+ const pendingAcks = new Set();
47
+ /** @type {Connection} */
48
+ const connection = Far('Connection', {
49
+ getLocalAddress() {
50
+ return localAddr;
51
+ },
52
+ getRemoteAddress() {
53
+ return remoteAddr;
54
+ },
55
+ async close() {
56
+ if (closed) {
57
+ throw closed;
58
+ }
59
+ current.delete(connection);
60
+ closed = Error('Connection closed');
61
+ for (const ackDeferred of [...pendingAcks.values()]) {
62
+ pendingAcks.delete(ackDeferred);
63
+ ackDeferred.reject(closed);
64
+ }
65
+ await E(handler)
66
+ .onClose(connection, undefined, handler)
67
+ .catch(rethrowUnlessMissing);
68
+ },
69
+ async send(data, opts) {
70
+ // console.log('send', data, local === srcHandler);
71
+ if (closed) {
72
+ throw closed;
73
+ }
74
+ const bytes = toBytes(data);
75
+ const ackDeferred = makePromiseKit();
76
+ pendingAcks.add(ackDeferred);
77
+ E(handler)
78
+ .onReceive(connection, bytes, handler, opts)
79
+ .catch(err => {
80
+ rethrowUnlessMissing(err);
81
+ return '';
82
+ })
83
+ .then(
84
+ ack => {
85
+ pendingAcks.delete(ackDeferred);
86
+ ackDeferred.resolve(toBytes(ack));
87
+ },
88
+ err => {
89
+ pendingAcks.delete(ackDeferred);
90
+ ackDeferred.reject(err);
91
+ },
92
+ );
93
+ return ackDeferred.promise;
94
+ },
95
+ });
96
+
97
+ current.add(connection);
98
+ E(handler)
99
+ .onOpen(connection, localAddr, remoteAddr, handler)
100
+ .catch(rethrowUnlessMissing);
101
+ return connection;
102
+ };
103
+
104
+ /**
105
+ * @param {ConnectionHandler} handler0
106
+ * @param {Endpoint} addr0
107
+ * @param {ConnectionHandler} handler1
108
+ * @param {Endpoint} addr1
109
+ * @param {WeakSet<Connection>} [current]
110
+ * @returns {[Connection, Connection]}
111
+ */
112
+ export function crossoverConnection(
113
+ handler0,
114
+ addr0,
115
+ handler1,
116
+ addr1,
117
+ current = new WeakSet(),
118
+ ) {
119
+ /** @type {Connection[]} */
120
+ const conns = [];
121
+ /** @type {ConnectionHandler[]} */
122
+ const handlers = [handler0, handler1];
123
+ /** @type {Endpoint[]} */
124
+ const addrs = [addr0, addr1];
125
+
126
+ function makeHalfConnection(l, r) {
127
+ let closed;
128
+ conns[l] = Far('Connection', {
129
+ getLocalAddress() {
130
+ return addrs[l];
131
+ },
132
+ getRemoteAddress() {
133
+ return addrs[r];
134
+ },
135
+ async send(packetBytes) {
136
+ if (closed) {
137
+ throw closed;
138
+ }
139
+ const ack = await E(handlers[r])
140
+ .onReceive(conns[r], toBytes(packetBytes), handlers[r])
141
+ .catch(rethrowUnlessMissing);
142
+ return toBytes(ack || '');
143
+ },
144
+ async close() {
145
+ if (closed) {
146
+ throw closed;
147
+ }
148
+ closed = Error('Connection closed');
149
+ current.delete(conns[l]);
150
+ await E(handlers[l])
151
+ .onClose(conns[l], undefined, handlers[l])
152
+ .catch(rethrowUnlessMissing);
153
+ },
154
+ });
155
+ }
156
+
157
+ makeHalfConnection(0, 1);
158
+ makeHalfConnection(1, 0);
159
+
160
+ /**
161
+ * @param {number} l local side of the connection
162
+ * @param {number} r remote side of the connection
163
+ */
164
+ function openHalfConnection(l, r) {
165
+ current.add(conns[l]);
166
+ E(handlers[l])
167
+ .onOpen(conns[l], addrs[l], addrs[r], handlers[l])
168
+ .catch(rethrowUnlessMissing);
169
+ }
170
+
171
+ openHalfConnection(0, 1);
172
+ openHalfConnection(1, 0);
173
+
174
+ const [conn0, conn1] = conns;
175
+ return [conn0, conn1];
176
+ }
177
+
178
+ /**
179
+ * Get the list of prefixes from longest to shortest.
180
+ *
181
+ * @param {string} addr
182
+ */
183
+ export function getPrefixes(addr) {
184
+ const parts = addr.split(ENDPOINT_SEPARATOR);
185
+
186
+ /** @type {string[]} */
187
+ const ret = [];
188
+ for (let i = parts.length; i > 0; i -= 1) {
189
+ // Try most specific match.
190
+ const prefix = parts.slice(0, i).join(ENDPOINT_SEPARATOR);
191
+ ret.push(prefix);
192
+ }
193
+ return ret;
194
+ }
195
+
196
+ /**
197
+ * Create a protocol that has a handler.
198
+ *
199
+ * @param {ProtocolHandler} protocolHandler
200
+ * @returns {Protocol} the local capability for connecting and listening
201
+ */
202
+ export function makeNetworkProtocol(protocolHandler) {
203
+ /** @type {LegacyMap<Port, Set<Closable>>} */
204
+ // Legacy because we're storing a JS Set
205
+ const currentConnections = makeLegacyMap('port');
206
+
207
+ /**
208
+ * Currently must be a single listenHandler. TODO: Do something sensible with
209
+ * multiple handlers?
210
+ *
211
+ * @type {MapStore<Endpoint, [Port, ListenHandler]>}
212
+ */
213
+ const listening = makeScalarMapStore('localAddr');
214
+
215
+ /** @type {MapStore<string, Port>} */
216
+ const boundPorts = makeScalarMapStore('localAddr');
217
+
218
+ /** @param {Endpoint} localAddr */
219
+ const bind = async localAddr => {
220
+ // Check if we are underspecified (ends in slash)
221
+ const underspecified = localAddr.endsWith(ENDPOINT_SEPARATOR);
222
+ for await (const _ of whileTrue(() => underspecified)) {
223
+ const portID = await E(protocolHandler).generatePortID(
224
+ localAddr,
225
+ protocolHandler,
226
+ );
227
+ const newAddr = `${localAddr}${portID}`;
228
+ if (!boundPorts.has(newAddr)) {
229
+ localAddr = newAddr;
230
+ break;
231
+ }
232
+ }
233
+
234
+ if (boundPorts.has(localAddr)) {
235
+ return boundPorts.get(localAddr);
236
+ }
237
+
238
+ /** @enum {number} */
239
+ const RevokeState = {
240
+ NOT_REVOKED: 0,
241
+ REVOKING: 1,
242
+ REVOKED: 2,
243
+ };
244
+
245
+ /** @type {RevokeState} */
246
+ let revoked = RevokeState.NOT_REVOKED;
247
+ const openConnections = new Set();
248
+
249
+ /** @type {Port} */
250
+ const port = Far('Port', {
251
+ getLocalAddress() {
252
+ // Works even after revoke().
253
+ return localAddr;
254
+ },
255
+ async addListener(listenHandler) {
256
+ !revoked || Fail`Port ${localAddr} is revoked`;
257
+ listenHandler || Fail`listenHandler is not defined`;
258
+ if (listening.has(localAddr)) {
259
+ // Last one wins.
260
+ const [lport, lhandler] = listening.get(localAddr);
261
+ if (lhandler === listenHandler) {
262
+ return;
263
+ }
264
+ listening.set(localAddr, [port, listenHandler]);
265
+ E(lhandler).onRemove(lport, lhandler).catch(rethrowUnlessMissing);
266
+ } else {
267
+ listening.init(localAddr, [port, listenHandler]);
268
+ }
269
+
270
+ // TODO: Check that the listener defines onAccept.
271
+
272
+ await E(protocolHandler).onListen(
273
+ port,
274
+ localAddr,
275
+ listenHandler,
276
+ protocolHandler,
277
+ );
278
+ await E(listenHandler)
279
+ .onListen(port, listenHandler)
280
+ .catch(rethrowUnlessMissing);
281
+ },
282
+ async removeListener(listenHandler) {
283
+ listening.has(localAddr) || Fail`Port ${localAddr} is not listening`;
284
+ listening.get(localAddr)[1] === listenHandler ||
285
+ Fail`Port ${localAddr} handler to remove is not listening`;
286
+ listening.delete(localAddr);
287
+ await E(protocolHandler).onListenRemove(
288
+ port,
289
+ localAddr,
290
+ listenHandler,
291
+ protocolHandler,
292
+ );
293
+ await E(listenHandler)
294
+ .onRemove(port, listenHandler)
295
+ .catch(rethrowUnlessMissing);
296
+ },
297
+ async connect(remotePort, connectionHandler = {}) {
298
+ !revoked || Fail`Port ${localAddr} is revoked`;
299
+ /** @type {Endpoint} */
300
+ const dst = harden(remotePort);
301
+ // eslint-disable-next-line no-use-before-define
302
+ const conn = await protocolImpl.outbound(port, dst, connectionHandler);
303
+ if (revoked) {
304
+ void E(conn).close();
305
+ } else {
306
+ openConnections.add(conn);
307
+ }
308
+ return conn;
309
+ },
310
+ async revoke() {
311
+ revoked !== RevokeState.REVOKED ||
312
+ Fail`Port ${localAddr} is already revoked`;
313
+ revoked = RevokeState.REVOKING;
314
+ await E(protocolHandler).onRevoke(port, localAddr, protocolHandler);
315
+ revoked = RevokeState.REVOKED;
316
+
317
+ // Clean up everything we did.
318
+ const ps = [...currentConnections.get(port)].map(conn =>
319
+ E(conn)
320
+ .close()
321
+ .catch(_ => {}),
322
+ );
323
+ if (listening.has(localAddr)) {
324
+ const listener = listening.get(localAddr)[1];
325
+ ps.push(port.removeListener(listener));
326
+ }
327
+ await Promise.all(ps);
328
+ currentConnections.delete(port);
329
+ boundPorts.delete(localAddr);
330
+ return `Port ${localAddr} revoked`;
331
+ },
332
+ });
333
+
334
+ await E(protocolHandler).onBind(port, localAddr, protocolHandler);
335
+ boundPorts.init(localAddr, port);
336
+ currentConnections.init(port, new Set());
337
+ return port;
338
+ };
339
+
340
+ /** @type {ProtocolImpl} */
341
+ const protocolImpl = Far('ProtocolImpl', {
342
+ bind,
343
+ async inbound(listenAddr, remoteAddr) {
344
+ let lastFailure = Error(`No listeners for ${listenAddr}`);
345
+ for await (const listenPrefix of getPrefixes(listenAddr)) {
346
+ if (!listening.has(listenPrefix)) {
347
+ continue;
348
+ }
349
+ const [port, listener] = listening.get(listenPrefix);
350
+ let localAddr;
351
+ await (async () => {
352
+ // See if our protocol is willing to receive this connection.
353
+ const localInstance = await E(protocolHandler)
354
+ .onInstantiate(port, listenPrefix, remoteAddr, protocolHandler)
355
+ .catch(rethrowUnlessMissing);
356
+ localAddr = localInstance
357
+ ? `${listenAddr}/${localInstance}`
358
+ : listenAddr;
359
+ })().catch(e => {
360
+ lastFailure = e;
361
+ });
362
+ if (!localAddr) {
363
+ continue;
364
+ }
365
+ // We have a legitimate inbound attempt.
366
+ let consummated;
367
+ const current = currentConnections.get(port);
368
+ const inboundAttempt = Far('InboundAttempt', {
369
+ getLocalAddress() {
370
+ // Return address metadata.
371
+ return localAddr;
372
+ },
373
+ getRemoteAddress() {
374
+ return remoteAddr;
375
+ },
376
+ async close() {
377
+ if (consummated) {
378
+ throw consummated;
379
+ }
380
+ consummated = Error(`Already closed`);
381
+ current.delete(inboundAttempt);
382
+ await E(listener)
383
+ .onReject(port, localAddr, remoteAddr, listener)
384
+ .catch(rethrowUnlessMissing);
385
+ },
386
+ async accept({
387
+ localAddress = localAddr,
388
+ remoteAddress = remoteAddr,
389
+ handler: rchandler,
390
+ }) {
391
+ if (consummated) {
392
+ throw consummated;
393
+ }
394
+ consummated = Error(`Already accepted`);
395
+ current.delete(inboundAttempt);
396
+
397
+ const lchandler = await E(listener).onAccept(
398
+ port,
399
+ localAddr,
400
+ remoteAddr,
401
+ listener,
402
+ );
403
+
404
+ return crossoverConnection(
405
+ lchandler,
406
+ localAddress,
407
+ rchandler,
408
+ remoteAddress,
409
+ current,
410
+ )[1];
411
+ },
412
+ });
413
+ current.add(inboundAttempt);
414
+ return inboundAttempt;
415
+ }
416
+ throw lastFailure;
417
+ },
418
+ async outbound(port, remoteAddr, lchandler) {
419
+ const localAddr =
420
+ /** @type {string} */
421
+ (await E(port).getLocalAddress());
422
+
423
+ // Allocate a local address.
424
+ const initialLocalInstance = await E(protocolHandler)
425
+ .onInstantiate(port, localAddr, remoteAddr, protocolHandler)
426
+ .catch(rethrowUnlessMissing);
427
+ const initialLocalAddr = initialLocalInstance
428
+ ? `${localAddr}/${initialLocalInstance}`
429
+ : localAddr;
430
+
431
+ let lastFailure;
432
+ let accepted;
433
+ await (async () => {
434
+ // Attempt the loopback connection.
435
+ const attempt = await protocolImpl.inbound(
436
+ remoteAddr,
437
+ initialLocalAddr,
438
+ );
439
+ accepted = await attempt.accept({ handler: lchandler });
440
+ })().catch(e => {
441
+ lastFailure = e;
442
+ });
443
+ if (accepted) {
444
+ return accepted;
445
+ }
446
+
447
+ const {
448
+ remoteAddress = remoteAddr,
449
+ handler: rchandler,
450
+ localAddress = localAddr,
451
+ } =
452
+ /** @type {Partial<AttemptDescription>} */
453
+ (
454
+ await E(protocolHandler).onConnect(
455
+ port,
456
+ initialLocalAddr,
457
+ remoteAddr,
458
+ lchandler,
459
+ protocolHandler,
460
+ )
461
+ );
462
+
463
+ if (!rchandler) {
464
+ throw lastFailure;
465
+ }
466
+
467
+ const current = currentConnections.get(port);
468
+ return crossoverConnection(
469
+ lchandler,
470
+ localAddress,
471
+ rchandler,
472
+ remoteAddress,
473
+ current,
474
+ )[0];
475
+ },
476
+ });
477
+
478
+ // Wire up the local protocol to the handler.
479
+ void E(protocolHandler).onCreate(protocolImpl, protocolHandler);
480
+
481
+ // Return the user-facing protocol.
482
+ return Far('binder', { bind });
483
+ }
484
+
485
+ /**
486
+ * Create a ConnectionHandler that just echoes its packets.
487
+ *
488
+ * @returns {ConnectionHandler}
489
+ */
490
+ export function makeEchoConnectionHandler() {
491
+ let closed;
492
+ /** @type {Connection} */
493
+ return Far('ConnectionHandler', {
494
+ async onReceive(_connection, bytes, _connectionHandler) {
495
+ if (closed) {
496
+ throw closed;
497
+ }
498
+ return bytes;
499
+ },
500
+ async onClose(_connection, _reason, _connectionHandler) {
501
+ if (closed) {
502
+ throw closed;
503
+ }
504
+ closed = Error('Connection closed');
505
+ },
506
+ });
507
+ }
508
+
509
+ export function makeNonceMaker(prefix = '', suffix = '') {
510
+ let nonce = 0;
511
+ return async () => {
512
+ nonce += 1;
513
+ return `${prefix}${nonce}${suffix}`;
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Create a protocol handler that just connects to itself.
519
+ *
520
+ * @param {ProtocolHandler['onInstantiate']} [onInstantiate]
521
+ * @returns {ProtocolHandler} The localhost handler
522
+ */
523
+ export function makeLoopbackProtocolHandler(
524
+ onInstantiate = makeNonceMaker('nonce/'),
525
+ ) {
526
+ /** @type {MapStore<string, [Port, ListenHandler]>} */
527
+ const listeners = makeScalarMapStore('localAddr');
528
+
529
+ const makePortID = makeNonceMaker('port');
530
+
531
+ return Far('ProtocolHandler', {
532
+ // eslint-disable-next-line no-empty-function
533
+ async onCreate(_impl, _protocolHandler) {
534
+ // TODO
535
+ },
536
+ async generatePortID(_protocolHandler) {
537
+ return makePortID();
538
+ },
539
+ async onBind(_port, _localAddr, _protocolHandler) {
540
+ // TODO: Maybe handle a bind?
541
+ },
542
+ async onConnect(_port, localAddr, remoteAddr, _chandler, protocolHandler) {
543
+ const [lport, lhandler] = listeners.get(remoteAddr);
544
+ const rchandler =
545
+ /** @type {ConnectionHandler} */
546
+ (await E(lhandler).onAccept(lport, remoteAddr, localAddr, lhandler));
547
+ // console.log(`rchandler is`, rchandler);
548
+ const remoteInstance = await E(protocolHandler)
549
+ .onInstantiate(lport, remoteAddr, localAddr, protocolHandler)
550
+ .catch(rethrowUnlessMissing);
551
+ return {
552
+ remoteInstance,
553
+ handler: rchandler,
554
+ };
555
+ },
556
+ onInstantiate,
557
+ async onListen(port, localAddr, listenHandler, _protocolHandler) {
558
+ // TODO: Implement other listener replacement policies.
559
+ if (listeners.has(localAddr)) {
560
+ const lhandler = listeners.get(localAddr)[1];
561
+ if (lhandler !== listenHandler) {
562
+ // Last-one-wins.
563
+ listeners.set(localAddr, [port, listenHandler]);
564
+ }
565
+ } else {
566
+ listeners.init(localAddr, [port, listenHandler]);
567
+ }
568
+ },
569
+ async onListenRemove(port, localAddr, listenHandler, _protocolHandler) {
570
+ const [lport, lhandler] = listeners.get(localAddr);
571
+ lport === port || Fail`Port does not match listener on ${localAddr}`;
572
+ lhandler === listenHandler ||
573
+ Fail`Listen handler does not match listener on ${localAddr}`;
574
+ listeners.delete(localAddr);
575
+ },
576
+ async onRevoke(_port, _localAddr, _protocolHandler) {
577
+ // TODO: maybe clean up?
578
+ },
579
+ });
580
+ }