@libp2p/interface-compliance-tests 3.0.7-e9cafd3d → 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 (51) hide show
  1. package/dist/src/connection/index.js +5 -5
  2. package/dist/src/connection/index.js.map +1 -1
  3. package/dist/src/connection-encryption/utils/index.d.ts.map +1 -1
  4. package/dist/src/connection-encryption/utils/index.js +1 -0
  5. package/dist/src/connection-encryption/utils/index.js.map +1 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/is-valid-tick.d.ts.map +1 -1
  8. package/dist/src/is-valid-tick.js.map +1 -1
  9. package/dist/src/mocks/connection-manager.d.ts.map +1 -1
  10. package/dist/src/mocks/connection.d.ts.map +1 -1
  11. package/dist/src/mocks/connection.js +30 -21
  12. package/dist/src/mocks/connection.js.map +1 -1
  13. package/dist/src/mocks/multiaddr-connection.d.ts.map +1 -1
  14. package/dist/src/mocks/multiaddr-connection.js +9 -0
  15. package/dist/src/mocks/multiaddr-connection.js.map +1 -1
  16. package/dist/src/mocks/muxer.d.ts +28 -0
  17. package/dist/src/mocks/muxer.d.ts.map +1 -1
  18. package/dist/src/mocks/muxer.js +85 -211
  19. package/dist/src/mocks/muxer.js.map +1 -1
  20. package/dist/src/mocks/upgrader.d.ts +1 -1
  21. package/dist/src/mocks/upgrader.d.ts.map +1 -1
  22. package/dist/src/stream-muxer/close-test.d.ts.map +1 -1
  23. package/dist/src/stream-muxer/close-test.js +100 -12
  24. package/dist/src/stream-muxer/close-test.js.map +1 -1
  25. package/dist/src/stream-muxer/fixtures/pb/message.d.ts +13 -0
  26. package/dist/src/stream-muxer/fixtures/pb/message.d.ts.map +1 -0
  27. package/dist/src/stream-muxer/fixtures/pb/message.js +67 -0
  28. package/dist/src/stream-muxer/fixtures/pb/message.js.map +1 -0
  29. package/dist/src/stream-muxer/spawner.d.ts.map +1 -1
  30. package/dist/src/stream-muxer/spawner.js +4 -3
  31. package/dist/src/stream-muxer/spawner.js.map +1 -1
  32. package/dist/src/stream-muxer/stress-test.js +1 -1
  33. package/dist/src/stream-muxer/stress-test.js.map +1 -1
  34. package/dist/src/transport/listen-test.js +1 -1
  35. package/dist/src/transport/listen-test.js.map +1 -1
  36. package/package.json +15 -14
  37. package/src/connection/index.ts +5 -5
  38. package/src/connection-encryption/utils/index.ts +1 -0
  39. package/src/index.ts +0 -1
  40. package/src/is-valid-tick.ts +0 -1
  41. package/src/mocks/connection-manager.ts +2 -2
  42. package/src/mocks/connection.ts +35 -24
  43. package/src/mocks/multiaddr-connection.ts +9 -0
  44. package/src/mocks/muxer.ts +105 -247
  45. package/src/mocks/upgrader.ts +1 -1
  46. package/src/stream-muxer/close-test.ts +110 -14
  47. package/src/stream-muxer/fixtures/pb/message.proto +7 -0
  48. package/src/stream-muxer/fixtures/pb/message.ts +87 -0
  49. package/src/stream-muxer/spawner.ts +3 -2
  50. package/src/stream-muxer/stress-test.ts +1 -1
  51. package/src/transport/listen-test.ts +1 -1
@@ -1,7 +1,6 @@
1
- import { CodeError } from '@libp2p/interface/errors'
1
+ import { AbstractStream, type AbstractStreamInit } from '@libp2p/interface/stream-muxer/stream'
2
2
  import { type Logger, logger } from '@libp2p/logger'
3
3
  import { abortableSource } from 'abortable-iterator'
4
- import { anySignal } from 'any-signal'
5
4
  import map from 'it-map'
6
5
  import * as ndjson from 'it-ndjson'
7
6
  import { pipe } from 'it-pipe'
@@ -9,254 +8,94 @@ import { type Pushable, pushable } from 'it-pushable'
9
8
  import { Uint8ArrayList } from 'uint8arraylist'
