@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.
- package/README.md +127 -0
- package/dist/index.min.js +4 -0
- package/dist/index.min.js.map +7 -0
- package/dist/src/config.d.ts +30 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +41 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +22 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/decode.d.ts +34 -0
- package/dist/src/decode.d.ts.map +1 -0
- package/dist/src/decode.js +76 -0
- package/dist/src/decode.js.map +1 -0
- package/dist/src/encode.d.ts +3 -0
- package/dist/src/encode.d.ts.map +1 -0
- package/dist/src/encode.js +19 -0
- package/dist/src/encode.js.map +1 -0
- package/dist/src/errors.d.ts +40 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +70 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/frame.d.ts +54 -0
- package/dist/src/frame.d.ts.map +1 -0
- package/dist/src/frame.js +36 -0
- package/dist/src/frame.js.map +1 -0
- package/dist/src/index.d.ts +85 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +84 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/muxer.d.ts +91 -0
- package/dist/src/muxer.d.ts.map +1 -0
- package/dist/src/muxer.js +428 -0
- package/dist/src/muxer.js.map +1 -0
- package/dist/src/stream.d.ts +96 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +253 -0
- package/dist/src/stream.js.map +1 -0
- package/package.json +95 -0
- package/src/config.ts +72 -0
- package/src/constants.ts +25 -0
- package/src/decode.ts +105 -0
- package/src/encode.ts +26 -0
- package/src/errors.ts +89 -0
- package/src/frame.ts +64 -0
- package/src/index.ts +90 -0
- package/src/muxer.ts +507 -0
- 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
|
+
}
|