@libp2p/utils 6.7.2-8484de8a2 → 6.7.2-87bc8d4fb

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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/index.min.js +1 -6
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/abort-options.d.ts +7 -0
  5. package/dist/src/abort-options.d.ts.map +1 -0
  6. package/dist/src/abort-options.js +14 -0
  7. package/dist/src/abort-options.js.map +1 -0
  8. package/dist/src/abstract-stream.d.ts +130 -14
  9. package/dist/src/abstract-stream.d.ts.map +1 -1
  10. package/dist/src/abstract-stream.js +321 -39
  11. package/dist/src/abstract-stream.js.map +1 -1
  12. package/dist/src/array-equals.d.ts +24 -0
  13. package/dist/src/array-equals.d.ts.map +1 -0
  14. package/dist/src/array-equals.js +31 -0
  15. package/dist/src/array-equals.js.map +1 -0
  16. package/dist/src/close-source.d.ts +4 -0
  17. package/dist/src/close-source.d.ts.map +1 -0
  18. package/dist/src/close-source.js +11 -0
  19. package/dist/src/close-source.js.map +1 -0
  20. package/dist/src/close.d.ts +21 -0
  21. package/dist/src/close.d.ts.map +1 -0
  22. package/dist/src/close.js +49 -0
  23. package/dist/src/close.js.map +1 -0
  24. package/dist/src/errors.d.ts +0 -8
  25. package/dist/src/errors.d.ts.map +1 -1
  26. package/dist/src/errors.js +0 -8
  27. package/dist/src/errors.js.map +1 -1
  28. package/dist/src/get-thin-waist-addresses.browser.d.ts +1 -1
  29. package/dist/src/get-thin-waist-addresses.browser.d.ts.map +1 -1
  30. package/dist/src/get-thin-waist-addresses.browser.js +3 -4
  31. package/dist/src/get-thin-waist-addresses.browser.js.map +1 -1
  32. package/dist/src/get-thin-waist-addresses.d.ts +1 -1
  33. package/dist/src/get-thin-waist-addresses.d.ts.map +1 -1
  34. package/dist/src/get-thin-waist-addresses.js +9 -7
  35. package/dist/src/get-thin-waist-addresses.js.map +1 -1
  36. package/dist/src/index.d.ts +1 -33
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +1 -33
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/multiaddr/is-global-unicast.d.ts.map +1 -1
  41. package/dist/src/multiaddr/is-global-unicast.js +9 -8
  42. package/dist/src/multiaddr/is-global-unicast.js.map +1 -1
  43. package/dist/src/multiaddr/is-ip-based.d.ts +6 -0
  44. package/dist/src/multiaddr/is-ip-based.d.ts.map +1 -0
  45. package/dist/src/multiaddr/is-ip-based.js +18 -0
  46. package/dist/src/multiaddr/is-ip-based.js.map +1 -0
  47. package/dist/src/multiaddr/is-link-local.d.ts.map +1 -1
  48. package/dist/src/multiaddr/is-link-local.js +16 -11
  49. package/dist/src/multiaddr/is-link-local.js.map +1 -1
  50. package/dist/src/multiaddr/is-loopback.d.ts.map +1 -1
  51. package/dist/src/multiaddr/is-loopback.js +5 -12
  52. package/dist/src/multiaddr/is-loopback.js.map +1 -1
  53. package/dist/src/multiaddr/is-network-address.d.ts.map +1 -1
  54. package/dist/src/multiaddr/is-network-address.js +16 -4
  55. package/dist/src/multiaddr/is-network-address.js.map +1 -1
  56. package/dist/src/multiaddr/is-private.d.ts.map +1 -1
  57. package/dist/src/multiaddr/is-private.js +10 -9
  58. package/dist/src/multiaddr/is-private.js.map +1 -1
  59. package/dist/src/queue/index.d.ts +0 -3
  60. package/dist/src/queue/index.d.ts.map +1 -1
  61. package/dist/src/queue/index.js +4 -20
  62. package/dist/src/queue/index.js.map +1 -1
  63. package/dist/src/rate-limiter.d.ts +15 -1
  64. package/dist/src/rate-limiter.d.ts.map +1 -1
  65. package/dist/src/rate-limiter.js +14 -1
  66. package/dist/src/rate-limiter.js.map +1 -1
  67. package/dist/src/stream-to-ma-conn.d.ts +23 -0
  68. package/dist/src/stream-to-ma-conn.d.ts.map +1 -0
  69. package/dist/src/stream-to-ma-conn.js +75 -0
  70. package/dist/src/stream-to-ma-conn.js.map +1 -0
  71. package/package.json +163 -19
  72. package/src/abort-options.ts +20 -0
  73. package/src/abstract-stream.ts +464 -51
  74. package/src/array-equals.ts +34 -0
  75. package/src/close-source.ts +14 -0
  76. package/src/close.ts +65 -0
  77. package/src/errors.ts +0 -10
  78. package/src/get-thin-waist-addresses.browser.ts +4 -5
  79. package/src/get-thin-waist-addresses.ts +12 -8
  80. package/src/index.ts +1 -33
  81. package/src/multiaddr/is-global-unicast.ts +11 -8
  82. package/src/multiaddr/is-ip-based.ts +21 -0
  83. package/src/multiaddr/is-link-local.ts +20 -11
  84. package/src/multiaddr/is-loopback.ts +7 -12
  85. package/src/multiaddr/is-network-address.ts +19 -4
  86. package/src/multiaddr/is-private.ts +14 -9
  87. package/src/queue/index.ts +4 -24
  88. package/src/rate-limiter.ts +30 -3
  89. package/src/stream-to-ma-conn.ts +106 -0
  90. package/dist/src/abstract-message-stream.d.ts +0 -129
  91. package/dist/src/abstract-message-stream.d.ts.map +0 -1
  92. package/dist/src/abstract-message-stream.js +0 -389
  93. package/dist/src/abstract-message-stream.js.map +0 -1
  94. package/dist/src/abstract-multiaddr-connection.d.ts +0 -26
  95. package/dist/src/abstract-multiaddr-connection.d.ts.map +0 -1
  96. package/dist/src/abstract-multiaddr-connection.js +0 -66
  97. package/dist/src/abstract-multiaddr-connection.js.map +0 -1
  98. package/dist/src/abstract-stream-muxer.d.ts +0 -53
  99. package/dist/src/abstract-stream-muxer.d.ts.map +0 -1
  100. package/dist/src/abstract-stream-muxer.js +0 -169
  101. package/dist/src/abstract-stream-muxer.js.map +0 -1
  102. package/dist/src/length-prefixed-decoder.d.ts +0 -37
  103. package/dist/src/length-prefixed-decoder.d.ts.map +0 -1
  104. package/dist/src/length-prefixed-decoder.js +0 -64
  105. package/dist/src/length-prefixed-decoder.js.map +0 -1
  106. package/dist/src/message-queue.d.ts +0 -61
  107. package/dist/src/message-queue.d.ts.map +0 -1
  108. package/dist/src/message-queue.js +0 -93
  109. package/dist/src/message-queue.js.map +0 -1
  110. package/dist/src/mock-muxer.d.ts +0 -57
  111. package/dist/src/mock-muxer.d.ts.map +0 -1
  112. package/dist/src/mock-muxer.js +0 -204
  113. package/dist/src/mock-muxer.js.map +0 -1
  114. package/dist/src/mock-stream.d.ts +0 -31
  115. package/dist/src/mock-stream.d.ts.map +0 -1
  116. package/dist/src/mock-stream.js +0 -69
  117. package/dist/src/mock-stream.js.map +0 -1
  118. package/dist/src/multiaddr/get-net-config.d.ts +0 -55
  119. package/dist/src/multiaddr/get-net-config.d.ts.map +0 -1
  120. package/dist/src/multiaddr/get-net-config.js +0 -54
  121. package/dist/src/multiaddr/get-net-config.js.map +0 -1
  122. package/dist/src/multiaddr/index.d.ts +0 -7
  123. package/dist/src/multiaddr/index.d.ts.map +0 -1
  124. package/dist/src/multiaddr/index.js +0 -7
  125. package/dist/src/multiaddr/index.js.map +0 -1
  126. package/dist/src/multiaddr/utils.d.ts +0 -5
  127. package/dist/src/multiaddr/utils.d.ts.map +0 -1
  128. package/dist/src/multiaddr/utils.js +0 -32
  129. package/dist/src/multiaddr/utils.js.map +0 -1
  130. package/dist/src/multiaddr-connection-pair.d.ts +0 -25
  131. package/dist/src/multiaddr-connection-pair.d.ts.map +0 -1
  132. package/dist/src/multiaddr-connection-pair.js +0 -103
  133. package/dist/src/multiaddr-connection-pair.js.map +0 -1
  134. package/dist/src/socket-writer.browser.d.ts +0 -2
  135. package/dist/src/socket-writer.browser.d.ts.map +0 -1
  136. package/dist/src/socket-writer.browser.js +0 -4
  137. package/dist/src/socket-writer.browser.js.map +0 -1
  138. package/dist/src/socket-writer.d.ts +0 -19
  139. package/dist/src/socket-writer.d.ts.map +0 -1
  140. package/dist/src/socket-writer.js +0 -43
  141. package/dist/src/socket-writer.js.map +0 -1
  142. package/dist/src/stream-pair.d.ts +0 -42
  143. package/dist/src/stream-pair.d.ts.map +0 -1
  144. package/dist/src/stream-pair.js +0 -40
  145. package/dist/src/stream-pair.js.map +0 -1
  146. package/dist/src/stream-utils.d.ts +0 -198
  147. package/dist/src/stream-utils.d.ts.map +0 -1
  148. package/dist/src/stream-utils.js +0 -369
  149. package/dist/src/stream-utils.js.map +0 -1
  150. package/src/abstract-message-stream.ts +0 -549
  151. package/src/abstract-multiaddr-connection.ts +0 -93
  152. package/src/abstract-stream-muxer.ts +0 -239
  153. package/src/length-prefixed-decoder.ts +0 -98
  154. package/src/message-queue.ts +0 -156
  155. package/src/mock-muxer.ts +0 -304
  156. package/src/mock-stream.ts +0 -101
  157. package/src/multiaddr/get-net-config.ts +0 -112
  158. package/src/multiaddr/index.ts +0 -6
  159. package/src/multiaddr/utils.ts +0 -46
  160. package/src/multiaddr-connection-pair.ts +0 -147
  161. package/src/socket-writer.browser.ts +0 -3
  162. package/src/socket-writer.ts +0 -64
  163. package/src/stream-pair.ts +0 -90
  164. package/src/stream-utils.ts +0 -873
