@libp2p/utils 6.7.2 → 7.0.0-55b7e5fea

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