Source: net/packets.js

/*!
 * packets.js - packets for bcoin
 * Copyright (c) 2014-2015, Fedor Indutny (MIT License)
 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

/**
 * @module net/packets
 */

const assert = require('bsert');
const bio = require('bufio');
const {BloomFilter} = require('bfilter');
const common = require('./common');
const util = require('../utils/util');
const bip152 = require('./bip152');
const NetAddress = require('./netaddress');
const consensus = require('../protocol/consensus');
const Headers = require('../primitives/headers');
const InvItem = require('../primitives/invitem');
const MemBlock = require('../primitives/memblock');
const MerkleBlock = require('../primitives/merkleblock');
const TX = require('../primitives/tx');
const {encoding} = bio;
const DUMMY = Buffer.alloc(0);
const {inspectSymbol} = require('../utils');

/**
 * Packet types.
 * @enum {Number}
 * @default
 */

exports.types = {
  VERSION: 0,
  VERACK: 1,
  PING: 2,
  PONG: 3,
  GETADDR: 4,
  ADDR: 5,
  INV: 6,
  GETDATA: 7,
  NOTFOUND: 8,
  GETBLOCKS: 9,
  GETHEADERS: 10,
  HEADERS: 11,
  SENDHEADERS: 12,
  BLOCK: 13,
  TX: 14,
  REJECT: 15,
  MEMPOOL: 16,
  FILTERLOAD: 17,
  FILTERADD: 18,
  FILTERCLEAR: 19,
  MERKLEBLOCK: 20,
  FEEFILTER: 21,
  SENDCMPCT: 22,
  CMPCTBLOCK: 23,
  GETBLOCKTXN: 24,
  BLOCKTXN: 25,
  UNKNOWN: 26,
  // Internal
  INTERNAL: 27,
  DATA: 28
};

/**
 * Packet types by value.
 * @const {Object}
 * @default
 */

exports.typesByVal = [
  'VERSION',
  'VERACK',
  'PING',
  'PONG',
  'GETADDR',
  'ADDR',
  'INV',
  'GETDATA',
  'NOTFOUND',
  'GETBLOCKS',
  'GETHEADERS',
  'HEADERS',
  'SENDHEADERS',
  'BLOCK',
  'TX',
  'REJECT',
  'MEMPOOL',
  'FILTERLOAD',
  'FILTERADD',
  'FILTERCLEAR',
  'MERKLEBLOCK',
  'FEEFILTER',
  'SENDCMPCT',
  'CMPCTBLOCK',
  'GETBLOCKTXN',
  'BLOCKTXN',
  'UNKNOWN',
  // Internal
  'INTERNAL',
  'DATA'
];

/**
 * Base Packet
 */

class Packet {
  /**
   * Create a base packet.
   * @constructor
   */

  constructor() {
    this.type = -1;
    this.cmd = '';
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return 0;
  }

  /**
   * Serialize packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    return bw;
  }

  /**
   * Serialize packet.
   * @returns {Buffer}
   */

  toRaw() {
    return DUMMY;
  }

  /**
   * Inject properties from buffer reader.
   * @param {BufferReader} br
   */

  fromReader(br) {
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this;
  }
}

/**
 * Version Packet
 * @extends Packet
 * @property {Number} version - Protocol version.
 * @property {Number} services - Service bits.
 * @property {Number} time - Timestamp of discovery.
 * @property {NetAddress} local - Our address.
 * @property {NetAddress} remote - Their address.
 * @property {Buffer} nonce
 * @property {String} agent - User agent string.
 * @property {Number} height - Chain height.
 * @property {Boolean} noRelay - Whether transactions
 * should be relayed immediately.
 */

class VersionPacket extends Packet {
  /**
   * Create a version packet.
   * @constructor
   * @param {Object?} options
   * @param {Number} options.version - Protocol version.
   * @param {Number} options.services - Service bits.
   * @param {Number} options.time - Timestamp of discovery.
   * @param {NetAddress} options.local - Our address.
   * @param {NetAddress} options.remote - Their address.
   * @param {Buffer} options.nonce
   * @param {String} options.agent - User agent string.
   * @param {Number} options.height - Chain height.
   * @param {Boolean} options.noRelay - Whether transactions
   * should be relayed immediately.
   */

  constructor(options) {
    super();

    this.cmd = 'version';
    this.type = exports.types.VERSION;

    this.version = common.PROTOCOL_VERSION;
    this.services = common.LOCAL_SERVICES;
    this.time = util.now();
    this.remote = new NetAddress();
    this.local = new NetAddress();
    this.nonce = common.ZERO_NONCE;
    this.agent = common.USER_AGENT;
    this.height = 0;
    this.noRelay = false;

    if (options)
      this.fromOptions(options);
  }

  /**
   * Inject properties from options.
   * @private
   * @param {Object} options
   */

