641 lines
20 KiB
JavaScript
641 lines
20 KiB
JavaScript
/**
|
|
* byte-buffer v2.0.0
|
|
* Copyright (c) 2012-2020 Tim Kurvers <tim@moonsphere.net>
|
|
* @license MIT
|
|
*
|
|
* Wrapper for JavaScript's ArrayBuffer/DataView.
|
|
*
|
|
* https://github.com/timkurvers/byte-buffer
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = global || self, global.ByteBuffer = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
function _classCallCheck(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
}
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
/* eslint-disable import/prefer-default-export */
|
|
// Extracts buffer from given source and optionally clones it
|
|
var extractBuffer = function extractBuffer(source) {
|
|
var clone = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
|
|
// Source is a byte-aware object
|
|
if (source && typeof source.byteLength !== 'undefined') {
|
|
// Determine whether source is a view or a raw buffer
|
|
if (typeof source.buffer !== 'undefined') {
|
|
return clone ? source.buffer.slice(0) : source.buffer;
|
|
}
|
|
|
|
return clone ? source.slice(0) : source;
|
|
} // Source is a sequence of bytes
|
|
|
|
|
|
if (source && typeof source.length !== 'undefined') {
|
|
// Although Uint8Array's constructor succeeds when given strings,
|
|
// it does not correctly instantiate the buffer
|
|
if (source.constructor === String) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return new Uint8Array(source).buffer;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
} // No buffer found
|
|
|
|
|
|
return null;
|
|
};
|
|
|
|
var ByteBuffer = /*#__PURE__*/function () {
|
|
// Creates a new ByteBuffer
|
|
// - from given source (assumed to be number of bytes when numeric)
|
|
// - with given byte order (defaults to big-endian)
|
|
// - with given implicit growth strategy (defaults to false)
|
|
function ByteBuffer() {
|
|
var source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
var order = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.constructor.BIG_ENDIAN;
|
|
var implicitGrowth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
|
|
_classCallCheck(this, ByteBuffer);
|
|
|
|
// Holds buffer
|
|
this._buffer = null; // Holds raw buffer
|
|
|
|
this._raw = null; // Holds internal view for reading/writing
|
|
|
|
this._view = null; // Holds byte order
|
|
|
|
this._order = !!order; // Holds implicit growth strategy
|
|
|
|
this._implicitGrowth = !!implicitGrowth; // Holds read/write index
|
|
|
|
this._index = 0; // Attempt to extract a buffer from given source
|
|
|
|
var buffer = extractBuffer(source, true); // On failure, assume source is a primitive indicating the number of bytes
|
|
|
|
if (!buffer) {
|
|
buffer = new ArrayBuffer(source);
|
|
} // Assign new buffer
|
|
|
|
|
|
this.buffer = buffer;
|
|
} // Sanitizes read/write index
|
|
|
|
|
|
_createClass(ByteBuffer, [{
|
|
key: "_sanitizeIndex",
|
|
value: function _sanitizeIndex() {
|
|
if (this._index < 0) {
|
|
this._index = 0;
|
|
}
|
|
|
|
if (this._index > this.length) {
|
|
this._index = this.length;
|
|
}
|
|
} // Retrieves buffer
|
|
|
|
}, {
|
|
key: "front",
|
|
// Sets index to front of the buffer
|
|
value: function front() {
|
|
this._index = 0;
|
|
return this;
|
|
} // Sets index to end of the buffer
|
|
|
|
}, {
|
|
key: "end",
|
|
value: function end() {
|
|
this._index = this.length;
|
|
return this;
|
|
} // Seeks given number of bytes
|
|
// Note: Backwards seeking is supported
|
|
|
|
}, {
|
|
key: "seek",
|
|
value: function seek() {
|
|
var bytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
|
|
this.index += bytes;
|
|
return this;
|
|
} // Reads sequence of given number of bytes (defaults to number of bytes available)
|
|
|
|
}, {
|
|
key: "read",
|
|
value: function read() {
|
|
var bytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.available;
|
|
|
|
if (bytes > this.available) {
|
|
throw new Error("Cannot read ".concat(bytes, " byte(s), ").concat(this.available, " available"));
|
|
}
|
|
|
|
if (bytes <= 0) {
|
|
throw new RangeError("Invalid number of bytes ".concat(bytes));
|
|
}
|
|
|
|
var value = new ByteBuffer(this._buffer.slice(this._index, this._index + bytes), this.order);
|
|
this._index += bytes;
|
|
return value;
|
|
} // Writes sequence of bytes
|
|
|
|
}, {
|
|
key: "write",
|
|
value: function write(sequence) {
|
|
var view; // Ensure we're dealing with a Uint8Array view
|
|
|
|
if (!(sequence instanceof Uint8Array)) {
|
|
// Extract the buffer from the sequence
|
|
var buffer = extractBuffer(sequence);
|
|
|
|
if (!buffer) {
|
|
throw new TypeError("Cannot write ".concat(sequence, ", not a sequence"));
|
|
} // And create a new Uint8Array view for it
|
|
|
|
|
|
view = new Uint8Array(buffer);
|
|
} else {
|
|
view = sequence;
|
|
}
|
|
|
|
var available = this.available;
|
|
|
|
if (view.byteLength > available) {
|
|
if (this._implicitGrowth) {
|
|
this.append(view.byteLength - available);
|
|
} else {
|
|
throw new Error("Cannot write ".concat(sequence, " using ").concat(view.byteLength, " byte(s), ").concat(this.available, " available"));
|
|
}
|
|
}
|
|
|
|
this._raw.set(view, this._index);
|
|
|
|
this._index += view.byteLength;
|
|
return this;
|
|
} // Reads UTF-8 encoded string of given number of bytes (defaults to number of bytes available)
|
|
//
|
|
// Based on David Flanagan's BufferView (https://github.com/davidflanagan/BufferView/blob/master/BufferView.js//L195)
|
|
|
|
}, {
|
|
key: "readString",
|
|
value: function readString() {
|
|
var bytes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.available;
|
|
|
|
if (bytes > this.available) {
|
|
throw new Error("Cannot read ".concat(bytes, " byte(s), ").concat(this.available, " available"));
|
|
}
|
|
|
|
if (bytes <= 0) {
|
|
throw new RangeError("Invalid number of bytes ".concat(bytes));
|
|
} // Local reference
|
|
|
|
|
|
var raw = this._raw; // Holds decoded characters
|
|
|
|
var codepoints = []; // Index into codepoints
|
|
|
|
var c = 0; // Bytes
|
|
|
|
var b1 = null;
|
|
var b2 = null;
|
|
var b3 = null;
|
|
var b4 = null; // Target index
|
|
|
|
var target = this._index + bytes;
|
|
|
|
while (this._index < target) {
|
|
b1 = raw[this._index];
|
|
|
|
if (b1 < 128) {
|
|
// One byte sequence
|
|
codepoints[c++] = b1;
|
|
this._index++;
|
|
} else if (b1 < 194) {
|
|
throw new Error('Unexpected continuation byte');
|
|
} else if (b1 < 224) {
|
|
// Two byte sequence
|
|
b2 = raw[this._index + 1];
|
|
|
|
if (b2 < 128 || b2 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
codepoints[c++] = ((b1 & 0x1F) << 6) + (b2 & 0x3F);
|
|
this._index += 2;
|
|
} else if (b1 < 240) {
|
|
// Three byte sequence
|
|
b2 = raw[this._index + 1];
|
|
|
|
if (b2 < 128 || b2 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
b3 = raw[this._index + 2];
|
|
|
|
if (b3 < 128 || b3 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
codepoints[c++] = ((b1 & 0x0F) << 12) + ((b2 & 0x3F) << 6) + (b3 & 0x3F);
|
|
this._index += 3;
|
|
} else if (b1 < 245) {
|
|
// Four byte sequence
|
|
b2 = raw[this._index + 1];
|
|
|
|
if (b2 < 128 || b2 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
b3 = raw[this._index + 2];
|
|
|
|
if (b3 < 128 || b3 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
b4 = raw[this._index + 3];
|
|
|
|
if (b4 < 128 || b4 > 191) {
|
|
throw new Error('Bad continuation byte');
|
|
}
|
|
|
|
var cp = ((b1 & 0x07) << 18) + ((b2 & 0x3F) << 12) + ((b3 & 0x3F) << 6) + (b4 & 0x3F);
|
|
cp -= 0x10000; // Turn code point into two surrogate pairs
|
|
|
|
codepoints[c++] = 0xD800 + ((cp & 0x0FFC00) >>> 10);
|
|
codepoints[c++] = 0xDC00 + (cp & 0x0003FF);
|
|
this._index += 4;
|
|
} else {
|
|
throw new Error('Illegal byte');
|
|
}
|
|
} // Browsers may have hardcoded or implicit limits on the array length when applying a function
|
|
// See: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply//apply_and_built-in_functions
|
|
|
|
|
|
var limit = 1 << 16;
|
|
var length = codepoints.length;
|
|
|
|
if (length < limit) {
|
|
return String.fromCharCode.apply(String, codepoints);
|
|
}
|
|
|
|
var chars = [];
|
|
var i = 0;
|
|
|
|
while (i < length) {
|
|
chars.push(String.fromCharCode.apply(String, codepoints.slice(i, i + limit)));
|
|
i += limit;
|
|
}
|
|
|
|
return chars.join('');
|
|
} // Writes UTF-8 encoded string
|
|
// Note: Does not write string length or terminator
|
|
//
|
|
// Based on David Flanagan's BufferView (https://github.com/davidflanagan/BufferView/blob/master/BufferView.js//L264)
|
|
|
|
}, {
|
|
key: "writeString",
|
|
value: function writeString(string) {
|
|
// Encoded UTF-8 bytes
|
|
var bytes = []; // String length, offset and byte offset
|
|
|
|
var length = string.length;
|
|
var i = 0;
|
|
var b = 0;
|
|
|
|
while (i < length) {
|
|
var c = string.charCodeAt(i);
|
|
|
|
if (c <= 0x7F) {
|
|
// One byte sequence
|
|
bytes[b++] = c;
|
|
} else if (c <= 0x7FF) {
|
|
// Two byte sequence
|
|
bytes[b++] = 0xC0 | (c & 0x7C0) >>> 6;
|
|
bytes[b++] = 0x80 | c & 0x3F;
|
|
} else if (c <= 0xD7FF || c >= 0xE000 && c <= 0xFFFF) {
|
|
// Three byte sequence
|
|
// Source character is not a UTF-16 surrogate
|
|
bytes[b++] = 0xE0 | (c & 0xF000) >>> 12;
|
|
bytes[b++] = 0x80 | (c & 0x0FC0) >>> 6;
|
|
bytes[b++] = 0x80 | c & 0x3F;
|
|
} else {
|
|
// Four byte sequence
|
|
if (i === length - 1) {
|
|
throw new Error("Unpaired surrogate ".concat(string[i], " (index ").concat(i, ")"));
|
|
} // Retrieve surrogate
|
|
|
|
|
|
var d = string.charCodeAt(++i);
|
|
|
|
if (c < 0xD800 || c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) {
|
|
throw new Error("Unpaired surrogate ".concat(string[i], " (index ").concat(i, ")"));
|
|
}
|
|
|
|
var cp = ((c & 0x03FF) << 10) + (d & 0x03FF) + 0x10000;
|
|
bytes[b++] = 0xF0 | (cp & 0x1C0000) >>> 18;
|
|
bytes[b++] = 0x80 | (cp & 0x03F000) >>> 12;
|
|
bytes[b++] = 0x80 | (cp & 0x000FC0) >>> 6;
|
|
bytes[b++] = 0x80 | cp & 0x3F;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
this.write(bytes);
|
|
return bytes.length;
|
|
} // Aliases for reading/writing UTF-8 encoded strings
|
|
// readUTFChars: this.::readString
|
|
// writeUTFChars: this.::writeString
|
|
// Reads UTF-8 encoded C-string (excluding the actual NULL-byte)
|
|
|
|
}, {
|
|
key: "readCString",
|
|
value: function readCString() {
|
|
var bytes = this._raw;
|
|
var length = bytes.length;
|
|
var i = this._index;
|
|
|
|
while (bytes[i] !== 0x00 && i < length) {
|
|
++i;
|
|
}
|
|
|
|
length = i - this._index;
|
|
|
|
if (length > 0) {
|
|
var string = this.readString(length);
|
|
this.readByte();
|
|
return string;
|
|
}
|
|
|
|
return null;
|
|
} // Writes UTF-8 encoded C-string (NULL-terminated)
|
|
|
|
}, {
|
|
key: "writeCString",
|
|
value: function writeCString(string) {
|
|
var bytes = this.writeString(string);
|
|
this.writeByte(0x00);
|
|
return ++bytes;
|
|
} // Prepends given number of bytes
|
|
|
|
}, {
|
|
key: "prepend",
|
|
value: function prepend(bytes) {
|
|
if (bytes <= 0) {
|
|
throw new RangeError("Invalid number of bytes ".concat(bytes));
|
|
}
|
|
|
|
var view = new Uint8Array(this.length + bytes);
|
|
view.set(this._raw, bytes);
|
|
this._index += bytes;
|
|
this.buffer = view.buffer;
|
|
return this;
|
|
} // Appends given number of bytes
|
|
|
|
}, {
|
|
key: "append",
|
|
value: function append(bytes) {
|
|
if (bytes <= 0) {
|
|
throw new RangeError("Invalid number of bytes ".concat(bytes));
|
|
}
|
|
|
|
var view = new Uint8Array(this.length + bytes);
|
|
view.set(this._raw, 0);
|
|
this.buffer = view.buffer;
|
|
return this;
|
|
} // Clips this buffer
|
|
|
|
}, {
|
|
key: "clip",
|
|
value: function clip() {
|
|
var begin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._index;
|
|
var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.length;
|
|
|
|
if (begin < 0) {
|
|
begin = this.length + begin;
|
|
}
|
|
|
|
var buffer = this._buffer.slice(begin, end);
|
|
|
|
this._index -= begin;
|
|
this.buffer = buffer;
|
|
return this;
|
|
} // Slices this buffer
|
|
|
|
}, {
|
|
key: "slice",
|
|
value: function slice() {
|
|
var begin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
var end = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.length;
|
|
var slice = new ByteBuffer(this._buffer.slice(begin, end), this.order);
|
|
return slice;
|
|
} // Clones this buffer
|
|
|
|
}, {
|
|
key: "clone",
|
|
value: function clone() {
|
|
var clone = new ByteBuffer(this._buffer.slice(0), this.order, this.implicitGrowth);
|
|
clone.index = this._index;
|
|
return clone;
|
|
} // Reverses this buffer
|
|
|
|
}, {
|
|
key: "reverse",
|
|
value: function reverse() {
|
|
Array.prototype.reverse.call(this._raw);
|
|
this._index = 0;
|
|
return this;
|
|
} // Array of bytes in this buffer
|
|
|
|
}, {
|
|
key: "toArray",
|
|
value: function toArray() {
|
|
return Array.prototype.slice.call(this._raw, 0);
|
|
} // Hex representation of this buffer with given spacer
|
|
|
|
}, {
|
|
key: "toHex",
|
|
value: function toHex() {
|
|
var spacer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ' ';
|
|
return Array.prototype.map.call(this._raw, function (byte) {
|
|
return "00".concat(byte.toString(16).toUpperCase()).slice(-2);
|
|
}).join(spacer);
|
|
} // ASCII representation of this buffer with given spacer and optional byte alignment
|
|
|
|
}, {
|
|
key: "toASCII",
|
|
value: function toASCII() {
|
|
var spacer = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ' ';
|
|
var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
var unknown = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "\uFFFD";
|
|
var prefix = align ? ' ' : '';
|
|
return Array.prototype.map.call(this._raw, function (byte) {
|
|
return byte < 0x20 || byte > 0x7E ? prefix + unknown : prefix + String.fromCharCode(byte);
|
|
}).join(spacer);
|
|
}
|
|
}, {
|
|
key: "buffer",
|
|
get: function get() {
|
|
return this._buffer;
|
|
} // Sets new buffer and sanitizes read/write index
|
|
,
|
|
set: function set(buffer) {
|
|
this._buffer = buffer;
|
|
this._raw = new Uint8Array(this._buffer);
|
|
this._view = new DataView(this._buffer);
|
|
|
|
this._sanitizeIndex();
|
|
} // Retrieves raw buffer
|
|
|
|
}, {
|
|
key: "raw",
|
|
get: function get() {
|
|
return this._raw;
|
|
} // Retrieves view
|
|
|
|
}, {
|
|
key: "view",
|
|
get: function get() {
|
|
return this._view;
|
|
} // Retrieves number of bytes
|
|
|
|
}, {
|
|
key: "length",
|
|
get: function get() {
|
|
return this._buffer.byteLength;
|
|
} // Retrieves number of bytes
|
|
// Note: This allows for ByteBuffer to be detected as a proper source by its own constructor
|
|
|
|
}, {
|
|
key: "byteLength",
|
|
get: function get() {
|
|
return this.length;
|
|
} // Retrieves byte order
|
|
|
|
}, {
|
|
key: "order",
|
|
get: function get() {
|
|
return this._order;
|
|
} // Sets byte order
|
|
,
|
|
set: function set(order) {
|
|
this._order = !!order;
|
|
} // Retrieves implicit growth strategy
|
|
|
|
}, {
|
|
key: "implicitGrowth",
|
|
get: function get() {
|
|
return this._implicitGrowth;
|
|
} // Sets implicit growth strategy
|
|
,
|
|
set: function set(implicitGrowth) {
|
|
this._implicitGrowth = !!implicitGrowth;
|
|
} // Retrieves read/write index
|
|
|
|
}, {
|
|
key: "index",
|
|
get: function get() {
|
|
return this._index;
|
|
} // Sets read/write index
|
|
,
|
|
set: function set(index) {
|
|
if (index < 0 || index > this.length) {
|
|
throw new RangeError("Invalid index ".concat(index, ", should be between 0 and ").concat(this.length));
|
|
}
|
|
|
|
this._index = index;
|
|
} // Retrieves number of available bytes
|
|
|
|
}, {
|
|
key: "available",
|
|
get: function get() {
|
|
return this.length - this._index;
|
|
}
|
|
}]);
|
|
|
|
return ByteBuffer;
|
|
}(); // Generic reader
|
|
|
|
|
|
var reader = function reader(method, bytes) {
|
|
return function () {
|
|
var order = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._order;
|
|
|
|
if (bytes > this.available) {
|
|
throw new Error("Cannot read ".concat(bytes, " byte(s), ").concat(this.available, " available"));
|
|
}
|
|
|
|
var value = this._view[method](this._index, order);
|
|
|
|
this._index += bytes;
|
|
return value;
|
|
};
|
|
}; // Generic writer
|
|
|
|
|
|
var writer = function writer(method, bytes) {
|
|
return function (value) {
|
|
var order = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this._order;
|
|
var available = this.available;
|
|
|
|
if (bytes > available) {
|
|
if (this._implicitGrowth) {
|
|
this.append(bytes - available);
|
|
} else {
|
|
throw new Error("Cannot write ".concat(value, " using ").concat(bytes, " byte(s), ").concat(available, " available"));
|
|
}
|
|
}
|
|
|
|
this._view[method](this._index, value, order);
|
|
|
|
this._index += bytes;
|
|
return this;
|
|
};
|
|
}; // Byte order constants
|
|
|
|
|
|
ByteBuffer.LITTLE_ENDIAN = true;
|
|
ByteBuffer.BIG_ENDIAN = false; // Readers for bytes, shorts, integers, floats and doubles
|
|
|
|
ByteBuffer.prototype.readByte = reader('getInt8', 1);
|
|
ByteBuffer.prototype.readUnsignedByte = reader('getUint8', 1);
|
|
ByteBuffer.prototype.readShort = reader('getInt16', 2);
|
|
ByteBuffer.prototype.readUnsignedShort = reader('getUint16', 2);
|
|
ByteBuffer.prototype.readInt = reader('getInt32', 4);
|
|
ByteBuffer.prototype.readUnsignedInt = reader('getUint32', 4);
|
|
ByteBuffer.prototype.readFloat = reader('getFloat32', 4);
|
|
ByteBuffer.prototype.readDouble = reader('getFloat64', 8); // Writers for bytes, shorts, integers, floats and doubles
|
|
|
|
ByteBuffer.prototype.writeByte = writer('setInt8', 1);
|
|
ByteBuffer.prototype.writeUnsignedByte = writer('setUint8', 1);
|
|
ByteBuffer.prototype.writeShort = writer('setInt16', 2);
|
|
ByteBuffer.prototype.writeUnsignedShort = writer('setUint16', 2);
|
|
ByteBuffer.prototype.writeInt = writer('setInt32', 4);
|
|
ByteBuffer.prototype.writeUnsignedInt = writer('setUint32', 4);
|
|
ByteBuffer.prototype.writeFloat = writer('setFloat32', 4);
|
|
ByteBuffer.prototype.writeDouble = writer('setFloat64', 8);
|
|
|
|
return ByteBuffer;
|
|
|
|
})));
|