Source: lib/message.js

'use strict';

const {
  MAGIC_BYTES,
  VERSION_NUMBER,
  HEADER_SIZE,
  MAX_MESSAGE_SIZE
} = require('../constants');

const crypto = require('crypto');
const Vector = require('./vector');

/**
 * The {@link Message} type defines the Application Messaging Protocol, or AMP.
 * Each {@link Actor} in the network receives and broadcasts messages,
 * selectively disclosing new routes to peers which may have open circuits.
 * @type {Object}
 */
class Message extends Vector {
  /**
   * The `Message` type is standardized in {@link Fabric} as a {@link Vector}, which can be added to any other vector to compute a resulting state.
   * @param  {Vector} message Message vector.  Will be serialized by {@link Vector#_serialize}.
   * @return {Vector} Instance of the message.
   */
  constructor (input = {}) {
    super(input);

    this.raw = {
      magic: Buffer.alloc(4),
      version: Buffer.alloc(4),
      type: Buffer.alloc(4),
      size: Buffer.alloc(4),
      hash: Buffer.alloc(32),
      data: null
    };

    this.raw.magic.writeUInt32BE(MAGIC_BYTES);
    this.raw.version.writeUInt32BE(VERSION_NUMBER);

    return this;
  }

  /**
   * Returns a {@link Buffer} of the complete message.
   * @return {Buffer} Buffer of the encoded {@link Message}.
   */
  asRaw () {
    return Buffer.concat([this.header, this.raw.data]);
  }

  static fromRaw (input) {
    if (!input) return null;
    if (input.length < HEADER_SIZE) return null;
    if (input.length > MAX_MESSAGE_SIZE) return new Error('Input too large.');

    let message = new Message();

    message.raw = {
      magic: input.slice(0, 4),
      version: input.slice(4, 8),
      type: input.slice(8, 12),
      size: input.slice(12, 16),
      hash: input.slice(16, 48)
    };

    if (message.raw.size) {
      let size = message.raw.size.readUInt32BE();
      message.data = input.slice(HEADER_SIZE, HEADER_SIZE + size);
    }

    return message;
  }

  static fromVector (vector) {
    let message = new Message();

    message.type = vector[0];
    message.data = vector[1];

    return message;
  }

  get [Symbol.toStringTag] () {
    return 'Message';
  }

  get id () {
    return crypto.createHash('sha256').update(this.asRaw()).digest('hex');
  }

  get magic () {
    return this.raw.magic;
  }

  get size () {
    return this.raw.size.readUInt32BE();
  }

  get version () {
    return this.raw.version.readUInt32BE();
  }

  get header () {
    return Buffer.concat([
      this.raw.magic,
      this.raw.version,
      this.raw.type,
      this.raw.size,
      this.raw.hash
    ]);
  }
}

Object.defineProperty(Message.prototype, 'type', {
  get () {
    return this.raw.type.readUInt32BE();
  },
  set (value) {
    this.raw.type.writeUInt32BE(value);
  }
});

Object.defineProperty(Message.prototype, 'data', {
  get () {
    if (!this.raw.data) return '';
    return this.raw.data.toString('ascii');
  },
  set (value) {
    if (!value) value = '';
    let hash = crypto.createHash('sha256').update(value.toString('ascii'));
    this.raw.hash = hash.digest();
    this.raw.data = Buffer.from(value);
    this.raw.size.writeUInt32BE(this.raw.data.byteLength);
  }
});

module.exports = Message;