text: /*!
   * util/databuffer.js - Forge-compatible Buffer based on Node.js Buffers
   *
   * Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
   */
  "use strict";
  
  var forge = require("../deps/forge.js"),
      base64url = require("./base64url.js");
  
  /**
   *
   */
  function DataBuffer(b, options) {
    options = options || {};
  
    // treat (views of) (Array)Buffers special
    // NOTE: default implementation creates copies, but efficiently
    //       wherever possible
    if (Buffer.isBuffer(b)) {
      this.data = b;
    } else if (forge.util.isArrayBuffer(b)) {
      b = new Uint8Array(b);
      this.data = Buffer.from(b);
    } else if (forge.util.isArrayBufferView(b)) {
      b = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
      this.data = Buffer.from(b);
    }
  
    if (this.data) {
      this.write = this.data.length;
      b = undefined;
    }
  
    // setup growth rate
    this.growSize = options.growSize || DataBuffer.DEFAULT_GROW_SIZE;
  
    // initialize pointers and data
    this.write = this.write || 0;
    this.read = this.read || 0;
    if (b) {
      this.putBytes(b);
    } else if (!this.data) {
      this.accommodate(0);
    }
  
    // massage read/write pointers
    options.readOffset = ("readOffset" in options) ?
                         options.readOffset :
                         this.read;
    this.write = ("writeOffset" in options) ?
                 options.writeOffset :
                 this.write;
    this.read = Math.min(options.readOffset, this.write);
  }
  DataBuffer.DEFAULT_GROW_SIZE = 16;
  
  DataBuffer.prototype.length = function() {
    return this.write - this.read;
  };
  DataBuffer.prototype.available = function() {
    return this.data.length - this.write;
  };
  DataBuffer.prototype.isEmpty = function() {
    return this.length() <= 0;
  };
  
  DataBuffer.prototype.accommodate = function(length) {
    if (!this.data) {
      // initializes a new buffer
      length = Math.max(this.write + length, this.growSize);
  
      this.data = Buffer.alloc(length);
    } else if (this.available() < length) {
      length = Math.max(length, this.growSize);
  
      // create a new empty buffer, and copy current one into it
      var src = this.data;
      var dst = Buffer.alloc(src.length + length);
      src.copy(dst, 0);
  
      // set data as the new buffer
      this.data = dst;
    }
    // ensure the rest is 0
    this.data.fill(0, this.write);
  
    return this;
  };
  DataBuffer.prototype.clear = function() {
    this.read = this.write = 0;
    this.data = Buffer.alloc(0);
    return this;
  };
  DataBuffer.prototype.truncate = function(count) {
    // chop off <count> bytes from the end
    this.write = this.read + Math.max(0, this.length() - count);
    // ensure the remainder is 0
    this.data.fill(0, this.write);
    return this;
  };
  DataBuffer.prototype.compact = function() {
    if (this.read > 0) {
      if (this.write === this.read) {
        this.read = this.write = 0;
      } else {
        this.data.copy(this.data, 0, this.read, this.write);
        this.write = this.write - this.read;
        this.read = 0;
      }
      // ensure remainder is 0
      this.data.fill(0, this.write);
    }
    return this;
  };
  DataBuffer.prototype.copy = function() {
    return new DataBuffer(this, {
      readOffset: this.read,
      writeOffset: this.write,
      growSize: this.growSize
    });
  };
  
  DataBuffer.prototype.equals = function(test) {
    if (!DataBuffer.isBuffer(test)) {
      return false;
    }
  
    if (test.length() !== this.length()) {
      return false;
    }
  
    var rval = true,
        delta = this.read - test.read;
    // constant time
    for (var idx = test.read; test.write > idx; idx++) {
      rval = rval && (this.data[idx + delta] === test.data[idx]);
    }
    return rval;
  };
  DataBuffer.prototype.at = function(idx) {
    return this.data[this.read + idx];
  };
  DataBuffer.prototype.setAt = function(idx, b) {
    this.data[this.read + idx] = b;
    return this;
  };
  DataBuffer.prototype.last = function() {
    return this.data[this.write - 1];
  };
  DataBuffer.prototype.bytes = function(count) {
    var rval;
    if (undefined === count) {
      count = this.length();
    } else if (count) {
      count = Math.min(count, this.length());
    }
  
    if (0 === count) {
      rval = "";
    } else {
      var begin = this.read,
          end = begin + count,
          data = this.data.slice(begin, end);
      rval = String.fromCharCode.apply(null, data);
    }
  
    return rval;
  };
  DataBuffer.prototype.buffer = function(count) {
    var rval;
    if (undefined === count) {
      count = this.length();
    } else if (count) {
      count = Math.min(count, this.length());
    }
  
    if (0 === count) {
      rval = new ArrayBuffer(0);
    } else {
      var begin = this.read,
          end = begin + count,
          data = this.data.slice(begin, end);
      rval = new Uint8Array(end - begin);
      rval.set(data);
    }
  
    return rval;
  };
  DataBuffer.prototype.native = function(count) {
    var rval;
    if ("undefined" === typeof count) {
      count = this.length();
    } else if (count) {
      count = Math.min(count, this.length());
    }
  
    if (0 === count) {
      rval = Buffer.alloc(0);
    } else {
      var begin = this.read,
          end = begin + count;
      rval = this.data.slice(begin, end);
    }
  
    return rval;
  };
  
  DataBuffer.prototype.toHex = function() {
    return this.toString("hex");
  };
  DataBuffer.prototype.toString = function(encoding) {
    // short circuit empty string
    if (0 === this.length()) {
      return "";
    }
  
    var view = this.data.slice(this.read, this.write);
    encoding = encoding || "utf8";
    // special cases, then built-in support
    switch (encoding) {
      case "raw":
        return view.toString("binary");
      case "base64url":
        return base64url.encode(view);
      case "utf16":
        return view.toString("ucs2");
      default:
        return view.toString(encoding);
    }
  };
  
  DataBuffer.prototype.fillWithByte = function(b, n) {
    if (!n) {
      n = this.available();
    }
    this.accommodate(n);
    this.data.fill(b, this.write, this.write + n);
    this.write += n;
  
    return this;
  };
  
  DataBuffer.prototype.getBuffer = function(count) {
    var rval = this.buffer(count);
    this.read += rval.byteLength;
  
    return rval;
  };
  DataBuffer.prototype.putBuffer = function(bytes) {
    return this.putBytes(bytes);
  };
  
  DataBuffer.prototype.getBytes = function(count) {
    var rval = this.bytes(count);
    this.read += rval.length;
    return rval;
  };
  DataBuffer.prototype.putBytes = function(bytes, encoding) {
    if ("string" === typeof bytes) {
      // fixup encoding
      encoding = encoding || "binary";
      switch (encoding) {
        case "utf16":
          // treat as UCS-2/UTF-16BE
          encoding = "ucs-2";
          break;
        case "raw":
          encoding = "binary";
          break;
        case "base64url":
          // NOTE: this returns a Buffer
          bytes = base64url.decode(bytes);
          break;
      }
  
      // replace bytes with decoded Buffer (if not already)
      if (!Buffer.isBuffer(bytes)) {
        bytes = Buffer.from(bytes, encoding);
      }
    }
  
    var src, dst;
    if (bytes instanceof DataBuffer) {
      // be slightly more efficient
      var orig = bytes;
      bytes = orig.data.slice(orig.read, orig.write);
      orig.read = orig.write;
    } else if (bytes instanceof forge.util.ByteStringBuffer) {
      bytes = bytes.getBytes();
    }
  
    // process array
    if (Buffer.isBuffer(bytes)) {
      src = bytes;
    } else if (Array.isArray(bytes)) {
      src = Buffer.from(bytes);
    } else if (forge.util.isArrayBuffer(bytes)) {
      src = new Uint8Array(bytes);
      src = Buffer.from(src);
    } else if (forge.util.isArrayBufferView(bytes)) {
      src = (bytes instanceof Uint8Array) ?
                bytes :
                new Uint8Array(bytes.buffer,
                               bytes.byteOffset,
                               bytes.byteLength);
      src = Buffer.from(src);
    } else {
      throw new TypeError("invalid source type");
    }
  
    this.accommodate(src.length);
    dst = this.data;
    src.copy(dst, this.write);
    this.write += src.length;
  
    return this;
  };
  
  DataBuffer.prototype.getNative = function(count) {
    var rval = this.native(count);
    this.read += rval.length;
    return rval;
  };
  DataBuffer.prototype.putNative = DataBuffer.prototype.putBuffer;
  
  DataBuffer.prototype.getByte = function() {
    var b = this.data[this.read];
    this.read = Math.min(this.read + 1, this.write);
    return b;
  };
  DataBuffer.prototype.putByte = function(b) {
    this.accommodate(1);
    this.data[this.write] = b & 0xff;
    this.write++;
  
    return this;
  };
  
  DataBuffer.prototype.getInt16 = function() {
    var n = (this.data[this.read] << 8) ^
            (this.data[this.read + 1]);
    this.read = Math.min(this.read + 2, this.write);
    return n;
  };
  DataBuffer.prototype.putInt16 = function(n) {
    this.accommodate(2);
    this.data[this.write] = (n >>> 8) & 0xff;
    this.data[this.write + 1] = n & 0xff;
    this.write += 2;
    return this;
  };
  
  DataBuffer.prototype.getInt24 = function() {
    var n = (this.data[this.read] << 16) ^
            (this.data[this.read + 1] << 8) ^
            this.data[this.read + 2];
    this.read = Math.min(this.read + 3, this.write);
    return n;
  };
  DataBuffer.prototype.putInt24 = function(n) {
    this.accommodate(3);
    this.data[this.write] = (n >>> 16) & 0xff;
    this.data[this.write + 1] = (n >>> 8) & 0xff;
    this.data[this.write + 2] = n & 0xff;
    this.write += 3;
    return this;
  };
  
  DataBuffer.prototype.getInt32 = function() {
    var n = (this.data[this.read] << 24) ^
            (this.data[this.read + 1] << 16) ^
            (this.data[this.read + 2] << 8) ^
            this.data[this.read + 3];
    this.read = Math.min(this.read + 4, this.write);
    return n;
  };
  DataBuffer.prototype.putInt32 = function(n) {
    this.accommodate(4);
    this.data[this.write] = (n >>> 24) & 0xff;
    this.data[this.write + 1] = (n >>> 16) & 0xff;
    this.data[this.write + 2] = (n >>> 8) & 0xff;
    this.data[this.write + 3] = n & 0xff;
    this.write += 4;
    return this;
  };
  
  DataBuffer.prototype.getInt16Le = function() {
    var n = (this.data[this.read + 1] << 8) ^
            this.data[this.read];
    this.read = Math.min(this.read + 2, this.write);
    return n;
  };
  DataBuffer.prototype.putInt16Le = function(n) {
    this.accommodate(2);
    this.data[this.write + 1] = (n >>> 8) & 0xff;
    this.data[this.write] = n & 0xff;
    this.write += 2;
    return this;
  };
  
  DataBuffer.prototype.getInt24Le = function() {
    var n = (this.data[this.read + 2] << 16) ^
            (this.data[this.read + 1] << 8) ^
            this.data[this.read];
    this.read = Math.min(this.read + 3, this.write);
    return n;
  };
  DataBuffer.prototype.putInt24Le = function(n) {
    this.accommodate(3);
    this.data[this.write + 2] = (n >>> 16) & 0xff;
    this.data[this.write + 1] = (n >>> 8) & 0xff;
    this.data[this.write] = n & 0xff;
    this.write += 3;
    return this;
  };
  DataBuffer.prototype.getInt32Le = function() {
    var n = (this.data[this.read + 3] << 24) ^
            (this.data[this.read + 2] << 16) ^
            (this.data[this.read + 1] << 8) ^
            this.data[this.read];
    this.read = Math.min(this.read + 4, this.write);
    return n;
  };
  DataBuffer.prototype.putInt32Le = function(n) {
    this.accommodate(4);
    this.data[this.write + 3] = (n >>> 24) & 0xff;
    this.data[this.write + 2] = (n >>> 16) & 0xff;
    this.data[this.write + 1] = (n >>> 8) & 0xff;
    this.data[this.write] = n & 0xff;
    this.write += 4;
    return this;
  };
  
  DataBuffer.prototype.getInt = function(bits) {
    var rval = 0;
    do {
      rval = (rval << 8) | this.getByte();
      bits -= 8;
    } while (bits > 0);
    return rval;
  };
  DataBuffer.prototype.putInt = function(n, bits) {
    this.accommodate(Math.ceil(bits / 8));
    do {
      bits -= 8;
      this.putByte((n >> bits) & 0xff);
    } while (bits > 0);
    return this;
  };
  
  DataBuffer.prototype.putSignedInt = function(n, bits) {
    if (n < 0) {
      n += 2 << (bits - 1);
    }
    return this.putInt(n, bits);
  };
  
  DataBuffer.prototype.putString = function(str) {
    return this.putBytes(str, "utf16");
  };
  
  DataBuffer.isBuffer = function(test) {
    return (test instanceof DataBuffer);
  };
  DataBuffer.asBuffer = function(orig) {
    return DataBuffer.isBuffer(orig) ?
           orig :
           orig ?
           new DataBuffer(orig) :
           new DataBuffer();
  };
  
  module.exports = DataBuffer;
  