10
9
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
11
10
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
12
- import type { Stream } from '@libp2p/interface/connection'
11
+ import type { AbortOptions } from '@libp2p/interface'
12
+ import type { Direction, Stream } from '@libp2p/interface/connection'
13
13
  import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface/stream-muxer'
14
14
  import type { Source } from 'it-stream-types'
15
15
 
16
16
  let muxers = 0
17
17
  let streams = 0
18
- const MAX_MESSAGE_SIZE = 1024 * 1024
19
18
 
20
19
  interface DataMessage {
21
20
  id: string
22
21
  type: 'data'
23
- direction: 'initiator' | 'recipient'
22
+ direction: Direction
24
23
  chunk: string
25
24
  }
26
25
 
27
26
  interface ResetMessage {
28
27
  id: string
29
28
  type: 'reset'
30
- direction: 'initiator' | 'recipient'
29
+ direction: Direction
31
30
  }
32
31
 
33
32
  interface CloseMessage {
34
33
  id: string
35
34
  type: 'close'
36
- direction: 'initiator' | 'recipient'
35
+ direction: Direction
37
36
  }
38
37
 
39
38
  interface CreateMessage {
40
39
  id: string
41
40
  type: 'create'
42
- direction: 'initiator'
41
+ direction: 'outbound'
43
42
  }
44
43
 
45
44
  type StreamMessage = DataMessage | ResetMessage | CloseMessage | CreateMessage
46
45
 
