Source: packet/packetlist.js

/**
 * This class represents a list of openpgp packets.
 * Take care when iterating over it - the packets themselves
 * are stored as numerical indices.
 * @requires enums
 * @requires packet
 * @requires packet/packet
 * @module packet/packetlist
 */

var packetParser = require('./packet.js'),
  packets = require('./all_packets.js'),
  enums = require('../enums.js');

/**
 * @constructor
 */
module.exports = function packetlist() {
  /** The number of packets contained within the list.
   * @readonly
   * @type {Integer} */
  this.length = 0;

  /**
   * Reads a stream of binary data and interprents it as a list of packets.
   * @param {String} A binary string of bytes.
   */
  this.read = function (bytes) {
    var i = 0;

    while (i < bytes.length) {
      var parsed = packetParser.read(bytes, i, bytes.length - i);
      i = parsed.offset;

      var tag = enums.read(enums.packet, parsed.tag);
      var packet = new packets[tag]();

      this.push(packet);

      packet.read(parsed.packet);
    }
  }

  /**
   * Creates a binary representation of openpgp objects contained within the
   * class instance.
   * @returns {String} A binary string of bytes containing valid openpgp packets.
   */
  this.write = function () {
    var bytes = '';

    for (var i = 0; i < this.length; i++) {
      var packetbytes = this[i].write();
      bytes += packetParser.writeHeader(this[i].tag, packetbytes.length);
      bytes += packetbytes;
    }

    return bytes;
  }

  /**
   * Adds a packet to the list. This is the only supported method of doing so;
   * writing to packetlist[i] directly will result in an error.
   */
  this.push = function (packet) {
    if (!packet) return;

    packet.packets = packet.packets || new packetlist();

    this[this.length] = packet;
    this.length++;
  }

  /**
  * Creates a new packetList with all packets that pass the test implemented by the provided function.
  */
  this.filter = function (callback) {

    var filtered = new packetlist();

    for (var i = 0; i < this.length; i++) {
      if (callback(this[i], i, this)) {
        filtered.push(this[i]);
      }
    }

    return filtered;
  }

  /**
  * Creates a new packetList with all packets from the given types
  */
  this.filterByTag = function () {
    var args = Array.prototype.slice.call(arguments);
    var filtered = new packetlist();
    var that = this;

    for (var i = 0; i < this.length; i++) {
      if (args.some(function(packetType) {return that[i].tag == packetType})) {
        filtered.push(this[i]);
      }
    }

    return filtered;
  } 

  /**
  * Executes the provided callback once for each element
  */
  this.forEach = function (callback) {
    for (var i = 0; i < this.length; i++) {
      callback(this[i]);
    }
  }

  /**
   * Traverses packet tree and returns first matching packet
   * @param  {module:enums.packet} type The packet type
   * @return {module:packet/packet|null}      
   */
  this.findPacket = function (type) {
    var packetlist = this.filterByTag(type);
    if (packetlist.length) {
      return packetlist[0];
    } else {
      var found = null;
      for (var i = 0; i < this.length; i++) {
        if (this[i].packets.length) {
          found = this[i].packets.findPacket(type);
          if (found) return found;
        }
      }
    }
    return null;
  }

  /**
   * Returns array of found indices by tag
   */
  this.indexOfTag = function () {
    var args = Array.prototype.slice.call(arguments);
    var tagIndex = [];
    var that = this;
    for (var i = 0; i < this.length; i++) {
      if (args.some(function(packetType) {return that[i].tag == packetType})) {
        tagIndex.push(i);
      }
    }
    return tagIndex;
  }

  /**
   * Returns slice of packetlist
   */
  this.slice = function (begin, end) {
    if (!end) {
      end = this.length
    }
    var part = new packetlist();
    for (var i = begin; i < end; i++) {
      part.push(this[i]);
    }
    return part;
  }

  /**
   * Concatenates packetlist or array of packets
   */
  this.concat = function (packetlist) {
    if (packetlist) {
      for (var i = 0; i < packetlist.length; i++) {
        this.push(packetlist[i]);
      }
    }
  }

}