@libp2p/mplex 11.0.47-8484de8a2 → 11.0.47-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.
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/src/encode.d.ts +2 -1
- package/dist/src/encode.d.ts.map +1 -1
- package/dist/src/encode.js +6 -4
- package/dist/src/encode.js.map +1 -1
- package/dist/src/index.d.ts +31 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/mplex.d.ts +53 -11
- package/dist/src/mplex.d.ts.map +1 -1
- package/dist/src/mplex.js +222 -56
- package/dist/src/mplex.js.map +1 -1
- package/dist/src/stream.d.ts +23 -17
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +28 -56
- package/dist/src/stream.js.map +1 -1
- package/package.json +15 -12
- package/src/encode.ts +7 -5
- package/src/index.ts +43 -10
- package/src/mplex.ts +273 -58
- package/src/stream.ts +46 -78
package/src/mplex.ts
CHANGED
@@ -1,16 +1,27 @@
|
|
1
|
-
import { MuxerClosedError } from '@libp2p/interface'
|
2
|
-
import {
|
1
|
+
import { TooManyOutboundProtocolStreamsError, MuxerClosedError } from '@libp2p/interface'
|
2
|
+
import { closeSource } from '@libp2p/utils/close-source'
|
3
|
+
import { RateLimiter } from '@libp2p/utils/rate-limiter'
|
4
|
+
import { pipe } from 'it-pipe'
|
5
|
+
import { pushable } from 'it-pushable'
|
3
6
|
import { toString as uint8ArrayToString } from 'uint8arrays'
|
4
|
-
import { Decoder
|
7
|
+
import { Decoder } from './decode.js'
|
8
|
+
import { encode } from './encode.js'
|
9
|
+
import { StreamInputBufferError } from './errors.js'
|
5
10
|
import { MessageTypes, MessageTypeNames } from './message-types.js'
|
6
11
|
import { createStream } from './stream.js'
|
7
12
|
import type { MplexInit } from './index.js'
|
8
13
|
import type { Message } from './message-types.js'
|
9
14
|
import type { MplexStream } from './stream.js'
|
10
|
-
import type {
|
15
|
+
import type { AbortOptions, ComponentLogger, Logger, Stream, StreamMuxer, StreamMuxerInit } from '@libp2p/interface'
|
16
|
+
import type { Pushable } from 'it-pushable'
|
17
|
+
import type { Sink, Source } from 'it-stream-types'
|
11
18
|
import type { Uint8ArrayList } from 'uint8arraylist'
|
12
19
|
|
20
|
+
const MAX_STREAMS_INBOUND_STREAMS_PER_CONNECTION = 1024
|
21
|
+
const MAX_STREAMS_OUTBOUND_STREAMS_PER_CONNECTION = 1024
|
22
|
+
const MAX_STREAM_BUFFER_SIZE = 1024 * 1024 * 4 // 4MB
|
13
23
|
const DISCONNECT_THRESHOLD = 5
|
24
|
+
const CLOSE_TIMEOUT = 500
|
14
25
|
|
15
26
|
function printMessage (msg: Message): any {
|
16
27
|
const output: any = {
|
@@ -19,34 +30,93 @@ function printMessage (msg: Message): any {
|
|
19
30
|
}
|
20
31
|
|
21
32
|
if (msg.type === MessageTypes.NEW_STREAM) {
|
22
|
-
output.data = uint8ArrayToString(msg.data.subarray())
|
33
|
+
output.data = uint8ArrayToString(msg.data instanceof Uint8Array ? msg.data : msg.data.subarray())
|
23
34
|
}
|
24
35
|
|
25
36
|
if (msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) {
|
26
|
-
output.data = uint8ArrayToString(msg.data.subarray(), 'base16')
|
37
|
+
output.data = uint8ArrayToString(msg.data instanceof Uint8Array ? msg.data : msg.data.subarray(), 'base16')
|
27
38
|
}
|
28
39
|
|
29
40
|
return output
|
30
41
|
}
|
31
42
|
|
32
|
-
export
|
43
|
+
export interface MplexComponents {
|
44
|
+
logger: ComponentLogger
|
45
|
+
}
|
46
|
+
|
47
|
+
interface MplexStreamMuxerInit extends MplexInit, StreamMuxerInit {
|
48
|
+
/**
|
49
|
+
* The default timeout to use in ms when shutting down the muxer.
|
50
|
+
*/
|
51
|
+
closeTimeout?: number
|
52
|
+
}
|
53
|
+
|
54
|
+
export class MplexStreamMuxer implements StreamMuxer {
|
55
|
+
public protocol = '/mplex/6.7.0'
|
56
|
+
|
57
|
+
public sink: Sink<Source<Uint8ArrayList | Uint8Array>, Promise<void>>
|
58
|
+
public source: AsyncGenerator<Uint8ArrayList | Uint8Array>
|
59
|
+
|
60
|
+
private readonly log: Logger
|
33
61
|
private _streamId: number
|
62
|
+
private readonly _streams: { initiators: Map<number, MplexStream>, receivers: Map<number, MplexStream> }
|
63
|
+
private readonly _init: MplexStreamMuxerInit
|
64
|
+
private readonly _source: Pushable<Message>
|
65
|
+
private readonly closeController: AbortController
|
34
66
|
private readonly rateLimiter: RateLimiter
|
35
|
-
private readonly
|
36
|
-
private readonly
|
37
|
-
private readonly decoder: Decoder
|
38
|
-
|
39
|
-
constructor (maConn: MultiaddrConnection, init: MplexInit) {
|
40
|
-
super(maConn, {
|
41
|
-
...init,
|
42
|
-
protocol: '/mplex/6.7.0',
|
43
|
-
name: 'mplex'
|
44
|
-
})
|
67
|
+
private readonly closeTimeout: number
|
68
|
+
private readonly logger: ComponentLogger
|
45
69
|
|
70
|
+
constructor (components: MplexComponents, init?: MplexStreamMuxerInit) {
|
71
|
+
init = init ?? {}
|
72
|
+
|
73
|
+
this.log = init.log?.newScope('mplex') ?? components.logger.forComponent('libp2p:mplex')
|
74
|
+
this.logger = components.logger
|
46
75
|
this._streamId = 0
|
47
|
-
this.
|
48
|
-
|
49
|
-
|
76
|
+
this._streams = {
|
77
|
+
/**
|
78
|
+
* Stream to ids map
|
79
|
+
*/
|
80
|
+
initiators: new Map<number, MplexStream>(),
|
81
|
+
/**
|
82
|
+
* Stream to ids map
|
83
|
+
*/
|
84
|
+
receivers: new Map<number, MplexStream>()
|
85
|
+
}
|
86
|
+
this._init = init
|
87
|
+
this.closeTimeout = init.closeTimeout ?? CLOSE_TIMEOUT
|
88
|
+
|
89
|
+
/**
|
90
|
+
* An iterable sink
|
91
|
+
*/
|
92
|
+
this.sink = this._createSink()
|
93
|
+
|
94
|
+
/**
|
95
|
+
* An iterable source
|
96
|
+
*/
|
97
|
+
this._source = pushable<Message>({
|
98
|
+
objectMode: true,
|
99
|
+
onEnd: (): void => {
|
100
|
+
// the source has ended, we can't write any more messages to gracefully
|
101
|
+
// close streams so all we can do is destroy them
|
102
|
+
for (const stream of this._streams.initiators.values()) {
|
103
|
+
stream.destroy()
|
104
|
+
}
|
105
|
+
|
106
|
+
for (const stream of this._streams.receivers.values()) {
|
107
|
+
stream.destroy()
|
108
|
+
}
|
109
|
+
}
|
110
|
+
})
|
111
|
+
this.source = pipe(
|
112
|
+
this._source,
|
113
|
+
source => encode(source)
|
114
|
+
)
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Close controller
|
118
|
+
*/
|
119
|
+
this.closeController = new AbortController()
|
50
120
|
|
51
121
|
this.rateLimiter = new RateLimiter({
|
52
122
|
points: init.disconnectThreshold ?? DISCONNECT_THRESHOLD,
|
@@ -54,75 +124,207 @@ export class MplexStreamMuxer extends AbstractStreamMuxer<MplexStream> {
|
|
54
124
|
})
|
55
125
|
}
|
56
126
|
|
57
|
-
|
58
|
-
|
59
|
-
|
127
|
+
/**
|
128
|
+
* Returns a Map of streams and their ids
|
129
|
+
*/
|
130
|
+
get streams (): Stream[] {
|
131
|
+
// Inbound and Outbound streams may have the same ids, so we need to make those unique
|
132
|
+
const streams: Stream[] = []
|
133
|
+
for (const stream of this._streams.initiators.values()) {
|
134
|
+
streams.push(stream)
|
135
|
+
}
|
136
|
+
|
137
|
+
for (const stream of this._streams.receivers.values()) {
|
138
|
+
streams.push(stream)
|
60
139
|
}
|
140
|
+
return streams
|
61
141
|
}
|
62
142
|
|
63
143
|
/**
|
64
144
|
* Initiate a new stream with the given name. If no name is
|
65
145
|
* provided, the id of the stream will be used.
|
66
146
|
*/
|
67
|
-
|
68
|
-
if (this.
|
147
|
+
newStream (name?: string): Stream {
|
148
|
+
if (this.closeController.signal.aborted) {
|
69
149
|
throw new MuxerClosedError('Muxer already closed')
|
70
150
|
}
|
71
|
-
|
72
151
|
const id = this._streamId++
|
152
|
+
name = name == null ? id.toString() : name.toString()
|
153
|
+
const registry = this._streams.initiators
|
154
|
+
return this._newStream({ id, name, type: 'initiator', registry })
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Close or abort all tracked streams and stop the muxer
|
159
|
+
*/
|
160
|
+
async close (options?: AbortOptions): Promise<void> {
|
161
|
+
if (this.closeController.signal.aborted) {
|
162
|
+
return
|
163
|
+
}
|
164
|
+
|
165
|
+
const signal = options?.signal ?? AbortSignal.timeout(this.closeTimeout)
|
166
|
+
|
167
|
+
try {
|
168
|
+
// try to gracefully close all streams
|
169
|
+
await Promise.all(
|
170
|
+
this.streams.map(async s => s.close({
|
171
|
+
signal
|
172
|
+
}))
|
173
|
+
)
|
73
174
|
|
74
|
-
|
175
|
+
this._source.end()
|
176
|
+
|
177
|
+
// try to gracefully close the muxer
|
178
|
+
await this._source.onEmpty({
|
179
|
+
signal
|
180
|
+
})
|
181
|
+
|
182
|
+
this.closeController.abort()
|
183
|
+
} catch (err: any) {
|
184
|
+
this.abort(err)
|
185
|
+
}
|
75
186
|
}
|
76
187
|
|
77
|
-
|
78
|
-
this.
|
188
|
+
abort (err: Error): void {
|
189
|
+
if (this.closeController.signal.aborted) {
|
190
|
+
return
|
191
|
+
}
|
192
|
+
|
193
|
+
this.streams.forEach(s => { s.abort(err) })
|
194
|
+
this.closeController.abort(err)
|
195
|
+
}
|
79
196
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
197
|
+
/**
|
198
|
+
* Called whenever an inbound stream is created
|
199
|
+
*/
|
200
|
+
_newReceiverStream (options: { id: number, name: string }): MplexStream {
|
201
|
+
const { id, name } = options
|
202
|
+
const registry = this._streams.receivers
|
203
|
+
return this._newStream({ id, name, type: 'receiver', registry })
|
204
|
+
}
|
205
|
+
|
206
|
+
_newStream (options: { id: number, name: string, type: 'initiator' | 'receiver', registry: Map<number, MplexStream> }): MplexStream {
|
207
|
+
const { id, name, type, registry } = options
|
208
|
+
|
209
|
+
this.log('new %s stream %s', type, id)
|
210
|
+
|
211
|
+
if (type === 'initiator' && this._streams.initiators.size === (this._init.maxOutboundStreams ?? MAX_STREAMS_OUTBOUND_STREAMS_PER_CONNECTION)) {
|
212
|
+
throw new TooManyOutboundProtocolStreamsError('Too many outbound streams open')
|
213
|
+
}
|
88
214
|
|
215
|
+
if (registry.has(id)) {
|
216
|
+
throw new Error(`${type} stream ${id} already exists!`)
|
217
|
+
}
|
218
|
+
|
219
|
+
const send = async (msg: Message): Promise<void> => {
|
220
|
+
if (this.log.enabled) {
|
221
|
+
this.log.trace('%s stream %s send', type, id, printMessage(msg))
|
222
|
+
}
|
223
|
+
|
224
|
+
this._source.push(msg)
|
225
|
+
}
|
226
|
+
|
227
|
+
const onEnd = (): void => {
|
228
|
+
this.log('%s stream with id %s and protocol %s ended', type, id, stream.protocol)
|
229
|
+
registry.delete(id)
|
230
|
+
|
231
|
+
if (this._init.onStreamEnd != null) {
|
232
|
+
this._init.onStreamEnd(stream)
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
const stream = createStream({ id, name, send, type, onEnd, maxMsgSize: this._init.maxMsgSize, log: this.log })
|
237
|
+
registry.set(id, stream)
|
89
238
|
return stream
|
90
239
|
}
|
91
240
|
|
92
|
-
|
241
|
+
/**
|
242
|
+
* Creates a sink with an abortable source. Incoming messages will
|
243
|
+
* also have their size restricted. All messages will be varint decoded.
|
244
|
+
*/
|
245
|
+
_createSink (): Sink<Source<Uint8ArrayList | Uint8Array>, Promise<void>> {
|
246
|
+
const sink: Sink<Source<Uint8ArrayList | Uint8Array>, Promise<void>> = async source => {
|
247
|
+
const abortListener = (): void => {
|
248
|
+
closeSource(source, this.log)
|
249
|
+
}
|
250
|
+
|
251
|
+
this.closeController.signal.addEventListener('abort', abortListener)
|
252
|
+
|
253
|
+
try {
|
254
|
+
const decoder = new Decoder(this._init.maxMsgSize, this._init.maxUnprocessedMessageQueueSize)
|
255
|
+
|
256
|
+
for await (const chunk of source) {
|
257
|
+
for (const msg of decoder.write(chunk)) {
|
258
|
+
await this._handleIncoming(msg)
|
259
|
+
}
|
260
|
+
}
|
261
|
+
|
262
|
+
this._source.end()
|
263
|
+
} catch (err: any) {
|
264
|
+
this.log('error in sink', err)
|
265
|
+
this._source.end(err) // End the source with an error
|
266
|
+
} finally {
|
267
|
+
this.closeController.signal.removeEventListener('abort', abortListener)
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
return sink
|
272
|
+
}
|
273
|
+
|
274
|
+
async _handleIncoming (message: Message): Promise<void> {
|
275
|
+
const { id, type } = message
|
276
|
+
|
93
277
|
if (this.log.enabled) {
|
94
278
|
this.log.trace('incoming message', printMessage(message))
|
95
279
|
}
|
96
280
|
|
97
281
|
// Create a new stream?
|
98
282
|
if (message.type === MessageTypes.NEW_STREAM) {
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
283
|
+
if (this._streams.receivers.size === (this._init.maxInboundStreams ?? MAX_STREAMS_INBOUND_STREAMS_PER_CONNECTION)) {
|
284
|
+
this.log('too many inbound streams open')
|
285
|
+
|
286
|
+
// not going to allow this stream, send the reset message manually
|
287
|
+
// instead of setting it up just to tear it down
|
288
|
+
this._source.push({
|
289
|
+
id,
|
290
|
+
type: MessageTypes.RESET_RECEIVER
|
291
|
+
})
|
292
|
+
|
293
|
+
// if we've hit our stream limit, and the remote keeps trying to open
|
294
|
+
// more new streams, if they are doing this very quickly maybe they
|
295
|
+
// are attacking us and we should close the connection
|
296
|
+
try {
|
297
|
+
await this.rateLimiter.consume('new-stream', 1)
|
298
|
+
} catch {
|
299
|
+
this.log('rate limit hit when opening too many new streams over the inbound stream limit - closing remote connection')
|
300
|
+
// since there's no backpressure in mplex, the only thing we can really do to protect ourselves is close the connection
|
301
|
+
this.abort(new Error('Too many open streams'))
|
302
|
+
return
|
303
|
+
}
|
304
|
+
|
106
305
|
return
|
107
306
|
}
|
108
307
|
|
109
|
-
const stream = this.
|
110
|
-
|
308
|
+
const stream = this._newReceiverStream({ id, name: uint8ArrayToString(message.data instanceof Uint8Array ? message.data : message.data.subarray()) })
|
309
|
+
|
310
|
+
if (this._init.onIncomingStream != null) {
|
311
|
+
this._init.onIncomingStream(stream)
|
312
|
+
}
|
111
313
|
|
112
314
|
return
|
113
315
|
}
|
114
316
|
|
115
|
-
const
|
116
|
-
const stream =
|
317
|
+
const list = (type & 1) === 1 ? this._streams.initiators : this._streams.receivers
|
318
|
+
const stream = list.get(id)
|
117
319
|
|
118
320
|
if (stream == null) {
|
119
|
-
this.log('missing stream %s for message type %s', id, MessageTypeNames[
|
321
|
+
this.log('missing stream %s for message type %s', id, MessageTypeNames[type])
|
120
322
|
|
121
323
|
// if the remote keeps sending us messages for streams that have been
|
122
324
|
// closed or were never opened they may be attacking us so if they do
|
123
325
|
// this very quickly all we can do is close the connection
|
124
326
|
try {
|
125
|
-
this.rateLimiter.consume('missing-stream', 1)
|
327
|
+
await this.rateLimiter.consume('missing-stream', 1)
|
126
328
|
} catch {
|
127
329
|
this.log('rate limit hit when receiving messages for streams that do not exist - closing remote connection')
|
128
330
|
// since there's no backpressure in mplex, the only thing we can really do to protect ourselves is close the connection
|
@@ -133,28 +335,41 @@ export class MplexStreamMuxer extends AbstractStreamMuxer<MplexStream> {
|
|
133
335
|
return
|
134
336
|
}
|
135
337
|
|
338
|
+
const maxBufferSize = this._init.maxStreamBufferSize ?? MAX_STREAM_BUFFER_SIZE
|
339
|
+
|
136
340
|
try {
|
137
|
-
switch (
|
341
|
+
switch (type) {
|
138
342
|
case MessageTypes.MESSAGE_INITIATOR:
|
139
343
|
case MessageTypes.MESSAGE_RECEIVER:
|
344
|
+
if (stream.sourceReadableLength() > maxBufferSize) {
|
345
|
+
// Stream buffer has got too large, reset the stream
|
346
|
+
this._source.push({
|
347
|
+
id: message.id,
|
348
|
+
type: type === MessageTypes.MESSAGE_INITIATOR ? MessageTypes.RESET_RECEIVER : MessageTypes.RESET_INITIATOR
|
349
|
+
})
|
350
|
+
|
351
|
+
// Inform the stream consumer they are not fast enough
|
352
|
+
throw new StreamInputBufferError('Input buffer full - increase Mplex maxBufferSize to accommodate slow consumers')
|
353
|
+
}
|
354
|
+
|
140
355
|
// We got data from the remote, push it into our local stream
|
141
|
-
stream.
|
356
|
+
stream.sourcePush(message.data)
|
142
357
|
break
|
143
358
|
case MessageTypes.CLOSE_INITIATOR:
|
144
359
|
case MessageTypes.CLOSE_RECEIVER:
|
145
|
-
// The remote has stopped writing
|
146
|
-
stream.
|
360
|
+
// The remote has stopped writing, so we can stop reading
|
361
|
+
stream.remoteCloseWrite()
|
147
362
|
break
|
148
363
|
case MessageTypes.RESET_INITIATOR:
|
149
364
|
case MessageTypes.RESET_RECEIVER:
|
150
365
|
// The remote has errored, stop reading and writing to the stream immediately
|
151
|
-
stream.
|
366
|
+
stream.reset()
|
152
367
|
break
|
153
368
|
default:
|
154
|
-
this.log('unknown message type')
|
369
|
+
this.log('unknown message type %s', type)
|
155
370
|
}
|
156
371
|
} catch (err: any) {
|
157
|
-
this.log.error('error while processing message
|
372
|
+
this.log.error('error while processing message', err)
|
158
373
|
stream.abort(err)
|
159
374
|
}
|
160
375
|
}
|
package/src/stream.ts
CHANGED
@@ -1,127 +1,95 @@
|
|
1
|
-
import { AbstractStream } from '@libp2p/utils'
|
1
|
+
import { AbstractStream } from '@libp2p/utils/abstract-stream'
|
2
2
|
import { Uint8ArrayList } from 'uint8arraylist'
|
3
3
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
4
4
|
import { MAX_MSG_SIZE } from './decode.js'
|
5
|
-
import { encode } from './encode.ts'
|
6
5
|
import { InitiatorMessageTypes, ReceiverMessageTypes } from './message-types.js'
|
7
|
-
import type {
|
8
|
-
import type { Logger
|
9
|
-
import type { AbstractStreamInit
|
10
|
-
import type { AbortOptions } from 'it-pushable'
|
6
|
+
import type { Message } from './message-types.js'
|
7
|
+
import type { Logger } from '@libp2p/interface'
|
8
|
+
import type { AbstractStreamInit } from '@libp2p/utils/abstract-stream'
|
11
9
|
|
12
10
|
export interface Options {
|
13
11
|
id: number
|
12
|
+
send(msg: Message): Promise<void>
|
14
13
|
log: Logger
|
15
|
-
|
14
|
+
name?: string
|
15
|
+
onEnd?(err?: Error): void
|
16
|
+
type?: 'initiator' | 'receiver'
|
16
17
|
maxMsgSize?: number
|
17
|
-
muxer: MplexStreamMuxer
|
18
18
|
}
|
19
19
|
|
20
20
|
interface MplexStreamInit extends AbstractStreamInit {
|
21
|
+
streamId: number
|
22
|
+
name: string
|
23
|
+
send(msg: Message): Promise<void>
|
24
|
+
|
25
|
+
/**
|
26
|
+
* The maximum allowable data size, any data larger than this will be
|
27
|
+
* chunked and sent in multiple data messages
|
28
|
+
*/
|
21
29
|
maxDataSize: number
|
22
|
-
muxer: MplexStreamMuxer
|
23
|
-
direction: MessageStreamDirection
|
24
30
|
}
|
25
31
|
|
26
32
|
export class MplexStream extends AbstractStream {
|
27
|
-
|
33
|
+
private readonly name: string
|
34
|
+
private readonly streamId: number
|
35
|
+
private readonly send: (msg: Message) => Promise<void>
|
28
36
|
private readonly types: Record<string, number>
|
29
37
|
private readonly maxDataSize: number
|
30
|
-
private readonly muxer: MplexStreamMuxer
|
31
38
|
|
32
39
|
constructor (init: MplexStreamInit) {
|
33
40
|
super(init)
|
34
41
|
|
35
42
|
this.types = init.direction === 'outbound' ? InitiatorMessageTypes : ReceiverMessageTypes
|
43
|
+
this.send = init.send
|
44
|
+
this.name = init.name
|
45
|
+
this.streamId = init.streamId
|
36
46
|
this.maxDataSize = init.maxDataSize
|
37
|
-
|
38
|
-
this.streamId = parseInt(this.id.substring(1))
|
47
|
+
}
|
39
48
|
|
40
|
-
|
41
|
-
|
42
|
-
// stream gets added to the streams array by the muxer superclass before
|
43
|
-
// we send the NEW_STREAM message, otherwise we create a race condition
|
44
|
-
// whereby we can receive the stream messages before the stream is added
|
45
|
-
// to the streams list
|
46
|
-
queueMicrotask(() => {
|
47
|
-
this.muxer.send(
|
48
|
-
encode({
|
49
|
-
id: this.streamId,
|
50
|
-
type: InitiatorMessageTypes.NEW_STREAM,
|
51
|
-
data: new Uint8ArrayList(uint8ArrayFromString(this.id))
|
52
|
-
})
|
53
|
-
)
|
54
|
-
})
|
55
|
-
}
|
49
|
+
async sendNewStream (): Promise<void> {
|
50
|
+
await this.send({ id: this.streamId, type: InitiatorMessageTypes.NEW_STREAM, data: new Uint8ArrayList(uint8ArrayFromString(this.name)) })
|
56
51
|
}
|
57
52
|
|
58
|
-
sendData (data: Uint8ArrayList):
|
59
|
-
|
60
|
-
const sentBytes = data.byteLength
|
53
|
+
async sendData (data: Uint8ArrayList): Promise<void> {
|
54
|
+
data = data.sublist()
|
61
55
|
|
62
56
|
while (data.byteLength > 0) {
|
63
57
|
const toSend = Math.min(data.byteLength, this.maxDataSize)
|
64
|
-
|
65
|
-
data = data.sublist(toSend)
|
66
|
-
|
67
|
-
list.append(
|
68
|
-
encode({
|
69
|
-
id: this.streamId,
|
70
|
-
type: this.types.MESSAGE,
|
71
|
-
data: slice
|
72
|
-
})
|
73
|
-
)
|
74
|
-
}
|
75
|
-
|
76
|
-
return {
|
77
|
-
sentBytes,
|
78
|
-
canSendMore: this.muxer.send(list)
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
sendReset (): boolean {
|
83
|
-
return this.muxer.send(
|
84
|
-
encode({
|
58
|
+
await this.send({
|
85
59
|
id: this.streamId,
|
86
|
-
type: this.types.
|
60
|
+
type: this.types.MESSAGE,
|
61
|
+
data: data.sublist(0, toSend)
|
87
62
|
})
|
88
|
-
)
|
89
|
-
}
|
90
63
|
|
91
|
-
|
92
|
-
|
93
|
-
encode({
|
94
|
-
id: this.streamId,
|
95
|
-
type: this.types.CLOSE
|
96
|
-
})
|
97
|
-
)
|
98
|
-
options?.signal?.throwIfAborted()
|
64
|
+
data.consume(toSend)
|
65
|
+
}
|
99
66
|
}
|
100
67
|
|
101
|
-
async
|
102
|
-
|
103
|
-
// mplex does not support close read, only close write
|
68
|
+
async sendReset (): Promise<void> {
|
69
|
+
await this.send({ id: this.streamId, type: this.types.RESET })
|
104
70
|
}
|
105
71
|
|
106
|
-
|
107
|
-
|
72
|
+
async sendCloseWrite (): Promise<void> {
|
73
|
+
await this.send({ id: this.streamId, type: this.types.CLOSE })
|
108
74
|
}
|
109
75
|
|
110
|
-
|
111
|
-
// mplex does not support
|
76
|
+
async sendCloseRead (): Promise<void> {
|
77
|
+
// mplex does not support close read, only close write
|
112
78
|
}
|
113
79
|
}
|
114
80
|
|
115
81
|
export function createStream (options: Options): MplexStream {
|
116
|
-
const { id,
|
82
|
+
const { id, name, send, onEnd, type = 'initiator', maxMsgSize = MAX_MSG_SIZE } = options
|
83
|
+
const direction = type === 'initiator' ? 'outbound' : 'inbound'
|
117
84
|
|
118
85
|
return new MplexStream({
|
119
|
-
|
120
|
-
|
86
|
+
id: type === 'initiator' ? (`i${id}`) : `r${id}`,
|
87
|
+
streamId: id,
|
88
|
+
name: `${name ?? id}`,
|
121
89
|
direction,
|
122
90
|
maxDataSize: maxMsgSize,
|
123
|
-
|
124
|
-
|
125
|
-
|
91
|
+
onEnd,
|
92
|
+
send,
|
93
|
+
log: options.log.newScope(`${direction}:${id}`)
|
126
94
|
})
|
127
95
|
}
|