47
- class MuxedStream {
48
- public id: string
49
- public input: Pushable<Uint8ArrayList>
50
- public stream: Stream
51
- public type: 'initiator' | 'recipient'
52
-
53
- private sinkEnded: boolean
54
- private sourceEnded: boolean
55
- private readonly abortController: AbortController
56
- private readonly resetController: AbortController
57
- private readonly closeController: AbortController
58
- private readonly log: Logger
59
-
60
- constructor (init: { id: string, type: 'initiator' | 'recipient', push: Pushable<StreamMessage>, onEnd: (err?: Error) => void }) {
61
- const { id, type, push, onEnd } = init
62
-
63
- this.log = logger(`libp2p:mock-muxer:stream:${id}:${type}`)
64
-
65
- this.id = id
66
- this.type = type
67
- this.abortController = new AbortController()
68
- this.resetController = new AbortController()
69
- this.closeController = new AbortController()
70
-
71
- this.sourceEnded = false
72
- this.sinkEnded = false
73
-
74
- let endErr: Error | undefined
75
-
76
- const onSourceEnd = (err?: Error): void => {
77
- if (this.sourceEnded) {
78
- return
79
- }
80
-
81
- this.log('onSourceEnd sink ended? %s', this.sinkEnded)
46
+ export interface MockMuxedStreamInit extends AbstractStreamInit {
47
+ push: Pushable<StreamMessage>
48
+ }
82
49
 
83
- this.sourceEnded = true
50
+ class MuxedStream extends AbstractStream {
51
+ private readonly push: Pushable<StreamMessage>
84
52
 
85
- if (err != null && endErr == null) {
86
- endErr = err
87
- }
53
+ constructor (init: MockMuxedStreamInit) {
54
+ super(init)
88
55
 
89
- if (this.sinkEnded) {
90
- this.stream.timeline.close = Date.now()
56
+ this.push = init.push
57
+ }
91
58
 
92
- if (onEnd != null) {
93
- onEnd(endErr)
94
- }
95
- }
59
+ sendNewStream (): void {
60
+ // If initiator, open a new stream
61
+ const createMsg: CreateMessage = {
62
+ id: this.id,
63
+ type: 'create',
64
+ direction: 'outbound'
96
65
  }
66
+ this.push.push(createMsg)
67
+ }
97
68
 
98
- const onSinkEnd = (err?: Error): void => {
99
- if (this.sinkEnded) {
100
- return
101
- }
102
-
103
- this.log('onSinkEnd source ended? %s', this.sourceEnded)
104
-
105
- this.sinkEnded = true
106
-
107
- if (err != null && endErr == null) {
108
- endErr = err
109
- }
110
-
111
- if (this.sourceEnded) {
112
- this.stream.timeline.close = Date.now()
113
-
114
- if (onEnd != null) {
115
- onEnd(endErr)
116
- }
117
- }
69
+ sendData (data: Uint8ArrayList): void {
70
+ const dataMsg: DataMessage = {
71
+ id: this.id,
72
+ type: 'data',
73
+ chunk: uint8ArrayToString(data.subarray(), 'base64pad'),
74
+ direction: this.direction
118
75
  }
76
+ this.push.push(dataMsg)
77
+ }
119
78
 
120
- this.input = pushable({
121
- onEnd: onSourceEnd
122
- })
123
-
124
- this.stream = {
125
- id,
126
- sink: async (source) => {
127
- if (this.sinkEnded) {
128
- throw new CodeError('stream closed for writing', 'ERR_SINK_ENDED')
129
- }
130
-
131
- const signal = anySignal([
132
- this.abortController.signal,
133
- this.resetController.signal,
134
- this.closeController.signal
135
- ])
136
-
137
- source = abortableSource(source, signal)
138
-
139
- try {
140
- if (this.type === 'initiator') {
141
- // If initiator, open a new stream
142
- const createMsg: CreateMessage = {
143
- id: this.id,
144
- type: 'create',
145
- direction: this.type
146
- }
147
- push.push(createMsg)
148
- }
149
-
150
- const list = new Uint8ArrayList()
151
-
152
- for await (const chunk of source) {
153
- list.append(chunk)
154
-
155
- while (list.length > 0) {
156
- const available = Math.min(list.length, MAX_MESSAGE_SIZE)
157
- const dataMsg: DataMessage = {
158
- id,
159
- type: 'data',
160
- chunk: uint8ArrayToString(list.subarray(0, available), 'base64pad'),
161
- direction: this.type
162
- }
163
-
164
- push.push(dataMsg)
165
- list.consume(available)
166
- }
167
- }
168
- } catch (err: any) {
169
- if (err.type === 'aborted' && err.message === 'The operation was aborted') {
170
- if (this.closeController.signal.aborted) {
171
- return
172
- }
173
-
174
- if (this.resetController.signal.aborted) {
175
- err.message = 'stream reset'
176
- err.code = 'ERR_STREAM_RESET'
177
- }
178
-
179
- if (this.abortController.signal.aborted) {
180
- err.message = 'stream aborted'
181
- err.code = 'ERR_STREAM_ABORT'
182
- }
183
- }
184
-
185
- // Send no more data if this stream was remotely reset
186
- if (err.code !== 'ERR_STREAM_RESET') {
187
- const resetMsg: ResetMessage = {
188
- id,
189
- type: 'reset',
190
- direction: this.type
191
- }
192
- push.push(resetMsg)
193
- }
194
-
195
- this.log('sink erred', err)
196
-
197
- this.input.end(err)
198
- onSinkEnd(err)
199
- return
200
- } finally {
201
- signal.clear()
202
- }
203
-
204
- this.log('sink ended')
205
-
206
- onSinkEnd()
207
-
208
- const closeMsg: CloseMessage = {
209
- id,
210
- type: 'close',
211
- direction: this.type
212
- }
213
- push.push(closeMsg)
214
- },
215
- source: this.input,
216
-
217
- // Close for reading
218
- close: () => {
219
- this.stream.closeRead()
220
- this.stream.closeWrite()
221
- },
222
-
223
- closeRead: () => {
224
- this.input.end()
225
- },
226
-
227
- closeWrite: () => {
228
- this.closeController.abort()
229
-
230
- const closeMsg: CloseMessage = {
231
- id,
232
- type: 'close',
233
- direction: this.type
234
- }
235
- push.push(closeMsg)
236
- onSinkEnd()
237
- },
238
-
239
- // Close for reading and writing (local error)
240
- abort: (err: Error) => {
241
- // End the source with the passed error
242
- this.input.end(err)
243
- this.abortController.abort()
244
- onSinkEnd(err)
245
- },
79
+ sendReset (): void {
80
+ const resetMsg: ResetMessage = {
81
+ id: this.id,
82
+ type: 'reset',
83
+ direction: this.direction
84
+ }
85
+ this.push.push(resetMsg)
86
+ }
246
87
 
247
- // Close immediately for reading and writing (remote error)
248
- reset: () => {
249
- const err = new CodeError('stream reset', 'ERR_STREAM_RESET')
250
- this.resetController.abort()
251
- this.input.end(err)
252
- onSinkEnd(err)
253
- },
254
- direction: type === 'initiator' ? 'outbound' : 'inbound',
255
- timeline: {
256
- open: Date.now()
257
- },
258
- metadata: {}
88
+ sendCloseWrite (): void {
89
+ const closeMsg: CloseMessage = {
90
+ id: this.id,
91
+ type: 'close',
92
+ direction: this.direction
259
93
  }
94
+ this.push.push(closeMsg)
95
+ }
96
+
97
+ sendCloseRead (): void {
98
+ // does not support close read, only close write
260
99
  }
261
100
  }
262
101
 
@@ -284,8 +123,14 @@ class MockMuxer implements StreamMuxer {
284
123
  this.closeController = new AbortController()
285
124
  // receives data from the muxer at the other end of the stream
286
125
  this.source = this.input = pushable({
287
- onEnd: (err) => {
288
- this.close(err)
126
+ onEnd: () => {
127
+ for (const stream of this.registryInitiatorStreams.values()) {
128
+ stream.destroy()
129
+ }
130
+
131
+ for (const stream of this.registryRecipientStreams.values()) {
132
+ stream.destroy()
133
+ }
289
134
  }
290
135
  })
291
136
 
@@ -321,18 +166,18 @@ class MockMuxer implements StreamMuxer {
321
166
  handleMessage (message: StreamMessage): void {
322
167
  let muxedStream: MuxedStream | undefined
323
168
 
324
- const registry = message.direction === 'initiator' ? this.registryRecipientStreams : this.registryInitiatorStreams
169
+ const registry = message.direction === 'outbound' ? this.registryRecipientStreams : this.registryInitiatorStreams
325
170
 
326
171
  if (message.type === 'create') {
327
172
  if (registry.has(message.id)) {
328
173
  throw new Error(`Already had stream for ${message.id}`)
329
174
  }
330
175
 
331
- muxedStream = this.createStream(message.id, 'recipient')
332
- registry.set(muxedStream.stream.id, muxedStream)
176
+ muxedStream = this.createStream(message.id, 'inbound')
177
+ registry.set(muxedStream.id, muxedStream)
333
178
 
334
179
  if (this.options.onIncomingStream != null) {
335
- this.options.onIncomingStream(muxedStream.stream)
180
+ this.options.onIncomingStream(muxedStream)
336
181
  }
337
182
  }
338
183
 
@@ -345,20 +190,19 @@ class MockMuxer implements StreamMuxer {
345
190
  }
346
191
 
347
192
  if (message.type === 'data') {
348
- muxedStream.input.push(new Uint8ArrayList(uint8ArrayFromString(message.chunk, 'base64pad')))
193
+ muxedStream.sourcePush(new Uint8ArrayList(uint8ArrayFromString(message.chunk, 'base64pad')))
349
194
  } else if (message.type === 'reset') {
350
- this.log('-> reset stream %s %s', muxedStream.type, muxedStream.stream.id)
351
- muxedStream.stream.reset()
195
+ this.log('-> reset stream %s %s', muxedStream.direction, muxedStream.id)
196
+ muxedStream.reset()
352
197
  } else if (message.type === 'close') {
353
- this.log('-> closing stream %s %s', muxedStream.type, muxedStream.stream.id)
354
- muxedStream.stream.closeRead()
198
+ this.log('-> closing stream %s %s', muxedStream.direction, muxedStream.id)
199
+ muxedStream.remoteCloseWrite()
355
200
  }
356
201
  }
357
202
 
358
203
  get streams (): Stream[] {
359
204
  return Array.from(this.registryRecipientStreams.values())
360
205
  .concat(Array.from(this.registryInitiatorStreams.values()))
361
- .map(({ stream }) => stream)
362
206
  }
363
207
 
364
208
  newStream (name?: string): Stream {
@@ -366,53 +210,67 @@ class MockMuxer implements StreamMuxer {
366
210
  throw new Error('Muxer already closed')
367
211
  }
368
212
  this.log('newStream %s', name)
369
- const storedStream = this.createStream(name, 'initiator')
370
- this.registryInitiatorStreams.set(storedStream.stream.id, storedStream)
213
+ const storedStream = this.createStream(name, 'outbound')
214
+ this.registryInitiatorStreams.set(storedStream.id, storedStream)
371
215
 
372
- return storedStream.stream
216
+ return storedStream
373
217
  }
374
218
 
375
- createStream (name?: string, type: 'initiator' | 'recipient' = 'initiator'): MuxedStream {
376
- const id = name ?? `${this.name}:stream:${streams++}`
219
+ createStream (name?: string, direction: Direction = 'outbound'): MuxedStream {
220
+ const id = name ?? `${streams++}`
377
221
 
378
- this.log('createStream %s %s', type, id)
222
+ this.log('createStream %s %s', direction, id)
379
223
 
380
224
  const muxedStream: MuxedStream = new MuxedStream({
381
225
  id,
382
- type,
226
+ direction,
383
227
  push: this.streamInput,
384
228
  onEnd: () => {
385
- this.log('stream ended %s %s', type, id)
229
+ this.log('stream ended')
386
230
 
387
- if (type === 'initiator') {
388
- this.registryInitiatorStreams.delete(id)
231
+ if (direction === 'outbound') {
232
+ this.registryInitiatorStreams.delete(muxedStream.id)
389
233
  } else {
390
- this.registryRecipientStreams.delete(id)
234
+ this.registryRecipientStreams.delete(muxedStream.id)
391
235
  }
392
236
 
393
237
  if (this.options.onStreamEnd != null) {
394
- this.options.onStreamEnd(muxedStream.stream)
238
+ this.options.onStreamEnd(muxedStream)
395
239
  }
396
- }
240
+ },
241
+ log: logger(`libp2p:mock-muxer:stream:${direction}:${id}`)
397
242
  })
398
243
 
399
244
  return muxedStream
400
245
  }
401
246
 
402
- close (err?: Error): void {
403
- if (this.closeController.signal.aborted) return
247
+ async close (options?: AbortOptions): Promise<void> {
248
+ if (this.closeController.signal.aborted) {
249
+ return
250
+ }
251
+
404
252
  this.log('closing muxed streams')
405
253
 
406
- if (err == null) {
407
- this.streams.forEach(s => {
408
- s.close()
409
- })
410
- } else {
411
- this.streams.forEach(s => {
412
- s.abort(err)
413
- })
414
- }
254
+ await Promise.all(
255
+ this.streams.map(async s => s.close())
256
+ )
257
+
415
258
  this.closeController.abort()
259
+ this.input.end()
260
+ }
261
+
262
+ abort (err: Error): void {
263
+ if (this.closeController.signal.aborted) {
264
+ return
265
+ }
266
+
267
+ this.log('aborting muxed streams')
268
+
269
+ this.streams.forEach(s => {
270
+ s.abort(err)
271
+ })
272
+
273
+ this.closeController.abort(err)
416
274
  this.input.end(err)
417
275
  }
418
276
  }
@@ -2,8 +2,8 @@ import { mockConnection } from './connection.js'
2
2
  import type { Libp2pEvents } from '@libp2p/interface'
3
3
  import type { Connection, MultiaddrConnection } from '@libp2p/interface/connection'
4
4
  import type { EventEmitter } from '@libp2p/interface/events'
5
+ import type { Upgrader, UpgraderOptions } from '@libp2p/interface/transport'
5
6
  import type { Registrar } from '@libp2p/interface-internal/registrar'
6
- import type { Upgrader, UpgraderOptions } from '@libp2p/interface-internal/upgrader'
7
7
 
8
8
  export interface MockUpgraderInit {
9
9
  registrar?: Registrar
@@ -6,9 +6,12 @@ import all from 'it-all'
6
6
  import drain from 'it-drain'
7
7
  import { duplexPair } from 'it-pair/duplex'
8
8
  import { pipe } from 'it-pipe'
9
+ import { pbStream } from 'it-protobuf-stream'
10
+ import toBuffer from 'it-to-buffer'
9
11
  import pDefer from 'p-defer'
10
12
  import { Uint8ArrayList } from 'uint8arraylist'
11
13
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
14
+ import { Message } from './fixtures/pb/message.js'
12
15
  import type { TestSetup } from '../index.js'
13
16
  import type { StreamMuxerFactory } from '@libp2p/interface/stream-muxer'
14
17
 
@@ -61,9 +64,9 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
61
64
 
62
65
  expect(dialer.streams).to.have.lengthOf(expectedStreams)
63
66
 
64
- // Pause, and then send some data and close the dialer
67
+ // Pause, and then close the dialer
65
68
  await delay(50)
66
- await pipe([randomBuffer()], dialer, drain)
69
+ await pipe([], dialer, drain)
67
70
 
68
71
  expect(openedStreams).to.have.equal(expectedStreams)
69
72
  expect(dialer.streams).to.have.lengthOf(0)
@@ -106,7 +109,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
106
109
  // Pause, and then close the dialer
107
110
  await delay(50)
108
111
 
109
- dialer.close()
112
+ await dialer.close()
110
113
 
111
114
  expect(openedStreams, 'listener - number of opened streams should match number of calls to newStream').to.have.equal(expectedStreams)
112
115
  expect(dialer.streams, 'all tracked streams should be deleted after the muxer has called close').to.have.lengthOf(0)
@@ -148,7 +151,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
148
151
  await delay(50)
149
152
 
150
153
  // close _with an error_
151
- dialer.close(new Error())
154
+ dialer.abort(new Error('Oh no!'))
152
155
 
153
156
  const timeoutError = new Error('timeout')
154
157
  for (const pipe of streamPipes) {
@@ -173,7 +176,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
173
176
  const dialerFactory = await common.setup()
174
177
  const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
175
178
 
176
- dialer.close()
179
+ await dialer.close()
177
180
 
178
181
  try {
179
182
  await dialer.newStream()
@@ -246,7 +249,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
246
249
  onIncomingStream: (stream) => {
247
250
  void Promise.resolve().then(async () => {
248
251
  // Immediate close for write
249
- stream.closeWrite()
252
+ await stream.closeWrite()
250
253
 
251
254
  const results = await pipe(stream, async (source) => {
252
255
  const data = []
@@ -275,16 +278,16 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
275
278
  await stream.sink(data)
276
279
 
277
280
  const err = await deferred.promise
278
- expect(err).to.have.property('message').that.matches(/stream closed for writing/)
281
+ expect(err).to.have.property('code', 'ERR_SINK_INVALID_STATE')
279
282
  })
280
283
 
281
284
  it('can close a stream for reading', async () => {
282
- const deferred = pDefer<any>()
283
-
285
+ const deferred = pDefer<Uint8ArrayList[]>()
284
286
  const p = duplexPair<Uint8Array>()
285
287
  const dialerFactory = await common.setup()
286
288
  const dialer = dialerFactory.createStreamMuxer({ direction: 'outbound' })
287
289
  const data = [randomBuffer(), randomBuffer()].map(d => new Uint8ArrayList(d))
290
+ const expected = toBuffer(data.map(d => d.subarray()))
288
291
 
289
292
  const listenerFactory = await common.setup()
290
293
  const listener = listenerFactory.createStreamMuxer({
@@ -298,7 +301,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
298
301
  void pipe(p[1], listener, p[1])
299
302
 
300
303
  const stream = await dialer.newStream()
301
- stream.closeRead()
304
+ await stream.closeRead()
302
305
 
303
306
  // Source should be done
304
307
  void Promise.resolve().then(async () => {
@@ -307,7 +310,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
307
310
  })
308
311
 
309
312
  const results = await deferred.promise
310
- expect(results).to.eql(data)
313
+ expect(toBuffer(results.map(b => b.subarray()))).to.equalBytes(expected)
311
314
  })
312
315
 
313
316
  it('calls onStreamEnd for closed streams not previously written', async () => {
@@ -322,7 +325,7 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
322
325
 
323
326
  const stream = await dialer.newStream()
324
327
 
325
- stream.close()
328
+ await stream.close()
326
329
  await deferred.promise
327
330
  })
328
331
 
@@ -338,9 +341,102 @@ export default (common: TestSetup<StreamMuxerFactory>): void => {
338
341
 
339
342
  const stream = await dialer.newStream()
340
343
 
341
- stream.closeWrite()
342
- stream.closeRead()
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')
343
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')
344
439
  })
440
+ */
345
441
  })
346
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
+ }