  fromOptions(options) {
    if (options.version != null)
      this.version = options.version;

    if (options.services != null)
      this.services = options.services;

    if (options.time != null)
      this.time = options.time;

    if (options.remote)
      this.remote.fromOptions(options.remote);

    if (options.local)
      this.local.fromOptions(options.local);

    if (options.nonce)
      this.nonce = options.nonce;

    if (options.agent)
      this.agent = options.agent;

    if (options.height != null)
      this.height = options.height;

    if (options.noRelay != null)
      this.noRelay = options.noRelay;

    return this;
  }

  /**
   * Instantiate version packet from options.
   * @param {Object} options
   * @returns {VersionPacket}
   */

  static fromOptions(options) {
    return new this().fromOptions(options);
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;
    size += 20;
    size += this.remote.getSize(false);
    size += this.local.getSize(false);
    size += 8;
    size += encoding.sizeVarString(this.agent, 'ascii');
    size += 5;
    return size;
  }

  /**
   * Write version packet to buffer writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeI32(this.version);
    bw.writeU32(this.services);
    bw.writeU32(0);
    bw.writeI64(this.time);
    this.remote.toWriter(bw, false);
    this.local.toWriter(bw, false);
    bw.writeBytes(this.nonce);
    bw.writeVarString(this.agent, 'ascii');
    bw.writeI32(this.height);
    bw.writeU8(this.noRelay ? 0 : 1);
    return bw;
  }

  /**
   * Serialize version packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.version = br.readI32();
    this.services = br.readU32();

    // Note: hi service bits
    // are currently unused.
    br.readU32();

    this.time = br.readI64();
    this.remote.fromReader(br, false);

    if (br.left() > 0) {
      this.local.fromReader(br, false);
      this.nonce = br.readBytes(8);
    }

    if (br.left() > 0)
      this.agent = br.readVarString('ascii', 256);

    if (br.left() > 0)
      this.height = br.readI32();

    if (br.left() > 0)
      this.noRelay = br.readU8() === 0;

    if (this.version === 10300)
      this.version = 300;

    assert(this.version >= 0, 'Version is negative.');
    assert(this.time >= 0, 'Timestamp is negative.');

    // No idea why so many peers do this.
    if (this.height < 0)
      this.height = 0;

    return this;
  }

  /**
   * Instantiate version packet from buffer reader.
   * @param {BufferReader} br
   * @returns {VersionPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate version packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {VersionPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data, enc);
  }
}

/**
 * Verack Packet
 * @extends Packet
 */

class VerackPacket extends Packet {
  /**
   * Create a `verack` packet.
   * @constructor
   */

  constructor() {
    super();
    this.cmd = 'verack';
    this.type = exports.types.VERACK;
  }

  /**
   * Instantiate verack packet from serialized data.
   * @param {BufferReader} br
   * @returns {VerackPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate verack packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {VerackPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Ping Packet
 * @extends Packet
 * @property {Buffer|null} nonce
 */

class PingPacket extends Packet {
  /**
   * Create a `ping` packet.
   * @constructor
   * @param {Buffer?} nonce
   */

  constructor(nonce) {
    super();

    this.cmd = 'ping';
    this.type = exports.types.PING;

    this.nonce = nonce || null;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.nonce ? 8 : 0;
  }

  /**
   * Serialize ping packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Serialize ping packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    if (this.nonce)
      bw.writeBytes(this.nonce);
    return bw;
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    if (br.left() >= 8)
      this.nonce = br.readBytes(8);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate ping packet from serialized data.
   * @param {BufferReader} br
   * @returns {PingPacket}
   */

  static fromReader(br) {
    return new this().fromRaw(br);
  }

  /**
   * Instantiate ping packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {PingPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Pong Packet
 * @extends Packet
 * @property {BN} nonce
 */

class PongPacket extends Packet {
  /**
   * Create a `pong` packet.
   * @constructor
   * @param {BN?} nonce
   */

  constructor(nonce) {
    super();

    this.cmd = 'pong';
    this.type = exports.types.PONG;

    this.nonce = nonce || common.ZERO_NONCE;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return 8;
  }

  /**
   * Serialize pong packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeBytes(this.nonce);
    return bw;
  }

  /**
   * Serialize pong packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.toWriter(bio.write(8)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.nonce = br.readBytes(8);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate pong packet from buffer reader.
   * @param {BufferReader} br
   * @returns {VerackPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate pong packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {VerackPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * GetAddr Packet
 * @extends Packet
 */

class GetAddrPacket extends Packet {
  /**
   * Create a `getaddr` packet.
   * @constructor
   */

  constructor() {
    super();
    this.cmd = 'getaddr';
    this.type = exports.types.GETADDR;
  }

