@libp2p/yamux 7.0.4-0f07e3df5

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 (49) hide show
  1. package/README.md +127 -0
  2. package/dist/index.min.js +4 -0
  3. package/dist/index.min.js.map +7 -0
  4. package/dist/src/config.d.ts +30 -0
  5. package/dist/src/config.d.ts.map +1 -0
  6. package/dist/src/config.js +41 -0
  7. package/dist/src/config.js.map +1 -0
  8. package/dist/src/constants.d.ts +12 -0
  9. package/dist/src/constants.d.ts.map +1 -0
  10. package/dist/src/constants.js +22 -0
  11. package/dist/src/constants.js.map +1 -0
  12. package/dist/src/decode.d.ts +34 -0
  13. package/dist/src/decode.d.ts.map +1 -0
  14. package/dist/src/decode.js +76 -0
  15. package/dist/src/decode.js.map +1 -0
  16. package/dist/src/encode.d.ts +3 -0
  17. package/dist/src/encode.d.ts.map +1 -0
  18. package/dist/src/encode.js +19 -0
  19. package/dist/src/encode.js.map +1 -0
  20. package/dist/src/errors.d.ts +40 -0
  21. package/dist/src/errors.d.ts.map +1 -0
  22. package/dist/src/errors.js +70 -0
  23. package/dist/src/errors.js.map +1 -0
  24. package/dist/src/frame.d.ts +54 -0
  25. package/dist/src/frame.d.ts.map +1 -0
  26. package/dist/src/frame.js +36 -0
  27. package/dist/src/frame.js.map +1 -0
  28. package/dist/src/index.d.ts +85 -0
  29. package/dist/src/index.d.ts.map +1 -0
  30. package/dist/src/index.js +84 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/muxer.d.ts +91 -0
  33. package/dist/src/muxer.d.ts.map +1 -0
  34. package/dist/src/muxer.js +428 -0
  35. package/dist/src/muxer.js.map +1 -0
  36. package/dist/src/stream.d.ts +96 -0
  37. package/dist/src/stream.d.ts.map +1 -0
  38. package/dist/src/stream.js +253 -0
  39. package/dist/src/stream.js.map +1 -0
  40. package/package.json +95 -0
  41. package/src/config.ts +72 -0
  42. package/src/constants.ts +25 -0
  43. package/src/decode.ts +105 -0
  44. package/src/encode.ts +26 -0
  45. package/src/errors.ts +89 -0
  46. package/src/frame.ts +64 -0
  47. package/src/index.ts +90 -0
  48. package/src/muxer.ts +507 -0
  49. package/src/stream.ts +313 -0
