@libp2p/interop 5.0.0 → 6.0.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,76 @@
1
+ import { Multiaddr, multiaddr } from '@multiformats/multiaddr'
2
+ import { expect } from 'aegir/chai'
3
+ import type { Daemon, DaemonFactory, NodeType, SpawnOptions } from '../index.js'
4
+ import { Status } from './pb/index.js'
5
+ import { echoHandler, reserve } from './util.js'
6
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
+ import type { IdentifyResult } from '@libp2p/daemon-client'
8
+ import { handshake } from 'it-handshake'
9
+
10
+ export function relayTests (factory: DaemonFactory): void {
11
+ const t: NodeType[] = ['go', 'js']
12
+ t.forEach(a => { t.forEach(b => { t.forEach(r => { relayTest(factory, a, b, r) }) }) })
13
+ }
14
+
15
+ function relayTest (factory: DaemonFactory, aType: NodeType, bType: NodeType, relayType: NodeType): void {
16
+ describe(`${aType} to ${bType} over relay ${relayType}`, () => {
17
+ const opts: SpawnOptions[] = [
18
+ { type: aType, noise: true, noListen: true },
19
+ { type: bType, noise: true, noListen: true },
20
+ { type: relayType, noise: true, relay: true }
21
+ ]
22
+
23
+ let aNode: Daemon
24
+ let bNode: Daemon
25
+ let relay: Daemon
26
+ let bId: IdentifyResult
27
+ let relayId: IdentifyResult
28
+ let bAddrViaRelay: Multiaddr
29
+
30
+ beforeEach(async function () {
31
+ this.timeout(20 * 1000)
32
+ ;[aNode, bNode, relay] = await Promise.all(opts.map(async o => await factory.spawn(o)))
33
+ ;[bId, relayId] = await Promise.all([bNode, relay].map(async d => await d.client.identify()))
34
+
35
+ // construct a relay address
36
+ bAddrViaRelay = multiaddr(`${relayId.addrs[0].toString()}/p2p/${relayId.peerId.toString()}/p2p-circuit/p2p/${bId.peerId.toString()}`)
37
+
38
+ // connect b to the relay
39
+ await bNode.client.connect(relayId.peerId, relayId.addrs)
40
+ })
41
+
42
+ afterEach(async function () {
43
+ await Promise.all([aNode, bNode, relay].map(async d => { await d.stop() }))
44
+ })
45
+
46
+ it('connects', async () => {
47
+ // b makes reservation on relay
48
+ const reserveResponse = await reserve(bNode, relayId.peerId)
49
+ expect(reserveResponse.status).to.eq(Status.OK)
50
+
51
+ // a dials b through relay
52
+ await aNode.client.connect(bId.peerId, [bAddrViaRelay])
53
+ await new Promise(resolve => setTimeout(resolve, 500))
54
+ const connectedPeers = await aNode.client.listPeers()
55
+ expect(connectedPeers.filter(p => p.equals(bId.peerId))).to.have.length(1)
56
+
57
+ // run an echo test
58
+ await bNode.client.registerStreamHandler(echoHandler.protocol, echoHandler.handler)
59
+ const stream = await aNode.client.openStream(bId.peerId, echoHandler.protocol)
60
+
61
+ // send some data, read the response
62
+ const input = uint8ArrayFromString('test')
63
+ const shake = handshake(stream)
64
+ shake.write(input)
65
+ const output = await shake.read()
66
+
67
+ expect(output?.subarray()).to.deep.equal(input)
68
+ })
69
+
70
+ it('fails to connect without a reservation', async () => {
71
+ // a dials b through relay
72
+ await expect(aNode.client.connect(bId.peerId, [bAddrViaRelay])).to.eventually.be.rejected
73
+ .with.property('message').that.matches(/NO_RESERVATION/)
74
+ })
75
+ })
76
+ }
@@ -0,0 +1,67 @@
1
+ syntax = "proto3";
2
+
3
+ message HopMessage {
4
+ enum Type {
5
+ RESERVE = 0;
6
+ CONNECT = 1;
7
+ STATUS = 2;
8
+ }
9
+
10
+ // the presence of this field is enforced at application level
11
+ optional Type type = 1;
12
+
13
+ optional Peer peer = 2;
14
+ optional Reservation reservation = 3;
15
+ optional Limit limit = 4;
16
+
17
+ optional Status status = 5;
18
+ }
19
+
20
+ message StopMessage {
21
+ enum Type {
22
+ CONNECT = 0;
23
+ STATUS = 1;
24
+ }
25
+
26
+ // the presence of this field is enforced at application level
27
+ optional Type type = 1;
28
+
29
+ optional Peer peer = 2;
30
+ optional Limit limit = 3;
31
+
32
+ optional Status status = 4;
33
+ }
34
+
35
+ message Peer {
36
+ bytes id = 1;
37
+ repeated bytes addrs = 2;
38
+ }
39
+
40
+ message Reservation {
41
+ uint64 expire = 1; // Unix expiration time (UTC)
42
+ repeated bytes addrs = 2; // relay addrs for reserving peer
43
+ optional bytes voucher = 3; // reservation voucher
44
+ }
45
+
46
+ message Limit {
47
+ optional uint32 duration = 1; // seconds
48
+ optional uint64 data = 2; // bytes
49
+ }
50
+
51
+ enum Status {
52
+ UNUSED = 0;
53
+ OK = 100;
54
+ RESERVATION_REFUSED = 200;
55
+ RESOURCE_LIMIT_EXCEEDED = 201;
56
+ PERMISSION_DENIED = 202;
57
+ CONNECTION_FAILED = 203;
58
+ NO_RESERVATION = 204;
59
+ MALFORMED_MESSAGE = 400;
60
+ UNEXPECTED_MESSAGE = 401;
61
+ }
62
+
63
+ message ReservationVoucher {
64
+ bytes relay = 1;
65
+ bytes peer = 2;
66
+ uint64 expiration = 3;
67
+ }
@@ -0,0 +1,549 @@
1
+ /* eslint-disable import/export */
2
+ /* eslint-disable complexity */
3
+ /* eslint-disable @typescript-eslint/no-namespace */
4
+ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5
+ /* eslint-disable @typescript-eslint/no-empty-interface */
6
+
7
+ import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
8
+ import type { Uint8ArrayList } from 'uint8arraylist'
9
+ import type { Codec } from 'protons-runtime'
10
+
11
+ export interface HopMessage {
12
+ type?: HopMessage.Type
13
+ peer?: Peer
14
+ reservation?: Reservation
15
+ limit?: Limit
16
+ status?: Status
17
+ }
18
+
19
+ export namespace HopMessage {
20
+ export enum Type {
21
+ RESERVE = 'RESERVE',
22
+ CONNECT = 'CONNECT',
23
+ STATUS = 'STATUS'
24
+ }
25
+
26
+ enum __TypeValues {
27
+ RESERVE = 0,
28
+ CONNECT = 1,
29
+ STATUS = 2
30
+ }
31
+
32
+ export namespace Type {
33
+ export const codec = (): Codec<Type> => {
34
+ return enumeration<Type>(__TypeValues)
35
+ }
36
+ }
37
+
38
+ let _codec: Codec<HopMessage>
39
+
40
+ export const codec = (): Codec<HopMessage> => {
41
+ if (_codec == null) {
42
+ _codec = message<HopMessage>((obj, w, opts = {}) => {
43
+ if (opts.lengthDelimited !== false) {
44
+ w.fork()
45
+ }
46
+
47
+ if (obj.type != null) {
48
+ w.uint32(8)
49
+ HopMessage.Type.codec().encode(obj.type, w)
50
+ }
51
+
52
+ if (obj.peer != null) {
53
+ w.uint32(18)
54
+ Peer.codec().encode(obj.peer, w, {
55
+ writeDefaults: false
56
+ })
57
+ }
58
+
59
+ if (obj.reservation != null) {
60
+ w.uint32(26)
61
+ Reservation.codec().encode(obj.reservation, w, {
62
+ writeDefaults: false
63
+ })
64
+ }
65
+
66
+ if (obj.limit != null) {
67
+ w.uint32(34)
68
+ Limit.codec().encode(obj.limit, w, {
69
+ writeDefaults: false
70
+ })
71
+ }
72
+
73
+ if (obj.status != null) {
74
+ w.uint32(40)
75
+ Status.codec().encode(obj.status, w)
76
+ }
77
+
78
+ if (opts.lengthDelimited !== false) {
79
+ w.ldelim()
80
+ }
81
+ }, (reader, length) => {
82
+ const obj: any = {}
83
+
84
+ const end = length == null ? reader.len : reader.pos + length
85
+
86
+ while (reader.pos < end) {
87
+ const tag = reader.uint32()
88
+
89
+ switch (tag >>> 3) {
90
+ case 1:
91
+ obj.type = HopMessage.Type.codec().decode(reader)
92
+ break
93
+ case 2:
94
+ obj.peer = Peer.codec().decode(reader, reader.uint32())
95
+ break
96
+ case 3:
97
+ obj.reservation = Reservation.codec().decode(reader, reader.uint32())
98
+ break
99
+ case 4:
100
+ obj.limit = Limit.codec().decode(reader, reader.uint32())
101
+ break
102
+ case 5:
103
+ obj.status = Status.codec().decode(reader)
104
+ break
105
+ default:
106
+ reader.skipType(tag & 7)
107
+ break
108
+ }
109
+ }
110
+
111
+ return obj
112
+ })
113
+ }
114
+
115
+ return _codec
116
+ }
117
+
118
+ export const encode = (obj: HopMessage): Uint8Array => {
119
+ return encodeMessage(obj, HopMessage.codec())
120
+ }
121
+
122
+ export const decode = (buf: Uint8Array | Uint8ArrayList): HopMessage => {
123
+ return decodeMessage(buf, HopMessage.codec())
124
+ }
125
+ }
126
+
127
+ export interface StopMessage {
128
+ type?: StopMessage.Type
129
+ peer?: Peer
130
+ limit?: Limit
131
+ status?: Status
132
+ }
133
+
134
+ export namespace StopMessage {
135
+ export enum Type {
136
+ CONNECT = 'CONNECT',
137
+ STATUS = 'STATUS'
138
+ }
139
+
140
+ enum __TypeValues {
141
+ CONNECT = 0,
142
+ STATUS = 1
143
+ }
144
+
145
+ export namespace Type {
146
+ export const codec = (): Codec<Type> => {
147
+ return enumeration<Type>(__TypeValues)
148
+ }
149
+ }
150
+
151
+ let _codec: Codec<StopMessage>
152
+
153
+ export const codec = (): Codec<StopMessage> => {
154
+ if (_codec == null) {
155
+ _codec = message<StopMessage>((obj, w, opts = {}) => {
156
+ if (opts.lengthDelimited !== false) {
157
+ w.fork()
158
+ }
159
+
160
+ if (obj.type != null) {
161
+ w.uint32(8)
162
+ StopMessage.Type.codec().encode(obj.type, w)
163
+ }
164
+
165
+ if (obj.peer != null) {
166
+ w.uint32(18)
167
+ Peer.codec().encode(obj.peer, w, {
168
+ writeDefaults: false
169
+ })
170
+ }
171
+
172
+ if (obj.limit != null) {
173
+ w.uint32(26)
174
+ Limit.codec().encode(obj.limit, w, {
175
+ writeDefaults: false
176
+ })
177
+ }
178
+
179
+ if (obj.status != null) {
180
+ w.uint32(32)
181
+ Status.codec().encode(obj.status, w)
182
+ }
183
+
184
+ if (opts.lengthDelimited !== false) {
185
+ w.ldelim()
186
+ }
187
+ }, (reader, length) => {
188
+ const obj: any = {}
189
+
190
+ const end = length == null ? reader.len : reader.pos + length
191
+
192
+ while (reader.pos < end) {
193
+ const tag = reader.uint32()
194
+
195
+ switch (tag >>> 3) {
196
+ case 1:
197
+ obj.type = StopMessage.Type.codec().decode(reader)
198
+ break
199
+ case 2:
200
+ obj.peer = Peer.codec().decode(reader, reader.uint32())
201
+ break
202
+ case 3:
203
+ obj.limit = Limit.codec().decode(reader, reader.uint32())
204
+ break
205
+ case 4:
206
+ obj.status = Status.codec().decode(reader)
207
+ break
208
+ default:
209
+ reader.skipType(tag & 7)
210
+ break
211
+ }
212
+ }
213
+
214
+ return obj
215
+ })
216
+ }
217
+
218
+ return _codec
219
+ }
220
+
221
+ export const encode = (obj: StopMessage): Uint8Array => {
222
+ return encodeMessage(obj, StopMessage.codec())
223
+ }
224
+
225
+ export const decode = (buf: Uint8Array | Uint8ArrayList): StopMessage => {
226
+ return decodeMessage(buf, StopMessage.codec())
227
+ }
228
+ }
229
+
230
+ export interface Peer {
231
+ id: Uint8Array
232
+ addrs: Uint8Array[]
233
+ }
234
+
235
+ export namespace Peer {
236
+ let _codec: Codec<Peer>
237
+
238
+ export const codec = (): Codec<Peer> => {
239
+ if (_codec == null) {
240
+ _codec = message<Peer>((obj, w, opts = {}) => {
241
+ if (opts.lengthDelimited !== false) {
242
+ w.fork()
243
+ }
244
+
245
+ if (opts.writeDefaults === true || (obj.id != null && obj.id.byteLength > 0)) {
246
+ w.uint32(10)
247
+ w.bytes(obj.id)
248
+ }
249
+
250
+ if (obj.addrs != null) {
251
+ for (const value of obj.addrs) {
252
+ w.uint32(18)
253
+ w.bytes(value)
254
+ }
255
+ }
256
+
257
+ if (opts.lengthDelimited !== false) {
258
+ w.ldelim()
259
+ }
260
+ }, (reader, length) => {
261
+ const obj: any = {
262
+ id: new Uint8Array(0),
263
+ addrs: []
264
+ }
265
+
266
+ const end = length == null ? reader.len : reader.pos + length
267
+
268
+ while (reader.pos < end) {
269
+ const tag = reader.uint32()
270
+
271
+ switch (tag >>> 3) {
272
+ case 1:
273
+ obj.id = reader.bytes()
274
+ break
275
+ case 2:
276
+ obj.addrs.push(reader.bytes())
277
+ break
278
+ default:
279
+ reader.skipType(tag & 7)
280
+ break
281
+ }
282
+ }
283
+
284
+ return obj
285
+ })
286
+ }
287
+
288
+ return _codec
289
+ }
290
+
291
+ export const encode = (obj: Peer): Uint8Array => {
292
+ return encodeMessage(obj, Peer.codec())
293
+ }
294
+
295
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => {
296
+ return decodeMessage(buf, Peer.codec())
297
+ }
298
+ }
299
+
300
+ export interface Reservation {
301
+ expire: bigint
302
+ addrs: Uint8Array[]
303
+ voucher?: Uint8Array
304
+ }
305
+
306
+ export namespace Reservation {
307
+ let _codec: Codec<Reservation>
308
+
309
+ export const codec = (): Codec<Reservation> => {
310
+ if (_codec == null) {
311
+ _codec = message<Reservation>((obj, w, opts = {}) => {
312
+ if (opts.lengthDelimited !== false) {
313
+ w.fork()
314
+ }
315
+
316
+ if (opts.writeDefaults === true || obj.expire !== 0n) {
317
+ w.uint32(8)
318
+ w.uint64(obj.expire)
319
+ }
320
+
321
+ if (obj.addrs != null) {
322
+ for (const value of obj.addrs) {
323
+ w.uint32(18)
324
+ w.bytes(value)
325
+ }
326
+ }
327
+
328
+ if (obj.voucher != null) {
329
+ w.uint32(26)
330
+ w.bytes(obj.voucher)
331
+ }
332
+
333
+ if (opts.lengthDelimited !== false) {
334
+ w.ldelim()
335
+ }
336
+ }, (reader, length) => {
337
+ const obj: any = {
338
+ expire: 0n,
339
+ addrs: []
340
+ }
341
+
342
+ const end = length == null ? reader.len : reader.pos + length
343
+
344
+ while (reader.pos < end) {
345
+ const tag = reader.uint32()
346
+
347
+ switch (tag >>> 3) {
348
+ case 1:
349
+ obj.expire = reader.uint64()
350
+ break
351
+ case 2:
352
+ obj.addrs.push(reader.bytes())
353
+ break
354
+ case 3:
355
+ obj.voucher = reader.bytes()
356
+ break
357
+ default:
358
+ reader.skipType(tag & 7)
359
+ break
360
+ }
361
+ }
362
+
363
+ return obj
364
+ })
365
+ }
366
+
367
+ return _codec
368
+ }
369
+
370
+ export const encode = (obj: Reservation): Uint8Array => {
371
+ return encodeMessage(obj, Reservation.codec())
372
+ }
373
+
374
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Reservation => {
375
+ return decodeMessage(buf, Reservation.codec())
376
+ }
377
+ }
378
+
379
+ export interface Limit {
380
+ duration?: number
381
+ data?: bigint
382
+ }
383
+
384
+ export namespace Limit {
385
+ let _codec: Codec<Limit>
386
+
387
+ export const codec = (): Codec<Limit> => {
388
+ if (_codec == null) {
389
+ _codec = message<Limit>((obj, w, opts = {}) => {
390
+ if (opts.lengthDelimited !== false) {
391
+ w.fork()
392
+ }
393
+
394
+ if (obj.duration != null) {
395
+ w.uint32(8)
396
+ w.uint32(obj.duration)
397
+ }
398
+
399
+ if (obj.data != null) {
400
+ w.uint32(16)
401
+ w.uint64(obj.data)
402
+ }
403
+
404
+ if (opts.lengthDelimited !== false) {
405
+ w.ldelim()
406
+ }
407
+ }, (reader, length) => {
408
+ const obj: any = {}
409
+
410
+ const end = length == null ? reader.len : reader.pos + length
411
+
412
+ while (reader.pos < end) {
413
+ const tag = reader.uint32()
414
+
415
+ switch (tag >>> 3) {
416
+ case 1:
417
+ obj.duration = reader.uint32()
418
+ break
419
+ case 2:
420
+ obj.data = reader.uint64()
421
+ break
422
+ default:
423
+ reader.skipType(tag & 7)
424
+ break
425
+ }
426
+ }
427
+
428
+ return obj
429
+ })
430
+ }
431
+
432
+ return _codec
433
+ }
434
+
435
+ export const encode = (obj: Limit): Uint8Array => {
436
+ return encodeMessage(obj, Limit.codec())
437
+ }
438
+
439
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Limit => {
440
+ return decodeMessage(buf, Limit.codec())
441
+ }
442
+ }
443
+
444
+ export enum Status {
445
+ UNUSED = 'UNUSED',
446
+ OK = 'OK',
447
+ RESERVATION_REFUSED = 'RESERVATION_REFUSED',
448
+ RESOURCE_LIMIT_EXCEEDED = 'RESOURCE_LIMIT_EXCEEDED',
449
+ PERMISSION_DENIED = 'PERMISSION_DENIED',
450
+ CONNECTION_FAILED = 'CONNECTION_FAILED',
451
+ NO_RESERVATION = 'NO_RESERVATION',
452
+ MALFORMED_MESSAGE = 'MALFORMED_MESSAGE',
453
+ UNEXPECTED_MESSAGE = 'UNEXPECTED_MESSAGE'
454
+ }
455
+
456
+ enum __StatusValues {
457
+ UNUSED = 0,
458
+ OK = 100,
459
+ RESERVATION_REFUSED = 200,
460
+ RESOURCE_LIMIT_EXCEEDED = 201,
461
+ PERMISSION_DENIED = 202,
462
+ CONNECTION_FAILED = 203,
463
+ NO_RESERVATION = 204,
464
+ MALFORMED_MESSAGE = 400,
465
+ UNEXPECTED_MESSAGE = 401
466
+ }
467
+
468
+ export namespace Status {
469
+ export const codec = (): Codec<Status> => {
470
+ return enumeration<Status>(__StatusValues)
471
+ }
472
+ }
473
+ export interface ReservationVoucher {
474
+ relay: Uint8Array
475
+ peer: Uint8Array
476
+ expiration: bigint
477
+ }
478
+
479
+ export namespace ReservationVoucher {
480
+ let _codec: Codec<ReservationVoucher>
481
+
482
+ export const codec = (): Codec<ReservationVoucher> => {
483
+ if (_codec == null) {
484
+ _codec = message<ReservationVoucher>((obj, w, opts = {}) => {
485
+ if (opts.lengthDelimited !== false) {
486
+ w.fork()
487
+ }
488
+
489
+ if (opts.writeDefaults === true || (obj.relay != null && obj.relay.byteLength > 0)) {
490
+ w.uint32(10)
491
+ w.bytes(obj.relay)
492
+ }
493
+
494
+ if (opts.writeDefaults === true || (obj.peer != null && obj.peer.byteLength > 0)) {
495
+ w.uint32(18)
496
+ w.bytes(obj.peer)
497
+ }
498
+
499
+ if (opts.writeDefaults === true || obj.expiration !== 0n) {
500
+ w.uint32(24)
501
+ w.uint64(obj.expiration)
502
+ }
503
+
504
+ if (opts.lengthDelimited !== false) {
505
+ w.ldelim()
506
+ }
507
+ }, (reader, length) => {
508
+ const obj: any = {
509
+ relay: new Uint8Array(0),
510
+ peer: new Uint8Array(0),
511
+ expiration: 0n
512
+ }
513
+
514
+ const end = length == null ? reader.len : reader.pos + length
515
+
516
+ while (reader.pos < end) {
517
+ const tag = reader.uint32()
518
+
519
+ switch (tag >>> 3) {
520
+ case 1:
521
+ obj.relay = reader.bytes()
522
+ break
523
+ case 2:
524
+ obj.peer = reader.bytes()
525
+ break
526
+ case 3:
527
+ obj.expiration = reader.uint64()
528
+ break
529
+ default:
530
+ reader.skipType(tag & 7)
531
+ break
532
+ }
533
+ }
534
+
535
+ return obj
536
+ })
537
+ }
538
+
539
+ return _codec
540
+ }
541
+
542
+ export const encode = (obj: ReservationVoucher): Uint8Array => {
543
+ return encodeMessage(obj, ReservationVoucher.codec())
544
+ }
545
+
546
+ export const decode = (buf: Uint8Array | Uint8ArrayList): ReservationVoucher => {
547
+ return decodeMessage(buf, ReservationVoucher.codec())
548
+ }
549
+ }
@@ -0,0 +1,35 @@
1
+ import type { PeerId } from '@libp2p/interface-peer-id'
2
+ import type { Daemon } from '../index.js'
3
+ import { HopMessage } from './pb/index.js'
4
+ import type { Duplex } from 'it-stream-types'
5
+ import type { Uint8ArrayList } from 'uint8arraylist'
6
+ import { pipe } from 'it-pipe'
7
+ import { pbStream } from 'it-pb-stream'
8
+
9
+ const RELAY_V2_HOP = '/libp2p/circuit/relay/0.2.0/hop'
10
+
11
+ export const reserve = async (d: Daemon, peerID: PeerId, message?: Partial<HopMessage>): Promise<HopMessage> => {
12
+ const stream = await d.client.openStream(peerID, RELAY_V2_HOP)
13
+ const pb = pbStream(stream)
14
+ pb.writePB({
15
+ type: HopMessage.Type.RESERVE,
16
+ ...(message ?? {})
17
+ }, HopMessage)
18
+
19
+ return await pb.readPB(HopMessage)
20
+ }
21
+
22
+ export const echoHandler = {
23
+ protocol: '/echo/1.0.0',
24
+ handler: async (stream: Duplex<Uint8ArrayList | Uint8Array, Uint8Array, Promise<void>>) => {
25
+ await pipe(
26
+ stream,
27
+ async function * (src) {
28
+ for await (const buf of src) {
29
+ yield buf.subarray()
30
+ }
31
+ },
32
+ stream
33
+ )
34
+ }
35
+ }