@libp2p/yamux 7.0.4

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