package/src/stream.ts ADDED
@@ -0,0 +1,313 @@
1
+ import { AbstractStream } from '@libp2p/utils'
2
+ import { Uint8ArrayList } from 'uint8arraylist'
3
+ import { INITIAL_STREAM_WINDOW, MAX_STREAM_WINDOW } from './constants.js'
4
+ import { isDataFrame } from './decode.ts'
5
+ import { InvalidFrameError, ReceiveWindowExceededError } from './errors.js'
6
+ import { Flag, FrameType, HEADER_LENGTH } from './frame.js'
7
+ import type { Frame } from './decode.ts'
8
+ import type { FrameHeader } from './frame.js'
9
+ import type { AbortOptions } from '@libp2p/interface'
10
+ import type { AbstractStreamInit, SendResult } from '@libp2p/utils'
11
+
12
+ export enum StreamState {
13
+ Init,
14
+ SYNSent,
15
+ SYNReceived,
16
+ Established,
17
+ Finished,
18
+ Paused
19
+ }
20
+
21
+ export interface YamuxStreamInit extends AbstractStreamInit {
22
+ streamId: number
23
+ sendFrame(header: FrameHeader, body?: Uint8ArrayList): boolean
24
+ getRTT(): number
25
+ initialStreamWindowSize?: number
26
+ maxMessageSize?: number
27
+ maxStreamWindowSize?: number
28
+ state: StreamState
29
+ }
30
+
31
+ /** YamuxStream is used to represent a logical stream within a session */
32
+ export class YamuxStream extends AbstractStream {
33
+ streamId: number
34
+ state: StreamState
35
+
36
+ /** The number of available bytes to send */
37
+ private sendWindowCapacity: number
38
+ /** The number of bytes available to receive in a full window */
39
+ private recvWindow: number
40
+ /** The number of available bytes to receive */
41
+ private recvWindowCapacity: number
42
+ private maxStreamWindowSize: number
43
+
44
+ /**
45
+ * An 'epoch' is the time it takes to process and read data
46
+ *
47
+ * Used in conjunction with RTT to determine whether to increase the recvWindow
48
+ */
49
+ private epochStart: number
50
+ private readonly getRTT: () => number
51
+
52
+ private readonly sendFrame: (header: FrameHeader, body?: Uint8ArrayList) => boolean
53
+
54
+ constructor (init: YamuxStreamInit) {
55
+ const initialWindowSize = init.initialStreamWindowSize ?? INITIAL_STREAM_WINDOW
56
+
57
+ super({
58
+ ...init,
59
+ maxMessageSize: initialWindowSize - HEADER_LENGTH
60
+ })
61
+
62
+ this.streamId = init.streamId
63
+ this.state = init.state
64
+ this.sendWindowCapacity = initialWindowSize
65
+ this.recvWindow = initialWindowSize
66
+ this.recvWindowCapacity = this.recvWindow
67
+ this.maxStreamWindowSize = init.maxStreamWindowSize ?? MAX_STREAM_WINDOW
68
+ this.epochStart = Date.now()
69
+ this.getRTT = init.getRTT
70
+ this.sendFrame = init.sendFrame
71
+
72
+ const setStateToFinishedOnCloseListener = (): void => {
73
+ this.state = StreamState.Finished
74
+ }
75
+ this.addEventListener('close', setStateToFinishedOnCloseListener)
76
+ }
77
+
78
+ /**
79
+ * Send a data message to the remote muxer
80
+ */
81
+ sendData (buf: Uint8ArrayList): SendResult {
82
+ const totalBytes = buf.byteLength
83
+ let sentBytes = 0
84
+ let canSendMore = true
85
+
86
+ this.log?.trace('send window capacity is %d bytes', this.sendWindowCapacity)
87
+
88
+ // send in chunks, waiting for window updates
89
+ while (buf.byteLength > 0) {
90
+ // we exhausted the send window, sending will resume later
91
+ if (this.sendWindowCapacity === 0) {
92
+ canSendMore = false
93
+ this.log?.trace('sent %d/%d bytes, exhausted send window, waiting for window update', sentBytes, totalBytes)
94
+ break
95
+ }
96
+
97
+ // send as much as we can
98
+ const toSend = Math.min(this.sendWindowCapacity, buf.byteLength)
99
+ const flags = this.getSendFlags()
100
+
101
+ const data = buf.sublist(0, toSend)
102
+ buf.consume(toSend)
103
+
104
+ const muxerSendMore = this.sendFrame({
105
+ type: FrameType.Data,
106
+ flag: flags,
107
+ streamID: this.streamId,
108
+ length: toSend
109
+ }, data)
110
+
111
+ this.sendWindowCapacity -= toSend
112
+ sentBytes += toSend
113
+
114
+ if (!muxerSendMore) {
115
+ canSendMore = muxerSendMore
116
+ this.log.trace('sent %d/%d bytes, wait for muxer to have more send capacity', sentBytes, totalBytes)
117
+ break
118
+ }
119
+ }
120
+
121
+ return {
122
+ sentBytes,
123
+ canSendMore
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Send a reset message to the remote muxer
129
+ */
130
+ async sendReset (): Promise<void> {
131
+ this.sendFrame({
132
+ type: FrameType.WindowUpdate,
133
+ flag: Flag.RST,
134
+ streamID: this.streamId,
135
+ length: 0
136
+ })
137
+ }
138
+
139
+ /**
140
+ * Send a message to the remote muxer, informing them no more data messages
141
+ * will be sent by this end of the stream
142
+ */
143
+ async sendCloseWrite (): Promise<void> {
144
+ const flags = this.getSendFlags() | Flag.FIN
145
+ this.sendFrame({
146
+ type: FrameType.WindowUpdate,
147
+ flag: flags,
148
+ streamID: this.streamId,
149
+ length: 0
150
+ })
151
+ }
152
+
153
+ /**
154
+ * Send a message to the remote muxer, informing them no more data messages
155
+ * will be read by this end of the stream - this is a no-op on Yamux streams
156
+ */
157
+ async sendCloseRead (options?: AbortOptions): Promise<void> {
158
+ options?.signal?.throwIfAborted()
159
+ }
160
+
161
+ /**
162
+ * Stop sending window updates temporarily - in the interim the the remote
163
+ * send window will exhaust and the remote will stop sending data
164
+ */
165
+ sendPause (): void {
166
+ this.state = StreamState.Paused
167
+ }
168
+
169
+ /**
170
+ * Start sending window updates as normal
171
+ */
172
+ sendResume (): void {
173
+ this.state = StreamState.Established
174
+ this.sendWindowUpdate()
175
+ }
176
+
177
+ /**
178
+ * handleWindowUpdate is called when the stream receives a window update frame
179
+ */
180
+ handleWindowUpdate (frame: Frame): void {
181
+ this.processFlags(frame.header.flag)
182
+
183
+ // increase send window
184
+ this.sendWindowCapacity += frame.header.length
185
+
186
+ // change the chunk size the superclass uses
187
+ this.maxMessageSize = this.sendWindowCapacity - HEADER_LENGTH
188
+
189
+ if (this.maxMessageSize < 0) {
190
+ this.maxMessageSize = 0
191
+ }
192
+
193
+ if (this.maxMessageSize === 0) {
194
+ return
195
+ }
196
+
197
+ // if writing is paused and the update increases our send window, notify
198
+ // writers that writing can resume
199
+ if (this.writeBuffer.byteLength > 0) {
200
+ this.log?.trace('window update of %d bytes allows more data to be sent, have %d bytes queued, sending data %s', frame.header.length, this.writeBuffer.byteLength, this.sendingData)
201
+ this.safeDispatchEvent('drain')
202
+ }
203
+ }
204
+
205
+ /**
206
+ * handleData is called when the stream receives a data frame
207
+ */
208
+ handleData (frame: Frame): void {
209
+ if (!isDataFrame(frame)) {
210
+ throw new InvalidFrameError('Frame was not data frame')
211
+ }
212
+
213
+ this.processFlags(frame.header.flag)
214
+
215
+ // check that our recv window is not exceeded
216
+ if (this.recvWindowCapacity < frame.header.length) {
217
+ throw new ReceiveWindowExceededError('Receive window exceeded')
218
+ }
219
+
220
+ this.recvWindowCapacity -= frame.header.length
221
+
222
+ this.onData(frame.data)
223
+
224
+ this.sendWindowUpdate()
225
+ }
226
+
227
+ /**
228
+ * processFlags is used to update the state of the stream based on set flags, if any.
229
+ */
230
+ private processFlags (flags: number): void {
231
+ if ((flags & Flag.ACK) === Flag.ACK) {
232
+ if (this.state === StreamState.SYNSent) {
233
+ this.state = StreamState.Established
234
+ }
235
+ }
236
+
237
+ if ((flags & Flag.FIN) === Flag.FIN) {
238
+ this.onRemoteCloseWrite()
239
+ }
240
+
241
+ if ((flags & Flag.RST) === Flag.RST) {
242
+ this.onRemoteReset()
243
+ }
244
+ }
245
+
246
+ /**
247
+ * getSendFlags determines any flags that are appropriate
248
+ * based on the current stream state.
249
+ *
250
+ * The state is updated as a side-effect.
251
+ */
252
+ private getSendFlags (): number {
253
+ switch (this.state) {
254
+ case StreamState.Init:
255
+ this.state = StreamState.SYNSent
256
+ return Flag.SYN
257
+ case StreamState.SYNReceived:
258
+ this.state = StreamState.Established
259
+ return Flag.ACK
260
+ default:
261
+ return 0
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Potentially sends a window update enabling further remote writes to take
267
+ * place.
268
+ */
269
+ sendWindowUpdate (): void {
270
+ if (this.state === StreamState.Paused) {
271
+ // we don't want any more data from the remote right now - update the
272
+ // epoch start as otherwise when we unpause we'd be looking at the epoch
273
+ // start from before we were paused
274
+ this.epochStart = Date.now()
275
+
276
+ return
277
+ }
278
+
279
+ // determine the flags if any
280
+ const flags = this.getSendFlags()
281
+
282
+ // If the stream has already been established
283
+ // and we've processed data within the time it takes for 4 round trips
284
+ // then we (up to) double the recvWindow
285
+ const now = Date.now()
286
+ const rtt = this.getRTT()
287
+
288
+ if (flags === 0 && rtt > -1 && (now - this.epochStart) <= (rtt * 4)) {
289
+ // we've already validated that maxStreamWindowSize can't be more than MAX_UINT32
290
+ this.recvWindow = Math.min(this.recvWindow * 2, this.maxStreamWindowSize)
291
+ }
292
+
293
+ if (this.recvWindowCapacity >= this.recvWindow && flags === 0) {
294
+ // a window update isn't needed
295
+ return
296
+ }
297
+
298
+ // update the receive window
299
+ const delta = this.recvWindow - this.recvWindowCapacity
300
+ this.recvWindowCapacity = this.recvWindow
301
+
302
+ // update the epoch start
303
+ this.epochStart = now
304
+
305
+ // send window update
306
+ this.sendFrame({
307
+ type: FrameType.WindowUpdate,
308
+ flag: flags,
309
+ streamID: this.streamId,
310
+ length: delta
311
+ })
312
+ }
313
+ }