/* Copyright 2013 Daniel Wirtz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * @license ByteBuffer.js (c) 2013 Daniel Wirtz * Released under the Apache License, Version 2.0 * see: https://github.com/dcodeIO/ByteBuffer.js for details */ // (function(global) { "use strict"; // Note that this library carefully avoids using the array access operator // (i.e. buffer[x]) on ArrayBufferView subclasses (e.g. Uint8Array), and // uses DataView instead. This is required for IE 8 compatibility. /** * @param {Function=} Long * @returns {Function} * @inner */ function loadByteBuffer(Long) { // Support node's Buffer if available, see http://nodejs.org/api/buffer.html var Buffer = null; if (typeof require === 'function') { try { var nodeBuffer = require("buffer"); Buffer = nodeBuffer && typeof nodeBuffer['Buffer'] === 'function' && typeof nodeBuffer['Buffer']['isBuffer'] === 'function' ? nodeBuffer['Buffer'] : null; } catch (e) {} } /** * Constructs a new ByteBuffer. * @class A full-featured ByteBuffer implementation in JavaScript using typed arrays. * @exports ByteBuffer * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. * @param {boolean=} littleEndian `true` to use little endian multi byte values, defaults to `false` for big * endian. * @param {boolean=} sparse If set to `true`, a ByteBuffer with array=view=null will be created which have to be * set manually afterwards. Defaults to `false`. * @expose */ var ByteBuffer = function(capacity, littleEndian, sparse) { capacity = typeof capacity !== 'undefined' ? parseInt(capacity, 10) : ByteBuffer.DEFAULT_CAPACITY; if (capacity < 1) capacity = ByteBuffer.DEFAULT_CAPACITY; /** * Backing ArrayBuffer. * @type {?ArrayBuffer} * @expose */ this.array = sparse ? null : new ArrayBuffer(capacity); /** * DataView to mess with the ArrayBuffer. * @type {?DataView} * @expose */ this.view = sparse ? null : new DataView(this.array); /** * Current read/write offset. Length- and capacity-independent index. Contents are the bytes between offset * and length, which are both absolute indexes. There is no capacity property, use * {@link ByteBuffer#capacity} instead. * @type {number} * @expose */ this.offset = 0; /** * Marked offset set through {@link ByteBuffer#mark}. Defaults to `-1` (no marked offset). * @type {number} * @expose */ this.markedOffset = -1; /** * Length of the contained data. Offset- and capacity-independent index. Contents are the bytes between * offset and length, which are both absolute indexes. There is no capacity property, use * {@link ByteBuffer#capacity} instead. * @type {number} * @expose */ this.length = 0; /** * Whether to use little endian multi byte values, defaults to `false` for big endian. * @type {boolean} * @expose */ this.littleEndian = typeof littleEndian != 'undefined' ? !!littleEndian : false; }; /** * Version string. * @type {string} * @const * @expose */ ByteBuffer.VERSION = "2.3.1"; /** * Default buffer capacity of `16`. The ByteBuffer will be automatically resized by a factor of 2 if required. * @type {number} * @const * @expose */ ByteBuffer.DEFAULT_CAPACITY = 16; /** * Little endian constant for usage in constructors instead of a boolean value. Evaluates to `true`. * @type {boolean} * @const * @expose */ ByteBuffer.LITTLE_ENDIAN = true; /** * Big endian constant for usage in constructors instead of a boolean value. Evaluates to `false`. * @type {boolean} * @const * @expose */ ByteBuffer.BIG_ENDIAN = false; /** * Long class for int64 support. May be `null` if the Long class has not been loaded and int64 support is * not available. * @type {?Long} * @const * @expose */ ByteBuffer.Long = Long || null; /** * Tests if the specified type is a ByteBuffer or ByteBuffer-like. * @param {*} bb ByteBuffer to test * @returns {boolean} true if it is a ByteBuffer or ByteBuffer-like, otherwise false * @expose */ ByteBuffer.isByteBuffer = function(bb) { return bb && ( (bb instanceof ByteBuffer) || ( typeof bb === 'object' && (bb.array === null || bb.array instanceof ArrayBuffer) && (bb.view === null || bb.view instanceof DataView) && typeof bb.offset === 'number' && typeof bb.markedOffset === 'number' && typeof bb.length === 'number' && typeof bb.littleEndian === 'boolean' ) ); }; /** * Allocates a new ByteBuffer. * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. * @param {boolean=} littleEndian `true` to use little endian multi byte values, defaults to `false` for big * endian. * @returns {!ByteBuffer} * @expose */ ByteBuffer.allocate = function(capacity, littleEndian) { return new ByteBuffer(capacity, littleEndian); }; /** * Converts a node.js <= 0.8 Buffer to an ArrayBuffer. * @param {!Buffer} b Buffer to convert * @returns {?ArrayBuffer} Converted buffer * @inner */ function b2ab(b) { var ab = new ArrayBuffer(b.length), view = new Uint8Array(ab); for (var i=0, k=b.length; i < k; ++i) view[i] = b[i]; return ab; } /** * Wraps an ArrayBuffer, any object containing an ArrayBuffer, a node buffer or a string. Sets the created * ByteBuffer's offset to 0 and its length to the wrapped object's byte length. * @param {!ArrayBuffer|!Buffer|!{array: !ArrayBuffer}|!{buffer: !ArrayBuffer}|string} buffer Anything that can * be wrapped * @param {(string|boolean)=} enc String encoding if a string is provided (hex, utf8, binary, defaults to base64) * @param {boolean=} littleEndian `true` to use little endian multi byte values, defaults to `false` for big * endian. * @returns {!ByteBuffer} * @throws {Error} If the specified object cannot be wrapped * @expose */ ByteBuffer.wrap = function(buffer, enc, littleEndian) { if (typeof enc === 'boolean') { littleEndian = enc; enc = "utf8"; } // Wrap a string if (typeof buffer === 'string') { switch (enc) { case "base64": return ByteBuffer.decode64(buffer, littleEndian); case "hex": return ByteBuffer.decodeHex(buffer, littleEndian); case "binary": return ByteBuffer.decodeBinary(buffer, littleEndian); default: return new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, littleEndian).writeUTF8String(buffer).flip(); } } var b; // Wrap Buffer if (Buffer && Buffer.isBuffer(buffer)) { b = new Uint8Array(buffer).buffer; // noop on node <= 0.8 buffer = (b === buffer) ? b2ab(buffer) : b; } // Refuse to wrap anything that's null or not an object if (buffer === null || typeof buffer !== 'object') { throw(new Error("Cannot wrap null or non-object")); } // Wrap ByteBuffer by cloning (preserve offsets) if (ByteBuffer.isByteBuffer(buffer)) { return ByteBuffer.prototype.clone.call(buffer); // Also makes ByteBuffer-like a ByteBuffer } // Wrap any object that is or contains an ArrayBuffer if (!!buffer["array"]) { buffer = buffer["array"]; } else if (!!buffer["buffer"]) { buffer = buffer["buffer"]; } if (!(buffer instanceof ArrayBuffer)) { throw(new Error("Cannot wrap buffer of type "+typeof(buffer)+", "+buffer.constructor.name)); } b = new ByteBuffer(0, littleEndian, true); b.array = buffer; b.view = b.array.byteLength > 0 ? new DataView(b.array) : null; b.offset = 0; b.length = buffer.byteLength; return b; }; /** * Switches little endian byte order. * @param {boolean=} littleEndian Defaults to `true`, otherwise uses big endian * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.LE = function(littleEndian) { this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : true; return this; }; /** * Switches big endian byte order. * @param {boolean=} bigEndian Defaults to `true`, otherwise uses little endian * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.BE = function(bigEndian) { this.littleEndian = typeof bigEndian !== 'undefined' ? !bigEndian : false; return this; }; /** * Resizes the ByteBuffer to the given capacity. Will do nothing if already that large or larger. * @param {number} capacity New capacity * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.resize = function(capacity) { if (capacity < 1) return false; if (this.array === null) { // Silently recreate this.array = new ArrayBuffer(capacity); this.view = new DataView(this.array); } if (this.array.byteLength < capacity) { var src = this.array; var srcView = new Uint8Array(src); var dst = new ArrayBuffer(capacity); var dstView = new Uint8Array(dst); dstView.set(srcView); this.array = dst; this.view = new DataView(dst); } return this; }; /** * Slices the ByteBuffer. This is independent of the ByteBuffer's actual offsets. Does not compact the underlying * ArrayBuffer (use {@link ByteBuffer#compact} or {@link ByteBuffer.wrap} instead). * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}. * @param {number=} end End offset, defaults to {@link ByteBuffer#length}. * @returns {!ByteBuffer} Clone of this ByteBuffer with slicing applied, backed by the same ArrayBuffer * @throws {Error} If the buffer cannot be sliced * @expose */ ByteBuffer.prototype.slice = function(begin, end) { if (this.array == null) { throw(new Error(this+" cannot be sliced: Already destroyed")); } if (typeof begin === 'undefined') begin = this.offset; if (typeof end === 'undefined') end = this.length; if (end <= begin) { var t = end; end = begin; begin = t; } if (begin < 0 || begin > this.array.byteLength || end < 1 || end > this.array.byteLength) { throw(new Error(this+" cannot be sliced: Index out of bounds (0-"+this.array.byteLength+" -> "+begin+"-"+end+")")); } var b = this.clone(); b.offset = begin; b.length = end; return b; }; /** * Makes sure that the specified capacity is available. If the current capacity is exceeded, it will be doubled. * If double the previous capacity is less than the required capacity, the required capacity will be used. * @param {number} capacity Required capacity * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.ensureCapacity = function(capacity) { if (this.array === null) return this.resize(capacity); if (this.array.byteLength < capacity) return this.resize(this.array.byteLength*2 >= capacity ? this.array.byteLength*2 : capacity); return this; }; /** * Makes the buffer ready for a new sequence of write or relative read operations. Sets `length=offset` and * `offset=0`. Always make sure to flip a buffer when all relative writing operations are complete. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.flip = function() { this.length = this.array == null ? 0 : this.offset; this.offset = 0; return this; }; /** * Marks an offset to be used with {@link ByteBuffer#reset}. * @param {number=} offset Offset to mark. Defaults to {@link ByteBuffer#offset}. * @returns {!ByteBuffer} this * @throws {Error} If the mark cannot be set * @see ByteBuffer#reset * @expose */ ByteBuffer.prototype.mark = function(offset) { if (this.array == null) { throw(new Error(this+" cannot be marked: Already destroyed")); } offset = typeof offset !== 'undefined' ? parseInt(offset, 10) : this.offset; if (offset < 0 || offset > this.array.byteLength) { throw(new Error(this+" cannot be marked: Offset to mark is less than 0 or bigger than the capacity ("+this.array.byteLength+"): "+offset)); } this.markedOffset = offset; return this; }; /** * Resets the ByteBuffer. If an offset has been marked through {@link ByteBuffer#mark} before, the offset will * be set to the marked offset and the marked offset will be discarded. Length will not be altered. If there is * no marked offset, sets `offset=0` and `length=0`. * @returns {!ByteBuffer} this * @see ByteBuffer#mark * @expose */ ByteBuffer.prototype.reset = function() { if (this.array === null) { throw(new Error(this+" cannot be reset: Already destroyed")); } if (this.markedOffset >= 0) { this.offset = this.markedOffset; this.markedOffset = -1; } else { this.offset = 0; this.length = 0; } return this; }; /** * Clones this ByteBuffer. The returned cloned ByteBuffer shares the same backing array but will have its own * offsets. * @returns {!ByteBuffer} Clone * @expose */ ByteBuffer.prototype.clone = function() { var b = new ByteBuffer(-1, this.littleEndian, /* no init, undocumented */ true); b.array = this.array; b.view = this.view; b.offset = this.offset; b.markedOffset = this.markedOffset; b.length = this.length; return b; }; /** * Copies this ByteBuffer. The copy has its own backing array and uses the same offsets as this one. * @returns {!ByteBuffer} Copy * @expose */ ByteBuffer.prototype.copy = function() { if (this.array == null) { return this.clone(); } var b = new ByteBuffer(this.array.byteLength, this.littleEndian); var src = new Uint8Array(this.array); var dst = new Uint8Array(b.array); dst.set(src); b.offset = this.offset; b.markedOffset = this.markedOffset; b.length = this.length; return b; }; /** * Gets the number of remaining readable bytes. Contents are the bytes between offset and length, so this * returns `length-offset`. * @returns {number} Remaining readable bytes. May be negative if `offset>length`. * @expose */ ByteBuffer.prototype.remaining = function() { if (this.array === null) return 0; return this.length - this.offset; }; /** * Gets the capacity of the backing buffer. This is independent from {@link ByteBuffer#length} and returns the * size of the entire backing array. * @returns {number} Capacity of the backing array or 0 if destroyed * @expose */ ByteBuffer.prototype.capacity = function() { return this.array != null ? this.array.byteLength : 0; }; /** * Compacts the ByteBuffer to be backed by an ArrayBuffer of its actual length. Will set `offset=0` and * `length=capacity`. * @returns {!ByteBuffer} this * @throws {Error} If the buffer cannot be compacted * @expose */ ByteBuffer.prototype.compact = function() { if (this.array == null) { throw(new Error(this+" cannot be compacted: Already destroyed")); } if (this.offset > this.length) { this.flip(); } if (this.offset === this.length) { this.array = new ArrayBuffer(0); this.view = null; // A DataView on a zero-length AB would throw return this; } if (this.offset === 0 && this.length === this.array.byteLength) { return this; // Already compacted } var srcView = new Uint8Array(this.array); var dst = new ArrayBuffer(this.length-this.offset); var dstView = new Uint8Array(dst); dstView.set(srcView.subarray(this.offset, this.length)); this.array = dst; if (this.markedOffset >= this.offset) { this.markedOffset -= this.offset; } else { this.markedOffset = -1; } this.offset = 0; this.length = this.array.byteLength; return this; }; /** * Manually destroys the ByteBuffer, releasing references to the backing array. Manually destroying a ByteBuffer * is usually not required but may be useful in limited memory environments. Most successive operations will * rise an error until {@link ByteBuffer#resize} or {@link ByteBuffer#ensureCapacity} is called to reinitialize * the backing array. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.destroy = function() { if (this.array !== null) { this.array = null; this.view = null; this.offset = 0; this.markedOffset = -1; this.length = 0; } return this; }; /** * Reverses the backing array and adapts offset and length to retain the same relative position on the reversed * data in inverse order. Example: "00<01 02>03 04".reverse() = "04 03<02 01>00". Also clears the marked * offset. * @returns {!ByteBuffer} this * @throws {Error} If the buffer is already destroyed * @expose */ ByteBuffer.prototype.reverse = function() { if (this.array === null) { throw(new Error(this+" cannot be reversed: Already destroyed")); } Array.prototype.reverse.call(new Uint8Array(this.array)); var o = this.offset; this.offset = this.array.byteLength - this.length; this.markedOffset = -1; this.length = this.array.byteLength - o; this.view = new DataView(this.array); return this; }; /** * Appends another ByteBuffer to this one. Appends only the portion between offset and length of the specified * ByteBuffer and overwrites any contents behind the specified offset up to the number of bytes contained in * the specified ByteBuffer. Offset and length of the specified ByteBuffer will remain the same. * @param {!*} src ByteBuffer or any object that can be wrapped to append * @param {number=} offset Offset to append at. Defaults to {@link ByteBuffer#offset}. * @returns {!ByteBuffer} this * @throws {Error} If the specified buffer is already destroyed * @expose */ ByteBuffer.prototype.append = function(src, offset) { if (!(src instanceof ByteBuffer)) { src = ByteBuffer.wrap(src); } if (src.array === null) { throw(new Error(src+" cannot be appended to "+this+": Already destroyed")); } var n = src.length - src.offset; if (n == 0) return this; // Nothing to append if (n < 0) { src = src.clone().flip(); n = src.length - src.offset; } offset = typeof offset !== 'undefined' ? offset : (this.offset+=n)-n; this.ensureCapacity(offset+n); // Reinitializes if required var srcView = new Uint8Array(src.array); var dstView = new Uint8Array(this.array); dstView.set(srcView.subarray(src.offset, src.length), offset); return this; }; /** * Prepends another ByteBuffer to this one. Prepends only the portion between offset and length of the specified * ByteBuffer and overwrites any contents before the specified offsets up to the number of bytes contained in * the specified ByteBuffer. Offset and length of the specified ByteBuffer will remain the same. * @param {!*} src ByteBuffer or any object that can be wrapped to prepend * @param {number=} offset Offset to prepend at. Defaults to {@link ByteBuffer#offset}. * @returns {!ByteBuffer} this * @throws {Error} If the specified buffer is already destroyed * @expose */ ByteBuffer.prototype.prepend = function(src, offset) { if (!(src instanceof ByteBuffer)) { src = ByteBuffer.wrap(src); } if (src.array === null) { throw(src+" cannot be prepended to "+this+": Already destroyed"); } var n = src.length - src.offset; if (n == 0) return this; // Nothing to prepend if (n < 0) { src = src.clone().flip(); n = src.length - src.offset; } var modify = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var diff = n-offset; if (diff > 0) { // Doesn't fit, so maybe resize and move the contents that are already contained this.ensureCapacity(this.length+diff); this.append(this, n); this.offset += diff; this.length += diff; this.append(src, 0); } else { this.append(src, offset-n); } if (modify) { this.offset -= n; } return this; }; /** * Writes an 8bit signed integer. * @param {number} value Value * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if * omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeInt8 = function(value, offset) { offset = typeof offset != 'undefined' ? offset : (this.offset+=1)-1; this.ensureCapacity(offset+1); this.view.setInt8(offset, value); return this; }; /** * Reads an 8bit signed integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readInt8 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=1)-1; if (offset >= this.array.byteLength) { throw(new Error("Cannot read int8 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getInt8(offset); }; /** * Writes a byte. This is an alias of {ByteBuffer#writeInt8}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeByte = ByteBuffer.prototype.writeInt8; /** * Reads a byte. This is an alias of {@link ByteBuffer#readInt8}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readByte = ByteBuffer.prototype.readInt8; /** * Writes an 8bit unsigned integer. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeUint8 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=1)-1; this.ensureCapacity(offset+1); this.view.setUint8(offset, value); return this; }; /** * Reads an 8bit unsigned integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readUint8 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=1)-1; if (offset+1 > this.array.byteLength) { throw(new Error("Cannot read uint8 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getUint8(offset); }; /** * Writes a 16bit signed integer. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeInt16 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=2)-2; this.ensureCapacity(offset+2); this.view.setInt16(offset, value, this.littleEndian); return this; }; /** * Reads a 16bit signed integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readInt16 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=2)-2; if (offset+2 > this.array.byteLength) { throw(new Error("Cannot read int16 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getInt16(offset, this.littleEndian); }; /** * Writes a short value. This is an alias of {@link ByteBuffer#writeInt16}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeShort = ByteBuffer.prototype.writeInt16; /** * Reads a short value. This is an alias of {@link ByteBuffer#readInt16}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readShort = ByteBuffer.prototype.readInt16; /** * Writes a 16bit unsigned integer. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeUint16 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=2)-2; this.ensureCapacity(offset+2); this.view.setUint16(offset, value, this.littleEndian); return this; }; /** * Reads a 16bit unsigned integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readUint16 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=2)-2; if (offset+2 > this.array.byteLength) { throw(new Error("Cannot read int16 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getUint16(offset, this.littleEndian); }; /** * Writes a 32bit signed integer. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeInt32 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=4)-4; this.ensureCapacity(offset+4); this.view.setInt32(offset, value, this.littleEndian); return this; }; /** * Reads a 32bit signed integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readInt32 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=4)-4; if (offset+4 > this.array.byteLength) { throw(new Error("Cannot read int32 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getInt32(offset, this.littleEndian); }; /** * Writes an integer. This is an alias of {@link ByteBuffer#writeInt32}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeInt = ByteBuffer.prototype.writeInt32; /** * Reads an integer. This is an alias of {@link ByteBuffer#readInt32}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readInt = ByteBuffer.prototype.readInt32; /** * Writes a 32bit unsigned integer. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeUint32 = function(value, offset) { offset = typeof offset != 'undefined' ? offset : (this.offset+=4)-4; this.ensureCapacity(offset+4); this.view.setUint32(offset, value, this.littleEndian); return this; }; /** * Reads a 32bit unsigned integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readUint32 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=4)-4; if (offset+4 > this.array.byteLength) { throw(new Error("Cannot read uint32 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getUint32(offset, this.littleEndian); }; /** * Writes a 32bit float. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeFloat32 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=4)-4; this.ensureCapacity(offset+4); this.view.setFloat32(offset, value, this.littleEndian); return this; }; /** * Reads a 32bit float. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readFloat32 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=4)-4; if (this.array === null || offset+4 > this.array.byteLength) { throw(new Error("Cannot read float32 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getFloat32(offset, this.littleEndian); }; /** * Writes a float. This is an alias of {@link ByteBuffer#writeFloat32}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeFloat = ByteBuffer.prototype.writeFloat32; /** * Reads a float. This is an alias of {@link ByteBuffer#readFloat32}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readFloat = ByteBuffer.prototype.readFloat32; /** * Writes a 64bit float. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeFloat64 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; this.ensureCapacity(offset+8); this.view.setFloat64(offset, value, this.littleEndian); return this; }; /** * Reads a 64bit float. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readFloat64 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; if (this.array === null || offset+8 > this.array.byteLength) { throw(new Error("Cannot read float64 from "+this+" at "+offset+": Capacity overflow")); } return this.view.getFloat64(offset, this.littleEndian); }; /** * Writes a double. This is an alias of {@link ByteBuffer#writeFloat64}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeDouble = ByteBuffer.prototype.writeFloat64; /** * Reads a double. This is an alias of {@link ByteBuffer#readFloat64}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readDouble = ByteBuffer.prototype.readFloat64; // Available with Long.js only if (Long) { /** * Writes a 64bit integer. Requires Long.js. * @function * @param {number|!Long} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeInt64 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; if (!(typeof value === 'object' && value instanceof Long)) value = Long.fromNumber(value, false); this.ensureCapacity(offset+8); if (this.littleEndian) { this.view.setInt32(offset, value.getLowBits(), true); this.view.setInt32(offset+4, value.getHighBits(), true); } else { this.view.setInt32(offset, value.getHighBits(), false); this.view.setInt32(offset+4, value.getLowBits(), false); } return this; }; /** * Reads a 64bit integer. Requires Long.js. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!Long} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readInt64 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; if (this.array === null || offset+8 > this.array.byteLength) { this.offset -= 8; throw(new Error("Cannot read int64 from "+this+" at "+offset+": Capacity overflow")); } var value; if (this.littleEndian) { value = Long.fromBits(this.view.getInt32(offset, true), this.view.getInt32(offset+4, true), false); } else { value = Long.fromBits(this.view.getInt32(offset+4, false), this.view.getInt32(offset, false), false); } return value; }; /** * Writes a 64bit unsigned integer. Requires Long.js. * @function * @param {number|!Long} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeUint64 = function(value, offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; if (!(typeof value === 'object' && value instanceof Long)) value = Long.fromNumber(value, true); this.ensureCapacity(offset+8); if (this.littleEndian) { this.view.setUint32(offset, value.getLowBitsUnsigned(), true); this.view.setUint32(offset+4, value.getHighBitsUnsigned(), true); } else { this.view.setUint32(offset, value.getHighBitsUnsigned(), false); this.view.setUint32(offset+4, value.getLowBitsUnsigned(), false); } return this; }; /** * Reads a 64bit unsigned integer. Requires Long.js. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!Long} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readUint64 = function(offset) { offset = typeof offset !== 'undefined' ? offset : (this.offset+=8)-8; if (this.array === null || offset+8 > this.array.byteLength) { this.offset -= 8; throw(new Error("Cannot read int64 from "+this+" at "+offset+": Capacity overflow")); } var value; if (this.littleEndian) { value = Long.fromBits(this.view.getUint32(offset, true), this.view.getUint32(offset+4, true), true); } else { value = Long.fromBits(this.view.getUint32(offset+4, false), this.view.getUint32(offset, false), true); } return value; }; /** * Writes a long. This is an alias of {@link ByteBuffer#writeInt64}. * @function * @param {number|!Long} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer} this * @expose */ ByteBuffer.prototype.writeLong = ByteBuffer.prototype.writeInt64; /** * Reads a long. This is an alias of {@link ByteBuffer#readInt64}. * @function * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!Long} * @throws {Error} If offset is out of bounds * @expose */ ByteBuffer.prototype.readLong = ByteBuffer.prototype.readInt64; } /** * Maximum number of bytes used by 32bit base 128 variable-length integer. * @type {number} * @const * @expose */ ByteBuffer.MAX_VARINT32_BYTES = 5; /** * Writes a 32bit base 128 variable-length integer as used in protobuf. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeVarint32 = function(value, offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; // ref: http://code.google.com/searchframe#WTeibokF6gE/trunk/src/google/protobuf/io/coded_stream.cc value = value >>> 0; this.ensureCapacity(offset+ByteBuffer.calculateVarint32(value)); var dst = this.view, size = 0; dst.setUint8(offset, value | 0x80); if (value >= (1 << 7)) { dst.setUint8(offset+1, (value >> 7) | 0x80); if (value >= (1 << 14)) { dst.setUint8(offset+2, (value >> 14) | 0x80); if (value >= (1 << 21)) { dst.setUint8(offset+3, (value >> 21) | 0x80); if (value >= (1 << 28)) { dst.setUint8(offset+4, (value >> 28) & 0x7F); size = 5; } else { dst.setUint8(offset+3, dst.getUint8(offset+3) & 0x7F); size = 4; } } else { dst.setUint8(offset+2, dst.getUint8(offset+2) & 0x7F); size = 3; } } else { dst.setUint8(offset+1, dst.getUint8(offset+1) & 0x7F); size = 2; } } else { dst.setUint8(offset, dst.getUint8(offset) & 0x7F); size = 1; } if (advance) { this.offset += size; return this; } else { return size; } }; /** * Reads a 32bit base 128 variable-length integer as used in protobuf. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read * and the actual number of bytes read. * @throws {Error} If it's not a valid varint * @expose */ ByteBuffer.prototype.readVarint32 = function(offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; // ref: src/google/protobuf/io/coded_stream.cc var count = 0, b, src = this.view; var value = 0 >>> 0; do { b = src.getUint8(offset+count); if (count < ByteBuffer.MAX_VARINT32_BYTES) { value |= ((b&0x7F)<<(7*count)) >>> 0; } ++count; } while (b & 0x80); value = value | 0; // Make sure to discard the higher order bits if (advance) { this.offset += count; return value; } else { return { "value": value, "length": count }; } }; /** * Writes a zigzag encoded 32bit base 128 encoded variable-length integer as used in protobuf. * @param {number} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeZigZagVarint32 = function(value, offset) { return this.writeVarint32(ByteBuffer.zigZagEncode32(value), offset); }; /** * Reads a zigzag encoded 32bit base 128 variable-length integer as used in protobuf. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read * and the actual number of bytes read. * @throws {Error} If it's not a valid varint * @expose */ ByteBuffer.prototype.readZigZagVarint32 = function(offset) { var dec = this.readVarint32(offset); if (typeof dec === 'object') { dec['value'] = ByteBuffer.zigZagDecode32(dec['value']); return dec; } return ByteBuffer.zigZagDecode32(dec); }; /** * Maximum number of bytes used by a 64bit base 128 variable-length integer. * @type {number} * @const * @expose */ ByteBuffer.MAX_VARINT64_BYTES = 10; /** * @type {number} * @const * @inner */ var TWO_PWR_7_DBL = 1 << 7; /** * @type {number} * @const * @inner */ var TWO_PWR_14_DBL = TWO_PWR_7_DBL * TWO_PWR_7_DBL; /** * @type {number} * @const * @inner */ var TWO_PWR_21_DBL = TWO_PWR_7_DBL * TWO_PWR_14_DBL; /** * @type {number} * @const * @inner */ var TWO_PWR_28_DBL = TWO_PWR_14_DBL * TWO_PWR_14_DBL; // Available with Long.js only if (Long) { /** * Writes a 64bit base 128 variable-length integer as used in protobuf. * @param {number|Long} value Value to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeVarint64 = function(value, offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; if (!(typeof value === 'object' && value instanceof Long)) value = Long.fromNumber(value, false); var part0 = value.toInt() >>> 0, part1 = value.shiftRightUnsigned(28).toInt() >>> 0, part2 = value.shiftRightUnsigned(56).toInt() >>> 0, size = ByteBuffer.calculateVarint64(value); this.ensureCapacity(offset+size); var dst = this.view; switch (size) { case 10: dst.setUint8(offset+9, (part2 >>> 7) | 0x80); case 9 : dst.setUint8(offset+8, (part2 ) | 0x80); case 8 : dst.setUint8(offset+7, (part1 >>> 21) | 0x80); case 7 : dst.setUint8(offset+6, (part1 >>> 14) | 0x80); case 6 : dst.setUint8(offset+5, (part1 >>> 7) | 0x80); case 5 : dst.setUint8(offset+4, (part1 ) | 0x80); case 4 : dst.setUint8(offset+3, (part0 >>> 21) | 0x80); case 3 : dst.setUint8(offset+2, (part0 >>> 14) | 0x80); case 2 : dst.setUint8(offset+1, (part0 >>> 7) | 0x80); case 1 : dst.setUint8(offset+0, (part0 ) | 0x80); } dst.setUint8(offset+size-1, dst.getUint8(offset+size-1) & 0x7F); if (advance) { this.offset += size; return this; } else { return size; } }; /** * Reads a 32bit base 128 variable-length integer as used in protobuf. Requires Long.js. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and * the actual number of bytes read. * @throws {Error} If it's not a valid varint * @expose */ ByteBuffer.prototype.readVarint64 = function(offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var start = offset; // ref: src/google/protobuf/io/coded_stream.cc var src = this.view, part0, part1 = 0, part2 = 0, b; b = src.getUint8(offset++); part0 = (b & 0x7F) ; if (b & 0x80) { b = src.getUint8(offset++); part0 |= (b & 0x7F) << 7; if (b & 0x80) { b = src.getUint8(offset++); part0 |= (b & 0x7F) << 14; if (b & 0x80) { b = src.getUint8(offset++); part0 |= (b & 0x7F) << 21; if (b & 0x80) { b = src.getUint8(offset++); part1 = (b & 0x7F) ; if (b & 0x80) { b = src.getUint8(offset++); part1 |= (b & 0x7F) << 7; if (b & 0x80) { b = src.getUint8(offset++); part1 |= (b & 0x7F) << 14; if (b & 0x80) { b = src.getUint8(offset++); part1 |= (b & 0x7F) << 21; if (b & 0x80) { b = src.getUint8(offset++); part2 = (b & 0x7F) ; if (b & 0x80) { b = src.getUint8(offset++); part2 |= (b & 0x7F) << 7; if (b & 0x80) { throw(new Error("Data must be corrupt: Buffer overrun")); }}}}}}}}}} var value = Long.from28Bits(part0, part1, part2, false); if (advance) { this.offset = offset; return value; } else { return { "value": value, "length": offset-start }; } }; /** * Writes a zigzag encoded 64bit base 128 encoded variable-length integer as used in protobuf. * @param {number} value Value to write * @param {number=} offset Offset to write to. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeZigZagVarint64 = function(value, offset) { return this.writeVarint64(ByteBuffer.zigZagEncode64(value), offset); }; /** * Reads a zigzag encoded 64bit base 128 variable-length integer as used in protobuf. * @param {number=} offset Offset to read from. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {Long|!{value: Long, length: number}} The value read if offset is omitted, else the value read and the actual number of bytes read. * @throws {Error} If it's not a valid varint * @expose */ ByteBuffer.prototype.readZigZagVarint64 = function(offset) { var dec = this.readVarint64(offset); if (typeof dec === 'object' && !(dec instanceof Long)) { dec['value'] = ByteBuffer.zigZagDecode64(dec['value']); return dec; } return ByteBuffer.zigZagDecode64(dec); }; } /** * Writes a base 128 variable-length integer as used in protobuf. This is an alias of {@link ByteBuffer#writeVarint32}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeVarint = ByteBuffer.prototype.writeVarint32; /** * Reads a base 128 variable-length integer as used in protobuf. This is an alias of {@link ByteBuffer#readVarint32}. * @function * @param {number=} offset Offset to read from. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {number|{value: number, length: number}} The value read if offset is omitted, else the value read and the actual number of bytes read. * @expose */ ByteBuffer.prototype.readVarint = ByteBuffer.prototype.readVarint32; /** * Writes a zigzag encoded base 128 encoded variable-length integer as used in protobuf. This is an alias of {@link ByteBuffer#writeZigZagVarint32}. * @function * @param {number} value Value to write * @param {number=} offset Offset to write to. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeZigZagVarint = ByteBuffer.prototype.writeZigZagVarint32; /** * Reads a zigzag encoded base 128 variable-length integer as used in protobuf. This is an alias of {@link ByteBuffer#readZigZagVarint32}. * @function * @param {number=} offset Offset to read from. Defaults to {@link ByteBuffer#offset} which will be modified only if omitted. * @returns {number|{value: number, length: number}} The value read if offset is omitted, else the value read and the actual number of bytes read. * @throws {Error} If it's not a valid varint * @expose */ ByteBuffer.prototype.readZigZagVarint = ByteBuffer.prototype.readZigZagVarint32; /** * Calculates the actual number of bytes required to encode a 32bit base 128 variable-length integer. * @param {number} value Value to encode * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT32_BYTES} * @expose */ ByteBuffer.calculateVarint32 = function(value) { // ref: src/google/protobuf/io/coded_stream.cc value = value >>> 0; if (value < TWO_PWR_7_DBL) { return 1; } else if (value < TWO_PWR_14_DBL) { return 2; } else if (value < TWO_PWR_21_DBL) { return 3; } else if (value < TWO_PWR_28_DBL) { return 4; } else { return 5; } }; // Available with Long.js only if (Long) { /** * Calculates the actual number of bytes required to encode a 64bit base 128 variable-length integer. * @param {number|!Long} value Value to encode * @returns {number} Number of bytes required. Capped to {@link ByteBuffer.MAX_VARINT64_BYTES} * @expose */ ByteBuffer.calculateVarint64 = function(value) { // ref: src/google/protobuf/io/coded_stream.cc if (!(typeof value === 'object' && value instanceof Long)) value = Long.fromNumber(value, false); var part0 = value.toInt() >>> 0, part1 = value.shiftRightUnsigned(28).toInt() >>> 0, part2 = value.shiftRightUnsigned(56).toInt() >>> 0; if (part2 == 0) { if (part1 == 0) { if (part0 < TWO_PWR_14_DBL) { return part0 < TWO_PWR_7_DBL ? 1 : 2; } else { return part0 < TWO_PWR_21_DBL ? 3 : 4; } } else { if (part1 < TWO_PWR_14_DBL) { return part1 < TWO_PWR_7_DBL ? 5 : 6; } else { return part1 < TWO_PWR_21_DBL ? 7 : 8; } } } else { return part2 < TWO_PWR_7_DBL ? 9 : 10; } }; } /** * Encodes a signed 32bit integer so that it can be effectively used with varint encoding. * @param {number} n Signed 32bit integer * @returns {number} Unsigned zigzag encoded 32bit integer * @expose */ ByteBuffer.zigZagEncode32 = function(n) { // ref: src/google/protobuf/wire_format_lite.h return (((n |= 0) << 1) ^ (n >> 31)) >>> 0; }; /** * Decodes a zigzag encoded signed 32bit integer. * @param {number} n Unsigned zigzag encoded 32bit integer * @returns {number} Signed 32bit integer * @expose */ ByteBuffer.zigZagDecode32 = function(n) { // ref: src/google/protobuf/wire_format_lite.h return ((n >>> 1) ^ -(n & 1)) | 0; }; // Available with Long.js only if (Long) { /** * Encodes a signed 64bit integer so that it can be effectively used with varint encoding. * @param {number|!Long} n Signed long * @returns {!Long} Unsigned zigzag encoded long * @expose */ ByteBuffer.zigZagEncode64 = function(n) { // ref: src/google/protobuf/wire_format_lite.h if (typeof n === 'object' && n instanceof Long) { if (n.unsigned) n = n.toSigned(); } else { n = Long.fromNumber(n, false); } return n.shiftLeft(1).xor(n.shiftRight(63)).toUnsigned(); }; /** * Decodes a zigzag encoded signed 64bit integer. * @param {!Long|number} n Unsigned zigzag encoded long or JavaScript number * @returns {!Long} Signed long * @throws {Error} If long support is not available * @expose */ ByteBuffer.zigZagDecode64 = function(n) { // ref: src/google/protobuf/wire_format_lite.h if (typeof n === 'object' && n instanceof Long) { if (!n.unsigned) n = n.toUnsigned(); } else { n = Long.fromNumber(n, true); } return n.shiftRightUnsigned(1).xor(n.and(Long.ONE).toSigned().negate()).toSigned(); }; } /** * Decodes a single UTF8 character from the specified ByteBuffer. The ByteBuffer's offsets are not modified. * @param {!ByteBuffer} src * @param {number} offset Offset to read from * @returns {!{char: number, length: number}} Decoded char code and the actual number of bytes read * @throws {Error} If the character cannot be decoded or there is a capacity overflow * @expose */ ByteBuffer.decodeUTF8Char = function(src, offset) { var a = src.readUint8(offset), b, c, d, e, f, start = offset, charCode; // ref: http://en.wikipedia.org/wiki/UTF-8#Description // It's quite huge but should be pretty fast. if ((a&0x80)==0) { charCode = a; offset += 1; } else if ((a&0xE0)==0xC0) { b = src.readUint8(offset+1); charCode = ((a&0x1F)<<6) | (b&0x3F); offset += 2; } else if ((a&0xF0)==0xE0) { b = src.readUint8(offset+1); c = src.readUint8(offset+2); charCode = ((a&0x0F)<<12) | ((b&0x3F)<<6) | (c&0x3F); offset += 3; } else if ((a&0xF8)==0xF0) { b = src.readUint8(offset+1); c = src.readUint8(offset+2); d = src.readUint8(offset+3); charCode = ((a&0x07)<<18) | ((b&0x3F)<<12) | ((c&0x3F)<<6) | (d&0x3F); offset += 4; } else if ((a&0xFC)==0xF8) { b = src.readUint8(offset+1); c = src.readUint8(offset+2); d = src.readUint8(offset+3); e = src.readUint8(offset+4); charCode = ((a&0x03)<<24) | ((b&0x3F)<<18) | ((c&0x3F)<<12) | ((d&0x3F)<<6) | (e&0x3F); offset += 5; } else if ((a&0xFE)==0xFC) { b = src.readUint8(offset+1); c = src.readUint8(offset+2); d = src.readUint8(offset+3); e = src.readUint8(offset+4); f = src.readUint8(offset+5); charCode = ((a&0x01)<<30) | ((b&0x3F)<<24) | ((c&0x3F)<<18) | ((d&0x3F)<<12) | ((e&0x3F)<<6) | (f&0x3F); offset += 6; } else { throw(new Error("Cannot decode UTF8 character at offset "+offset+": charCode (0x"+a.toString(16)+") is invalid")); } return { "char": charCode , "length": offset-start }; }; /** * Encodes a single UTF8 character to the specified ByteBuffer. The ByteBuffer's offsets are not modified. * @param {number} charCode Character to encode as char code * @param {!ByteBuffer} dst ByteBuffer to encode to * @param {number} offset Offset to write to * @returns {number} Actual number of bytes written * @throws {Error} If the character cannot be encoded * @expose */ ByteBuffer.encodeUTF8Char = function(charCode, dst, offset) { var start = offset; // ref: http://en.wikipedia.org/wiki/UTF-8#Description // It's quite huge but should be pretty fast. if (charCode < 0) { throw(new Error("Cannot encode UTF8 character: charCode ("+charCode+") is negative")); } if (charCode < 0x80) { dst.writeUint8(charCode&0x7F, offset); offset += 1; } else if (charCode < 0x800) { dst.writeUint8(((charCode>>6)&0x1F)|0xC0, offset) .writeUint8((charCode&0x3F)|0x80, offset+1); offset += 2; } else if (charCode < 0x10000) { dst.writeUint8(((charCode>>12)&0x0F)|0xE0, offset) .writeUint8(((charCode>>6)&0x3F)|0x80, offset+1) .writeUint8((charCode&0x3F)|0x80, offset+2); offset += 3; } else if (charCode < 0x200000) { dst.writeUint8(((charCode>>18)&0x07)|0xF0, offset) .writeUint8(((charCode>>12)&0x3F)|0x80, offset+1) .writeUint8(((charCode>>6)&0x3F)|0x80, offset+2) .writeUint8((charCode&0x3F)|0x80, offset+3); offset += 4; } else if (charCode < 0x4000000) { dst.writeUint8(((charCode>>24)&0x03)|0xF8, offset) .writeUint8(((charCode>>18)&0x3F)|0x80, offset+1) .writeUint8(((charCode>>12)&0x3F)|0x80, offset+2) .writeUint8(((charCode>>6)&0x3F)|0x80, offset+3) .writeUint8((charCode&0x3F)|0x80, offset+4); offset += 5; } else if (charCode < 0x80000000) { dst.writeUint8(((charCode>>30)&0x01)|0xFC, offset) .writeUint8(((charCode>>24)&0x3F)|0x80, offset+1) .writeUint8(((charCode>>18)&0x3F)|0x80, offset+2) .writeUint8(((charCode>>12)&0x3F)|0x80, offset+3) .writeUint8(((charCode>>6)&0x3F)|0x80, offset+4) .writeUint8((charCode&0x3F)|0x80, offset+5); offset += 6; } else { throw(new Error("Cannot encode UTF8 character: charCode (0x"+charCode.toString(16)+") is too large (>= 0x80000000)")); } return offset-start; }; /** * Calculates the actual number of bytes required to encode the specified char code. * @param {number} charCode Character to encode as char code * @returns {number} Number of bytes required to encode the specified char code * @throws {Error} If the character cannot be calculated (too large) * @expose */ ByteBuffer.calculateUTF8Char = function(charCode) { if (charCode < 0) { throw(new Error("Cannot calculate length of UTF8 character: charCode ("+charCode+") is negative")); } if (charCode < 0x80) { return 1; } else if (charCode < 0x800) { return 2; } else if (charCode < 0x10000) { return 3; } else if (charCode < 0x200000) { return 4; } else if (charCode < 0x4000000) { return 5; } else if (charCode < 0x80000000) { return 6; } else { throw(new Error("Cannot calculate length of UTF8 character: charCode (0x"+charCode.toString(16)+") is too large (>= 0x80000000)")); } }; /** * Calculates the number of bytes required to store an UTF8 encoded string. * @param {string} str String to calculate * @returns {number} Number of bytes required */ ByteBuffer.calculateUTF8String = function(str) { str = ""+str; var bytes = 0; for (var i=0, k=str.length; i i ? bb.readUint8(i++) : 0; o3 = bb.length > i ? bb.readUint8(i++) : 0; bits = o1 << 16 | o2 << 8 | o3; h1 = bits >> 18 & 0x3f; h2 = bits >> 12 & 0x3f; h3 = bits >> 6 & 0x3f; h4 = bits & 0x3f; out[oi++] = B64.charAt(h1) + B64.charAt(h2) + B64.charAt(h3) + B64.charAt(h4); } while (i < bb.length); var enc = out.join(''), r = (bb.length - bb.offset) % 3; return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); }; /** * Decodes a base64 encoded string to a ByteBuffer. * @param {string} str Base64 encoded string * @param {boolean=} littleEndian `true` to use little endian byte order, defaults to `false` for big endian. * @returns {!ByteBuffer} ByteBuffer * @throws {Error} If the argument is not a valid base64 encoded string * @expose */ ByteBuffer.decode64 = function(str, littleEndian) { // ref: http://phpjs.org/functions/base64_decode/ if (typeof str !== 'string') { throw(new Error("Illegal argument: Not a string")); } var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, out = new ByteBuffer(Math.ceil(str.length / 3), littleEndian); do { h1 = B64.indexOf(str.charAt(i++)); h2 = B64.indexOf(str.charAt(i++)); h3 = B64.indexOf(str.charAt(i++)); h4 = B64.indexOf(str.charAt(i++)); if (h1 < 0 || h2 < 0 || h3 < 0 || h4 < 0) { throw(new Error("Illegal argument: Not a valid base64 encoded string")); } bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; o1 = bits >> 16 & 0xff; o2 = bits >> 8 & 0xff; o3 = bits & 0xff; if (h3 == 64) { out.writeUint8(o1); } else if (h4 == 64) { out.writeUint8(o1) .writeUint8(o2); } else { out.writeUint8(o1) .writeUint8(o2) .writeUint8(o3); } } while (i < str.length); return out.flip(); }; /** * Encodes a ByteBuffer to a hex encoded string. * @param {!ByteBuffer} bb ByteBuffer to encode. Will be cloned and flipped if length < offset. * @returns {string} Hex encoded string * @throws {Error} If the argument is not a valid ByteBuffer * @expose */ ByteBuffer.encodeHex = function(bb) { if (!(bb instanceof ByteBuffer)) { bb = ByteBuffer.wrap(bb); } else if (bb.length < bb.offset) { bb = bb.clone().flip(); } if (bb.array === null) return ""; var val, out = []; for (var i=bb.offset, k=bb.length; i 255) throw(new Error("Illegal argument: Not a binary string (char code "+val+")")); view.setUint8(i, val); } var bb = new ByteBuffer(k, littleEndian, true); bb.array = dst; bb.view = view; bb.length = k; return bb; }; /** * Writes an UTF8 string. * @param {string} str String to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeUTF8String = function(str, offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var start = offset; var encLen = ByteBuffer.calculateUTF8String(str); // See [1] this.ensureCapacity(offset+encLen); for (var i=0, j=str.length; ilength ? "+" : "")+offset-length)+" bytes")); } if (advance) { this.offset = offset; return result; } else { return { "string": result, "length": offset-start } } }; /** * Writes a string with prepended number of characters, which is also encoded as an UTF8 character.. * @param {string} str String to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. * @expose */ ByteBuffer.prototype.writeLString = function(str, offset) { str = ""+str; var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var encLen = ByteBuffer.encodeUTF8Char(str.length, this, offset); encLen += this.writeUTF8String(str, offset+encLen); if (advance) { this.offset += encLen; return this; } else { return encLen; } }; /** * Reads a string with a prepended number of characters, which is also encoded as an UTF8 character. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {string|{string: string, length: number}} The string read if offset is omitted, else the string read * and the actual number of bytes read. * @throws {Error} If the string cannot be decoded * @expose */ ByteBuffer.prototype.readLString = function(offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var lenDec = ByteBuffer.decodeUTF8Char(this, offset), dec = this.readUTF8String(lenDec["char"], offset+lenDec["length"]); if (advance) { this.offset += lenDec["length"]+dec["length"]; return dec["string"]; } else { return { "string": dec["string"], "length": lenDec["length"]+dec["length"] }; } }; /** * Writes a string with prepended number of characters, which is encoded as a 32bit base 128 variable-length * integer. * @param {string} str String to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written * @expose */ ByteBuffer.prototype.writeVString = function(str, offset) { str = ""+str; var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var encLen = this.writeVarint32(ByteBuffer.calculateUTF8String(str), offset); encLen += this.writeUTF8String(str, offset+encLen); if (advance) { this.offset += encLen; return this; } else { return encLen; } }; /** * Reads a string with prepended number of characters, which is encoded as a 32bit base 128 variable-length * integer. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string * read and the actual number of bytes read. * @throws {Error} If the string cannot be decoded or if it is not preceeded by a valid varint * @expose */ ByteBuffer.prototype.readVString = function(offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var lenDec = this.readVarint32(offset); var dec = this.readUTF8StringBytes(lenDec["value"], offset+lenDec["length"]); if (advance) { this.offset += lenDec["length"]+dec["length"]; return dec["string"]; } else { return { "string": dec["string"], "length": lenDec["length"]+dec["length"] }; } }; /** * Writes a string followed by a NULL character (Uint8). Beware: The source string must not contain NULL * characters unless this is actually intended. This is not checked. If you have the option it is recommended * to use {@link ByteBuffer#writeLString} or {@link ByteBuffer#writeVString} with the corresponding reading * methods instead. * @param {string} str String to write * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written * @expose */ ByteBuffer.prototype.writeCString = function(str, offset) { str = ""+str; var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var encLen = this.writeUTF8String(str, offset); this.writeUint8(0, offset+encLen); if (advance) { this.offset += encLen+1; return this; } else { return encLen+1; } }; /** * Reads a string followed by a NULL character (Uint8). * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string * read and the actual number of bytes read. * @throws {Error} If the string cannot be decoded * @expose */ ByteBuffer.prototype.readCString = function(offset) { var advance = typeof offset === 'undefined'; offset = typeof offset !== 'undefined' ? offset : this.offset; var dec, result = "", start = offset; do { dec = ByteBuffer.decodeUTF8Char(this, offset); offset += dec["length"]; if (dec["char"] != 0) result += String.fromCharCode(dec["char"]); } while (dec["char"] != 0); if (advance) { this.offset = offset; return result; } else { return { "string": result, "length": offset-start }; } }; /** * Serializes and writes a JSON payload. * @param {*} data Data payload to serialize * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} if omitted. * @param {function(*)=} stringify Stringify implementation to use. Defaults to {@link JSON.stringify}. * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number if bytes written * @expose */ ByteBuffer.prototype.writeJSON = function(data, offset, stringify) { stringify = typeof stringify === 'function' ? stringify : JSON.stringify; return this.writeLString(stringify(data), offset); }; /** * Reads a JSON payload and unserializes it. * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} if omitted. * @param {function(string)=} parse Parse implementation to use. Defaults to {@link JSON.parse}. * @returns {!*|!{data: *, length: number}} Data payload if offset is omitted, else the data payload and the * actual number of bytes read * @throws {Error} If the data cannot be decoded * @expose */ ByteBuffer.prototype.readJSON = function(offset, parse) { parse = typeof parse === 'function' ? parse : JSON.parse; var result = this.readLString(offset); if (typeof result === 'string') { return parse(result); } else { return { "data": parse(result["string"]), "length": result["length"] }; } }; /** * Returns a textual two columns (hex, ascii) representation of this ByteBuffer's backing array. * @param {number=} wrap Wrap length. Defaults to 16. * @returns {string} Hex representation as of " 00<01 02>03... ASCII DATA" with marked offsets * @expose */ ByteBuffer.prototype.toColumns = function(wrap) { if (this.array === null) return "DESTROYED"; wrap = typeof wrap !== 'undefined' ? parseInt(wrap, 10) : 16; if (wrap < 1) wrap = 16; // Left colum: hex with offsets var out = "", lines = [], val, view = this.view; if (this.offset == 0 && this.length == 0) { out += "|"; } else if (this.length == 0) { out += ">"; } else if (this.offset == 0) { out += "<"; } else { out += " "; } for (var i=0, k=this.array.byteLength; i0 && i%wrap == 0) { while (out.length < 3*wrap+1) out += " "; // Make it equal to maybe show something on the right lines.push(out); out = " "; } val = view.getUint8(i).toString(16).toUpperCase(); if (val.length < 2) val = "0"+val; out += val; if (i+1 == this.offset && i+1 == this.length) { out += "|"; } else if (i+1 == this.offset) { out += "<"; } else if (i+1 == this.length) { out += ">"; } else { out += " "; } } if (out != " ") { lines.push(out); } // Make it equal for (i=0, k=lines.length; i0 && i%wrap == 0) { lines[n] += " "+out; out = ""; n++; } val = view.getUint8(i); out += val > 32 && val < 127 ? String.fromCharCode(val) : "."; } if (out != "") { lines[n] += " "+out; } return lines.join("\n"); }; /** * Prints debug information about this ByteBuffer's contents. * @param {function(string)=} out Output function to call, defaults to console.log * @expose */ ByteBuffer.prototype.printDebug = function(out) { if (typeof out !== 'function') out = console.log.bind(console); out( (this.array != null ? "ByteBuffer(offset="+this.offset+",markedOffset="+this.markedOffset+",length="+this.length+",capacity="+this.array.byteLength+")" : "ByteBuffer(DESTROYED)")+"\n"+ "-------------------------------------------------------------------\n"+ this.toColumns()+"\n" ); }; /** * Returns the ByteBuffer's contents between offset and length as a hex string. * @param {boolean=} debug `true` to return the entire backing array with marked offsets, defaults to `false` * @returns {string} Hex string or debug string * @expose */ ByteBuffer.prototype.toHex = function(debug) { var out = "", val, view = this.view, i, k; if (!debug) { return ByteBuffer.encodeHex(this); } else { if (this.array === null) return "DESTROYED"; if (this.offset == 0 && this.length == 0) { out += "|"; } else if (this.length == 0) { out += ">"; } else if (this.offset == 0) { out += "<"; } else { out += " "; } for (i=0, k=this.array.byteLength; i= this.length) return ""; return ByteBuffer.encode64(this); }; /** * Returns the ByteBuffer's contents as an UTF8 encoded string. * @returns {string} * @expose */ ByteBuffer.prototype.toUTF8 = function() { if (this.array === null || this.offset >= this.length) return ""; return this.readUTF8StringBytes(this.length - this.offset, this.offset)["string"]; }; /** * Converts the ByteBuffer to a string. * @param {string=} enc Output encoding. Returns an informative string representation by default but also allows * direct conversion to "utf8", "hex", "base64" and "binary" encoding. "debug" returns a hex representation with * marked offsets. * @returns {string} String representation * @expose */ ByteBuffer.prototype.toString = function(enc) { enc = enc || ""; switch (enc) { case "utf8": return this.toUTF8(); case "base64": return this.toBase64(); case "hex": return this.toHex(); case "binary": return this.toBinary(); case "debug": return this.toHex(true); default: if (this.array === null) { return "ByteBuffer(DESTROYED)"; } return "ByteBuffer(offset="+this.offset+",markedOffset="+this.markedOffset+",length="+this.length+",capacity="+this.array.byteLength+")"; } }; /** * Returns an ArrayBuffer compacted to contain this ByteBuffer's actual contents. Will transparently * {@link ByteBuffer#flip} the ByteBuffer if its offset is larger than its length. Will return a reference to * the unmodified backing buffer if offset=0 and length=capacity unless forceCopy is set to true. * @param {boolean=} forceCopy `true` forces the creation of a copy, defaults to `false` * @returns {?ArrayBuffer} Compacted ArrayBuffer or null if already destroyed * @expose */ ByteBuffer.prototype.toArrayBuffer = function(forceCopy) { if (this.array === null) return null; var b = this.clone(); if (b.offset > b.length) { b.flip(); } var copied = false; if (b.offset > 0 || b.length < b.array.byteLength) { b.compact(); // Will always create a new backing buffer because of the above condition copied = true; } return forceCopy && !copied ? b.copy().array : b.array; }; // Available with node.js only if (Buffer) { /** * Returns a node Buffer compacted to contain this ByteBuffer's actual contents. Will transparently * {@link ByteBuffer#flip} the ByteBuffer if its offset is larger than its length. Will also copy all data (not * a reference). * @returns {?Buffer} Compacted node Buffer or null if already destroyed * @expose */ ByteBuffer.prototype.toBuffer = function() { if (this.array === null) return null; var offset = this.offset, length = this.length; if (offset > length) { var temp = offset; offset = length; length = temp; } return new Buffer(new Uint8Array(this.array).subarray(offset, length)); }; } return ByteBuffer; } // Enable module loading if available if (typeof module !== 'undefined' && module["exports"]) { // CommonJS module["exports"] = loadByteBuffer(require("long")); } else if (typeof define !== 'undefined' && define["amd"]) { // AMD define("ByteBuffer", ["Math/Long"], function(Long) { return loadByteBuffer(Long); }); } else { // Shim if (!global["dcodeIO"]) global["dcodeIO"] = {}; global["dcodeIO"]["ByteBuffer"] = loadByteBuffer(global["dcodeIO"]["Long"]); } })(this);