  /**
   * Instantiate getaddr packet from buffer reader.
   * @param {BufferReader} br
   * @returns {GetAddrPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate getaddr packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {GetAddrPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Addr Packet
 * @extends Packet
 * @property {NetAddress[]} items
 */

class AddrPacket extends Packet {
  /**
   * Create a `addr` packet.
   * @constructor
   * @param {(NetAddress[])?} items
   */

  constructor(items) {
    super();

    this.cmd = 'addr';
    this.type = exports.types.ADDR;

    this.items = items || [];
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;
    size += encoding.sizeVarint(this.items.length);
    size += 30 * this.items.length;
    return size;
  }

  /**
   * Serialize addr packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeVarint(this.items.length);

    for (const item of this.items)
      item.toWriter(bw, true);

    return bw;
  }

  /**
   * Serialize addr packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    const br = bio.read(data);
    const count = br.readVarint();

    for (let i = 0; i < count; i++)
      this.items.push(NetAddress.fromReader(br, true));

    return this;
  }

  /**
   * Instantiate addr packet from Buffer reader.
   * @param {BufferReader} br
   * @returns {AddrPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate addr packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {AddrPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Inv Packet
 * @extends Packet
 * @property {InvItem[]} items
 */

class InvPacket extends Packet {
  /**
   * Create a `inv` packet.
   * @constructor
   * @param {(InvItem[])?} items
   */

  constructor(items) {
    super();

    this.cmd = 'inv';
    this.type = exports.types.INV;

    this.items = items || [];
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;
    size += encoding.sizeVarint(this.items.length);
    size += 36 * this.items.length;
    return size;
  }

  /**
   * Serialize inv packet to writer.
   * @param {Buffer} bw
   */

  toWriter(bw) {
    assert(this.items.length <= common.MAX_INV);

    bw.writeVarint(this.items.length);

    for (const item of this.items)
      item.toWriter(bw);

    return bw;
  }

  /**
   * Serialize inv packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    const count = br.readVarint();

    assert(count <= common.MAX_INV, 'Inv item count too high.');

    for (let i = 0; i < count; i++)
      this.items.push(InvItem.fromReader(br));

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate inv packet from buffer reader.
   * @param {BufferReader} br
   * @returns {InvPacket}
   */

  static fromReader(br) {
    return new this().fromRaw(br);
  }

  /**
   * Instantiate inv packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {InvPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * GetData Packet
 * @extends InvPacket
 */

class GetDataPacket extends InvPacket {
  /**
   * Create a `getdata` packet.
   * @constructor
   * @param {(InvItem[])?} items
   */

  constructor(items) {
    super(items);
    this.cmd = 'getdata';
    this.type = exports.types.GETDATA;
  }

  /**
   * Instantiate getdata packet from buffer reader.
   * @param {BufferReader} br
   * @returns {GetDataPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate getdata packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {GetDataPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * NotFound Packet
 * @extends InvPacket
 */

class NotFoundPacket extends InvPacket {
  /**
   * Create a `notfound` packet.
   * @constructor
   * @param {(InvItem[])?} items
   */

  constructor(items) {
    super(items);
    this.cmd = 'notfound';
    this.type = exports.types.NOTFOUND;
  }

  /**
   * Instantiate notfound packet from buffer reader.
   * @param {BufferReader} br
   * @returns {NotFoundPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate notfound packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {NotFoundPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * GetBlocks Packet
 * @extends Packet
 * @property {Hash[]} locator
 * @property {Hash|null} stop
 */

class GetBlocksPacket extends Packet {
  /**
   * Create a `getblocks` packet.
   * @constructor
   * @param {Hash[]} locator
   * @param {Hash?} stop
   */

  constructor(locator, stop) {
    super();

    this.cmd = 'getblocks';
    this.type = exports.types.GETBLOCKS;

    this.version = common.PROTOCOL_VERSION;
    this.locator = locator || [];
    this.stop = stop || null;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;
    size += 4;
    size += encoding.sizeVarint(this.locator.length);
    size += 32 * this.locator.length;
    size += 32;
    return size;
  }

  /**
   * Serialize getblocks packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    assert(this.locator.length <= common.MAX_INV, 'Too many block hashes.');

    bw.writeU32(this.version);
    bw.writeVarint(this.locator.length);

    for (const hash of this.locator)
      bw.writeHash(hash);

    bw.writeHash(this.stop || consensus.ZERO_HASH);

    return bw;
  }

  /**
   * Serialize getblocks packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.version = br.readU32();

    const count = br.readVarint();

    assert(count <= common.MAX_INV, 'Too many block hashes.');

    for (let i = 0; i < count; i++)
      this.locator.push(br.readHash());

    this.stop = br.readHash();

    if (this.stop.equals(consensus.ZERO_HASH))
      this.stop = null;

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate getblocks packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {GetBlocksPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * GetHeader Packets
 * @extends GetBlocksPacket
 */

class GetHeadersPacket extends GetBlocksPacket {
  /**
   * Create a `getheaders` packet.
   * @constructor
   * @param {Hash[]} locator
   * @param {Hash?} stop
   */