@@ -0,0 +1,20 @@
1
+ import { anySignal } from 'any-signal'
2
+ import { setMaxListeners } from 'main-event'
3
+ import type { AbortOptions } from '@libp2p/interface'
4
+ import type { ClearableSignal } from 'any-signal'
5
+
6
+ export function createTimeoutOptions (timeout: number): AbortOptions
7
+ export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): { signal: ClearableSignal }
8
+ export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): AbortOptions {
9
+ let signal = AbortSignal.timeout(timeout)
10
+ setMaxListeners(Infinity, signal)
11
+
12
+ if (existingSignals.length > 0) {
13
+ signal = anySignal([signal, ...existingSignals])
14
+ setMaxListeners(Infinity, signal)
15
+ }
16
+
17
+ return {
18
+ signal
19
+ }
20
+ }
@@ -1,102 +1,515 @@
1
- import { pEvent } from 'p-event'
2
- import { AbstractMessageStream } from './abstract-message-stream.js'
3
- import type { MessageStreamInit } from './abstract-message-stream.js'
4
- import type { AbortOptions, Stream } from '@libp2p/interface'
1
+ import { StreamResetError, StreamStateError } from '@libp2p/interface'
2
+ import { pushable } from 'it-pushable'
3
+ import defer from 'p-defer'
4
+ import { raceSignal } from 'race-signal'
5
+ import { Uint8ArrayList } from 'uint8arraylist'
6
+ import { closeSource } from './close-source.js'
7
+ import type { AbortOptions, Direction, ReadStatus, Stream, StreamStatus, StreamTimeline, WriteStatus } from '@libp2p/interface'
8
+ import type { Logger } from '@libp2p/logger'
9
+ import type { Pushable } from 'it-pushable'
10
+ import type { Source } from 'it-stream-types'
11
+ import type { DeferredPromise } from 'p-defer'
5
12
 
