Source: utils/hashlist.js

/*!
 * hashlist.js - memory optimal hash list
 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 */

'use strict';

const assert = require('bsert');

/*
 * Constants
 */

const DUMMY = Buffer.allocUnsafe(0);

/**
 * HashList
 */

class HashList {
  /**
   * @param {Number} size
   */

  constructor(size) {
    assert((size >>> 0) === size);
    assert((size & 1) === 0);
    this.size = size;
    this.data = DUMMY;
    this.pos = 0;
  }

  get length() {
    return (this.pos / this.size) >>> 0;
  }

  set length(len) {
    this.pos = (len >>> 0) * this.size;

    if (this.pos > this.data.length)
      this.pos = this.data.length;
  }

  [Symbol.iterator]() {
    return this.values();
  }

  keys() {
    return this.values();
  }

  *values() {
    for (let i = 0; i < this.pos; i += this.size)
      yield this.data.slice(i, i + this.size);
  }

  /**
   * @returns {HashList}
   */

  clone() {
    // @ts-ignore
    const list = new this.constructor();
    list.data = copy(this.data);
    list.pos = this.pos;
    return list;
  }

  push(hash) {
    assert(Buffer.isBuffer(hash) && hash.length === this.size);

    if (this.data.length === 0)
      this.data = Buffer.allocUnsafe(256 * this.size);

    if (this.pos === this.data.length)
      this.data = realloc(this.data, this.pos * 2);

    assert(this.pos + this.size <= this.data.length);

    this.pos += hash.copy(this.data, this.pos);

    return this;
  }

  pop() {
    if (this.pos === 0)
      return null;

    this.pos -= this.size;

    return this.data.slice(this.pos, this.pos + this.size);
  }

  clear() {
    this.pos = 0;
    return this;
  }

  encode() {
    return this.data.slice(0, this.pos);
  }

  decode(data) {
    assert(Buffer.isBuffer(data));
    assert((data.length % this.size) === 0);
    this.data = data;
    this.pos = data.length;
    return this;
  }

  static decode(data, size) {
    return new HashList(size).decode(data);
  }

  *valuesSafe() {
    for (let i = 0; i < this.pos; i += this.size)
      yield copy(this.data.slice(i, i + this.size));
  }

  popSafe() {
    const hash = this.pop();

    if (!hash)
      return null;

    return copy(hash);
  }

  encodeSafe() {
    return copy(this.encode());
  }

  decodeSafe(data) {
    return this.decode(copy(data));
  }

  static decodeSafe(data, size) {
    return new HashList(size).decodeSafe(data);
  }
}

/*
 * Helpers
 */

function realloc(data, size) {
  assert(Buffer.isBuffer(data));
  assert((size >>> 0) === size);
  assert(size >= data.length);
  const buf = Buffer.allocUnsafe(size);
  data.copy(buf, 0);
  return buf;
}

function copy(buf) {
  return realloc(buf, buf.length);
}

/*
 * Expose
 */

module.exports = HashList;