UNPKG

@libp2p/mplex

Version:

JavaScript implementation of https://github.com/libp2p/mplex

135 lines 5.52 kB
import { MuxerClosedError } from '@libp2p/interface'; import { RateLimiter, AbstractStreamMuxer } from '@libp2p/utils'; import { toString as uint8ArrayToString } from 'uint8arrays'; import { Decoder, MAX_MSG_QUEUE_SIZE, MAX_MSG_SIZE } from "./decode.js"; import { MessageTypes, MessageTypeNames } from "./message-types.js"; import { createStream } from "./stream.js"; const DISCONNECT_THRESHOLD = 5; function printMessage(msg) { const output = { ...msg, type: `${MessageTypeNames[msg.type]} (${msg.type})` }; if (msg.type === MessageTypes.NEW_STREAM) { output.data = uint8ArrayToString(msg.data.subarray()); } if (msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) { output.data = uint8ArrayToString(msg.data.subarray(), 'base16'); } return output; } export class MplexStreamMuxer extends AbstractStreamMuxer { _streamId; rateLimiter; maxMessageSize; maxUnprocessedMessageQueueSize; decoder; constructor(maConn, init) { super(maConn, { ...init, protocol: '/mplex/6.7.0', name: 'mplex' }); this._streamId = 0; this.maxMessageSize = init.maxMessageSize ?? MAX_MSG_SIZE; this.maxUnprocessedMessageQueueSize = init.maxUnprocessedMessageQueueSize ?? MAX_MSG_QUEUE_SIZE; this.decoder = new Decoder(this.maxMessageSize, this.maxUnprocessedMessageQueueSize); this.rateLimiter = new RateLimiter({ points: init.disconnectThreshold ?? DISCONNECT_THRESHOLD, duration: 1 }); } onData(data) { for (const msg of this.decoder.write(data)) { this.handleMessage(msg); } } /** * Initiate a new stream with the given name. If no name is * provided, the id of the stream will be used. */ onCreateStream(options) { if (this.status !== 'open') { throw new MuxerClosedError('Muxer already closed'); } const id = this._streamId++; return this._newStream(id, 'outbound', options); } _newStream(id, direction, options) { this.log('new %s stream %s', direction, id); const stream = createStream({ ...options, id, direction, maxMsgSize: this.maxMessageSize, log: this.log, muxer: this }); return stream; } handleMessage(message) { if (this.log.enabled) { this.log.trace('incoming message', printMessage(message)); } // Create a new stream? if (message.type === MessageTypes.NEW_STREAM) { // close the connection if the remote opens too many streams too quickly try { this.rateLimiter.consume('new-stream', 1); } catch { this.log('rate limit hit when opening too many new streams over the inbound stream limit - closing remote connection'); // since there's no backpressure in mplex, the only thing we can really do to protect ourselves is close the connection this.abort(new Error('Too many open streams')); return; } const stream = this._newStream(message.id, 'inbound', this.streamOptions); this.onRemoteStream(stream); return; } const id = `${(message.type & 1) === 1 ? 'i' : 'r'}${message.id}`; const stream = this.streams.find(s => s.id === id); if (stream == null) { this.log('missing stream %s for message type %s', id, MessageTypeNames[message.type]); // if the remote keeps sending us messages for streams that have been // closed or were never opened they may be attacking us so if they do // this very quickly all we can do is close the connection try { this.rateLimiter.consume('missing-stream', 1); } catch { this.log('rate limit hit when receiving messages for streams that do not exist - closing remote connection'); // since there's no backpressure in mplex, the only thing we can really do to protect ourselves is close the connection this.abort(new Error('Too many messages for missing streams')); return; } return; } try { switch (message.type) { case MessageTypes.MESSAGE_INITIATOR: case MessageTypes.MESSAGE_RECEIVER: // We got data from the remote, push it into our local stream stream.onData(message.data); break; case MessageTypes.CLOSE_INITIATOR: case MessageTypes.CLOSE_RECEIVER: // The remote has stopped writing stream.onRemoteCloseWrite(); break; case MessageTypes.RESET_INITIATOR: case MessageTypes.RESET_RECEIVER: // The remote has errored, stop reading and writing to the stream immediately stream.onRemoteReset(); break; default: this.log('unknown message type'); } } catch (err) { this.log.error('error while processing message - %e', err); stream.abort(err); } } } //# sourceMappingURL=mplex.js.map