lib/predictor.js

'use strict'

class Predictor {
  constructor () {
    this.size = 0
  }

  /**
   * Add the size of a fixed-length integer to the predicted size.
   *
   * @params {Number} value Value of integer. Irrelevant here, but included in
   *   order to have the same interface as the Writer.
   * @params {Number} length Size of integer in bytes.
   */
  writeUInt (value, length) {
    this.size += length
  }

  /**
   * Calculate the size of a variable-length integer.
   *
   * A VARUINT is a variable length integer encoded as base128 where the highest
   * bit indicates that another byte is following. The first byte contains the
   * seven least significant bits of the number represented.
   *
   * @param {Number} value Integer to be encoded
   */
  writeVarUInt (value) {
    if (Buffer.isBuffer(value)) {
      // If the integer was already passed as a buffer, we can just treat it as
      // an octet string.
      this.writeVarOctetString(value)
    } else if (!Number.isInteger(value)) {
      throw new Error('UInt must be an integer')
    } else if (value < 0) {
      throw new Error('UInt must be positive')
    }

    const length = Math.ceil(value.toString(2).length / 8)
    this.writeVarOctetString({ length })
  }

  /**
   * Skip bytes for a fixed-length octet string.
   *
   * Just an alias for skip. Included to provide consistency with Writer.
   *
   * @param {Buffer} buffer Data to write.
   * @param {Number} length Length of data according to the format.
   */
  writeOctetString (buffer, length) {
    this.skip(length)
  }

  /**
   * Calculate the size of a variable-length octet string.
   *
   * A variable-length octet string is a length-prefixed set of arbitrary bytes.
   *
   * @param {Buffer} value Contents of the octet string.
   */
  writeVarOctetString (buffer) {
    // Skip initial byte
    this.skip(1)

    // Skip separate length field if there is one
    if (buffer.length > 127) {
      const lengthOfLength = Math.ceil(buffer.length.toString(2).length / 8)
      this.skip(lengthOfLength)
    }

    this.skip(buffer.length)
  }

  /**
   * Pretend to write a series of bytes.
   *
   * @param {Buffer} Bytes to write.
   */
  write (bytes) {
    this.size += bytes.length
  }

  /**
   * Add this many bytes to the predicted size.
   *
   * @param {Number} Number of bytes to pretend to write.
   */
  skip (bytes) {
    this.size += bytes
  }

  /**
   * Get the size the buffer would have if this was a real writer.
   *
   * @return {Number} Size in bytes.
   */
  getSize () {
    return this.size
  }
}

// Create writeUInt{8,16,32,64} shortcuts
;[1, 2, 4, 8].forEach((bytes) => {
  Predictor.prototype['writeUInt' + bytes * 8] = function (value) {
    return this['writeUInt'](value, bytes)
  }
})

module.exports = Predictor