  constructor(locator, stop) {
    super(locator, stop);
    this.cmd = 'getheaders';
    this.type = exports.types.GETHEADERS;
  }

  /**
   * Instantiate getheaders packet from buffer reader.
   * @param {BufferReader} br
   * @returns {GetHeadersPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate getheaders packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {GetHeadersPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Headers Packet
 * @extends Packet
 * @property {Headers[]} items
 */

class HeadersPacket extends Packet {
  /**
   * Create a `headers` packet.
   * @constructor
   * @param {(Headers[])?} items
   */

  constructor(items) {
    super();

    this.cmd = 'headers';
    this.type = exports.types.HEADERS;

    this.items = items || [];
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;

    size += encoding.sizeVarint(this.items.length);

    for (const item of this.items)
      size += item.getSize();

    return size;
  }

  /**
   * Serialize headers packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    assert(this.items.length <= 2000, 'Too many headers.');

    bw.writeVarint(this.items.length);

    for (const item of this.items)
      item.toWriter(bw);

    return bw;
  }

  /**
   * Serialize headers packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    const count = br.readVarint();

    assert(count <= 2000, 'Too many headers.');

    for (let i = 0; i < count; i++)
      this.items.push(Headers.fromReader(br));

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate headers packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {VerackPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * SendHeaders Packet
 * @extends Packet
 */

class SendHeadersPacket extends Packet {
  /**
   * Create a `sendheaders` packet.
   * @constructor
   */

  constructor() {
    super();
    this.cmd = 'sendheaders';
    this.type = exports.types.SENDHEADERS;
  }

  /**
   * Instantiate sendheaders packet from buffer reader.
   * @param {BufferReader} br
   * @returns {SendHeadersPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate sendheaders packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {SendHeadersPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Block Packet
 * @extends Packet
 * @property {Block} block
 * @property {Boolean} witness
 */

class BlockPacket extends Packet {
  /**
   * Create a `block` packet.
   * @constructor
   * @param {Block|null} block
   * @param {Boolean?} witness
   */

  constructor(block, witness) {
    super();

    this.cmd = 'block';
    this.type = exports.types.BLOCK;

    this.block = block || new MemBlock();
    this.witness = witness || false;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    if (this.witness)
      return this.block.getSize();
    return this.block.getBaseSize();
  }

  /**
   * Serialize block packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    if (this.witness)
      return this.block.toWriter(bw);
    return this.block.toNormalWriter(bw);
  }

  /**
   * Serialize block packet.
   * @returns {Buffer}
   */

  toRaw() {
    if (this.witness)
      return this.block.toRaw();
    return this.block.toNormal();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.block.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.block.fromRaw(data);
    return this;
  }

  /**
   * Instantiate block packet from buffer reader.
   * @param {BufferReader} br
   * @returns {BlockPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate block packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {BlockPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * TX Packet
 * @extends Packet
 * @property {TX} block
 * @property {Boolean} witness
 */

class TXPacket extends Packet {
  /**
   * Create a `tx` packet.
   * @constructor
   * @param {TX|null} tx
   * @param {Boolean?} witness
   */

  constructor(tx, witness) {
    super();

    this.cmd = 'tx';
    this.type = exports.types.TX;

    this.tx = tx || new TX();
    this.witness = witness || false;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    if (this.witness)
      return this.tx.getSize();
    return this.tx.getBaseSize();
  }

  /**
   * Serialize tx packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    if (this.witness)
      return this.tx.toWriter(bw);
    return this.tx.toNormalWriter(bw);
  }

  /**
   * Serialize tx packet.
   * @returns {Buffer}
   */

