@libp2p/interface-compliance-tests 3.0.7-ea8a0637 → 3.0.7-eabf6f36

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 (183) hide show
  1. package/README.md +9 -0
  2. package/dist/src/connection/index.d.ts +5 -0
  3. package/dist/src/connection/index.d.ts.map +1 -0
  4. package/dist/src/connection/index.js +150 -0
  5. package/dist/src/connection/index.js.map +1 -0
  6. package/dist/src/connection-encryption/index.d.ts +5 -0
  7. package/dist/src/connection-encryption/index.d.ts.map +1 -0
  8. package/dist/src/connection-encryption/index.js +71 -0
  9. package/dist/src/connection-encryption/index.js.map +1 -0
  10. package/dist/src/connection-encryption/utils/index.d.ts +3 -0
  11. package/dist/src/connection-encryption/utils/index.d.ts.map +1 -0
  12. package/dist/src/connection-encryption/utils/index.js +19 -0
  13. package/dist/src/connection-encryption/utils/index.js.map +1 -0
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/is-valid-tick.d.ts.map +1 -1
  16. package/dist/src/is-valid-tick.js.map +1 -1
  17. package/dist/src/mocks/connection-encrypter.d.ts +3 -0
  18. package/dist/src/mocks/connection-encrypter.d.ts.map +1 -0
  19. package/dist/src/mocks/connection-encrypter.js +98 -0
  20. package/dist/src/mocks/connection-encrypter.js.map +1 -0
  21. package/dist/src/mocks/connection-gater.d.ts +3 -0
  22. package/dist/src/mocks/connection-gater.d.ts.map +1 -0
  23. package/dist/src/mocks/connection-gater.js +17 -0
  24. package/dist/src/mocks/connection-gater.js.map +1 -0
  25. package/dist/src/mocks/connection-manager.d.ts +29 -0
  26. package/dist/src/mocks/connection-manager.d.ts.map +1 -0
  27. package/dist/src/mocks/connection-manager.js +145 -0
  28. package/dist/src/mocks/connection-manager.js.map +1 -0
  29. package/dist/src/mocks/connection.d.ts +32 -0
  30. package/dist/src/mocks/connection.d.ts.map +1 -0
  31. package/dist/src/mocks/connection.js +167 -0
  32. package/dist/src/mocks/connection.js.map +1 -0
  33. package/dist/src/mocks/duplex.d.ts +3 -0
  34. package/dist/src/mocks/duplex.d.ts.map +1 -0
  35. package/dist/src/mocks/duplex.js +9 -0
  36. package/dist/src/mocks/duplex.js.map +1 -0
  37. package/dist/src/mocks/index.d.ts +13 -0
  38. package/dist/src/mocks/index.d.ts.map +1 -0
  39. package/dist/src/mocks/index.js +11 -0
  40. package/dist/src/mocks/index.js.map +1 -0
  41. package/dist/src/mocks/metrics.d.ts +3 -0
  42. package/dist/src/mocks/metrics.d.ts.map +1 -0
  43. package/dist/src/mocks/metrics.js +115 -0
  44. package/dist/src/mocks/metrics.js.map +1 -0
  45. package/dist/src/mocks/multiaddr-connection.d.ts +17 -0
  46. package/dist/src/mocks/multiaddr-connection.d.ts.map +1 -0
  47. package/dist/src/mocks/multiaddr-connection.js +60 -0
  48. package/dist/src/mocks/multiaddr-connection.js.map +1 -0
  49. package/dist/src/mocks/muxer.d.ts +36 -0
  50. package/dist/src/mocks/muxer.d.ts.map +1 -0
  51. package/dist/src/mocks/muxer.js +213 -0
  52. package/dist/src/mocks/muxer.js.map +1 -0
  53. package/dist/src/mocks/peer-discovery.d.ts +22 -0
  54. package/dist/src/mocks/peer-discovery.d.ts.map +1 -0
  55. package/dist/src/mocks/peer-discovery.js +47 -0
  56. package/dist/src/mocks/peer-discovery.js.map +1 -0
  57. package/dist/src/mocks/registrar.d.ts +18 -0
  58. package/dist/src/mocks/registrar.d.ts.map +1 -0
  59. package/dist/src/mocks/registrar.js +66 -0
  60. package/dist/src/mocks/registrar.js.map +1 -0
  61. package/dist/src/mocks/upgrader.d.ts +10 -0
  62. package/dist/src/mocks/upgrader.d.ts.map +1 -0
  63. package/dist/src/mocks/upgrader.js +31 -0
  64. package/dist/src/mocks/upgrader.js.map +1 -0
  65. package/dist/src/peer-discovery/index.d.ts +5 -0
  66. package/dist/src/peer-discovery/index.d.ts.map +1 -0
  67. package/dist/src/peer-discovery/index.js +66 -0
  68. package/dist/src/peer-discovery/index.js.map +1 -0
  69. package/dist/src/pubsub/api.d.ts +6 -0
  70. package/dist/src/pubsub/api.d.ts.map +1 -0
  71. package/dist/src/pubsub/api.js +87 -0
  72. package/dist/src/pubsub/api.js.map +1 -0
  73. package/dist/src/pubsub/connection-handlers.d.ts +6 -0
  74. package/dist/src/pubsub/connection-handlers.d.ts.map +1 -0
  75. package/dist/src/pubsub/connection-handlers.js +329 -0
  76. package/dist/src/pubsub/connection-handlers.js.map +1 -0
  77. package/dist/src/pubsub/emit-self.d.ts +6 -0
  78. package/dist/src/pubsub/emit-self.d.ts.map +1 -0
  79. package/dist/src/pubsub/emit-self.js +80 -0
  80. package/dist/src/pubsub/emit-self.js.map +1 -0
  81. package/dist/src/pubsub/index.d.ts +18 -0
  82. package/dist/src/pubsub/index.d.ts.map +1 -0
  83. package/dist/src/pubsub/index.js +17 -0
  84. package/dist/src/pubsub/index.js.map +1 -0
  85. package/dist/src/pubsub/messages.d.ts +6 -0
  86. package/dist/src/pubsub/messages.d.ts.map +1 -0
  87. package/dist/src/pubsub/messages.js +48 -0
  88. package/dist/src/pubsub/messages.js.map +1 -0
  89. package/dist/src/pubsub/multiple-nodes.d.ts +6 -0
  90. package/dist/src/pubsub/multiple-nodes.d.ts.map +1 -0
  91. package/dist/src/pubsub/multiple-nodes.js +350 -0
  92. package/dist/src/pubsub/multiple-nodes.js.map +1 -0
  93. package/dist/src/pubsub/two-nodes.d.ts +6 -0
  94. package/dist/src/pubsub/two-nodes.d.ts.map +1 -0
  95. package/dist/src/pubsub/two-nodes.js +217 -0
  96. package/dist/src/pubsub/two-nodes.js.map +1 -0
  97. package/dist/src/pubsub/utils.d.ts +6 -0
  98. package/dist/src/pubsub/utils.d.ts.map +1 -0
  99. package/dist/src/pubsub/utils.js +22 -0
  100. package/dist/src/pubsub/utils.js.map +1 -0
  101. package/dist/src/stream-muxer/base-test.d.ts +5 -0
  102. package/dist/src/stream-muxer/base-test.d.ts.map +1 -0
  103. package/dist/src/stream-muxer/base-test.js +153 -0
  104. package/dist/src/stream-muxer/base-test.js.map +1 -0
  105. package/dist/src/stream-muxer/close-test.d.ts +5 -0
  106. package/dist/src/stream-muxer/close-test.d.ts.map +1 -0
  107. package/dist/src/stream-muxer/close-test.js +357 -0
  108. package/dist/src/stream-muxer/close-test.js.map +1 -0
  109. package/dist/src/stream-muxer/fixtures/pb/message.d.ts +13 -0
  110. package/dist/src/stream-muxer/fixtures/pb/message.d.ts.map +1 -0
  111. package/dist/src/stream-muxer/fixtures/pb/message.js +67 -0
  112. package/dist/src/stream-muxer/fixtures/pb/message.js.map +1 -0
  113. package/dist/src/stream-muxer/index.d.ts +5 -0
  114. package/dist/src/stream-muxer/index.d.ts.map +1 -0
  115. package/dist/src/stream-muxer/index.js +13 -0
  116. package/dist/src/stream-muxer/index.js.map +1 -0
  117. package/dist/src/stream-muxer/mega-stress-test.d.ts +5 -0
  118. package/dist/src/stream-muxer/mega-stress-test.d.ts.map +1 -0
  119. package/dist/src/stream-muxer/mega-stress-test.js +11 -0
  120. package/dist/src/stream-muxer/mega-stress-test.js.map +1 -0
  121. package/dist/src/stream-muxer/spawner.d.ts +4 -0
  122. package/dist/src/stream-muxer/spawner.d.ts.map +1 -0
  123. package/dist/src/stream-muxer/spawner.js +37 -0
  124. package/dist/src/stream-muxer/spawner.js.map +1 -0
  125. package/dist/src/stream-muxer/stress-test.d.ts +5 -0
  126. package/dist/src/stream-muxer/stress-test.d.ts.map +1 -0
  127. package/dist/src/stream-muxer/stress-test.js +23 -0
  128. package/dist/src/stream-muxer/stress-test.js.map +1 -0
  129. package/dist/src/transport/dial-test.d.ts +5 -0
  130. package/dist/src/transport/dial-test.d.ts.map +1 -0
  131. package/dist/src/transport/dial-test.js +98 -0
  132. package/dist/src/transport/dial-test.js.map +1 -0
  133. package/dist/src/transport/filter-test.d.ts +5 -0
  134. package/dist/src/transport/filter-test.d.ts.map +1 -0
  135. package/dist/src/transport/filter-test.js +18 -0
  136. package/dist/src/transport/filter-test.js.map +1 -0
  137. package/dist/src/transport/index.d.ts +15 -0
  138. package/dist/src/transport/index.d.ts.map +1 -0
  139. package/dist/src/transport/index.js +11 -0
  140. package/dist/src/transport/index.js.map +1 -0
  141. package/dist/src/transport/listen-test.d.ts +5 -0
  142. package/dist/src/transport/listen-test.d.ts.map +1 -0
  143. package/dist/src/transport/listen-test.js +152 -0
  144. package/dist/src/transport/listen-test.js.map +1 -0
  145. package/package.json +72 -5
  146. package/src/connection/index.ts +182 -0
  147. package/src/connection-encryption/index.ts +97 -0
  148. package/src/connection-encryption/utils/index.ts +24 -0
  149. package/src/index.ts +0 -1
  150. package/src/is-valid-tick.ts +0 -1
  151. package/src/mocks/connection-encrypter.ts +113 -0
  152. package/src/mocks/connection-gater.ts +18 -0
  153. package/src/mocks/connection-manager.ts +211 -0
  154. package/src/mocks/connection.ts +226 -0
  155. package/src/mocks/duplex.ts +10 -0
  156. package/src/mocks/index.ts +12 -0
  157. package/src/mocks/metrics.ts +162 -0
  158. package/src/mocks/multiaddr-connection.ts +76 -0
  159. package/src/mocks/muxer.ts +303 -0
  160. package/src/mocks/peer-discovery.ts +60 -0
  161. package/src/mocks/registrar.ts +88 -0
  162. package/src/mocks/upgrader.ts +49 -0
  163. package/src/peer-discovery/index.ts +90 -0
  164. package/src/pubsub/api.ts +114 -0
  165. package/src/pubsub/connection-handlers.ts +413 -0
  166. package/src/pubsub/emit-self.ts +99 -0
  167. package/src/pubsub/index.ts +34 -0
  168. package/src/pubsub/messages.ts +59 -0
  169. package/src/pubsub/multiple-nodes.ts +440 -0
  170. package/src/pubsub/two-nodes.ts +273 -0
  171. package/src/pubsub/utils.ts +29 -0
  172. package/src/stream-muxer/base-test.ts +196 -0
  173. package/src/stream-muxer/close-test.ts +442 -0
  174. package/src/stream-muxer/fixtures/pb/message.proto +7 -0
  175. package/src/stream-muxer/fixtures/pb/message.ts +87 -0
  176. package/src/stream-muxer/index.ts +15 -0
  177. package/src/stream-muxer/mega-stress-test.ts +14 -0
  178. package/src/stream-muxer/spawner.ts +55 -0
  179. package/src/stream-muxer/stress-test.ts +27 -0
  180. package/src/transport/dial-test.ts +124 -0
  181. package/src/transport/filter-test.ts +25 -0
  182. package/src/transport/index.ts +25 -0
  183. package/src/transport/listen-test.ts +191 -0
