@libp2p/interface-compliance-tests 6.1.8 → 6.1.9-05d559f54

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.
Files changed (66) hide show
  1. package/dist/src/matchers.d.ts +6 -0
  2. package/dist/src/matchers.d.ts.map +1 -1
  3. package/dist/src/matchers.js +6 -0
  4. package/dist/src/matchers.js.map +1 -1
  5. package/dist/src/mocks/index.d.ts +0 -2
  6. package/dist/src/mocks/index.d.ts.map +1 -1
  7. package/dist/src/mocks/index.js +0 -2
  8. package/dist/src/mocks/index.js.map +1 -1
  9. package/dist/src/mocks/muxer.d.ts +8 -3
  10. package/dist/src/mocks/muxer.d.ts.map +1 -1
  11. package/dist/src/mocks/muxer.js +15 -6
  12. package/dist/src/mocks/muxer.js.map +1 -1
  13. package/dist/src/mocks/upgrader.d.ts.map +1 -1
  14. package/dist/src/mocks/upgrader.js +0 -1
  15. package/dist/src/mocks/upgrader.js.map +1 -1
  16. package/dist/src/transport/index.d.ts +23 -10
  17. package/dist/src/transport/index.d.ts.map +1 -1
  18. package/dist/src/transport/index.js +418 -6
  19. package/dist/src/transport/index.js.map +1 -1
  20. package/dist/src/transport/utils.d.ts +17 -0
  21. package/dist/src/transport/utils.d.ts.map +1 -0
  22. package/dist/src/transport/utils.js +63 -0
  23. package/dist/src/transport/utils.js.map +1 -0
  24. package/package.json +16 -15
  25. package/src/matchers.ts +6 -0
  26. package/src/mocks/index.ts +0 -2
  27. package/src/mocks/muxer.ts +24 -10
  28. package/src/mocks/upgrader.ts +1 -3
  29. package/src/transport/index.ts +570 -16
  30. package/src/transport/utils.ts +76 -0
  31. package/dist/src/connection/index.d.ts +0 -5
  32. package/dist/src/connection/index.d.ts.map +0 -1
  33. package/dist/src/connection/index.js +0 -135
  34. package/dist/src/connection/index.js.map +0 -1
  35. package/dist/src/mocks/connection-gater.d.ts +0 -3
  36. package/dist/src/mocks/connection-gater.d.ts.map +0 -1
  37. package/dist/src/mocks/connection-gater.js +0 -17
  38. package/dist/src/mocks/connection-gater.js.map +0 -1
  39. package/dist/src/mocks/metrics.d.ts +0 -3
  40. package/dist/src/mocks/metrics.d.ts.map +0 -1
  41. package/dist/src/mocks/metrics.js +0 -286
  42. package/dist/src/mocks/metrics.js.map +0 -1
  43. package/dist/src/mocks/peer-discovery.d.ts +0 -21
  44. package/dist/src/mocks/peer-discovery.d.ts.map +0 -1
  45. package/dist/src/mocks/peer-discovery.js +0 -47
  46. package/dist/src/mocks/peer-discovery.js.map +0 -1
  47. package/dist/src/transport/dial-test.d.ts +0 -5
  48. package/dist/src/transport/dial-test.d.ts.map +0 -1
  49. package/dist/src/transport/dial-test.js +0 -99
  50. package/dist/src/transport/dial-test.js.map +0 -1
  51. package/dist/src/transport/filter-test.d.ts +0 -5
  52. package/dist/src/transport/filter-test.d.ts.map +0 -1
  53. package/dist/src/transport/filter-test.js +0 -24
  54. package/dist/src/transport/filter-test.js.map +0 -1
  55. package/dist/src/transport/listen-test.d.ts +0 -5
  56. package/dist/src/transport/listen-test.d.ts.map +0 -1
  57. package/dist/src/transport/listen-test.js +0 -154
  58. package/dist/src/transport/listen-test.js.map +0 -1
  59. package/dist/typedoc-urls.json +0 -49
  60. package/src/connection/index.ts +0 -166
  61. package/src/mocks/connection-gater.ts +0 -18
  62. package/src/mocks/metrics.ts +0 -385
  63. package/src/mocks/peer-discovery.ts +0 -59
  64. package/src/transport/dial-test.ts +0 -125
  65. package/src/transport/filter-test.ts +0 -32
  66. package/src/transport/listen-test.ts +0 -192
