@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/src/mplex.ts CHANGED
@@ -1,16 +1,27 @@
1
- import { MuxerClosedError } from '@libp2p/interface'
2
- import { RateLimiter, AbstractStreamMuxer } from '@libp2p/utils'
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, MAX_MSG_QUEUE_SIZE, MAX_MSG_SIZE } from './decode.js'
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 { CreateStreamOptions, MultiaddrConnection, MessageStreamDirection } from '@libp2p/interface'
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 class MplexStreamMuxer extends AbstractStreamMuxer<MplexStream> {
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 maxMessageSize: number
36
- private readonly maxUnprocessedMessageQueueSize: number
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.maxMessageSize = init.maxMessageSize ?? MAX_MSG_SIZE
48
- this.maxUnprocessedMessageQueueSize = init.maxUnprocessedMessageQueueSize ?? MAX_MSG_QUEUE_SIZE
49
- this.decoder = new Decoder(this.maxMessageSize, this.maxUnprocessedMessageQueueSize)
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
- onData (data: Uint8Array | Uint8ArrayList): void {
58
- for (const msg of this.decoder.write(data)) {
59
- this.handleMessage(msg)
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
- onCreateStream (options: CreateStreamOptions): MplexStream {
68
- if (this.status !== 'open') {
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
- return this._newStream(id, 'outbound', options)
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
- _newStream (id: number, direction: MessageStreamDirection, options?: CreateStreamOptions): MplexStream {
78
- this.log('new %s stream %s', direction, id)
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
- const stream = createStream({
81
- ...options,
82
- id,
83
- direction,
84
- maxMsgSize: this.maxMessageSize,
85
- log: this.log,
86
- muxer: this
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
- handleMessage (message: Message): void {
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
- // close the connection if the remote opens too many streams too quickly
100
- try {
101
- this.rateLimiter.consume('new-stream', 1)
102
- } catch {
103
- this.log('rate limit hit when opening too many new streams over the inbound stream limit - closing remote connection')
104
- // since there's no backpressure in mplex, the only thing we can really do to protect ourselves is close the connection
105
- this.abort(new Error('Too many open streams'))
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._newStream(message.id, 'inbound', this.streamOptions)
110
- this.onRemoteStream(stream)
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 id = `${(message.type & 1) === 1 ? 'i' : 'r'}${message.id}`
116
- const stream = this.streams.find(s => s.id === id)
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[message.type])
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 (message.type) {
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.onData(message.data)
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.onRemoteCloseWrite()
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.onRemoteReset()
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 - %e', err)
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 { MplexStreamMuxer } from './mplex.ts'
8
- import type { Logger, MessageStreamDirection } from '@libp2p/interface'
9
- import type { AbstractStreamInit, SendResult } from '@libp2p/utils'
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
- direction: MessageStreamDirection
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
- public readonly streamId: number
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
- this.muxer = init.muxer
38
- this.streamId = parseInt(this.id.substring(1))
47
+ }
39
48
 
40
- if (init.direction === 'outbound') {
41
- // open the stream on the receiver end. do this in a microtask so the
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): SendResult {
59
- const list = new Uint8ArrayList()
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
- const slice = data.sublist(0, toSend)
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.RESET
60
+ type: this.types.MESSAGE,
61
+ data: data.sublist(0, toSend)
87
62
  })
88
- )
89
- }
90
63
 
91
- async sendCloseWrite (options?: AbortOptions): Promise<void> {
92
- this.muxer.send(
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 sendCloseRead (options?: AbortOptions): Promise<void> {
102
- options?.signal?.throwIfAborted()
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
- sendPause (): void {
107
- // mplex does not support backpressure
72
+ async sendCloseWrite (): Promise<void> {
73
+ await this.send({ id: this.streamId, type: this.types.CLOSE })
108
74
  }
109
75
 
110
- sendResume (): void {
111
- // mplex does not support backpressure
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, muxer, direction, maxMsgSize = MAX_MSG_SIZE } = options
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
- ...options,
120
- id: direction === 'outbound' ? (`i${id}`) : `r${id}`,
86
+ id: type === 'initiator' ? (`i${id}`) : `r${id}`,
87
+ streamId: id,
88
+ name: `${name ?? id}`,
121
89
  direction,
122
90
  maxDataSize: maxMsgSize,
123
- muxer,
124
- log: options.log.newScope(`${direction}:${id}`),
125
- protocol: ''
91
+ onEnd,
92
+ send,
93
+ log: options.log.newScope(`${direction}:${id}`)
126
94
  })
127
95
  }