@libp2p/utils 6.7.1 → 6.7.2-a02cb0461

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 (120) hide show
  1. package/README.md +16 -1
  2. package/dist/index.min.js +6 -1
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/abstract-message-stream.d.ts +129 -0
  5. package/dist/src/abstract-message-stream.d.ts.map +1 -0
  6. package/dist/src/abstract-message-stream.js +389 -0
  7. package/dist/src/abstract-message-stream.js.map +1 -0
  8. package/dist/src/abstract-multiaddr-connection.d.ts +26 -0
  9. package/dist/src/abstract-multiaddr-connection.d.ts.map +1 -0
  10. package/dist/src/abstract-multiaddr-connection.js +66 -0
  11. package/dist/src/abstract-multiaddr-connection.js.map +1 -0
  12. package/dist/src/abstract-stream-muxer.d.ts +53 -0
  13. package/dist/src/abstract-stream-muxer.d.ts.map +1 -0
  14. package/dist/src/abstract-stream-muxer.js +169 -0
  15. package/dist/src/abstract-stream-muxer.js.map +1 -0
  16. package/dist/src/abstract-stream.d.ts +14 -130
  17. package/dist/src/abstract-stream.d.ts.map +1 -1
  18. package/dist/src/abstract-stream.js +39 -321
  19. package/dist/src/abstract-stream.js.map +1 -1
  20. package/dist/src/errors.d.ts +8 -0
  21. package/dist/src/errors.d.ts.map +1 -1
  22. package/dist/src/errors.js +8 -0
  23. package/dist/src/errors.js.map +1 -1
  24. package/dist/src/index.d.ts +33 -1
  25. package/dist/src/index.d.ts.map +1 -1
  26. package/dist/src/index.js +33 -1
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/length-prefixed-decoder.d.ts +37 -0
  29. package/dist/src/length-prefixed-decoder.d.ts.map +1 -0
  30. package/dist/src/length-prefixed-decoder.js +64 -0
  31. package/dist/src/length-prefixed-decoder.js.map +1 -0
  32. package/dist/src/message-queue.d.ts +61 -0
  33. package/dist/src/message-queue.d.ts.map +1 -0
  34. package/dist/src/message-queue.js +93 -0
  35. package/dist/src/message-queue.js.map +1 -0
  36. package/dist/src/mock-muxer.d.ts +57 -0
  37. package/dist/src/mock-muxer.d.ts.map +1 -0
  38. package/dist/src/mock-muxer.js +204 -0
  39. package/dist/src/mock-muxer.js.map +1 -0
  40. package/dist/src/mock-stream.d.ts +31 -0
  41. package/dist/src/mock-stream.d.ts.map +1 -0
  42. package/dist/src/mock-stream.js +69 -0
  43. package/dist/src/mock-stream.js.map +1 -0
  44. package/dist/src/multiaddr/index.d.ts +7 -0
  45. package/dist/src/multiaddr/index.d.ts.map +1 -0
  46. package/dist/src/multiaddr/index.js +7 -0
  47. package/dist/src/multiaddr/index.js.map +1 -0
  48. package/dist/src/multiaddr-connection-pair.d.ts +25 -0
  49. package/dist/src/multiaddr-connection-pair.d.ts.map +1 -0
  50. package/dist/src/multiaddr-connection-pair.js +103 -0
  51. package/dist/src/multiaddr-connection-pair.js.map +1 -0
  52. package/dist/src/queue/index.d.ts +5 -0
  53. package/dist/src/queue/index.d.ts.map +1 -1
  54. package/dist/src/queue/index.js +24 -9
  55. package/dist/src/queue/index.js.map +1 -1
  56. package/dist/src/rate-limiter.d.ts +1 -15
  57. package/dist/src/rate-limiter.d.ts.map +1 -1
  58. package/dist/src/rate-limiter.js +1 -14
  59. package/dist/src/rate-limiter.js.map +1 -1
  60. package/dist/src/socket-writer.browser.d.ts +2 -0
  61. package/dist/src/socket-writer.browser.d.ts.map +1 -0
  62. package/dist/src/socket-writer.browser.js +4 -0
  63. package/dist/src/socket-writer.browser.js.map +1 -0
  64. package/dist/src/socket-writer.d.ts +19 -0
  65. package/dist/src/socket-writer.d.ts.map +1 -0
  66. package/dist/src/socket-writer.js +43 -0
  67. package/dist/src/socket-writer.js.map +1 -0
  68. package/dist/src/stream-pair.d.ts +42 -0
  69. package/dist/src/stream-pair.d.ts.map +1 -0
  70. package/dist/src/stream-pair.js +40 -0
  71. package/dist/src/stream-pair.js.map +1 -0
  72. package/dist/src/stream-utils.d.ts +199 -0
  73. package/dist/src/stream-utils.d.ts.map +1 -0
  74. package/dist/src/stream-utils.js +369 -0
  75. package/dist/src/stream-utils.js.map +1 -0
  76. package/package.json +19 -163
  77. package/src/abstract-message-stream.ts +549 -0
  78. package/src/abstract-multiaddr-connection.ts +93 -0
  79. package/src/abstract-stream-muxer.ts +239 -0
  80. package/src/abstract-stream.ts +51 -464
  81. package/src/errors.ts +10 -0
  82. package/src/index.ts +33 -1
  83. package/src/length-prefixed-decoder.ts +98 -0
  84. package/src/message-queue.ts +156 -0
  85. package/src/mock-muxer.ts +304 -0
  86. package/src/mock-stream.ts +101 -0
  87. package/src/multiaddr/index.ts +6 -0
  88. package/src/multiaddr-connection-pair.ts +147 -0
  89. package/src/queue/index.ts +30 -9
  90. package/src/rate-limiter.ts +3 -30
  91. package/src/socket-writer.browser.ts +3 -0
  92. package/src/socket-writer.ts +64 -0
  93. package/src/stream-pair.ts +90 -0
  94. package/src/stream-utils.ts +874 -0
  95. package/dist/src/abort-options.d.ts +0 -7
  96. package/dist/src/abort-options.d.ts.map +0 -1
  97. package/dist/src/abort-options.js +0 -14
  98. package/dist/src/abort-options.js.map +0 -1
  99. package/dist/src/array-equals.d.ts +0 -24
  100. package/dist/src/array-equals.d.ts.map +0 -1
  101. package/dist/src/array-equals.js +0 -31
  102. package/dist/src/array-equals.js.map +0 -1
  103. package/dist/src/close-source.d.ts +0 -4
  104. package/dist/src/close-source.d.ts.map +0 -1
  105. package/dist/src/close-source.js +0 -11
  106. package/dist/src/close-source.js.map +0 -1
  107. package/dist/src/close.d.ts +0 -21
  108. package/dist/src/close.d.ts.map +0 -1
  109. package/dist/src/close.js +0 -49
  110. package/dist/src/close.js.map +0 -1
  111. package/dist/src/stream-to-ma-conn.d.ts +0 -23
  112. package/dist/src/stream-to-ma-conn.d.ts.map +0 -1
  113. package/dist/src/stream-to-ma-conn.js +0 -75
  114. package/dist/src/stream-to-ma-conn.js.map +0 -1
  115. package/dist/typedoc-urls.json +0 -147
  116. package/src/abort-options.ts +0 -20
  117. package/src/array-equals.ts +0 -34
  118. package/src/close-source.ts +0 -14
  119. package/src/close.ts +0 -65
  120. package/src/stream-to-ma-conn.ts +0 -105