@@ -27,9 +27,15 @@ interface ResetMessage {
27
27
  direction: Direction
28
28
  }
29
29
 
30
- interface CloseMessage {
30
+ interface CloseWriteMessage {
31
31
  id: string
32
- type: 'close'
32
+ type: 'closeWrite'
33
+ direction: Direction
34
+ }
35
+
36
+ interface CloseReadMessage {
37
+ id: string
38
+ type: 'closeRead'
33
39
  direction: Direction
34
40
  }
35
41
 
@@ -39,7 +45,7 @@ interface CreateMessage {
39
45
  direction: 'outbound'
40
46
  }
41
47
 
42
- type StreamMessage = DataMessage | ResetMessage | CloseMessage | CreateMessage
48
+ type StreamMessage = DataMessage | ResetMessage | CloseWriteMessage | CloseReadMessage | CreateMessage
43
49
 
44
50
  export interface MockMuxedStreamInit extends AbstractStreamInit {
45
51
  push: Pushable<StreamMessage>
@@ -84,16 +90,21 @@ class MuxedStream extends AbstractStream {
84
90
  }
85
91
 
86
92
  sendCloseWrite (): void {
87
- const closeMsg: CloseMessage = {
93
+ const closeMsg: CloseWriteMessage = {
88
94
  id: this.id,
89
- type: 'close',
95
+ type: 'closeWrite',
90
96
  direction: this.direction
91
97
  }
92
98
  this.push.push(closeMsg)
93
99
  }
94
100
 
95
101
  sendCloseRead (): void {
96
- // does not support close read, only close write
102
+ const closeMsg: CloseReadMessage = {
103
+ id: this.id,
104
+ type: 'closeRead',
105
+ direction: this.direction
106
+ }
107
+ this.push.push(closeMsg)
97
108
  }
98
109
  }
99
110
 
@@ -153,10 +164,10 @@ class MockMuxer implements StreamMuxer {
153
164
  }
154
165
  )
155
166
 
156
- this.log('muxed stream ended')
167
+ this.log('muxer ended')
157
168
  this.input.end()
158
169
  } catch (err: any) {
159
- this.log('muxed stream errored', err)
170
+ this.log.error('muxer errored - %e', err)
160
171
  this.input.end(err)
161
172
  }
162
173
  }
@@ -192,9 +203,12 @@ class MockMuxer implements StreamMuxer {
192
203
  } else if (message.type === 'reset') {
193
204
  this.log('-> reset stream %s %s', muxedStream.direction, muxedStream.id)
194
205
  muxedStream.reset()
195
- } else if (message.type === 'close') {
196
- this.log('-> closing stream %s %s', muxedStream.direction, muxedStream.id)
206
+ } else if (message.type === 'closeWrite') {
207
+ this.log('-> closing writeable end of stream %s %s', muxedStream.direction, muxedStream.id)
197
208
  muxedStream.remoteCloseWrite()
209
+ } else if (message.type === 'closeRead') {
210
+ this.log('-> closing readable end of stream %s %s', muxedStream.direction, muxedStream.id)
211
+ muxedStream.remoteCloseRead()
198
212
  }
199
213
  }
200
214
 
@@ -28,7 +28,7 @@ class MockUpgrader implements Upgrader {
28
28
  return connection
29
29
  }
30
30
 
