forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			479 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			479 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
 | 
						|
 | 
						|
'use strict';
 | 
						|
 | 
						|
const net = require('net');
 | 
						|
const tls = require('tls');
 | 
						|
const { randomFillSync } = require('crypto');
 | 
						|
 | 
						|
const PerMessageDeflate = require('./permessage-deflate');
 | 
						|
const { EMPTY_BUFFER } = require('./constants');
 | 
						|
const { isValidStatusCode } = require('./validation');
 | 
						|
const { mask: applyMask, toBuffer } = require('./buffer-util');
 | 
						|
 | 
						|
const kByteLength = Symbol('kByteLength');
 | 
						|
const maskBuffer = Buffer.alloc(4);
 | 
						|
 | 
						|
/**
 | 
						|
 * HyBi Sender implementation.
 | 
						|
 */
 | 
						|
class Sender {
 | 
						|
  /**
 | 
						|
   * Creates a Sender instance.
 | 
						|
   *
 | 
						|
   * @param {(net.Socket|tls.Socket)} socket The connection socket
 | 
						|
   * @param {Object} [extensions] An object containing the negotiated extensions
 | 
						|
   * @param {Function} [generateMask] The function used to generate the masking
 | 
						|
   *     key
 | 
						|
   */
 | 
						|
  constructor(socket, extensions, generateMask) {
 | 
						|
    this._extensions = extensions || {};
 | 
						|
 | 
						|
    if (generateMask) {
 | 
						|
      this._generateMask = generateMask;
 | 
						|
      this._maskBuffer = Buffer.alloc(4);
 | 
						|
    }
 | 
						|
 | 
						|
    this._socket = socket;
 | 
						|
 | 
						|
    this._firstFragment = true;
 | 
						|
    this._compress = false;
 | 
						|
 | 
						|
    this._bufferedBytes = 0;
 | 
						|
    this._deflating = false;
 | 
						|
    this._queue = [];
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Frames a piece of data according to the HyBi WebSocket protocol.
 | 
						|
   *
 | 
						|
   * @param {(Buffer|String)} data The data to frame
 | 
						|
   * @param {Object} options Options object
 | 
						|
   * @param {Boolean} [options.fin=false] Specifies whether or not to set the
 | 
						|
   *     FIN bit
 | 
						|
   * @param {Function} [options.generateMask] The function used to generate the
 | 
						|
   *     masking key
 | 
						|
   * @param {Boolean} [options.mask=false] Specifies whether or not to mask
 | 
						|
   *     `data`
 | 
						|
   * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
 | 
						|
   *     key
 | 
						|
   * @param {Number} options.opcode The opcode
 | 
						|
   * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
 | 
						|
   *     modified
 | 
						|
   * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
 | 
						|
   *     RSV1 bit
 | 
						|
   * @return {(Buffer|String)[]} The framed data
 | 
						|
   * @public
 | 
						|
   */
 | 
						|
  static frame(data, options) {
 | 
						|
    let mask;
 | 
						|
    let merge = false;
 | 
						|
    let offset = 2;
 | 
						|
    let skipMasking = false;
 | 
						|
 | 
						|
    if (options.mask) {
 | 
						|
      mask = options.maskBuffer || maskBuffer;
 | 
						|
 | 
						|
      if (options.generateMask) {
 | 
						|
        options.generateMask(mask);
 | 
						|
      } else {
 | 
						|
        randomFillSync(mask, 0, 4);
 | 
						|
      }
 | 
						|
 | 
						|
      skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
 | 
						|
      offset = 6;
 | 
						|
    }
 | 
						|
 | 
						|
    let dataLength;
 | 
						|
 | 
						|
    if (typeof data === 'string') {
 | 
						|
      if (
 | 
						|
        (!options.mask || skipMasking) &&
 | 
						|
        options[kByteLength] !== undefined
 | 
						|
      ) {
 | 
						|
        dataLength = options[kByteLength];
 | 
						|
      } else {
 | 
						|
        data = Buffer.from(data);
 | 
						|
        dataLength = data.length;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      dataLength = data.length;
 | 
						|
      merge = options.mask && options.readOnly && !skipMasking;
 | 
						|
    }
 | 
						|
 | 
						|
    let payloadLength = dataLength;
 | 
						|
 | 
						|
    if (dataLength >= 65536) {
 | 
						|
      offset += 8;
 | 
						|
      payloadLength = 127;
 | 
						|
    } else if (dataLength > 125) {
 | 
						|
      offset += 2;
 | 
						|
      payloadLength = 126;
 | 
						|
    }
 | 
						|
 | 
						|
    const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
 | 
						|
 | 
						|
    target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
 | 
						|
    if (options.rsv1) target[0] |= 0x40;
 | 
						|
 | 
						|
    target[1] = payloadLength;
 | 
						|
 | 
						|
    if (payloadLength === 126) {
 | 
						|
      target.writeUInt16BE(dataLength, 2);
 | 
						|
    } else if (payloadLength === 127) {
 | 
						|
      target[2] = target[3] = 0;
 | 
						|
      target.writeUIntBE(dataLength, 4, 6);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!options.mask) return [target, data];
 | 
						|
 | 
						|
    target[1] |= 0x80;
 | 
						|
    target[offset - 4] = mask[0];
 | 
						|
    target[offset - 3] = mask[1];
 | 
						|
    target[offset - 2] = mask[2];
 | 
						|
    target[offset - 1] = mask[3];
 | 
						|
 | 
						|
    if (skipMasking) return [target, data];
 | 
						|
 | 
						|
    if (merge) {
 | 
						|
      applyMask(data, mask, target, offset, dataLength);
 | 
						|
      return [target];
 | 
						|
    }
 | 
						|
 | 
						|
    applyMask(data, mask, data, 0, dataLength);
 | 
						|
    return [target, data];
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a close message to the other peer.
 | 
						|
   *
 | 
						|
   * @param {Number} [code] The status code component of the body
 | 
						|
   * @param {(String|Buffer)} [data] The message component of the body
 | 
						|
   * @param {Boolean} [mask=false] Specifies whether or not to mask the message
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @public
 | 
						|
   */
 | 
						|
  close(code, data, mask, cb) {
 | 
						|
    let buf;
 | 
						|
 | 
						|
    if (code === undefined) {
 | 
						|
      buf = EMPTY_BUFFER;
 | 
						|
    } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
 | 
						|
      throw new TypeError('First argument must be a valid error code number');
 | 
						|
    } else if (data === undefined || !data.length) {
 | 
						|
      buf = Buffer.allocUnsafe(2);
 | 
						|
      buf.writeUInt16BE(code, 0);
 | 
						|
    } else {
 | 
						|
      const length = Buffer.byteLength(data);
 | 
						|
 | 
						|
      if (length > 123) {
 | 
						|
        throw new RangeError('The message must not be greater than 123 bytes');
 | 
						|
      }
 | 
						|
 | 
						|
      buf = Buffer.allocUnsafe(2 + length);
 | 
						|
      buf.writeUInt16BE(code, 0);
 | 
						|
 | 
						|
      if (typeof data === 'string') {
 | 
						|
        buf.write(data, 2);
 | 
						|
      } else {
 | 
						|
        buf.set(data, 2);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    const options = {
 | 
						|
      [kByteLength]: buf.length,
 | 
						|
      fin: true,
 | 
						|
      generateMask: this._generateMask,
 | 
						|
      mask,
 | 
						|
      maskBuffer: this._maskBuffer,
 | 
						|
      opcode: 0x08,
 | 
						|
      readOnly: false,
 | 
						|
      rsv1: false
 | 
						|
    };
 | 
						|
 | 
						|
    if (this._deflating) {
 | 
						|
      this.enqueue([this.dispatch, buf, false, options, cb]);
 | 
						|
    } else {
 | 
						|
      this.sendFrame(Sender.frame(buf, options), cb);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a ping message to the other peer.
 | 
						|
   *
 | 
						|
   * @param {*} data The message to send
 | 
						|
   * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @public
 | 
						|
   */
 | 
						|
  ping(data, mask, cb) {
 | 
						|
    let byteLength;
 | 
						|
    let readOnly;
 | 
						|
 | 
						|
    if (typeof data === 'string') {
 | 
						|
      byteLength = Buffer.byteLength(data);
 | 
						|
      readOnly = false;
 | 
						|
    } else {
 | 
						|
      data = toBuffer(data);
 | 
						|
      byteLength = data.length;
 | 
						|
      readOnly = toBuffer.readOnly;
 | 
						|
    }
 | 
						|
 | 
						|
    if (byteLength > 125) {
 | 
						|
      throw new RangeError('The data size must not be greater than 125 bytes');
 | 
						|
    }
 | 
						|
 | 
						|
    const options = {
 | 
						|
      [kByteLength]: byteLength,
 | 
						|
      fin: true,
 | 
						|
      generateMask: this._generateMask,
 | 
						|
      mask,
 | 
						|
      maskBuffer: this._maskBuffer,
 | 
						|
      opcode: 0x09,
 | 
						|
      readOnly,
 | 
						|
      rsv1: false
 | 
						|
    };
 | 
						|
 | 
						|
    if (this._deflating) {
 | 
						|
      this.enqueue([this.dispatch, data, false, options, cb]);
 | 
						|
    } else {
 | 
						|
      this.sendFrame(Sender.frame(data, options), cb);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a pong message to the other peer.
 | 
						|
   *
 | 
						|
   * @param {*} data The message to send
 | 
						|
   * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @public
 | 
						|
   */
 | 
						|
  pong(data, mask, cb) {
 | 
						|
    let byteLength;
 | 
						|
    let readOnly;
 | 
						|
 | 
						|
    if (typeof data === 'string') {
 | 
						|
      byteLength = Buffer.byteLength(data);
 | 
						|
      readOnly = false;
 | 
						|
    } else {
 | 
						|
      data = toBuffer(data);
 | 
						|
      byteLength = data.length;
 | 
						|
      readOnly = toBuffer.readOnly;
 | 
						|
    }
 | 
						|
 | 
						|
    if (byteLength > 125) {
 | 
						|
      throw new RangeError('The data size must not be greater than 125 bytes');
 | 
						|
    }
 | 
						|
 | 
						|
    const options = {
 | 
						|
      [kByteLength]: byteLength,
 | 
						|
      fin: true,
 | 
						|
      generateMask: this._generateMask,
 | 
						|
      mask,
 | 
						|
      maskBuffer: this._maskBuffer,
 | 
						|
      opcode: 0x0a,
 | 
						|
      readOnly,
 | 
						|
      rsv1: false
 | 
						|
    };
 | 
						|
 | 
						|
    if (this._deflating) {
 | 
						|
      this.enqueue([this.dispatch, data, false, options, cb]);
 | 
						|
    } else {
 | 
						|
      this.sendFrame(Sender.frame(data, options), cb);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a data message to the other peer.
 | 
						|
   *
 | 
						|
   * @param {*} data The message to send
 | 
						|
   * @param {Object} options Options object
 | 
						|
   * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
 | 
						|
   *     or text
 | 
						|
   * @param {Boolean} [options.compress=false] Specifies whether or not to
 | 
						|
   *     compress `data`
 | 
						|
   * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
 | 
						|
   *     last one
 | 
						|
   * @param {Boolean} [options.mask=false] Specifies whether or not to mask
 | 
						|
   *     `data`
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @public
 | 
						|
   */
 | 
						|
  send(data, options, cb) {
 | 
						|
    const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
 | 
						|
    let opcode = options.binary ? 2 : 1;
 | 
						|
    let rsv1 = options.compress;
 | 
						|
 | 
						|
    let byteLength;
 | 
						|
    let readOnly;
 | 
						|
 | 
						|
    if (typeof data === 'string') {
 | 
						|
      byteLength = Buffer.byteLength(data);
 | 
						|
      readOnly = false;
 | 
						|
    } else {
 | 
						|
      data = toBuffer(data);
 | 
						|
      byteLength = data.length;
 | 
						|
      readOnly = toBuffer.readOnly;
 | 
						|
    }
 | 
						|
 | 
						|
    if (this._firstFragment) {
 | 
						|
      this._firstFragment = false;
 | 
						|
      if (
 | 
						|
        rsv1 &&
 | 
						|
        perMessageDeflate &&
 | 
						|
        perMessageDeflate.params[
 | 
						|
          perMessageDeflate._isServer
 | 
						|
            ? 'server_no_context_takeover'
 | 
						|
            : 'client_no_context_takeover'
 | 
						|
        ]
 | 
						|
      ) {
 | 
						|
        rsv1 = byteLength >= perMessageDeflate._threshold;
 | 
						|
      }
 | 
						|
      this._compress = rsv1;
 | 
						|
    } else {
 | 
						|
      rsv1 = false;
 | 
						|
      opcode = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    if (options.fin) this._firstFragment = true;
 | 
						|
 | 
						|
    if (perMessageDeflate) {
 | 
						|
      const opts = {
 | 
						|
        [kByteLength]: byteLength,
 | 
						|
        fin: options.fin,
 | 
						|
        generateMask: this._generateMask,
 | 
						|
        mask: options.mask,
 | 
						|
        maskBuffer: this._maskBuffer,
 | 
						|
        opcode,
 | 
						|
        readOnly,
 | 
						|
        rsv1
 | 
						|
      };
 | 
						|
 | 
						|
      if (this._deflating) {
 | 
						|
        this.enqueue([this.dispatch, data, this._compress, opts, cb]);
 | 
						|
      } else {
 | 
						|
        this.dispatch(data, this._compress, opts, cb);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      this.sendFrame(
 | 
						|
        Sender.frame(data, {
 | 
						|
          [kByteLength]: byteLength,
 | 
						|
          fin: options.fin,
 | 
						|
          generateMask: this._generateMask,
 | 
						|
          mask: options.mask,
 | 
						|
          maskBuffer: this._maskBuffer,
 | 
						|
          opcode,
 | 
						|
          readOnly,
 | 
						|
          rsv1: false
 | 
						|
        }),
 | 
						|
        cb
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Dispatches a message.
 | 
						|
   *
 | 
						|
   * @param {(Buffer|String)} data The message to send
 | 
						|
   * @param {Boolean} [compress=false] Specifies whether or not to compress
 | 
						|
   *     `data`
 | 
						|
   * @param {Object} options Options object
 | 
						|
   * @param {Boolean} [options.fin=false] Specifies whether or not to set the
 | 
						|
   *     FIN bit
 | 
						|
   * @param {Function} [options.generateMask] The function used to generate the
 | 
						|
   *     masking key
 | 
						|
   * @param {Boolean} [options.mask=false] Specifies whether or not to mask
 | 
						|
   *     `data`
 | 
						|
   * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
 | 
						|
   *     key
 | 
						|
   * @param {Number} options.opcode The opcode
 | 
						|
   * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
 | 
						|
   *     modified
 | 
						|
   * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
 | 
						|
   *     RSV1 bit
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  dispatch(data, compress, options, cb) {
 | 
						|
    if (!compress) {
 | 
						|
      this.sendFrame(Sender.frame(data, options), cb);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
 | 
						|
 | 
						|
    this._bufferedBytes += options[kByteLength];
 | 
						|
    this._deflating = true;
 | 
						|
    perMessageDeflate.compress(data, options.fin, (_, buf) => {
 | 
						|
      if (this._socket.destroyed) {
 | 
						|
        const err = new Error(
 | 
						|
          'The socket was closed while data was being compressed'
 | 
						|
        );
 | 
						|
 | 
						|
        if (typeof cb === 'function') cb(err);
 | 
						|
 | 
						|
        for (let i = 0; i < this._queue.length; i++) {
 | 
						|
          const params = this._queue[i];
 | 
						|
          const callback = params[params.length - 1];
 | 
						|
 | 
						|
          if (typeof callback === 'function') callback(err);
 | 
						|
        }
 | 
						|
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      this._bufferedBytes -= options[kByteLength];
 | 
						|
      this._deflating = false;
 | 
						|
      options.readOnly = false;
 | 
						|
      this.sendFrame(Sender.frame(buf, options), cb);
 | 
						|
      this.dequeue();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Executes queued send operations.
 | 
						|
   *
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  dequeue() {
 | 
						|
    while (!this._deflating && this._queue.length) {
 | 
						|
      const params = this._queue.shift();
 | 
						|
 | 
						|
      this._bufferedBytes -= params[3][kByteLength];
 | 
						|
      Reflect.apply(params[0], this, params.slice(1));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Enqueues a send operation.
 | 
						|
   *
 | 
						|
   * @param {Array} params Send operation parameters.
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  enqueue(params) {
 | 
						|
    this._bufferedBytes += params[3][kByteLength];
 | 
						|
    this._queue.push(params);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Sends a frame.
 | 
						|
   *
 | 
						|
   * @param {Buffer[]} list The frame to send
 | 
						|
   * @param {Function} [cb] Callback
 | 
						|
   * @private
 | 
						|
   */
 | 
						|
  sendFrame(list, cb) {
 | 
						|
    if (list.length === 2) {
 | 
						|
      this._socket.cork();
 | 
						|
      this._socket.write(list[0]);
 | 
						|
      this._socket.write(list[1], cb);
 | 
						|
      this._socket.uncork();
 | 
						|
    } else {
 | 
						|
      this._socket.write(list[0], cb);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
module.exports = Sender;
 |