6
- export interface AbstractStreamInit extends MessageStreamInit {
13
+ const DEFAULT_SEND_CLOSE_WRITE_TIMEOUT = 5000
14
+
15
+ export interface AbstractStreamInit {
7
16
  /**
8
17
  * A unique identifier for this stream
9
18
  */
10
19
  id: string
11
20
 
12
21
  /**
13
- * The protocol name for the stream, if it is known
22
+ * The stream direction
23
+ */
24
+ direction: Direction
25
+
26
+ /**
27
+ * A Logger implementation used to log stream-specific information
28
+ */
29
+ log: Logger
30
+
31
+ /**
32
+ * User specific stream metadata
33
+ */
34
+ metadata?: Record<string, unknown>
35
+
36
+ /**
37
+ * Invoked when the stream ends
38
+ */
39
+ onEnd?(err?: Error): void
40
+
41
+ /**
42
+ * Invoked when the readable end of the stream is closed
43
+ */
44
+ onCloseRead?(): void
45
+
46
+ /**
47
+ * Invoked when the writable end of the stream is closed
48
+ */
49
+ onCloseWrite?(): void
50
+
51
+ /**
52
+ * Invoked when the stream has been reset by the remote
53
+ */
54
+ onReset?(): void
55
+
56
+ /**
57
+ * Invoked when the stream has errored
14
58
  */
15
- protocol?: string
59
+ onAbort?(err: Error): void
60
+
61
+ /**
62
+ * How long to wait in ms for stream data to be written to the underlying
63
+ * connection when closing the writable end of the stream.
64
+ *
65
+ * @default 500
66
+ */
67
+ closeTimeout?: number
68
+
69
+ /**
70
+ * After the stream sink has closed, a limit on how long it takes to send
71
+ * a close-write message to the remote peer.
72
+ */
73
+ sendCloseWriteTimeout?: number
16
74
  }
17
75
 
18
- export abstract class AbstractStream extends AbstractMessageStream implements Stream {
76
+ function isPromise <T = unknown> (thing: any): thing is Promise<T> {
77
+ if (thing == null) {
78
+ return false
79
+ }
80
+
81
+ return typeof thing.then === 'function' &&
82
+ typeof thing.catch === 'function' &&
83
+ typeof thing.finally === 'function'
84
+ }
85
+
86
+ export abstract class AbstractStream implements Stream {
19
87
  public id: string
20
- public protocol: string
88
+ public direction: Direction
89
+ public timeline: StreamTimeline
90
+ public protocol?: string
91
+ public metadata: Record<string, unknown>
92
+ public source: AsyncGenerator<Uint8ArrayList, void, unknown>
93
+ public status: StreamStatus
94
+ public readStatus: ReadStatus
95
+ public writeStatus: WriteStatus
96
+ public readonly log: Logger
97
+
98
+ private readonly sinkController: AbortController
99
+ private readonly sinkEnd: DeferredPromise<void>
100
+ private readonly closed: DeferredPromise<void>
101
+ private endErr: Error | undefined
102
+ private readonly streamSource: Pushable<Uint8ArrayList>
103
+ private readonly onEnd?: (err?: Error) => void
104
+ private readonly onCloseRead?: () => void
105
+ private readonly onCloseWrite?: () => void
106
+ private readonly onReset?: () => void
107
+ private readonly onAbort?: (err: Error) => void
108
+ private readonly sendCloseWriteTimeout: number
109
+ private sendingData?: DeferredPromise<void>
21
110
 
22
111
  constructor (init: AbstractStreamInit) {
23
- super(init)
112
+ this.sinkController = new AbortController()
113
+ this.sinkEnd = defer()
114
+ this.closed = defer()
115
+ this.log = init.log
116
+
117
+ // stream status
118
+ this.status = 'open'
119
+ this.readStatus = 'ready'
120
+ this.writeStatus = 'ready'
24
121
 
25
122
  this.id = init.id
26
- this.protocol = init.protocol ?? ''
123
+ this.metadata = init.metadata ?? {}
124
+ this.direction = init.direction
125
+ this.timeline = {
126
+ open: Date.now()
127
+ }
128
+ this.sendCloseWriteTimeout = init.sendCloseWriteTimeout ?? DEFAULT_SEND_CLOSE_WRITE_TIMEOUT
129
+
130
+ this.onEnd = init.onEnd
131
+ this.onCloseRead = init.onCloseRead
132
+ this.onCloseWrite = init.onCloseWrite
133
+ this.onReset = init.onReset
134
+ this.onAbort = init.onAbort
135
+
136
+ this.source = this.streamSource = pushable<Uint8ArrayList>({
137
+ onEnd: (err) => {
138
+ if (err != null) {
139
+ this.log.trace('source ended with error', err)
140
+ } else {
141
+ this.log.trace('source ended')
142
+ }
143
+
144
+ this.onSourceEnd(err)
145
+ }
146
+ })
147
+
148
+ // necessary because the libp2p upgrader wraps the sink function
149
+ this.sink = this.sink.bind(this)
150
+ }
151
+
152
+ async sink (source: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
153
+ if (this.writeStatus !== 'ready') {
154
+ throw new StreamStateError(`writable end state is "${this.writeStatus}" not "ready"`)
155
+ }
156
+
157
+ try {
158
+ this.writeStatus = 'writing'
159
+
160
+ const options: AbortOptions = {
161
+ signal: this.sinkController.signal
162
+ }
163
+
164
+ if (this.direction === 'outbound') { // If initiator, open a new stream
165
+ const res = this.sendNewStream(options)
166
+
167
+ if (isPromise(res)) {
168
+ await res
169
+ }
170
+ }
171
+
172
+ const abortListener = (): void => {
173
+ closeSource(source, this.log)
174
+ }
175
+
176
+ try {
177
+ this.sinkController.signal.addEventListener('abort', abortListener)
178
+
179
+ this.log.trace('sink reading from source')
180
+
181
+ for await (let data of source) {
182
+ data = data instanceof Uint8Array ? new Uint8ArrayList(data) : data
183
+
184
+ const res = this.sendData(data, options)
185
+
186
+ if (isPromise(res)) {
187
+ this.sendingData = defer()
188
+ await res
189
+ this.sendingData.resolve()
190
+ this.sendingData = undefined
191
+ }
192
+ }
193
+ } finally {
194
+ this.sinkController.signal.removeEventListener('abort', abortListener)
195
+ }
196
+
197
+ this.log.trace('sink finished reading from source, write status is "%s"', this.writeStatus)
198
+
199
+ if (this.writeStatus === 'writing') {
200
+ this.writeStatus = 'closing'
201
+
202
+ this.log.trace('send close write to remote')
203
+ await this.sendCloseWrite({
204
+ signal: AbortSignal.timeout(this.sendCloseWriteTimeout)
205
+ })
206
+
207
+ this.writeStatus = 'closed'
208
+ }
209
+
210
+ this.onSinkEnd()
211
+ } catch (err: any) {
212
+ this.log.trace('sink ended with error, calling abort with error', err)
213
+ this.abort(err)
214
+
215
+ throw err
216
+ } finally {
217
+ this.log.trace('resolve sink end')
218
+ this.sinkEnd.resolve()
219
+ }
27
220
  }
28
221
 
222
+ protected onSourceEnd (err?: Error): void {
223
+ if (this.timeline.closeRead != null) {
224
+ return
225
+ }
226
+
227
+ this.timeline.closeRead = Date.now()
228
+ this.readStatus = 'closed'
229
+
230
+ if (err != null && this.endErr == null) {
231
+ this.endErr = err
232
+ }
233
+
234
+ this.onCloseRead?.()
235
+
236
+ if (this.timeline.closeWrite != null) {
237
+ this.log.trace('source and sink ended')
238
+ this.timeline.close = Date.now()
239
+
240
+ if (this.status !== 'aborted' && this.status !== 'reset') {
241
+ this.status = 'closed'
242
+ }
243
+
244
+ if (this.onEnd != null) {
245
+ this.onEnd(this.endErr)
246
+ }
247
+
248
+ this.closed.resolve()
249
+ } else {
250
+ this.log.trace('source ended, waiting for sink to end')
251
+ }
252
+ }
253
+
254
+ protected onSinkEnd (err?: Error): void {
255
+ if (this.timeline.closeWrite != null) {
256
+ return
257
+ }
258
+
259
+ this.timeline.closeWrite = Date.now()
260
+ this.writeStatus = 'closed'
261
+
262
+ if (err != null && this.endErr == null) {
263
+ this.endErr = err
264
+ }
265
+
266
+ this.onCloseWrite?.()
267
+
268
+ if (this.timeline.closeRead != null) {
269
+ this.log.trace('sink and source ended')
270
+ this.timeline.close = Date.now()
271
+
272
+ if (this.status !== 'aborted' && this.status !== 'reset') {
273
+ this.status = 'closed'
274
+ }
275
+
276
+ if (this.onEnd != null) {
277
+ this.onEnd(this.endErr)
278
+ }
279
+
280
+ this.closed.resolve()
281
+ } else {
282
+ this.log.trace('sink ended, waiting for source to end')
283
+ }
284
+ }
285
+
286
+ // Close for both Reading and Writing
29
287
  async close (options?: AbortOptions): Promise<void> {
288
+ if (this.status !== 'open') {
289
+ return
290
+ }
291
+
292
+ this.log.trace('closing gracefully')
293
+
294
+ this.status = 'closing'
295
+
296
+ // wait for read and write ends to close
297
+ await raceSignal(Promise.all([
298
+ this.closeWrite(options),
299
+ this.closeRead(options),
300
+ this.closed.promise
301
+ ]), options?.signal)
302
+
303
+ this.status = 'closed'
304
+
305
+ this.log.trace('closed gracefully')
306
+ }
307
+
308
+ async closeRead (options: AbortOptions = {}): Promise<void> {
309
+ if (this.readStatus === 'closing' || this.readStatus === 'closed') {
310
+ return
311
+ }
312
+
313
+ this.log.trace('closing readable end of stream with starting read status "%s"', this.readStatus)
314
+
315
+ const readStatus = this.readStatus
316
+ this.readStatus = 'closing'
317
+
318
+ if (this.status !== 'reset' && this.status !== 'aborted' && this.timeline.closeRead == null) {
319
+ this.log.trace('send close read to remote')
320
+ await this.sendCloseRead(options)
321
+ }
322
+
323
+ if (readStatus === 'ready') {
324
+ this.log.trace('ending internal source queue with %d queued bytes', this.streamSource.readableLength)
325
+ this.streamSource.end()
326
+ }
327
+
328
+ this.log.trace('closed readable end of stream')
329
+ }
330
+
331
+ async closeWrite (options: AbortOptions = {}): Promise<void> {
30
332
  if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
31
333
  return
32
334
  }
33
335
 
34
- this.writeStatus = 'closing'
336
+ this.log.trace('closing writable end of stream with starting write status "%s"', this.writeStatus)
35
337
 
36
- // if we are currently sending data, wait for all the data to be written
37
- // into the underlying transport
38
- if (this.sendingData || this.writeBuffer.byteLength > 0) {
39
- this.log('waiting for write queue to become idle before closing writable end of stream, %d unsent bytes', this.writeBuffer.byteLength)
40
- await pEvent(this, 'idle', {
41
- ...options,
42
- rejectionEvents: [
43
- 'close'
44
- ]
45
- })
338
+ if (this.writeStatus === 'ready') {
339
+ this.log.trace('sink was never sunk, sink an empty array')
340
+
341
+ await raceSignal(this.sink([]), options.signal)
342
+ }
343
+
344
+ if (this.writeStatus === 'writing') {
345
+ // try to let sending outgoing data succeed
346
+ if (this.sendingData != null) {
347
+ await raceSignal(this.sendingData.promise, options.signal)
348
+ }
349
+
350
+ // stop reading from the source passed to `.sink`
351
+ this.log.trace('aborting source passed to .sink')
352
+ this.sinkController.abort()
353
+ await raceSignal(this.sinkEnd.promise, options.signal)
46
354
  }
47
355
 
48
- // now that the underlying transport has all the data, if the buffer is full
49
- // wait for it to be emptied
50
- if (this.writableNeedsDrain) {
51
- this.log('waiting for write queue to drain before closing writable end of stream, %d unsent bytes, sending %s', this.writeBuffer.byteLength, this.sendingData)
52
- await pEvent(this, 'drain', {
53
- ...options,
54
- rejectionEvents: [
55
- 'close'
56
- ]
356
+ this.writeStatus = 'closed'
357
+
358
+ this.log.trace('closed writable end of stream')
359
+ }
360
+
361
+ /**
362
+ * Close immediately for reading and writing and send a reset message (local
363
+ * error)
364
+ */
365
+ abort (err: Error): void {
366
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
367
+ return
368
+ }
369
+
370
+ this.log('abort with error', err)
371
+
372
+ // try to send a reset message
373
+ this.log('try to send reset to remote')
374
+ const res = this.sendReset()
375
+
376
+ if (isPromise(res)) {
377
+ res.catch((err) => {
378
+ this.log.error('error sending reset message', err)
57
379
  })
58
- this.log('write queue drained, closing writable end of stream, %d unsent bytes, sending %s', this.writeBuffer.byteLength, this.sendingData)
59
380
  }
60
381
 
61
- await this.sendCloseWrite(options)
382
+ this.status = 'aborted'
383
+ this.timeline.abort = Date.now()
384
+ this._closeSinkAndSource(err)
385
+ this.onAbort?.(err)
386
+ }
62
387
 
63
- this.writeStatus = 'closed'
388
+ /**
389
+ * Receive a reset message - close immediately for reading and writing (remote
390
+ * error)
391
+ */
392
+ reset (): void {
393
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
394
+ return
395
+ }
396
+
397
+ const err = new StreamResetError('stream reset')
64
398
 
65
- this.log('closed writable end gracefully')
399
+ this.status = 'reset'
400
+ this.timeline.reset = Date.now()
401
+ this._closeSinkAndSource(err)
402
+ this.onReset?.()
403
+ }
404
+
405
+ _closeSinkAndSource (err?: Error): void {
406
+ this._closeSink(err)
407
+ this._closeSource(err)
408
+ }
66
409
 
67
- if (this.remoteWriteStatus === 'closed') {
68
- this.onTransportClosed()
410
+ _closeSink (err?: Error): void {
411
+ // if the sink function is running, cause it to end
412
+ if (this.writeStatus === 'writing') {
413
+ this.log.trace('end sink source')
414
+ this.sinkController.abort()
69
415
  }
416
+
417
+ this.onSinkEnd(err)
70
418
  }
71
419
 
72
- async closeRead (options?: AbortOptions): Promise<void> {
420
+ _closeSource (err?: Error): void {
421
+ // if the source is not ending, end it
422
+ if (this.readStatus !== 'closing' && this.readStatus !== 'closed') {
423
+ this.log.trace('ending source with %d bytes to be read by consumer', this.streamSource.readableLength)
424
+ this.readStatus = 'closing'
425
+ this.streamSource.end(err)
426
+ }
427
+ }
428
+
429
+ /**
430
+ * The remote closed for writing so we should expect to receive no more
431
+ * messages
432
+ */
433
+ remoteCloseWrite (): void {
73
434
  if (this.readStatus === 'closing' || this.readStatus === 'closed') {
435
+ this.log('received remote close write but local source is already closed')
74
436
  return
75
437
  }
76
438
 
77
- // throw away any unread data
78
- if (this.readBuffer.byteLength > 0) {
79
- this.readBuffer.consume(this.readBuffer.byteLength)
439
+ this.log.trace('remote close write')
440
+ this._closeSource()
441
+ }
442
+
443
+ /**
444
+ * The remote closed for reading so we should not send any more
445
+ * messages
446
+ */
447
+ remoteCloseRead (): void {
448
+ if (this.writeStatus === 'closing' || this.writeStatus === 'closed') {
449
+ this.log('received remote close read but local sink is already closed')
450
+ return
80
451
  }
81
452
 
82
- this.readStatus = 'closing'
453
+ this.log.trace('remote close read')
454
+ this._closeSink()
455
+ }
456
+
457
+ /**
458
+ * The underlying muxer has closed, no more messages can be sent or will
459
+ * be received, close immediately to free up resources
460
+ */
461
+ destroy (): void {
462
+ if (this.status === 'closed' || this.status === 'aborted' || this.status === 'reset') {
463
+ this.log('received destroy but we are already closed')
464
+ return
465
+ }
83
466
 
84
- await this.sendCloseRead(options)
467
+ this.log.trace('stream destroyed')
85
468
 
86
- this.readStatus = 'closed'
469
+ this._closeSinkAndSource()
470
+ }
87
471
 
88
- this.log('closed readable end gracefully')
472
+ /**
473
+ * When an extending class reads data from it's implementation-specific source,
474
+ * call this method to allow the stream consumer to read the data.
475
+ */
476
+ sourcePush (data: Uint8ArrayList): void {
477
+ this.streamSource.push(data)
89
478
  }
90
479
 
91
480
  /**
92
- * Send a message to the remote end of the stream, informing them that we will
93
- * send no more data messages.
481
+ * Returns the amount of unread data - can be used to prevent large amounts of
482
+ * data building up when the stream consumer is too slow.
483
+ */
484
+ sourceReadableLength (): number {
485
+ return this.streamSource.readableLength
486
+ }
487
+
488
+ /**
489
+ * Send a message to the remote muxer informing them a new stream is being
490
+ * opened
491
+ */
492
+ abstract sendNewStream (options?: AbortOptions): void | Promise<void>
493
+
494
+ /**
495
+ * Send a data message to the remote muxer
496
+ */
497
+ abstract sendData (buf: Uint8ArrayList, options?: AbortOptions): void | Promise<void>
498
+
499
+ /**
500
+ * Send a reset message to the remote muxer
501
+ */
502
+ abstract sendReset (options?: AbortOptions): void | Promise<void>
503
+
504
+ /**
505
+ * Send a message to the remote muxer, informing them no more data messages
506
+ * will be sent by this end of the stream
94
507
  */
95
- abstract sendCloseWrite (options?: AbortOptions): Promise<void>
508
+ abstract sendCloseWrite (options?: AbortOptions): void | Promise<void>
96
509
 
97
510
  /**
98
- * If supported, send a message to the remote end of the stream, informing
99
- * them that we will read no more data messages.
511
+ * Send a message to the remote muxer, informing them no more data messages
512
+ * will be read by this end of the stream
100
513
  */
101
- abstract sendCloseRead (options?: AbortOptions): Promise<void>
514
+ abstract sendCloseRead (options?: AbortOptions): void | Promise<void>
102
515
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Provides strategies ensure arrays are equivalent.
5
+ *
6
+ * @example
7
+ *
8
+ * ```typescript
9
+ * import { arrayEquals } from '@libp2p/utils/array-equals'
10
+ * import { multiaddr } from '@multformats/multiaddr'
11
+ *
12
+ * const ma1 = multiaddr('/ip4/127.0.0.1/tcp/9000'),
13
+ * const ma2 = multiaddr('/ip4/82.41.53.1/tcp/9000')
14
+ *
15
+ * console.info(arrayEquals([ma1], [ma1])) // true
16
+ * console.info(arrayEquals([ma1], [ma2])) // false
17
+ * ```
18
+ */
19
+
20
+ /**
21
+ * Verify if two arrays of non primitive types with the "equals" function are equal.
22
+ * Compatible with multiaddr, peer-id and others.
23
+ */
24
+ export function arrayEquals (a: any[], b: any[]): boolean {
25
+ const sort = (a: any, b: any): number => a.toString().localeCompare(b.toString())
26
+
27
+ if (a.length !== b.length) {
28
+ return false
29
+ }
30
+
31
+ b.sort(sort)
32
+
33
+ return a.sort(sort).every((item, index) => b[index].equals(item))
34
+ }
@@ -0,0 +1,14 @@
1
+ import { getIterator } from 'get-iterator'
2
+ import { isPromise } from './is-promise.js'
3
+ import type { Logger } from '@libp2p/logger'
4
+ import type { Source } from 'it-stream-types'
5
+
6
+ export function closeSource (source: Source<unknown>, log: Logger): void {
7
+ const res = getIterator(source).return?.()
8
+
9
+ if (isPromise(res)) {
10
+ res.catch(err => {
11
+ log.error('could not cause iterator to return', err)
12
+ })
13
+ }
14
+ }