31
- async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<Connection> {
31
+ async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<void> {
32
32
  const connection = mockConnection(multiaddrConnection, {
33
33
  direction: 'inbound',
34
34
  registrar: this.registrar,
@@ -36,8 +36,6 @@ class MockUpgrader implements Upgrader {
36
36
  })
37
37
 
38
38
  this.events?.safeDispatchEvent('connection:open', { detail: connection })
39
-
40
- return connection
41
39
  }
42
40
  }
43
41
 
@@ -1,27 +1,581 @@
1
- import dial from './dial-test.js'
2
- import filter from './filter-test.js'
3
- import listen from './listen-test.js'
1
+ import { stop } from '@libp2p/interface'
2
+ import { expect } from 'aegir/chai'
3
+ import delay from 'delay'
4
+ import drain from 'it-drain'
5
+ import { pushable } from 'it-pushable'
6
+ import pDefer from 'p-defer'
7
+ import { pEvent } from 'p-event'
8
+ import pRetry from 'p-retry'
9
+ import pWaitFor from 'p-wait-for'
10
+ import { raceSignal } from 'race-signal'
11
+ import { isValidTick } from '../is-valid-tick.js'
12
+ import { createPeer, getTransportManager, getUpgrader, slowNetwork } from './utils.js'
4
13
  import type { TestSetup } from '../index.js'
5
- import type { Transport } from '@libp2p/interface'
14
+ import type { Echo } from '@libp2p/echo'
15
+ import type { Connection, Libp2p, Stream, StreamHandler } from '@libp2p/interface'
6
16
  import type { Multiaddr } from '@multiformats/multiaddr'
17
+ import type { MultiaddrMatcher } from '@multiformats/multiaddr-matcher'
18
+ import type { Libp2pInit } from 'libp2p'
19
+ import type { DeferredPromise } from 'p-defer'
7
20
 
8
- export interface Connector {
9
- delay(ms: number): void
10
- restore(): void
21
+ export interface TransportTestFixtures {
22
+ /**
23
+ * Addresses that will be used to dial listeners - both addresses must resolve
24
+ * to the same node
25
+ */
26
+ dialAddrs?: [Multiaddr, Multiaddr]
27
+
28
+ /**
29
+ * Filter out any addresses that cannot be dialed by the transport
30
+ */
31
+ dialMultiaddrMatcher: MultiaddrMatcher
32
+
33
+ /**
34
+ * Filter out any addresses that cannot be listened on by the transport
35
+ */
36
+ listenMultiaddrMatcher: MultiaddrMatcher
37
+
38
+ /**
39
+ * Config that creates a libp2p node that can dial a listener
40
+ */
41
+ dialer: Libp2pInit
42
+
43
+ /**
44
+ * Config that creates a libp2p node that can accept dials
45
+ */
46
+ listener?: Libp2pInit
11
47
  }
12
48
 
13
- export interface TransportTestFixtures {
14
- listenAddrs: Multiaddr[]
15
- dialAddrs: Multiaddr[]
16
- dialer: Transport
17
- listener: Transport
18
- connector: Connector
49
+ async function getSetup (common: TestSetup<TransportTestFixtures>): Promise<{ dialer: Libp2p<{ echo: Echo }>, listener?: Libp2p<{ echo: Echo }>, dialAddrs: Multiaddr[], dialMultiaddrMatcher: MultiaddrMatcher, listenMultiaddrMatcher: MultiaddrMatcher }> {
50
+ const setup = await common.setup()
51
+ const dialer = await createPeer(setup.dialer)
52
+ let listener
53
+
54
+ if (setup.listener != null) {
55
+ listener = await createPeer(setup.listener)
56
+ }
57
+
58
+ const dialAddrs = listener?.getMultiaddrs() ?? setup.dialAddrs
59
+
60
+ if (dialAddrs == null) {
61
+ throw new Error('Listener config or dial addresses must be specified')
62
+ }
63
+
64
+ return {
65
+ dialer,
66
+ listener,
67
+ dialAddrs: dialAddrs.filter(ma => setup.dialMultiaddrMatcher.exactMatch(ma)),
68
+ dialMultiaddrMatcher: setup.dialMultiaddrMatcher,
69
+ listenMultiaddrMatcher: setup.listenMultiaddrMatcher
70
+ }
19
71
  }
20
72
 
21
73
  export default (common: TestSetup<TransportTestFixtures>): void => {
22
74
  describe('interface-transport', () => {
23
- dial(common)
24
- listen(common)
25
- filter(common)
75
+ let dialer: Libp2p<{ echo: Echo }>
76
+ let listener: Libp2p<{ echo: Echo }> | undefined
77
+ let dialAddrs: Multiaddr[]
78
+ let dialMultiaddrMatcher: MultiaddrMatcher
79
+
80
+ afterEach(async () => {
81
+ await stop(dialer, listener)
82
+ await common.teardown()
83
+ })
84
+
85
+ it('simple', async () => {
86
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
87
+
88
+ const input = Uint8Array.from([0, 1, 2, 3, 4])
89
+ const output = await dialer.services.echo.echo(dialAddrs[0], input, {
90
+ signal: AbortSignal.timeout(5000)
91
+ })
92
+
93
+ expect(output).to.equalBytes(input)
94
+ })
95
+
96
+ it('should listen on multiple addresses', async () => {
97
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
98
+
99
+ const input = Uint8Array.from([0, 1, 2, 3, 4])
100
+
101
+ await expect(dialer.services.echo.echo(dialAddrs[0], input, {
102
+ signal: AbortSignal.timeout(5000)
103
+ })).to.eventually.deep.equal(input)
104
+
105
+ await expect(dialer.services.echo.echo(dialAddrs[1], input, {
106
+ signal: AbortSignal.timeout(5000)
107
+ })).to.eventually.deep.equal(input)
108
+ })
109
+
110
+ it('can close connections', async () => {
111
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
112
+
113
+ const conn = await dialer.dial(dialAddrs[0], {
114
+ signal: AbortSignal.timeout(5000)
115
+ })
116
+
117
+ await conn.close()
118
+ expect(isValidTick(conn.timeline.close)).to.equal(true)
119
+ })
120
+
121
+ it('abort before dialing throws AbortError', async () => {
122
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
123
+
124
+ const controller = new AbortController()
125
+ controller.abort()
126
+
127
+ await expect(dialer.dial(dialAddrs[0], {
128
+ signal: controller.signal
129
+ })).to.eventually.be.rejected()
130
+ .with.property('name', 'AbortError')
131
+ })
132
+
133
+ it('abort while dialing throws AbortError', async () => {
134
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
135
+ slowNetwork(dialer, 100)
136
+
137
+ const controller = new AbortController()
138
+ setTimeout(() => { controller.abort() }, 50)
139
+
140
+ await expect(dialer.dial(dialAddrs[0], {
141
+ signal: controller.signal
142
+ })).to.eventually.be.rejected()
143
+ .with.property('name', 'AbortError')
144
+ })
145
+
146
+ it('should close all streams when the connection closes', async () => {
147
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
148
+
149
+ let incomingConnectionPromise: DeferredPromise<Connection> | undefined
150
+
151
+ if (listener != null) {
152
+ incomingConnectionPromise = pDefer<Connection>()
153
+
154
+ listener.addEventListener('connection:open', (event) => {
155
+ const conn = event.detail
156
+
157
+ if (conn.remotePeer.equals(dialer.peerId)) {
158
+ incomingConnectionPromise?.resolve(conn)
159
+ }
160
+ })
161
+ }
162
+
163
+ const connection = await dialer.dial(dialAddrs[0])
164
+ let remoteConn: Connection | undefined
165
+
166
+ if (incomingConnectionPromise != null) {
167
+ remoteConn = await incomingConnectionPromise.promise
168
+ }
169
+
170
+ for (let i = 0; i < 5; i++) {
171
+ await connection.newStream('/echo/1.0.0', {
172
+ maxOutboundStreams: 5
173
+ })
174
+ }
175
+
176
+ const streams = connection.streams
177
+
178
+ // Close the connection and verify all streams have been closed
179
+ await connection.close()
180
+
181
+ await pWaitFor(() => connection.streams.length === 0, {
182
+ timeout: 5000
183
+ })
184
+
185
+ if (remoteConn != null) {
186
+ await pWaitFor(() => remoteConn.streams.length === 0, {
187
+ timeout: 5000
188
+ })
189
+ }
190
+
191
+ expect(streams.find(stream => stream.status === 'open')).to.be.undefined()
192
+ })
193
+
194
+ it('should not handle connection if upgradeInbound rejects', async function () {
195
+ ({ dialer, listener, dialAddrs, dialMultiaddrMatcher } = await getSetup(common))
196
+
197
+ if (listener == null) {
198
+ return this.skip()
199
+ }
200
+
201
+ const upgrader = getUpgrader(listener)
202
+ upgrader.upgradeInbound = async () => {
203
+ await delay(100)
204
+ throw new Error('Oh noes!')
205
+ }
206
+
207
+ await expect(dialer.dial(dialAddrs[0])).to.eventually.be.rejected
208
+ .with.property('name', 'EncryptionFailedError')
209
+
210
+ expect(dialer.getConnections().filter(conn => {
211
+ return dialMultiaddrMatcher.exactMatch(conn.remoteAddr)
212
+ })).to.have.lengthOf(0)
213
+
214
+ if (listener != null) {
215
+ const remoteConnections = listener.getConnections(dialer.peerId)
216
+ .filter(conn => dialMultiaddrMatcher.exactMatch(conn.remoteAddr))
217
+ expect(remoteConnections).to.have.lengthOf(0)
218
+ }
219
+ })
220
+
221
+ it('should omit peerid in listening addresses', async function () {
222
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
223
+
224
+ if (listener == null) {
225
+ return this.skip()
226
+ }
227
+
228
+ const tm = getTransportManager(listener)
229
+ const transportListeners = tm.getListeners()
230
+
231
+ for (const transportListener of transportListeners) {
232
+ for (const ma of transportListener.getAddrs()) {
233
+ expect(ma.toString()).to.not.include(`/p2p/${listener.peerId}`)
234
+ }
235
+ }
236
+ })
237
+
238
+ it('should handle one big write', async function () {
239
+ const timeout = 120_000
240
+ this.timeout(timeout);
241
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
242
+
243
+ const input = new Uint8Array(1024 * 1024 * 10).fill(5)
244
+ const output = await dialer.services.echo.echo(dialAddrs[0], input, {
245
+ signal: AbortSignal.timeout(timeout)
246
+ })
247
+
248
+ expect(output).to.equalBytes(input)
249
+ })
250
+
251
+ it('should handle many small writes', async function () {
252
+ const timeout = 120_000
253
+ this.timeout(timeout);
254
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
255
+
256
+ for (let i = 0; i < 2000; i++) {
257
+ const input = new Uint8Array(1024).fill(5)
258
+ const output = await dialer.services.echo.echo(dialAddrs[0], input, {
259
+ signal: AbortSignal.timeout(timeout)
260
+ })
261
+
262
+ expect(output).to.equalBytes(input)
263
+ }
264
+ })
265
+
266
+ it('can close a stream for reading but send a large amount of data', async function () {
267
+ const timeout = 120_000
268
+ this.timeout(timeout);
269
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
270
+
271
+ if (listener == null) {
272
+ return this.skip()
273
+ }
274
+
275
+ const protocol = '/send-data/1.0.0'
276
+ const chunkSize = 1024
277
+ const bytes = chunkSize * 1024 * 10
278
+ const deferred = pDefer()
279
+
280
+ await listener.handle(protocol, ({ stream }) => {
281
+ Promise.resolve().then(async () => {
282
+ let read = 0
283
+
284
+ for await (const buf of stream.source) {
285
+ read += buf.byteLength
286
+
287
+ if (read === bytes) {
288
+ deferred.resolve()
289
+ break
290
+ }
291
+ }
292
+ })
293
+ .catch(err => {
294
+ deferred.reject(err)
295
+ stream.abort(err)
296
+ })
297
+ })
298
+
299
+ const stream = await dialer.dialProtocol(dialAddrs[0], protocol)
300
+
301
+ await stream.closeRead()
302
+
303
+ await stream.sink((async function * () {
304
+ for (let i = 0; i < bytes; i += chunkSize) {
305
+ yield new Uint8Array(chunkSize)
306
+ }
307
+ })())
308
+
309
+ await stream.close()
310
+
311
+ await deferred.promise
312
+ })
313
+
314
+ it('can close a stream for writing but receive a large amount of data', async function () {
315
+ const timeout = 120_000
316
+ this.timeout(timeout);
317
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
318
+
319
+ if (listener == null) {
320
+ return this.skip()
321
+ }
322
+
323
+ const protocol = '/receive-data/1.0.0'
324
+ const chunkSize = 1024
325
+ const bytes = chunkSize * 1024 * 10
326
+ const deferred = pDefer()
327
+
328
+ await listener.handle(protocol, ({ stream }) => {
329
+ Promise.resolve().then(async () => {
330
+ await stream.sink((async function * () {
331
+ for (let i = 0; i < bytes; i += chunkSize) {
332
+ yield new Uint8Array(chunkSize)
333
+ }
334
+ })())
335
+
336
+ await stream.close()
337
+ })
338
+ .catch(err => {
339
+ deferred.reject(err)
340
+ stream.abort(err)
341
+ })
342
+ })
343
+
344
+ const stream = await dialer.dialProtocol(dialAddrs[0], protocol)
345
+
346
+ await stream.closeWrite()
347
+
348
+ let read = 0
349
+
350
+ for await (const buf of stream.source) {
351
+ read += buf.byteLength
352
+ }
353
+
354
+ expect(read).to.equal(bytes)
355
+ })
356
+
357
+ it('can close local stream for writing and reading while a remote stream is writing', async function () {
358
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
359
+
360
+ if (listener == null) {
361
+ return this.skip()
362
+ }
363
+
364
+ /**
365
+ * NodeA NodeB
366
+ * | <--- STOP_SENDING |
367
+ * | FIN ---> |
368
+ * | <--- FIN |
369
+ * | FIN_ACK ---> |
370
+ * | <--- FIN_ACK |
371
+ */
372
+
373
+ const getRemoteStream = pDefer<Stream>()
374
+ const protocol = '/close-local-while-remote-writes/1.0.0'
375
+
376
+ const streamHandler: StreamHandler = ({ stream }) => {
377
+ void Promise.resolve().then(async () => {
378
+ getRemoteStream.resolve(stream)
379
+ })
380
+ }
381
+
382
+ await listener.handle(protocol, (info) => {
383
+ streamHandler(info)
384
+ }, {
385
+ runOnLimitedConnection: true
386
+ })
387
+
388
+ const connection = await dialer.dial(dialAddrs[0])
389
+
390
+ // open a stream on the echo protocol
391
+ const stream = await connection.newStream(protocol, {
392
+ runOnLimitedConnection: true
393
+ })
394
+
395
+ // close the write end immediately
396
+ const p = stream.closeWrite()
397
+
398
+ const remoteStream = await getRemoteStream.promise
399
+ // close the readable end of the remote stream
400
+ await remoteStream.closeRead()
401
+
402
+ // keep the remote write end open, this should delay the FIN_ACK reply to the local stream
403
+ const remoteInputStream = pushable<Uint8Array>()
404
+ void remoteStream.sink(remoteInputStream)
405
+
406
+ // wait for remote to receive local close-write
407
+ await pRetry(() => {
408
+ if (remoteStream.readStatus !== 'closed') {
409
+ throw new Error('Remote stream read status ' + remoteStream.readStatus)
410
+ }
411
+ }, {
412
+ minTimeout: 100
413
+ })
414
+
415
+ // remote closes write
416
+ remoteInputStream.end()
417
+
418
+ // wait to receive FIN_ACK
419
+ await p
420
+
421
+ // wait for remote to notice closure
422
+ await pRetry(() => {
423
+ if (remoteStream.status !== 'closed') {
424
+ throw new Error('Remote stream not closed')
425
+ }
426
+ })
427
+
428
+ assertStreamClosed(stream)
429
+ assertStreamClosed(remoteStream)
430
+ })
431
+
432
+ it('can close local stream for writing and reading while a remote stream is writing using source/sink', async function () {
433
+ ({ dialer, listener, dialAddrs } = await getSetup(common))
434
+
435
+ if (listener == null) {
436
+ return this.skip()
437
+ }
438
+
439
+ /**
440
+ * NodeA NodeB
441
+ * | FIN ---> |
442
+ * | <--- FIN |
443
+ * | FIN_ACK ---> |
444
+ * | <--- FIN_ACK |
445
+ */
446
+
447
+ const getRemoteStream = pDefer<Stream>()
448
+ const protocol = '/close-local-while-remote-reads/1.0.0'
449
+
450
+ const streamHandler: StreamHandler = ({ stream }) => {
451
+ void Promise.resolve().then(async () => {
452
+ getRemoteStream.resolve(stream)
453
+ })
454
+ }
455
+
456
+ await listener.handle(protocol, (info) => {
457
+ streamHandler(info)
458
+ }, {
459
+ runOnLimitedConnection: true
460
+ })
461
+
462
+ const connection = await dialer.dial(dialAddrs[0])
463
+
464
+ // open a stream on the echo protocol
465
+ const stream = await connection.newStream(protocol, {
466
+ runOnLimitedConnection: true
467
+ })
468
+
469
+ // keep the remote write end open, this should delay the FIN_ACK reply to the local stream
470
+ const p = stream.sink([])
471
+
472
+ const remoteStream = await getRemoteStream.promise
473
+ // close the readable end of the remote stream
474
+ await remoteStream.closeRead()
475
+ // readable end should finish
476
+ await drain(remoteStream.source)
477
+
478
+ // wait for remote to receive local close-write
479
+ await pRetry(() => {
480
+ if (remoteStream.readStatus !== 'closed') {
481
+ throw new Error('Remote stream read status ' + remoteStream.readStatus)
482
+ }
483
+ }, {
484
+ minTimeout: 100
485
+ })
486
+
487
+ // remote closes write
488
+ await remoteStream.sink([])
489
+
490
+ // wait to receive FIN_ACK
491
+ await p
492
+
493
+ // close read end of stream
494
+ await stream.closeRead()
495
+ // readable end should finish
496
+ await drain(stream.source)
497
+
498
+ // wait for remote to notice closure
499
+ await pRetry(() => {
500
+ if (remoteStream.status !== 'closed') {
501
+ throw new Error('Remote stream not closed')
502
+ }
503
+ })
504
+
505
+ assertStreamClosed(stream)
506
+ assertStreamClosed(remoteStream)
507
+ })
508
+ })
509
+
510
+ describe('events', () => {
511
+ let dialer: Libp2p<{ echo: Echo }>
512
+ let listener: Libp2p<{ echo: Echo }> | undefined
513
+ let listenMultiaddrMatcher: MultiaddrMatcher
514
+
515
+ afterEach(async () => {
516
+ await stop(dialer, listener)
517
+ await common.teardown()
518
+ })
519
+
520
+ it('emits listening', async function () {
521
+ ({ dialer, listener, listenMultiaddrMatcher } = await getSetup(common))
522
+
523
+ if (listener == null) {
524
+ return this.skip()
525
+ }
526
+
527
+ await listener.stop()
528
+
529
+ const transportListeningPromise = pDefer()
530
+
531
+ listener.addEventListener('transport:listening', (event) => {
532
+ const transportListener = event.detail
533
+
534
+ if (transportListener.getAddrs().some(ma => listenMultiaddrMatcher.exactMatch(ma))) {
535
+ transportListeningPromise.resolve()
536
+ }
537
+ })
538
+
539
+ await listener.start()
540
+
541
+ await raceSignal(transportListeningPromise.promise, AbortSignal.timeout(1000), {
542
+ errorMessage: 'Did not emit listening event'
543
+ })
544
+ })
545
+
546
+ it('emits close', async function () {
547
+ ({ dialer, listener } = await getSetup(common))
548
+
549
+ if (listener == null) {
550
+ return this.skip()
551
+ }
552
+
553
+ const transportManager = getTransportManager(listener)
554
+ const transportListener = transportManager.getListeners()
555
+ .filter(listener => listener.getAddrs().some(ma => listenMultiaddrMatcher.exactMatch(ma)))
556
+ .pop()
557
+
558
+ if (transportListener == null) {
559
+ throw new Error('Could not find address listener')
560
+ }
561
+
562
+ const p = pEvent(transportListener, 'close')
563
+
564
+ await listener.stop()
565
+
566
+ await raceSignal(p, AbortSignal.timeout(1000), {
567
+ errorMessage: 'Did not emit close event'
568
+ })
569
+ })
26
570
  })
27
571
  }
572
+
573
+ function assertStreamClosed (stream: Stream): void {
574
+ expect(stream.status).to.equal('closed')
575
+ expect(stream.readStatus).to.equal('closed')
576
+ expect(stream.writeStatus).to.equal('closed')
577
+
578
+ expect(stream.timeline.close).to.be.a('number')
579
+ expect(stream.timeline.closeRead).to.be.a('number')
580
+ expect(stream.timeline.closeWrite).to.be.a('number')
581
+ }