  toRaw() {
    if (this.witness)
      return this.tx.toRaw();
    return this.tx.toNormal();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.tx.fromRaw(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.tx.fromRaw(data);
    return this;
  }

  /**
   * Instantiate tx packet from buffer reader.
   * @param {BufferReader} br
   * @returns {TXPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate tx packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {TXPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Reject Packet
 * @extends Packet
 * @property {(Number|String)?} code - Code
 * (see {@link RejectPacket.codes}).
 * @property {String?} msg - Message.
 * @property {String?} reason - Reason.
 * @property {(Hash|Buffer)?} data - Transaction or block hash.
 */

class RejectPacket extends Packet {
  /**
   * Create reject packet.
   * @constructor
   */

  constructor(options) {
    super();

    this.cmd = 'reject';
    this.type = exports.types.REJECT;

    this.message = '';
    this.code = RejectPacket.codes.INVALID;
    this.reason = '';
    this.hash = null;

    if (options)
      this.fromOptions(options);
  }

  /**
   * Inject properties from options object.
   * @private
   * @param {Object} options
   */

  fromOptions(options) {
    let code = options.code;

    if (options.message)
      this.message = options.message;

    if (code != null) {
      if (typeof code === 'string')
        code = RejectPacket.codes[code.toUpperCase()];

      if (code >= RejectPacket.codes.INTERNAL)
        code = RejectPacket.codes.INVALID;

      this.code = code;
    }

    if (options.reason)
      this.reason = options.reason;

    if (options.hash)
      this.hash = options.hash;

    return this;
  }

  /**
   * Instantiate reject packet from options.
   * @param {Object} options
   * @returns {RejectPacket}
   */

  static fromOptions(options) {
    return new this().fromOptions(options);
  }

  /**
   * Get uint256le hash if present.
   * @returns {Hash}
   */

  rhash() {
    return this.hash ? util.revHex(this.hash) : null;
  }

  /**
   * Get symbolic code.
   * @returns {String}
   */

  getCode() {
    const code = RejectPacket.codesByVal[this.code];

    if (!code)
      return this.code.toString(10);

    return code.toLowerCase();
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    let size = 0;

    size += encoding.sizeVarString(this.message, 'ascii');
    size += 1;
    size += encoding.sizeVarString(this.reason, 'ascii');

    if (this.hash)
      size += 32;

    return size;
  }

  /**
   * Serialize reject packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    assert(this.message.length <= 12);
    assert(this.reason.length <= 111);

    bw.writeVarString(this.message, 'ascii');
    bw.writeU8(this.code);
    bw.writeVarString(this.reason, 'ascii');

    if (this.hash)
      bw.writeHash(this.hash);

    return bw;
  }

  /**
   * Serialize reject packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.message = br.readVarString('ascii', 12);
    this.code = br.readU8();
    this.reason = br.readVarString('ascii', 111);

    switch (this.message) {
      case 'block':
      case 'tx':
        this.hash = br.readHash();
        break;
      default:
        this.hash = null;
        break;
    }

    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate reject packet from buffer reader.
   * @param {BufferReader} br
   * @returns {RejectPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate reject packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {RejectPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data, enc);
  }

  /**
   * Inject properties from reason message and object.
   * @private
   * @param {Number|String} code
   * @param {String} reason
   * @param {String?} msg
   * @param {Hash?} hash
   */

  fromReason(code, reason, msg, hash) {
    if (typeof code === 'string')
      code = RejectPacket.codes[code.toUpperCase()];

    if (!code)
      code = RejectPacket.codes.INVALID;

    if (code >= RejectPacket.codes.INTERNAL)
      code = RejectPacket.codes.INVALID;

    this.message = '';
    this.code = code;
    this.reason = reason;

    if (msg) {
      assert(hash);
      this.message = msg;
      this.hash = hash;
    }

    return this;
  }

  /**
   * Instantiate reject packet from reason message.
   * @param {Number} code
   * @param {String} reason
   * @param {String?} msg
   * @param {Hash?} hash
   * @returns {RejectPacket}
   */

  static fromReason(code, reason, msg, hash) {
    return new this().fromReason(code, reason, msg, hash);
  }

  /**
   * Instantiate reject packet from verify error.
   * @param {VerifyError} err
   * @param {(TX|Block)?} obj
   * @returns {RejectPacket}
   */

  static fromError(err, obj) {
    return this.fromReason(err.code, err.reason, obj);
  }

  /**
   * Inspect reject packet.
   * @returns {String}
   */

  [inspectSymbol]() {
    const code = RejectPacket.codesByVal[this.code] || this.code;
    const hash = this.hash ? util.revHex(this.hash) : null;
    return '<Reject:'
      + ` msg=${this.message}`
      + ` code=${code}`
      + ` reason=${this.reason}`
      + ` hash=${hash}`
      + '>';
  }
}

/**
 * Reject codes. Note that `internal` and higher
 * are not meant for use on the p2p network.
 * @enum {Number}
 * @default
 */

RejectPacket.codes = {
  MALFORMED: 0x01,
  INVALID: 0x10,
  OBSOLETE: 0x11,
  DUPLICATE: 0x12,
  NONSTANDARD: 0x40,
  DUST: 0x41,
  INSUFFICIENTFEE: 0x42,
  CHECKPOINT: 0x43,
  // Internal codes (NOT FOR USE ON NETWORK)
  INTERNAL: 0x100,
  HIGHFEE: 0x101,
  ALREADYKNOWN: 0x102,
  CONFLICT: 0x103
};

/**
 * Reject codes by value.
 * @const {Object}
 */

RejectPacket.codesByVal = {
  0x01: 'MALFORMED',
  0x10: 'INVALID',
  0x11: 'OBSOLETE',
  0x12: 'DUPLICATE',
  0x40: 'NONSTANDARD',
  0x41: 'DUST',
  0x42: 'INSUFFICIENTFEE',
  0x43: 'CHECKPOINT',
  // Internal codes (NOT FOR USE ON NETWORK)
  0x100: 'INTERNAL',
  0x101: 'HIGHFEE',
  0x102: 'ALREADYKNOWN',
  0x103: 'CONFLICT'
};

/**
 * Mempool Packet
 * @extends Packet
 */

class MempoolPacket extends Packet {
  /**
   * Create a `mempool` packet.
   * @constructor
   */

  constructor() {
    super();
    this.cmd = 'mempool';
    this.type = exports.types.MEMPOOL;
  }

  /**
   * Instantiate mempool packet from buffer reader.
   * @param {BufferReader} br
   * @returns {VerackPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate mempool packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {VerackPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * FilterLoad Packet
 * @extends Packet
 */

class FilterLoadPacket extends Packet {
  /**
   * Create a `filterload` packet.
   * @constructor
   * @param {BloomFilter|null} filter
   */

  constructor(filter) {
    super();

    this.cmd = 'filterload';
    this.type = exports.types.FILTERLOAD;

    this.filter = filter || new BloomFilter();
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.filter.getSize();
  }

  /**
   * Serialize filterload packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    return this.filter.toWriter(bw);
  }

  /**
   * Serialize filterload packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.filter.toRaw();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.filter.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.filter.fromRaw(data);
    return this;
  }

  /**
   * Instantiate filterload packet from buffer reader.
   * @param {BufferReader} br
   * @returns {FilterLoadPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate filterload packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {FilterLoadPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }

  /**
   * Ensure the filter is within the size limits.
   * @returns {Boolean}
   */

  isWithinConstraints() {
    return this.filter.isWithinConstraints();
  }
}

/**
 * FilterAdd Packet
 * @extends Packet
 * @property {Buffer} data
 */

class FilterAddPacket extends Packet {
  /**
   * Create a `filteradd` packet.
   * @constructor
   * @param {Buffer?} data
   */

  constructor(data) {
    super();

    this.cmd = 'filteradd';
    this.type = exports.types.FILTERADD;

    this.data = data || DUMMY;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return encoding.sizeVarBytes(this.data);
  }

  /**
   * Serialize filteradd packet to writer.
   * @returns {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeVarBytes(this.data);
    return bw;
  }

  /**
   * Serialize filteradd packet.
   * @returns {Buffer}
   */

  toRaw() {
    const size = this.getSize();
    return this.toWriter(bio.write(size)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.data = br.readVarBytes();
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate filteradd packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {FilterAddPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * FilterClear Packet
 * @extends Packet
 */

class FilterClearPacket extends Packet {
  /**
   * Create a `filterclear` packet.
   * @constructor
   */

  constructor() {
    super();
    this.cmd = 'filterclear';
    this.type = exports.types.FILTERCLEAR;
  }

  /**
   * Instantiate filterclear packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {FilterClearPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * MerkleBlock Packet
 * @extends Packet
 * @property {MerkleBlock} block
 */

class MerkleBlockPacket extends Packet {
  /**
   * Create a `merkleblock` packet.
   * @constructor
   * @param {MerkleBlock?} block
   */

  constructor(block) {
    super();

    this.cmd = 'merkleblock';
    this.type = exports.types.MERKLEBLOCK;

    this.block = block || new MerkleBlock();
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.block.getSize();
  }

  /**
   * Serialize merkleblock packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    return this.block.toWriter(bw);
  }

  /**
   * Serialize merkleblock packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.block.toRaw();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.block.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.block.fromRaw(data);
    return this;
  }

  /**
   * Instantiate merkleblock packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {MerkleBlockPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * FeeFilter Packet
 * @extends Packet
 * @property {Rate} rate
 */

class FeeFilterPacket extends Packet {
  /**
   * Create a `feefilter` packet.
   * @constructor
   * @param {Rate?} rate
   */

  constructor(rate) {
    super();

    this.cmd = 'feefilter';
    this.type = exports.types.FEEFILTER;

    this.rate = rate || 0;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return 8;
  }

  /**
   * Serialize feefilter packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeI64(this.rate);
    return bw;
  }

  /**
   * Serialize feefilter packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.toWriter(bio.write(8)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.rate = br.readI64();
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate feefilter packet from buffer reader.
   * @param {BufferReader} br
   * @returns {FeeFilterPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate feefilter packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {FeeFilterPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * SendCmpct Packet
 * @extends Packet
 * @property {Number} mode
 * @property {Number} version
 */

class SendCmpctPacket extends Packet {
  /**
   * Create a `sendcmpct` packet.
   * @constructor
   * @param {Number|null} mode
   * @param {Number|null} version
   */

  constructor(mode, version) {
    super();

    this.cmd = 'sendcmpct';
    this.type = exports.types.SENDCMPCT;

    this.mode = mode || 0;
    this.version = version || 1;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return 9;
  }

  /**
   * Serialize sendcmpct packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeU8(this.mode);
    bw.writeU64(this.version);
    return bw;
  }

  /**
   * Serialize sendcmpct packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.toWriter(bio.write(9)).render();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.mode = br.readU8();
    this.version = br.readU64();
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    return this.fromReader(bio.read(data));
  }

  /**
   * Instantiate sendcmpct packet from buffer reader.
   * @param {BufferReader} br
   * @returns {SendCmpctPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate sendcmpct packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {SendCmpctPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * CmpctBlock Packet
 * @extends Packet
 * @property {Block} block
 * @property {Boolean} witness
 */

class CmpctBlockPacket extends Packet {
  /**
   * Create a `cmpctblock` packet.
   * @constructor
   * @param {Block|null} block
   * @param {Boolean|null} witness
   */

  constructor(block, witness) {
    super();

    this.cmd = 'cmpctblock';
    this.type = exports.types.CMPCTBLOCK;

    this.block = block || new bip152.CompactBlock();
    this.witness = witness || false;
  }

  /**
   * Serialize cmpctblock packet.
   * @returns {Buffer}
   */

  getSize() {
    if (this.witness)
      return this.block.getSize(true);
    return this.block.getSize(false);
  }

  /**
   * Serialize cmpctblock packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    if (this.witness)
      return this.block.toWriter(bw);
    return this.block.toNormalWriter(bw);
  }

  /**
   * Serialize cmpctblock packet.
   * @returns {Buffer}
   */

  toRaw() {
    if (this.witness)
      return this.block.toRaw();
    return this.block.toNormal();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.block.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.block.fromRaw(data);
    return this;
  }

  /**
   * Instantiate cmpctblock packet from buffer reader.
   * @param {BufferReader} br
   * @returns {CmpctBlockPacket}
   */

  static fromReader(br) {
    return new this().fromRaw(br);
  }

  /**
   * Instantiate cmpctblock packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {CmpctBlockPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * GetBlockTxn Packet
 * @extends Packet
 * @property {TXRequest} request
 */

class GetBlockTxnPacket extends Packet {
  /**
   * Create a `getblocktxn` packet.
   * @constructor
   * @param {TXRequest?} request
   */

  constructor(request) {
    super();

    this.cmd = 'getblocktxn';
    this.type = exports.types.GETBLOCKTXN;

    this.request = request || new bip152.TXRequest();
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.request.getSize();
  }

  /**
   * Serialize getblocktxn packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    return this.request.toWriter(bw);
  }

  /**
   * Serialize getblocktxn packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.request.toRaw();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.request.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.request.fromRaw(data);
    return this;
  }

  /**
   * Instantiate getblocktxn packet from buffer reader.
   * @param {BufferReader} br
   * @returns {GetBlockTxnPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate getblocktxn packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {GetBlockTxnPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * BlockTxn Packet
 * @extends Packet
 * @property {TXResponse} response
 * @property {Boolean} witness
 */

class BlockTxnPacket extends Packet {
  /**
   * Create a `blocktxn` packet.
   * @constructor
   * @param {TXResponse?} response
   * @param {Boolean?} witness
   */

  constructor(response, witness) {
    super();

    this.cmd = 'blocktxn';
    this.type = exports.types.BLOCKTXN;

    this.response = response || new bip152.TXResponse();
    this.witness = witness || false;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    if (this.witness)
      return this.response.getSize(true);
    return this.response.getSize(false);
  }

  /**
   * Serialize blocktxn packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    if (this.witness)
      return this.response.toWriter(bw);
    return this.response.toNormalWriter(bw);
  }

  /**
   * Serialize blocktxn packet.
   * @returns {Buffer}
   */

  toRaw() {
    if (this.witness)
      return this.response.toRaw();
    return this.response.toNormal();
  }

  /**
   * Inject properties from buffer reader.
   * @private
   * @param {BufferReader} br
   */

  fromReader(br) {
    this.response.fromReader(br);
    return this;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(data) {
    this.response.fromRaw(data);
    return this;
  }

  /**
   * Instantiate blocktxn packet from buffer reader.
   * @param {BufferReader} br
   * @returns {BlockTxnPacket}
   */

  static fromReader(br) {
    return new this().fromReader(br);
  }

  /**
   * Instantiate blocktxn packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {BlockTxnPacket}
   */

  static fromRaw(data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(data);
  }
}

/**
 * Unknown Packet
 * @extends Packet
 * @property {String} cmd
 * @property {Buffer} data
 */

class UnknownPacket extends Packet {
  /**
   * Create an unknown packet.
   * @constructor
   * @param {String|null} cmd
   * @param {Buffer|null} data
   */

  constructor(cmd, data) {
    super();

    this.cmd = cmd;
    this.type = exports.types.UNKNOWN;
    this.data = data;
  }

  /**
   * Get serialization size.
   * @returns {Number}
   */

  getSize() {
    return this.data.length;
  }

  /**
   * Serialize unknown packet to writer.
   * @param {BufferWriter} bw
   */

  toWriter(bw) {
    bw.writeBytes(this.data);
    return bw;
  }

  /**
   * Serialize unknown packet.
   * @returns {Buffer}
   */

  toRaw() {
    return this.data;
  }

  /**
   * Inject properties from serialized data.
   * @private
   * @param {Buffer} data
   */

  fromRaw(cmd, data) {
    assert(Buffer.isBuffer(data));
    this.cmd = cmd;
    this.data = data;
    return this;
  }

  /**
   * Instantiate unknown packet from serialized data.
   * @param {Buffer} data
   * @param {String?} enc
   * @returns {UnknownPacket}
   */

  static fromRaw(cmd, data, enc) {
    if (typeof data === 'string')
      data = Buffer.from(data, enc);
    return new this().fromRaw(cmd, data);
  }
}

/**
 * Parse a payload.
 * @param {String} cmd
 * @param {Buffer} data
 * @returns {Packet}
 */

exports.fromRaw = function fromRaw(cmd, data) {
  switch (cmd) {
    case 'version':
      return VersionPacket.fromRaw(data);
    case 'verack':
      return VerackPacket.fromRaw(data);
    case 'ping':
      return PingPacket.fromRaw(data);
    case 'pong':
      return PongPacket.fromRaw(data);
    case 'getaddr':
      return GetAddrPacket.fromRaw(data);
    case 'addr':
      return AddrPacket.fromRaw(data);
    case 'inv':
      return InvPacket.fromRaw(data);
    case 'getdata':
      return GetDataPacket.fromRaw(data);
    case 'notfound':
      return NotFoundPacket.fromRaw(data);
    case 'getblocks':
      return GetBlocksPacket.fromRaw(data);
    case 'getheaders':
      return GetHeadersPacket.fromRaw(data);
    case 'headers':
      return HeadersPacket.fromRaw(data);
    case 'sendheaders':
      return SendHeadersPacket.fromRaw(data);
    case 'block':
      return BlockPacket.fromRaw(data);
    case 'tx':
      return TXPacket.fromRaw(data);
    case 'reject':
      return RejectPacket.fromRaw(data);
    case 'mempool':
      return MempoolPacket.fromRaw(data);
    case 'filterload':
      return FilterLoadPacket.fromRaw(data);
    case 'filteradd':
      return FilterAddPacket.fromRaw(data);
    case 'filterclear':
      return FilterClearPacket.fromRaw(data);
    case 'merkleblock':
      return MerkleBlockPacket.fromRaw(data);
    case 'feefilter':
      return FeeFilterPacket.fromRaw(data);
    case 'sendcmpct':
      return SendCmpctPacket.fromRaw(data);
    case 'cmpctblock':
      return CmpctBlockPacket.fromRaw(data);
    case 'getblocktxn':
      return GetBlockTxnPacket.fromRaw(data);
    case 'blocktxn':
      return BlockTxnPacket.fromRaw(data);
    default:
      return UnknownPacket.fromRaw(cmd, data);
  }
};

/*
 * Expose
 */

exports.Packet = Packet;
exports.VersionPacket = VersionPacket;
exports.VerackPacket = VerackPacket;
exports.PingPacket = PingPacket;
exports.PongPacket = PongPacket;
exports.GetAddrPacket = GetAddrPacket;
exports.AddrPacket = AddrPacket;
exports.InvPacket = InvPacket;
exports.GetDataPacket = GetDataPacket;
exports.NotFoundPacket = NotFoundPacket;
exports.GetBlocksPacket = GetBlocksPacket;
exports.GetHeadersPacket = GetHeadersPacket;
exports.HeadersPacket = HeadersPacket;
exports.SendHeadersPacket = SendHeadersPacket;
exports.BlockPacket = BlockPacket;
exports.TXPacket = TXPacket;
exports.RejectPacket = RejectPacket;
exports.MempoolPacket = MempoolPacket;
exports.FilterLoadPacket = FilterLoadPacket;
exports.FilterAddPacket = FilterAddPacket;
exports.FilterClearPacket = FilterClearPacket;
exports.MerkleBlockPacket = MerkleBlockPacket;
exports.FeeFilterPacket = FeeFilterPacket;
exports.SendCmpctPacket = SendCmpctPacket;
exports.CmpctBlockPacket = CmpctBlockPacket;
exports.GetBlockTxnPacket = GetBlockTxnPacket;
exports.BlockTxnPacket = BlockTxnPacket;
exports.UnknownPacket = UnknownPacket;