@@ -0,0 +1,549 @@
1
+ import { StreamResetError, TypedEventEmitter, StreamMessageEvent, StreamBufferError, StreamResetEvent, StreamAbortEvent, StreamCloseEvent, StreamStateError } from '@libp2p/interface'
2
+ import { pushable } from 'it-pushable'
3
+ import { Uint8ArrayList } from 'uint8arraylist'
4
+ import type { MessageStreamEvents, MessageStreamStatus, MessageStream, AbortOptions, MessageStreamTimeline, MessageStreamDirection, EventHandler, StreamOptions, MessageStreamReadStatus, MessageStreamWriteStatus } from '@libp2p/interface'
5
+ import type { Logger } from '@libp2p/logger'
6
+
7
+ const DEFAULT_MAX_READ_BUFFER_LENGTH = Math.pow(2, 20) * 4 // 4MB
8
+
9
+ export interface MessageStreamInit extends StreamOptions {
10
+ /**
11
+ * A Logger implementation used to log stream-specific information
12
+ */
13
+ log: Logger
14
+
15
+ /**
16
+ * The stream direction
17
+ */
18
+ direction?: MessageStreamDirection
19
+
20
+ /**
21
+ * By default all available bytes are passed to the `sendData` method of
22
+ * extending classes, if smaller chunks are required, pass a value here.
23
+ */
24
+ maxMessageSize?: number
25
+ }
26
+
27
+ export interface SendResult {
28
+ /**
29
+ * The number of bytes from the passed buffer that were sent
30
+ */
31
+ sentBytes: number
32
+
33
+ /**
34
+ * If the underlying resource can accept more data immediately. If `true`,
35
+ * `sent` must equal the `.byteLength` of the buffer passed to `sendData`.
36
+ */
37
+ canSendMore: boolean
38
+ }
39
+
40
+ export abstract class AbstractMessageStream<Timeline extends MessageStreamTimeline = MessageStreamTimeline> extends TypedEventEmitter<MessageStreamEvents> implements MessageStream {
41
+ public status: MessageStreamStatus
42
+ public readonly timeline: Timeline
43
+ public inactivityTimeout: number
44
+ public maxReadBufferLength: number
45
+ public maxWriteBufferLength?: number
46
+ public readonly log: Logger
47
+ public direction: MessageStreamDirection
48
+ public maxMessageSize?: number
49
+
50
+ public readStatus: MessageStreamReadStatus
51
+ public writeStatus: MessageStreamWriteStatus
52
+ public remoteReadStatus: MessageStreamReadStatus
53
+ public remoteWriteStatus: MessageStreamWriteStatus
54
+
55
+ public writableNeedsDrain: boolean
56
+
57
+ /**
58
+ * Any data stored here is emitted before any new incoming data.
59
+ *
60
+ * This is used when the stream is paused or if data is pushed onto the stream
61
+ */
62
+ protected readonly readBuffer: Uint8ArrayList
63
+ protected readonly writeBuffer: Uint8ArrayList
64
+ protected sendingData: boolean
65
+
66
+ constructor (init: MessageStreamInit) {
67
+ super()
68
+
69
+ this.status = 'open'
70
+ this.log = init.log
71
+ this.direction = init.direction ?? 'outbound'
72
+ this.inactivityTimeout = init.inactivityTimeout ?? 120_000
73
+ this.maxReadBufferLength = init.maxReadBufferLength ?? DEFAULT_MAX_READ_BUFFER_LENGTH
74
+ this.maxWriteBufferLength = init.maxWriteBufferLength
75
+ this.maxMessageSize = init.maxMessageSize
76
+ this.readBuffer = new Uint8ArrayList()
77
+ this.writeBuffer = new Uint8ArrayList()
78
+
79
+ this.readStatus = 'readable'
80
+ this.remoteReadStatus = 'readable'
81
+ this.writeStatus = 'writable'
82
+ this.remoteWriteStatus = 'writable'
83
+ this.sendingData = false
84
+ this.writableNeedsDrain = false
85
+
86
+ // @ts-expect-error type could have required fields other than 'open'
87
+ this.timeline = {
88
+ open: Date.now()
89
+ }
90
+
91
+ this.processSendQueue = this.processSendQueue.bind(this)
92
+
93
+ const continueSendingOnDrain = (): void => {
94
+ this.log.trace('drain event received, continue sending data')
95
+ this.writableNeedsDrain = false
96
+ this.processSendQueue()
97
+ }
98
+ this.addEventListener('drain', continueSendingOnDrain)
99
+ }
100
+
101
+ async * [Symbol.asyncIterator] (): AsyncGenerator<Uint8Array | Uint8ArrayList> {
102
+ if (this.readStatus !== 'readable' && this.readStatus !== 'paused') {
103
+ return
104
+ }
105
+
106
+ const output = pushable<Uint8Array | Uint8ArrayList>()
107
+
108
+ const streamAsyncIterableOnMessageListener = (evt: StreamMessageEvent): void => {
109
+ output.push(evt.data)
110
+ }
111
+ this.addEventListener('message', streamAsyncIterableOnMessageListener)
112
+
113
+ const streamAsyncIterableOnCloseListener = (evt: StreamCloseEvent): void => {
114
+ output.end(evt.error)
115
+ }
116
+ this.addEventListener('close', streamAsyncIterableOnCloseListener)
117
+
118
+ const streamAsyncIterableOnRemoteCloseWriteListener = (): void => {
119
+ output.end()
120
+ }
121
+ this.addEventListener('remoteCloseWrite', streamAsyncIterableOnRemoteCloseWriteListener)
122
+
123
+ try {
124
+ yield * output
125
+ } finally {
126
+ this.removeEventListener('message', streamAsyncIterableOnMessageListener)
127
+ this.removeEventListener('close', streamAsyncIterableOnCloseListener)
128
+ this.removeEventListener('remoteCloseWrite', streamAsyncIterableOnRemoteCloseWriteListener)
129
+ }
130
+ }
131
+
132
+ isReadable (): boolean {
133
+ return this.status === 'open'
134
+ }
135
+
136
+ send (data: Uint8Array | Uint8ArrayList): boolean {
137
+ if (this.writeStatus === 'closed' || this.writeStatus === 'closing') {
138
+ throw new StreamStateError(`Cannot write to a stream that is ${this.writeStatus}`)
139
+ }
140
+
141
+ this.log.trace('append %d bytes to write buffer', data.byteLength)
142
+ this.writeBuffer.append(data)
143
+
144
+ return this.processSendQueue()
145
+ }
146
+
147
+ /**
148
+ * Close immediately for reading and writing and send a reset message (local
149
+ * error)
150
+ */
151
+ abort (err: Error): void {
152
+ if (this.status === 'aborted' || this.status === 'reset' || this.status === 'closed') {
153
+ return
154
+ }
155
+
156
+ this.log.error('abort with error - %e', err)
157
+
158
+ this.status = 'aborted'
159
+
160
+ // throw away unread data
161
+ if (this.readBuffer.byteLength > 0) {
162
+ this.readBuffer.consume(this.readBuffer.byteLength)
163
+ }
164
+
165
+ // throw away unsent data
166
+ if (this.writeBuffer.byteLength > 0) {
167
+ this.writeBuffer.consume(this.writeBuffer.byteLength)
168
+ this.safeDispatchEvent('idle')
169
+ }
170
+
171
+ this.writeStatus = 'closed'
172
+ this.remoteWriteStatus = 'closed'
173
+
174
+ this.readStatus = 'closed'
175
+ this.remoteReadStatus = 'closed'
176
+ this.timeline.close = Date.now()
177
+
178
+ try {
179
+ this.sendReset(err)
180
+ } catch (err: any) {
181
+ this.log('failed to send reset to remote - %e', err)
182
+ }
183
+
184
+ this.dispatchEvent(new StreamAbortEvent(err))
185
+ }
186
+
187
+ pause (): void {
188
+ if (this.readStatus === 'closed' || this.readStatus === 'closing') {
189
+ throw new StreamStateError('Cannot pause a stream that is closing/closed')
190
+ }
191
+
192
+ if (this.readStatus === 'paused') {
193
+ return
194
+ }
195
+
196
+ this.readStatus = 'paused'
197
+ this.sendPause()
198
+ }
199
+
200
+ resume (): void {
201
+ if (this.readStatus === 'closed' || this.readStatus === 'closing') {
202
+ throw new StreamStateError('Cannot resume a stream that is closing/closed')
203
+ }
204
+
205
+ if (this.readStatus === 'readable') {
206
+ return
207
+ }
208
+
209
+ this.readStatus = 'readable'
210
+ // emit any data that accumulated while we were paused
211
+ this.dispatchReadBuffer()
212
+ this.sendResume()
213
+ }
214
+
215
+ push (data: Uint8Array | Uint8ArrayList): void {
216
+ if (this.readStatus === 'closed' || this.readStatus === 'closing') {
217
+ throw new StreamStateError(`Cannot push data onto a stream that is ${this.readStatus}`)
218
+ }
219
+
220
+ if (data.byteLength === 0) {
221
+ return
222
+ }
223
+
224
+ this.readBuffer.append(data)
225
+
226
+ if (this.readStatus === 'paused' || this.listenerCount('message') === 0) {
227
+ // abort if the read buffer is too large
228
+ this.checkReadBufferLength()
229
+
230
+ return
231
+ }
232
+
233
+ // TODO: use a microtask instead?
234
+ setTimeout(() => {
235
+ this.dispatchReadBuffer()
236
+ }, 0)
237
+ }
238
+
239
+ /**
240
+ * When an extending class reads data from it's implementation-specific source,
241
+ * call this method to allow the stream consumer to read the data.
242
+ */
243
+ onData (data: Uint8Array | Uint8ArrayList): void {
244
+ if (data.byteLength === 0) {
245
+ // this.log('ignoring empty data')
246
+ return
247
+ }
248
+
249
+ // discard the data if our readable end is closed
250
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
251
+ this.log('ignoring data - read status %s', this.readStatus)
252
+ return
253
+ }
254
+
255
+ this.readBuffer.append(data)
256
+ this.dispatchReadBuffer()
257
+ }
258
+
259
+ addEventListener<K extends keyof MessageStreamEvents>(type: K, listener: EventHandler<MessageStreamEvents[K]> | null, options?: boolean | AddEventListenerOptions): void
260
+ addEventListener (type: string, listener: EventHandler<Event>, options?: boolean | AddEventListenerOptions): void
261
+ addEventListener (...args: any[]): void {
262
+ // @ts-expect-error cannot ensure args has enough members
263
+ super.addEventListener.apply(this, args)
264
+
265
+ // if a 'message' listener is being added and we have queued data, dispatch
266
+ // the data
267
+ if (args[0] === 'message' && this.readBuffer.byteLength > 0) {
268
+ // event listeners can be added in constructors and often use object
269
+ // properties - if this the case we can access a class member before it
270
+ // has been initialized so dispatch the message in the microtask queue
271
+ queueMicrotask(() => {
272
+ this.dispatchReadBuffer()
273
+ })
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Receive a reset message - close immediately for reading and writing (remote
279
+ * error)
280
+ */
281
+ onRemoteReset (): void {
282
+ this.log('remote reset')
283
+
284
+ this.status = 'reset'
285
+ this.writeStatus = 'closed'
286
+ this.remoteWriteStatus = 'closed'
287
+ this.remoteReadStatus = 'closed'
288
+ this.timeline.close = Date.now()
289
+
290
+ if (this.readBuffer.byteLength === 0) {
291
+ this.readStatus = 'closed'
292
+ }
293
+
294
+ const err = new StreamResetError()
295
+ this.dispatchEvent(new StreamResetEvent(err))
296
+ }
297
+
298
+ /**
299
+ * The underlying resource or transport this stream uses has closed - it is
300
+ * not possible to send any more messages though any data still in the read
301
+ * buffer may still be read
302
+ */
303
+ onTransportClosed (err?: Error): void {
304
+ this.log('transport closed')
305
+
306
+ if (this.readStatus === 'readable' && this.readBuffer.byteLength === 0) {
307
+ this.readStatus = 'closed'
308
+ }
309
+
310
+ if (this.remoteReadStatus !== 'closed') {
311
+ this.remoteReadStatus = 'closed'
312
+ }
313
+
314
+ if (this.remoteWriteStatus !== 'closed') {
315
+ this.remoteWriteStatus = 'closed'
316
+ }
317
+
318
+ if (this.writeStatus !== 'closed') {
319
+ this.writeStatus = 'closed'
320
+ }
321
+
322
+ if (err != null) {
323
+ this.abort(err)
324
+ } else {
325
+ if (this.status === 'open' || this.status === 'closing') {
326
+ this.timeline.close = Date.now()
327
+ this.status = 'closed'
328
+ this.writeStatus = 'closed'
329
+ this.remoteWriteStatus = 'closed'
330
+ this.remoteReadStatus = 'closed'
331
+ this.dispatchEvent(new StreamCloseEvent())
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Called by extending classes when the remote closes its writable end
338
+ */
339
+ onRemoteCloseWrite (): void {
340
+ if (this.remoteWriteStatus === 'closed') {
341
+ return
342
+ }
343
+
344
+ this.log.trace('on remote close write')
345
+
346
+ this.remoteWriteStatus = 'closed'
347
+
348
+ this.safeDispatchEvent('remoteCloseWrite')
349
+
350
+ if (this.writeStatus === 'closed') {
351
+ this.onTransportClosed()
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Called by extending classes when the remote closes its readable end
357
+ */
358
+ onRemoteCloseRead (): void {
359
+ this.log.trace('on remote close read')
360
+
361
+ this.remoteReadStatus = 'closed'
362
+
363
+ // throw away any unsent bytes if the remote closes it's readable end
364
+ if (this.writeBuffer.byteLength > 0) {
365
+ this.writeBuffer.consume(this.writeBuffer.byteLength)
366
+ this.safeDispatchEvent('idle')
367
+ }
368
+ }
369
+
370
+ protected processSendQueue (): boolean {
371
+ // bail if the underlying transport is full
372
+ if (this.writableNeedsDrain) {
373
+ this.log.trace('not processing send queue as drain is required')
374
+ this.checkWriteBufferLength()
375
+
376
+ return false
377
+ }
378
+
379
+ // bail if there is no data to send
380
+ if (this.writeBuffer.byteLength === 0) {
381
+ this.log.trace('not processing send queue as no bytes to send')
382
+ return true
383
+ }
384
+
385
+ // bail if we are already sending data
386
+ if (this.sendingData) {
387
+ this.log.trace('not processing send queue as already sending data')
388
+ return true
389
+ }
390
+
391
+ this.sendingData = true
392
+
393
+ this.log.trace('processing send queue with %d queued bytes', this.writeBuffer.byteLength)
394
+
395
+ try {
396
+ let canSendMore = true
397
+ const totalBytes = this.writeBuffer.byteLength
398
+ let sentBytes = 0
399
+
400
+ // send as much data as possible while we have data to send and the
401
+ // underlying muxer can still accept data
402
+ while (this.writeBuffer.byteLength > 0) {
403
+ const end = Math.min(this.maxMessageSize ?? this.writeBuffer.byteLength, this.writeBuffer.byteLength)
404
+
405
+ // this can happen if a subclass changes the max message size dynamically
406
+ if (end === 0) {
407
+ canSendMore = false
408
+ break
409
+ }
410
+
411
+ // chunk to send to the remote end
412
+ const toSend = this.writeBuffer.sublist(0, end)
413
+
414
+ // copy toSend in case the extending class modifies the list
415
+ const willSend = new Uint8ArrayList(toSend)
416
+
417
+ this.writeBuffer.consume(toSend.byteLength)
418
+
419
+ // sending data can cause buffers to fill up, events to be emitted and
420
+ // this method to be invoked again
421
+ const sendResult = this.sendData(toSend)
422
+ canSendMore = sendResult.canSendMore
423
+ sentBytes += sendResult.sentBytes
424
+
425
+ if (sendResult.sentBytes !== willSend.byteLength) {
426
+ willSend.consume(sendResult.sentBytes)
427
+ this.writeBuffer.prepend(willSend)
428
+ }
429
+
430
+ if (!canSendMore) {
431
+ break
432
+ }
433
+ }
434
+
435
+ if (!canSendMore) {
436
+ this.log.trace('sent %d/%d bytes, pausing sending because underlying stream is full, %d bytes left in the write buffer', sentBytes, totalBytes, this.writeBuffer.byteLength)
437
+ this.writableNeedsDrain = true
438
+ this.checkWriteBufferLength()
439
+ }
440
+
441
+ // we processed all bytes in the queue, resolve the write queue idle promise
442
+ if (this.writeBuffer.byteLength === 0) {
443
+ this.safeDispatchEvent('idle')
444
+ }
445
+
446
+ return canSendMore
447
+ } finally {
448
+ this.sendingData = false
449
+ }
450
+ }
451
+
452
+ protected dispatchReadBuffer (): void {
453
+ try {
454
+ if (this.listenerCount('message') === 0) {
455
+ this.log.trace('not dispatching pause buffer as there are no listeners for the message event')
456
+ return
457
+ }
458
+
459
+ if (this.readBuffer.byteLength === 0) {
460
+ this.log.trace('not dispatching pause buffer as there is no data to dispatch')
461
+ return
462
+ }
463
+
464
+ if (this.readStatus === 'paused') {
465
+ this.log.trace('not dispatching pause buffer we are paused')
466
+ return
467
+ }
468
+
469
+ // discard the pause buffer if our readable end is closed
470
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
471
+ this.log('dropping %d bytes because the readable end is %s', this.readBuffer.byteLength, this.readStatus)
472
+ this.readBuffer.consume(this.readBuffer.byteLength)
473
+ return
474
+ }
475
+
476
+ const buf = this.readBuffer.sublist()
477
+ this.readBuffer.consume(buf.byteLength)
478
+
479
+ this.dispatchEvent(new StreamMessageEvent(buf))
480
+ } finally {
481
+ if (this.remoteWriteStatus === 'closed') {
482
+ this.readStatus = 'closed'
483
+ }
484
+
485
+ // abort if we failed to consume the read buffer and it is too large
486
+ this.checkReadBufferLength()
487
+ }
488
+ }
489
+
490
+ private checkReadBufferLength (): void {
491
+ if (this.readBuffer.byteLength > this.maxReadBufferLength) {
492
+ this.abort(new StreamBufferError(`Read buffer length of ${this.readBuffer.byteLength} exceeded limit of ${this.maxReadBufferLength}, read status is ${this.readStatus}`))
493
+ }
494
+ }
495
+
496
+ private checkWriteBufferLength (): void {
497
+ if (this.maxWriteBufferLength == null) {
498
+ return
499
+ }
500
+
501
+ if (this.writeBuffer.byteLength > this.maxWriteBufferLength) {
502
+ this.abort(new StreamBufferError(`Write buffer length of ${this.writeBuffer.byteLength} exceeded limit of ${this.maxWriteBufferLength}, write status is ${this.writeStatus}`))
503
+ }
504
+ }
505
+
506
+ public onMuxerNeedsDrain (): void {
507
+ this.writableNeedsDrain = true
508
+ }
509
+
510
+ public onMuxerDrain (): void {
511
+ this.safeDispatchEvent('drain')
512
+ }
513
+
514
+ /**
515
+ * Send a data message to the remote end of the stream. Implementations of
516
+ * this method should return the number of bytes from the passed buffer that
517
+ * were sent successfully and if the underlying resource can accept more data.
518
+ *
519
+ * The implementation should always attempt to send the maximum amount of data
520
+ * possible.
521
+ *
522
+ * Returning a result that means the data was only partially sent but that the
523
+ * underlying resource can accept more data is invalid.
524
+ */
525
+ abstract sendData (data: Uint8ArrayList): SendResult
526
+
527
+ /**
528
+ * Send a reset message to the remote end of the stream
529
+ */
530
+ abstract sendReset (err: Error): void
531
+
532
+ /**
533
+ * If supported, instruct the remote end of the stream to temporarily stop
534
+ * sending data messages
535
+ */
536
+ abstract sendPause (): void
537
+
538
+ /**
539
+ * If supported, inform the remote end of the stream they may resume sending
540
+ * data messages
541
+ */
542
+ abstract sendResume (): void
543
+
544
+ /**
545
+ * Stop accepting new data to send and return a promise that resolves when any
546
+ * unsent data has been written into the underlying resource.
547
+ */
548
+ abstract close (options?: AbortOptions): Promise<void>
549
+ }
@@ -0,0 +1,93 @@
1
+ import { pEvent } from 'p-event'
2
+ import { AbstractMessageStream } from './abstract-message-stream.ts'
3
+ import type { MessageStreamInit } from './abstract-message-stream.ts'
4
+ import type { CounterGroup, Logger, MultiaddrConnection, MessageStreamDirection } from '@libp2p/interface'
5
+ import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
6
+
7
+ export interface AbstractMultiaddrConnectionInit extends Omit<MessageStreamInit, 'log'> {
8
+ remoteAddr: Multiaddr
9
+ direction: MessageStreamDirection
10
+ log: Logger
11
+ inactivityTimeout?: number
12
+ localAddr?: Multiaddr
13
+ metricPrefix?: string
14
+ metrics?: CounterGroup
15
+ }
16
+
17
+ export abstract class AbstractMultiaddrConnection extends AbstractMessageStream implements MultiaddrConnection {
18
+ public remoteAddr: Multiaddr
19
+
20
+ private metricPrefix: string
21
+ private metrics?: CounterGroup
22
+
23
+ constructor (init: AbstractMultiaddrConnectionInit) {
24
+ super(init)
25
+
26
+ this.metricPrefix = init.metricPrefix ?? ''
27
+ this.metrics = init.metrics
28
+ this.remoteAddr = init.remoteAddr
29
+
30
+ this.addEventListener('close', (evt) => {
31
+ this.metrics?.increment({ [`${this.metricPrefix}end`]: true })
32
+
33
+ if (evt.error != null) {
34
+ if (evt.local) {
35
+ this.metrics?.increment({ [`${this.metricPrefix}abort`]: true })
36
+ } else {
37
+ this.metrics?.increment({ [`${this.metricPrefix}reset`]: true })
38
+ }
39
+ } else {
40
+ if (evt.local) {
41
+ this.metrics?.increment({ [`${this.metricPrefix}_local_close`]: true })
42
+ } else {
43
+ this.metrics?.increment({ [`${this.metricPrefix}_remote_close`]: true })
44
+ }
45
+ }
46
+ })
47
+ }
48
+
49
+ async close (options?: AbortOptions): Promise<void> {
50
+ if (this.status !== 'open') {
51
+ return
52
+ }
53
+
54
+ this.status = 'closing'
55
+ this.writeStatus = 'closing'
56
+ this.remoteWriteStatus = 'closing'
57
+ this.remoteReadStatus = 'closing'
58
+
59
+ // if we are currently sending data, wait for all the data to be written
60
+ // into the underlying transport
61
+ if (this.sendingData || this.writeBuffer.byteLength > 0) {
62
+ this.log('waiting for write queue to become idle before closing writable end of stream, %d unsent bytes', this.writeBuffer.byteLength)
63
+ await pEvent(this, 'idle', {
64
+ ...options,
65
+ rejectionEvents: [
66
+ 'close'
67
+ ]
68
+ })
69
+ }
70
+
71
+ // now that the underlying transport has all the data, if the buffer is full
72
+ // wait for it to be emptied
73
+ if (this.writableNeedsDrain) {
74
+ this.log('waiting for write queue to drain before closing writable end of stream, %d unsent bytes', this.writeBuffer.byteLength)
75
+ await pEvent(this, 'drain', {
76
+ ...options,
77
+ rejectionEvents: [
78
+ 'close'
79
+ ]
80
+ })
81
+ }
82
+
83
+ await this.sendClose(options)
84
+
85
+ this.onTransportClosed()
86
+ }
87
+
88
+ /**
89
+ * Wait for any unsent data to be written to the underlying resource, then
90
+ * close the resource and resolve the returned promise
91
+ */
92
+ abstract sendClose (options?: AbortOptions): Promise<void>
93
+ }