packages/net/src/parser.js

/**
 * Parses binary packet Buffers in little endian
 * @class
 * @memberof module:@perion/net
 */
 class Parser {
  /**
   * @constructor
   * @param {Buffer} data The input data Buffer
   */
  constructor(data) {
    this.data = data;
    this.offset = 0;
    this.parsed = [];
  }
  /**
   * 
   * Static constructor
   * @static
   * @param {Buffer} data The input data Buffer
   * @return {Parser}
   */
  static from(data) {
    return new Parser(data);
  }
  /**
   * Reads a byte
   * @return {Parser} Returns a reference to the current Parser
   */
  byte() {
    const byte = this.data.readIntLE(this.offset, 1);
    this.offset += 1;
    this.parsed.push(byte);
    return this;
  }
  /**
   * Reads a boolean
   * @return {Parser} Returns a reference to the current Parser
   */
  bool() {
    const bool = this.byte().get();
    this.parsed.push(bool === 1 ? true : false);
    return this;
  }
  /**
   * Reads an unsigned byte
   * @return {Parser} Returns a reference to the current Parser
   */
  ubyte() {
    const uByte = this.data.readUIntLE(this.offset, 1);
    this.offset += 1;
    this.parsed.push(uByte);
    return this;
  }
  /**
   * Reads a short
   * @return {Parser} Returns a reference to the current Parser
   */
  short() {
    const short = this.data.readInt16LE(this.offset);
    this.offset += 2;
    this.parsed.push(short);
    return this;
  }
  /**
   * Reads an unsigned short
   * @return {Parser} Returns a reference to the current Parser
   */
  ushort() {
    const uShort = this.data.readUInt16LE(this.offset);
    this.offset += 2;
    this.parsed.push(uShort);
    return this;
  }
  /**
   * Reads a character
   * @return {Parser} Returns a reference to the current Parser
   */
  char() {
    this.parsed.push(String.fromCharCode(97 + this.short().get()));
    return this;
  }
  /**
   * Reads an integer
   * @return {Parser} Returns a reference to the current Parser
   */
  int() {
    const int = this.data.readInt32LE(this.offset);
    this.offset += 4;
    this.parsed.push(int);
    return this;
  }
  /**
   * Reads an unsigned integer
   * @return {Parser} Returns a reference to the current Parser
   */
  uint() {
    const uInt = this.data.readUInt32LE(this.offset);
    this.offset += 4;
    this.parsed.push(uInt);
    return this;
  }
  /**
   * Reads a long
   * @return {Parser} Returns a reference to the current Parser
   */
  long() {
    const long = this.data.readBigInt64LE(this.offset);
    this.offset += 8;
    this.parsed.push(long);
    return this;
  }
  /**
   * Reads an unsigned long
   * @return {Parser} Returns a reference to the current Parser
   */
  ulong() {
    const uLong = this.data.readBigUInt64LE(this.offset);
    this.offset += 8;
    this.parsed.push(uLong);
    return this;
  }
  /**
   * Reads a double
   * @return {Parser} Returns a reference to the current Parser
   */
  double() {
    const double = this.data.readDoubleLE(this.offset);
    this.offset += 8;
    this.parsed.push(double);
    return this;
  }
  /**
   * Reads an ASCII string
   * @param {number} length Length of the string
   * @return {Parser} Returns a reference to the current Parser
   */
  ascii(length) {
    const stringBuffer = Buffer.alloc(length);
    for (let i = 0; i < length; i++) {
      stringBuffer[i] = this.byte().get();
    }
    this.parsed.push(stringBuffer.toString('ascii'));
    return this;
  }
  /**
   * Reads a null terminated ASCII string
   * @return {Parser} Returns a reference to the current Parser
   */
  nullascii() {
    const stringArr = [];
    const done = false; /** eslint */
    while (!done) {
      const byte = this.byte().get(); /** Read until 0 */
      if (byte === 0) break;
      stringArr.push(byte);
    }
    this.parsed.push(Buffer.from(stringArr).toString('ascii'));
    return this;
  }
  /**
   * Reads a MapleStory ASCII string
   * @return {Parser} Returns a reference to the current Parser
   */
  mapleascii() {
    const length = this.short().get();
    this.parsed.push(this.ascii(length).get());
    return this;
  }
  /**
   * Reads a position (x, y)
   * @return {Parser} Returns a reference to the current Parser
   */
  pos() {
    const x = this.short().get();
    const y = this.short().get();
    this.parsed.push({x, y});
    return this;
  }
  /**
   * Reads a specified length
   * @param {number} length
   * @return {Parser} Returns a reference to the current Parser
   */
  read(length) {
    const ret = Buffer.alloc(length);
    for (let i = 0; i < length; i++) {
      ret[i] = this.byte().get();
    }
    this.parsed.push(ret);
    return this;
  }
  /**
   * Skips the cursor a specified length
   * @param {number} length
   * @return {Parser} Returns a reference to the current Parser
   */
  skip(length) {
    this.offset += length;
    return this;
  }
  /**
   * Moves the cursor to a specified byte offset
   * @param {number} offset
   * @return {Parser} Returns a reference to the current Parser
   */
  seek(offset) {
    this.offset = offset;
    return this;
  }
  /**
   * Collects the parsed values and maps them to the input array strings
   * Will clear the parsed values
   * @param {Array<string>} fieldNames Names mapped to values
   * @return {Map<string, any>} The object with {name:value}
   */
  collect(fieldNames) {
    if (fieldNames.length !== this.parsed.length) return false;
    const ret = {};
    const length = fieldNames.length;
    for (let i = 0; i < length; i++) {
      ret[fieldNames[i]] = this.parsed[i];
    }
    this.parsed = [];
    return ret;
  }
  /**
   * Gets the last parsed value, removes it from the parsed list.
   * Set the option flag removeParsed=false to override
   * @param {Object} options {removeParsed}
   * @return {any} Any piece of data
   */
  get(removeParsed=true) {
    if (!removeParsed) return this.parsed[this.parsed.length - 1];
    const latest = this.parsed.pop();
    return latest;
  }
}
module.exports = Parser;