@@ -0,0 +1,442 @@
1
+ /* eslint max-nested-callbacks: ["error", 8] */
2
+ import { abortableSource } from 'abortable-iterator'
3
+ import { expect } from 'aegir/chai'
4
+ import delay from 'delay'
5
+ import all from 'it-all'
6
+ import drain from 'it-drain'
7
+ import { duplexPair } from 'it-pair/duplex'
8
+ import { pipe } from 'it-pipe'
9
+ import { pbStream } from 'it-protobuf-stream'
10
+ import toBuffer from 'it-to-buffer'
11
+ import pDefer from 'p-defer'
12
+ import { Uint8ArrayList } from 'uint8arraylist'
13
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
14
+ import { Message } from './fixtures/pb/message.js'
15
+ import type { TestSetup } from '../index.js'
16
+ import type { StreamMuxerFactory } from '@libp2p/interface/stream-muxer'
17
+
18
+ function randomBuffer (): Uint8Array {
19
+ return uint8ArrayFromString(Math.random().toString())
20
+ }
21
+
22
+ const infiniteRandom = {
23
+ [Symbol.asyncIterator]: async function * () {
24
+ while (true) {
25
+ yield new Uint8ArrayList(randomBuffer())
26
+ await delay(50)
27
+ }
28
+ }
29
+ }
30
+
31
+ export default (common: TestSetup<StreamMuxerFactory>): void => {
32
+ describe('close', () => {
33
+ it('closing underlying socket closes streams', async () => {
34
+ let openedStreams = 0
35
+ const expectedStreams = 5
36
+ const dialerFactory = await common.setup()
37
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
38
+
39
+ // Listener is echo server :)
40
+ const listenerFactory = await common.setup()
41
+ const listener = listenerFactory.createStreamMuxer({
42
+ direction: 'inbound',
43
+ onIncomingStream: (stream) => {
44
+ openedStreams++
45
+ void pipe(stream, stream)
46
+ }
47
+ })
48
+
49
+ const p = duplexPair<Uint8Array>()
50
+ void pipe(p[0], dialer, p[0])
51
+ void pipe(p[1], listener, p[1])
52
+
53
+ const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream()))
54
+
55
+ void Promise.all(
56
+ streams.map(async stream => {
57
+ await pipe(
58
+ infiniteRandom,
59
+ stream,
60
+ drain
61
+ )
62
+ })
63
+ )
64
+
65
+ expect(dialer.streams).to.have.lengthOf(expectedStreams)
66
+
67
+ // Pause, and then close the dialer
68
+ await delay(50)
69
+ await pipe([], dialer, drain)
70
+
71
+ expect(openedStreams).to.have.equal(expectedStreams)
72
+ expect(dialer.streams).to.have.lengthOf(0)
73
+ })
74
+
75
+ it('calling close closes streams', async () => {
76
+ let openedStreams = 0
77
+ const expectedStreams = 5
78
+ const dialerFactory = await common.setup()
79
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
80
+
81
+ // Listener is echo server :)
82
+ const listenerFactory = await common.setup()
83
+ const listener = listenerFactory.createStreamMuxer({
84
+ direction: 'inbound',
85
+ onIncomingStream: (stream) => {
86
+ openedStreams++
87
+ void pipe(stream, stream).catch(() => {})
88
+ }
89
+ })
90
+
91
+ const p = duplexPair<Uint8Array>()
92
+ void pipe(p[0], dialer, p[0])
93
+ void pipe(p[1], listener, p[1])
94
+
95
+ const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream()))
96
+
97
+ void Promise.all(
98
+ streams.map(async stream => {
99
+ await pipe(
100
+ infiniteRandom,
101
+ stream,
102
+ drain
103
+ )
104
+ })
105
+ )
106
+
107
+ expect(dialer.streams, 'dialer - number of opened streams should match number of calls to newStream').to.have.lengthOf(expectedStreams)
108
+
109
+ // Pause, and then close the dialer
110
+ await delay(50)
111
+
112
+ await dialer.close()
113
+
114
+ expect(openedStreams, 'listener - number of opened streams should match number of calls to newStream').to.have.equal(expectedStreams)
115
+ expect(dialer.streams, 'all tracked streams should be deleted after the muxer has called close').to.have.lengthOf(0)
116
+ })
117
+
118
+ it('calling close with an error aborts streams', async () => {
119
+ let openedStreams = 0
120
+ const expectedStreams = 5
121
+ const dialerFactory = await common.setup()
122
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
123
+
124
+ // Listener is echo server :)
125
+ const listenerFactory = await common.setup()
126
+ const listener = listenerFactory.createStreamMuxer({
127
+ direction: 'inbound',
128
+ onIncomingStream: (stream) => {
129
+ openedStreams++
130
+ void pipe(stream, stream).catch(() => {})
131
+ }
132
+ })
133
+
134
+ const p = duplexPair<Uint8Array>()
135
+ void pipe(p[0], dialer, p[0])
136
+ void pipe(p[1], listener, p[1])
137
+
138
+ const streams = await Promise.all(Array(expectedStreams).fill(0).map(async () => dialer.newStream()))
139
+
140
+ const streamPipes = streams.map(async stream => {
141
+ await pipe(
142
+ infiniteRandom,
143
+ stream,
144
+ drain
145
+ )
146
+ })
147
+
148
+ expect(dialer.streams, 'dialer - number of opened streams should match number of calls to newStream').to.have.lengthOf(expectedStreams)
149
+
150
+ // Pause, and then close the dialer
151
+ await delay(50)
152
+
153
+ // close _with an error_
154
+ dialer.abort(new Error('Oh no!'))
155
+
156
+ const timeoutError = new Error('timeout')
157
+ for (const pipe of streamPipes) {
158
+ try {
159
+ await Promise.race([
160
+ pipe,
161
+ new Promise((_resolve, reject) => setTimeout(() => { reject(timeoutError) }, 20))
162
+ ])
163
+ expect.fail('stream pipe with infinite source should never return')
164
+ } catch (e) {
165
+ if (e === timeoutError) {
166
+ expect.fail('expected stream pipe to throw an error after muxer closed with error')
167
+ }
168
+ }
169
+ }
170
+
171
+ expect(openedStreams, 'listener - number of opened streams should match number of calls to newStream').to.have.equal(expectedStreams)
172
+ expect(dialer.streams, 'all tracked streams should be deleted after the muxer has called close').to.have.lengthOf(0)
173
+ })
174
+
175
+ it('calling newStream after close throws an error', async () => {
176
+ const dialerFactory = await common.setup()
177
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
178
+
179
+ await dialer.close()
180
+
181
+ try {
182
+ await dialer.newStream()
183
+ expect.fail('newStream should throw if called after close')
184
+ } catch (e) {
185
+ expect(dialer.streams, 'closed muxer should have no streams').to.have.lengthOf(0)
186
+ }
187
+ })
188
+
189
+ it('closing one of the muxed streams doesn\'t close others', async () => {
190
+ const p = duplexPair<Uint8Array>()
191
+ const dialerFactory = await common.setup()
192
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
193
+
194
+ // Listener is echo server :)
195
+ const listenerFactory = await common.setup()
196
+ const listener = listenerFactory.createStreamMuxer({
197
+ direction: 'inbound',
198
+ onIncomingStream: (stream) => {
199
+ void pipe(stream, stream).catch(() => {})
200
+ }
201
+ })
202
+
203
+ void pipe(p[0], dialer, p[0])
204
+ void pipe(p[1], listener, p[1])
205
+
206
+ const stream = await dialer.newStream()
207
+ const streams = await Promise.all(Array.from(Array(5), async () => dialer.newStream()))
208
+ let closed = false
209
+ const controllers: AbortController[] = []
210
+
211
+ const streamResults = streams.map(async stream => {
212
+ const controller = new AbortController()
213
+ controllers.push(controller)
214
+
215
+ try {
216
+ const abortableRand = abortableSource(infiniteRandom, controller.signal, { abortCode: 'ERR_TEST_ABORT' })
217
+ await pipe(abortableRand, stream, drain)
218
+ } catch (err: any) {
219
+ if (err.code !== 'ERR_TEST_ABORT') throw err
220
+ }
221
+
222
+ if (!closed) throw new Error('stream should not have ended yet!')
223
+ })
224
+
225
+ // Pause, and then send some data and close the first stream
226
+ await delay(50)
227
+ await pipe([new Uint8ArrayList(randomBuffer())], stream, drain)
228
+ closed = true
229
+
230
+ // Abort all the other streams later
231
+ await delay(50)
232
+ controllers.forEach(c => { c.abort() })
233
+
234
+ // These should now all resolve without error
235
+ await Promise.all(streamResults)
236
+ })
237
+
238
+ it('can close a stream for writing', async () => {
239
+ const deferred = pDefer<Error>()
240
+
241
+ const p = duplexPair<Uint8Array>()
242
+ const dialerFactory = await common.setup()
243
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
244
+ const data = [randomBuffer(), randomBuffer()]
245
+
246
+ const listenerFactory = await common.setup()
247
+ const listener = listenerFactory.createStreamMuxer({
248
+ direction: 'inbound',
249
+ onIncomingStream: (stream) => {
250
+ void Promise.resolve().then(async () => {
251
+ // Immediate close for write
252
+ await stream.closeWrite()
253
+
254
+ const results = await pipe(stream, async (source) => {
255
+ const data = []
256
+ for await (const chunk of source) {
257
+ data.push(chunk.slice())
258
+ }
259
+ return data
260
+ })
261
+ expect(results).to.eql(data)
262
+
263
+ try {
264
+ await stream.sink([new Uint8ArrayList(randomBuffer())])
265
+ } catch (err: any) {
266
+ deferred.resolve(err)
267
+ }
268
+
269
+ deferred.reject(new Error('should not support writing to closed writer'))
270
+ })
271
+ }
272
+ })
273
+
274
+ void pipe(p[0], dialer, p[0])
275
+ void pipe(p[1], listener, p[1])
276
+
277
+ const stream = await dialer.newStream()
278
+ await stream.sink(data)
279
+
280
+ const err = await deferred.promise
281
+ expect(err).to.have.property('code', 'ERR_SINK_INVALID_STATE')
282
+ })
283
+
284
+ it('can close a stream for reading', async () => {
285
+ const deferred = pDefer<Uint8ArrayList[]>()
286
+ const p = duplexPair<Uint8Array>()
287
+ const dialerFactory = await common.setup()
288
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
289
+ const data = [randomBuffer(), randomBuffer()].map(d => new Uint8ArrayList(d))
290
+ const expected = toBuffer(data.map(d => d.subarray()))
291
+
292
+ const listenerFactory = await common.setup()
293
+ const listener = listenerFactory.createStreamMuxer({
294
+ direction: 'inbound',
295
+ onIncomingStream: (stream) => {
296
+ void all(stream.source).then(deferred.resolve, deferred.reject)
297
+ }
298
+ })
299
+
300
+ void pipe(p[0], dialer, p[0])
301
+ void pipe(p[1], listener, p[1])
302
+
303
+ const stream = await dialer.newStream()
304
+ await stream.closeRead()
305
+
306
+ // Source should be done
307
+ void Promise.resolve().then(async () => {
308
+ expect(await stream.source.next()).to.have.property('done', true)
309
+ await stream.sink(data)
310
+ })
311
+
312
+ const results = await deferred.promise
313
+ expect(toBuffer(results.map(b => b.subarray()))).to.equalBytes(expected)
314
+ })
315
+
316
+ it('calls onStreamEnd for closed streams not previously written', async () => {
317
+ const deferred = pDefer()
318
+
319
+ const onStreamEnd = (): void => { deferred.resolve() }
320
+ const dialerFactory = await common.setup()
321
+ const dialer = dialerFactory.createStreamMuxer({
322
+ direction: 'outbound',
323
+ onStreamEnd
324
+ })
325
+
326
+ const stream = await dialer.newStream()
327
+
328
+ await stream.close()
329
+ await deferred.promise
330
+ })
331
+
332
+ it('calls onStreamEnd for read and write closed streams not previously written', async () => {
333
+ const deferred = pDefer()
334
+
335
+ const onStreamEnd = (): void => { deferred.resolve() }
336
+ const dialerFactory = await common.setup()
337
+ const dialer = dialerFactory.createStreamMuxer({
338
+ direction: 'outbound',
339
+ onStreamEnd
340
+ })
341
+
342
+ const stream = await dialer.newStream()
343
+
344
+ await stream.closeWrite()
345
+ await stream.closeRead()
346
+ await deferred.promise
347
+ })
348
+
349
+ it('should wait for all data to be sent when closing streams', async () => {
350
+ const deferred = pDefer<Message>()
351
+
352
+ const p = duplexPair<Uint8Array>()
353
+ const dialerFactory = await common.setup()
354
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
355
+
356
+ const listenerFactory = await common.setup()
357
+ const listener = listenerFactory.createStreamMuxer({
358
+ direction: 'inbound',
359
+ onIncomingStream: (stream) => {
360
+ const pb = pbStream(stream)
361
+
362
+ void pb.read(Message)
363
+ .then(async message => {
364
+ deferred.resolve(message)
365
+ await pb.unwrap().close()
366
+ })
367
+ .catch(err => {
368
+ deferred.reject(err)
369
+ })
370
+ }
371
+ })
372
+
373
+ void pipe(p[0], dialer, p[0])
374
+ void pipe(p[1], listener, p[1])
375
+
376
+ const message = {
377
+ message: 'hello world',
378
+ value: 5,
379
+ flag: true
380
+ }
381
+
382
+ const stream = await dialer.newStream()
383
+
384
+ const pb = pbStream(stream)
385
+ await pb.write(message, Message)
386
+ await pb.unwrap().close()
387
+
388
+ await expect(deferred.promise).to.eventually.deep.equal(message)
389
+ })
390
+ /*
391
+ it('should abort closing a stream with outstanding data to read', async () => {
392
+ const deferred = pDefer<Message>()
393
+
394
+ const p = duplexPair<Uint8Array>()
395
+ const dialerFactory = await common.setup()
396
+ const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
397
+
398
+ const listenerFactory = await common.setup()
399
+ const listener = listenerFactory.createStreamMuxer({
400
+ direction: 'inbound',
401
+ onIncomingStream: (stream) => {
402
+ const pb = pbStream(stream)
403
+
404
+ void pb.read(Message)
405
+ .then(async message => {
406
+ await pb.write(message, Message)
407
+ await pb.unwrap().close()
408
+ deferred.resolve(message)
409
+ })
410
+ .catch(err => {
411
+ deferred.reject(err)
412
+ })
413
+ }
414
+ })
415
+
416
+ void pipe(p[0], dialer, p[0])
417
+ void pipe(p[1], listener, p[1])
418
+
419
+ const message = {
420
+ message: 'hello world',
421
+ value: 5,
422
+ flag: true
423
+ }
424
+
425
+ const stream = await dialer.newStream()
426
+
427
+ const pb = pbStream(stream)
428
+ await pb.write(message, Message)
429
+
430
+ console.info('await write back')
431
+ await deferred.promise
432
+
433
+ // let message arrive
434
+ await delay(100)
435
+
436
+ // close should time out as message is never read
437
+ await expect(pb.unwrap().close()).to.eventually.be.rejected
438
+ .with.property('code', 'ERR_CLOSE_READ_ABORTED')
439
+ })
440
+ */
441
+ })
442
+ }
@@ -0,0 +1,7 @@
1
+ syntax = "proto3";
2
+
3
+ message Message {
4
+ string message = 1;
5
+ uint32 value = 2;
6
+ bool flag = 3;
7
+ }
@@ -0,0 +1,87 @@
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 { encodeMessage, decodeMessage, message } from 'protons-runtime'
8
+ import type { Codec } from 'protons-runtime'
9
+ import type { Uint8ArrayList } from 'uint8arraylist'
10
+
11
+ export interface Message {
12
+ message: string
13
+ value: number
14
+ flag: boolean
15
+ }
16
+
17
+ export namespace Message {
18
+ let _codec: Codec<Message>
19
+
20
+ export const codec = (): Codec<Message> => {
21
+ if (_codec == null) {
22
+ _codec = message<Message>((obj, w, opts = {}) => {
23
+ if (opts.lengthDelimited !== false) {
24
+ w.fork()
25
+ }
26
+
27
+ if ((obj.message != null && obj.message !== '')) {
28
+ w.uint32(10)
29
+ w.string(obj.message)
30
+ }
31
+
32
+ if ((obj.value != null && obj.value !== 0)) {
33
+ w.uint32(16)
34
+ w.uint32(obj.value)
35
+ }
36
+
37
+ if ((obj.flag != null && obj.flag !== false)) {
38
+ w.uint32(24)
39
+ w.bool(obj.flag)
40
+ }
41
+
42
+ if (opts.lengthDelimited !== false) {
43
+ w.ldelim()
44
+ }
45
+ }, (reader, length) => {
46
+ const obj: any = {
47
+ message: '',
48
+ value: 0,
49
+ flag: false
50
+ }
51
+
52
+ const end = length == null ? reader.len : reader.pos + length
53
+
54
+ while (reader.pos < end) {
55
+ const tag = reader.uint32()
56
+
57
+ switch (tag >>> 3) {
58
+ case 1:
59
+ obj.message = reader.string()
60
+ break
61
+ case 2:
62
+ obj.value = reader.uint32()
63
+ break
64
+ case 3:
65
+ obj.flag = reader.bool()
66
+ break
67
+ default:
68
+ reader.skipType(tag & 7)
69
+ break
70
+ }
71
+ }
72
+
73
+ return obj
74
+ })
75
+ }
76
+
77
+ return _codec
78
+ }
79
+
80
+ export const encode = (obj: Partial<Message>): Uint8Array => {
81
+ return encodeMessage(obj, Message.codec())
82
+ }
83
+
84
+ export const decode = (buf: Uint8Array | Uint8ArrayList): Message => {
85
+ return decodeMessage(buf, Message.codec())
86
+ }
87
+ }
@@ -0,0 +1,15 @@
1
+ import baseTest from './base-test.js'
2
+ import closeTest from './close-test.js'
3
+ import megaStressTest from './mega-stress-test.js'
4
+ import stressTest from './stress-test.js'
5
+ import type { TestSetup } from '../index.js'
6
+ import type { StreamMuxerFactory } from '@libp2p/interface/stream-muxer'
7
+
8
+ export default (common: TestSetup<StreamMuxerFactory>): void => {
9
+ describe('interface-stream-muxer', () => {
10
+ baseTest(common)
11
+ closeTest(common)
12
+ stressTest(common)
13
+ megaStressTest(common)
14
+ })
15
+ }
@@ -0,0 +1,14 @@
1
+ import spawn from './spawner.js'
2
+ import type { TestSetup } from '../index.js'
3
+ import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface/stream-muxer'
4
+
5
+ export default (common: TestSetup<StreamMuxerFactory>): void => {
6
+ const createMuxer = async (init?: StreamMuxerInit): Promise<StreamMuxer> => {
7
+ const factory = await common.setup()
8
+ return factory.createStreamMuxer(init)
9
+ }
10
+
11
+ describe.skip('mega stress test', function () {
12
+ it('10,000 streams with 10,000 msg', async () => { await spawn(createMuxer, 10000, 10000, 5000) })
13
+ })
14
+ }
@@ -0,0 +1,55 @@
1
+ import { expect } from 'aegir/chai'
2
+ import all from 'it-all'
3
+ import drain from 'it-drain'
4
+ import { duplexPair } from 'it-pair/duplex'
5
+ import { pipe } from 'it-pipe'
6
+ import pLimit from 'p-limit'
7
+ import { Uint8ArrayList } from 'uint8arraylist'
8
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
9
+ import type { StreamMuxer, StreamMuxerInit } from '@libp2p/interface/stream-muxer'
10
+
11
+ export default async (createMuxer: (init?: StreamMuxerInit) => Promise<StreamMuxer>, nStreams: number, nMsg: number, limit?: number): Promise<void> => {
12
+ const [dialerSocket, listenerSocket] = duplexPair<Uint8Array>()
13
+
14
+ const msg = new Uint8ArrayList(uint8ArrayFromString('simple msg'))
15
+
16
+ const listener = await createMuxer({
17
+ direction: 'inbound',
18
+ onIncomingStream: (stream) => {
19
+ void pipe(
20
+ stream,
21
+ drain
22
+ ).then(async () => {
23
+ await stream.close()
24
+ })
25
+ .catch(err => { stream.abort(err) })
26
+ }
27
+ })
28
+ const dialer = await createMuxer({ direction: 'outbound' })
29
+
30
+ void pipe(listenerSocket, listener, listenerSocket)
31
+ void pipe(dialerSocket, dialer, dialerSocket)
32
+
33
+ const spawnStream = async (): Promise<void> => {
34
+ const stream = await dialer.newStream()
35
+ expect(stream).to.exist // eslint-disable-line
36
+
37
+ const res = await pipe(
38
+ (async function * () {
39
+ for (let i = 0; i < nMsg; i++) {
40
+ yield msg
41
+ }
42
+ }()),
43
+ stream,
44
+ async (source) => all(source)
45
+ )
46
+
47
+ expect(res).to.be.eql([])
48
+ }
49
+
50
+ const limiter = pLimit(limit ?? Infinity)
51
+
52
+ await Promise.all(
53
+ Array.from(Array(nStreams), async () => { await limiter(async () => { await spawnStream() }) })
54
+ )
55
+ }
@@ -0,0 +1,27 @@
1
+ import spawn from './spawner.js'
2
+ import type { TestSetup } from '../index.js'
3
+ import type { StreamMuxerFactory, StreamMuxerInit, StreamMuxer } from '@libp2p/interface/stream-muxer'
4
+
5
+ export default (common: TestSetup<StreamMuxerFactory>): void => {
6
+ const createMuxer = async (init?: StreamMuxerInit): Promise<StreamMuxer> => {
7
+ const factory = await common.setup()
8
+ return factory.createStreamMuxer(init)
9
+ }
10
+
11
+ describe('stress test', function () {
12
+ this.timeout(1600000)
13
+
14
+ it('1 stream with 1 msg', async () => { await spawn(createMuxer, 1, 1) })
15
+ it('1 stream with 10 msg', async () => { await spawn(createMuxer, 1, 10) })
16
+ it('1 stream with 100 msg', async () => { await spawn(createMuxer, 1, 100) })
17
+ it('10 streams with 1 msg', async () => { await spawn(createMuxer, 10, 1) })
18
+ it('10 streams with 10 msg', async () => { await spawn(createMuxer, 10, 10) })
19
+ it('10 streams with 100 msg', async () => { await spawn(createMuxer, 10, 100) })
20
+ it('100 streams with 1 msg', async () => { await spawn(createMuxer, 100, 1) })
21
+ it('100 streams with 10 msg', async () => { await spawn(createMuxer, 100, 10) })
22
+ it('100 streams with 100 msg', async () => { await spawn(createMuxer, 100, 100) })
23
+ it('1000 streams with 1 msg', async () => { await spawn(createMuxer, 1000, 1) })
24
+ it('1000 streams with 10 msg', async () => { await spawn(createMuxer, 1000, 10) })
25
+ it('1000 streams with 100 msg', async () => { await spawn(createMuxer, 1000, 100) })
26
+ })
27
+ }