From 570fb182d4f3bdc831e1cc1d8f98ea71ff508b7b Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Tue, 13 Jul 2021 11:54:53 -0700 Subject: [PATCH] Fully move to protobufjs --- background.html | 1 - bower.json | 6 - components/bytebuffer/dist/ByteBufferAB.js | 3293 ----------- components/long/dist/Long.js | 1209 ---- components/protobuf/dist/ProtoBuf.js | 5250 ----------------- libtextsecure/key_worker.js | 63 - libtextsecure/protobufs.js | 70 - libtextsecure/test/.eslintrc.js | 2 - libtextsecure/test/_test.js | 1 - libtextsecure/test/crypto_test.js | 144 - libtextsecure/test/index.html | 8 +- package.json | 2 +- patches/protobufjs+6.10.2.patch | 84 + preload.js | 1 - sticker-creator/index.html | 4 - sticker-creator/preload.js | 1 - test/.eslintrc.js | 2 - test/_test.js | 1 - test/crypto_test.js | 499 -- test/index.html | 15 +- test/protobuf_test.js | 126 - test/setup-test-node.js | 6 - ts/Bytes.ts | 9 +- ts/Crypto.ts | 64 +- ts/context/Bytes.ts | 11 + ts/model-types.d.ts | 9 +- ts/services/storage.ts | 163 +- ts/services/storageRecordOps.ts | 241 +- .../util/arePinnedConversationsEqual_test.ts | 34 +- ts/test-both/util/timestampLongUtils_test.ts | 5 +- ts/test-electron/Crypto_test.ts | 564 ++ .../services/senderCertificate_test.ts | 9 +- .../util/isByteBufferEmpty_test.ts | 31 - ts/test-node/Proto_unknown_field_test.ts | 95 + ts/textsecure.d.ts | 1145 +--- ts/textsecure/Crypto.ts | 15 +- ts/textsecure/Helpers.ts | 19 +- ts/textsecure/MessageReceiver.ts | 32 +- ts/textsecure/WebAPI.ts | 63 +- ts/util/arePinnedConversationsEqual.ts | 65 +- ts/util/isByteBufferEmpty.ts | 10 - ts/util/lint/exceptions.json | 16 +- ts/util/lint/linter.ts | 2 + ts/util/timestampLongUtils.ts | 12 +- ts/window.d.ts | 48 +- yarn.lock | 84 +- 46 files changed, 1133 insertions(+), 12401 deletions(-) delete mode 100644 components/bytebuffer/dist/ByteBufferAB.js delete mode 100644 components/long/dist/Long.js delete mode 100644 components/protobuf/dist/ProtoBuf.js delete mode 100644 libtextsecure/key_worker.js delete mode 100644 libtextsecure/protobufs.js delete mode 100644 libtextsecure/test/crypto_test.js create mode 100644 patches/protobufjs+6.10.2.patch delete mode 100644 test/crypto_test.js delete mode 100644 test/protobuf_test.js create mode 100644 ts/test-electron/Crypto_test.ts delete mode 100644 ts/test-electron/util/isByteBufferEmpty_test.ts create mode 100644 ts/test-node/Proto_unknown_field_test.ts delete mode 100644 ts/util/isByteBufferEmpty.ts diff --git a/background.html b/background.html index 8a198acc5d..44ccb5769b 100644 --- a/background.html +++ b/background.html @@ -347,7 +347,6 @@ type="text/javascript" src="libtextsecure/protocol_wrapper.js" > - diff --git a/bower.json b/bower.json index c874301fc9..6a6a22a50e 100644 --- a/bower.json +++ b/bower.json @@ -13,12 +13,6 @@ "devDependencies": { }, "preen": { - "bytebuffer": [ - "dist/ByteBufferAB.js" - ], - "long": [ - "dist/Long.js" - ], "mp3lameencoder": [ "lib/Mp3LameEncoder.js" ], diff --git a/components/bytebuffer/dist/ByteBufferAB.js b/components/bytebuffer/dist/ByteBufferAB.js deleted file mode 100644 index df5e118480..0000000000 --- a/components/bytebuffer/dist/ByteBufferAB.js +++ /dev/null @@ -1,3293 +0,0 @@ -/* - Copyright 2013-2014 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-2014 Daniel Wirtz - * This version of ByteBuffer.js uses an ArrayBuffer as its backing buffer which is accessed through a DataView and is - * compatible with modern browsers. - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/ByteBuffer.js for details - */ // -(function(global) { - "use strict"; - - /** - * @param {function(new: Long, number, number, boolean=)=} Long - * @returns {function(new: ByteBuffer, number=, boolean=, boolean=)}} - * @inner - */ - function loadByteBuffer(Long) { - - /** - * Constructs a new ByteBuffer. - * @class The swiss army knife for binary data in JavaScript. - * @exports ByteBuffer - * @constructor - * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @expose - */ - var ByteBuffer = function(capacity, littleEndian, noAssert) { - if (typeof capacity === 'undefined') capacity = ByteBuffer.DEFAULT_CAPACITY; - if (typeof littleEndian === 'undefined') littleEndian = ByteBuffer.DEFAULT_ENDIAN; - if (typeof noAssert === 'undefined') noAssert = ByteBuffer.DEFAULT_NOASSERT; - if (!noAssert) { - capacity = capacity | 0; - if (capacity < 0) - throw RangeError("Illegal capacity"); - littleEndian = !!littleEndian; - noAssert = !!noAssert; - } - - /** - * Backing buffer. - * @type {!ArrayBuffer} - * @expose - */ - this.buffer = capacity === 0 ? EMPTY_BUFFER : new ArrayBuffer(capacity); - - /** - * Data view to manipulate the backing buffer. Becomes `null` if the backing buffer has a capacity of `0`. - * @type {?DataView} - * @expose - */ - this.view = capacity === 0 ? null : new DataView(this.buffer); - - /** - * Absolute read/write offset. - * @type {number} - * @expose - * @see ByteBuffer#flip - * @see ByteBuffer#clear - */ - this.offset = 0; - - /** - * Marked offset. - * @type {number} - * @expose - * @see ByteBuffer#mark - * @see ByteBuffer#reset - */ - this.markedOffset = -1; - - /** - * Absolute limit of the contained data. Set to the backing buffer's capacity upon allocation. - * @type {number} - * @expose - * @see ByteBuffer#flip - * @see ByteBuffer#clear - */ - this.limit = capacity; - - /** - * Whether to use little endian byte order, defaults to `false` for big endian. - * @type {boolean} - * @expose - */ - this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : false; - - /** - * Whether to skip assertions of offsets and values, defaults to `false`. - * @type {boolean} - * @expose - */ - this.noAssert = !!noAssert; - }; - - /** - * ByteBuffer version. - * @type {string} - * @const - * @expose - */ - ByteBuffer.VERSION = "3.5.5"; - - /** - * Little endian constant that can be used instead of its boolean value. Evaluates to `true`. - * @type {boolean} - * @const - * @expose - */ - ByteBuffer.LITTLE_ENDIAN = true; - - /** - * Big endian constant that can be used instead of its boolean value. Evaluates to `false`. - * @type {boolean} - * @const - * @expose - */ - ByteBuffer.BIG_ENDIAN = false; - - /** - * Default initial capacity of `16`. - * @type {number} - * @expose - */ - ByteBuffer.DEFAULT_CAPACITY = 16; - - /** - * Default endianess of `false` for big endian. - * @type {boolean} - * @expose - */ - ByteBuffer.DEFAULT_ENDIAN = ByteBuffer.BIG_ENDIAN; - - /** - * Default no assertions flag of `false`. - * @type {boolean} - * @expose - */ - ByteBuffer.DEFAULT_NOASSERT = false; - - /** - * A `Long` class for representing a 64-bit two's-complement integer value. May be `null` if Long.js has not been loaded - * and int64 support is not available. - * @type {?Long} - * @const - * @see https://github.com/dcodeIO/Long.js - * @expose - */ - ByteBuffer.Long = Long || null; - - /** - * @alias ByteBuffer.prototype - * @inner - */ - var ByteBufferPrototype = ByteBuffer.prototype; - - // helpers - - /** - * @type {!ArrayBuffer} - * @inner - */ - var EMPTY_BUFFER = new ArrayBuffer(0); - - /** - * String.fromCharCode reference for compile-time renaming. - * @type {function(...number):string} - * @inner - */ - var stringFromCharCode = String.fromCharCode; - - /** - * Creates a source function for a string. - * @param {string} s String to read from - * @returns {function():number|null} Source function returning the next char code respectively `null` if there are - * no more characters left. - * @throws {TypeError} If the argument is invalid - * @inner - */ - function stringSource(s) { - var i=0; return function() { - return i < s.length ? s.charCodeAt(i++) : null; - }; - } - - /** - * Creates a destination function for a string. - * @returns {function(number=):undefined|string} Destination function successively called with the next char code. - * Returns the final string when called without arguments. - * @inner - */ - function stringDestination() { - var cs = [], ps = []; return function() { - if (arguments.length === 0) - return ps.join('')+stringFromCharCode.apply(String, cs); - if (cs.length + arguments.length > 1024) - ps.push(stringFromCharCode.apply(String, cs)), - cs.length = 0; - Array.prototype.push.apply(cs, arguments); - }; - } - - /** - * Allocates a new ByteBuffer backed by a buffer of the specified capacity. - * @param {number=} capacity Initial capacity. Defaults to {@link ByteBuffer.DEFAULT_CAPACITY}. - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} - * @expose - */ - ByteBuffer.allocate = function(capacity, littleEndian, noAssert) { - return new ByteBuffer(capacity, littleEndian, noAssert); - }; - - /** - * Concatenates multiple ByteBuffers into one. - * @param {!Array.} buffers Buffers to concatenate - * @param {(string|boolean)=} encoding String encoding if `buffers` contains a string ("base64", "hex", "binary", - * defaults to "utf8") - * @param {boolean=} littleEndian Whether to use little or big endian byte order for the resulting ByteBuffer. Defaults - * to {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values for the resulting ByteBuffer. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} Concatenated ByteBuffer - * @expose - */ - ByteBuffer.concat = function(buffers, encoding, littleEndian, noAssert) { - if (typeof encoding === 'boolean' || typeof encoding !== 'string') { - noAssert = littleEndian; - littleEndian = encoding; - encoding = undefined; - } - var capacity = 0; - for (var i=0, k=buffers.length, length; i 0) capacity += length; - } - if (capacity === 0) - return new ByteBuffer(0, littleEndian, noAssert); - var bb = new ByteBuffer(capacity, littleEndian, noAssert), - bi; - var view = new Uint8Array(bb.buffer); - i=0; while (i} buffer Anything that can be wrapped - * @param {(string|boolean)=} encoding String encoding if `buffer` is a string ("base64", "hex", "binary", defaults to - * "utf8") - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} A ByteBuffer wrapping `buffer` - * @expose - */ - ByteBuffer.wrap = function(buffer, encoding, littleEndian, noAssert) { - if (typeof encoding !== 'string') { - noAssert = littleEndian; - littleEndian = encoding; - encoding = undefined; - } - if (typeof buffer === 'string') { - if (typeof encoding === 'undefined') - encoding = "utf8"; - switch (encoding) { - case "base64": - return ByteBuffer.fromBase64(buffer, littleEndian); - case "hex": - return ByteBuffer.fromHex(buffer, littleEndian); - case "binary": - return ByteBuffer.fromBinary(buffer, littleEndian); - case "utf8": - return ByteBuffer.fromUTF8(buffer, littleEndian); - case "debug": - return ByteBuffer.fromDebug(buffer, littleEndian); - default: - throw Error("Unsupported encoding: "+encoding); - } - } - if (buffer === null || typeof buffer !== 'object') - throw TypeError("Illegal buffer"); - var bb; - if (ByteBuffer.isByteBuffer(buffer)) { - bb = ByteBufferPrototype.clone.call(buffer); - bb.markedOffset = -1; - return bb; - } - if (buffer instanceof Uint8Array) { // Extract ArrayBuffer from Uint8Array - bb = new ByteBuffer(0, littleEndian, noAssert); - if (buffer.length > 0) { // Avoid references to more than one EMPTY_BUFFER - bb.buffer = buffer.buffer; - bb.offset = buffer.byteOffset; - bb.limit = buffer.byteOffset + buffer.length; - bb.view = buffer.length > 0 ? new DataView(buffer.buffer) : null; - } - } else if (buffer instanceof ArrayBuffer) { // Reuse ArrayBuffer - bb = new ByteBuffer(0, littleEndian, noAssert); - if (buffer.byteLength > 0) { - bb.buffer = buffer; - bb.offset = 0; - bb.limit = buffer.byteLength; - bb.view = buffer.byteLength > 0 ? new DataView(buffer) : null; - } - } else if (Object.prototype.toString.call(buffer) === "[object Array]") { // Create from octets - bb = new ByteBuffer(buffer.length, littleEndian, noAssert); - bb.limit = buffer.length; - for (i=0; i>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 1; - var capacity0 = this.buffer.byteLength; - if (offset > capacity0) - this.resize((capacity0 *= 2) > offset ? capacity0 : offset); - offset -= 1; - this.view.setInt8(offset, value); - if (relative) this.offset += 1; - return this; - }; - - /** - * Writes an 8bit signed integer. This is an alias of {@link ByteBuffer#writeInt8}. - * @function - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeByte = ByteBufferPrototype.writeInt8; - - /** - * Reads an 8bit signed integer. - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readInt8 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - var value = this.view.getInt8(offset); - if (relative) this.offset += 1; - return value; - }; - - /** - * Reads an 8bit signed integer. This is an alias of {@link ByteBuffer#readInt8}. - * @function - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readByte = ByteBufferPrototype.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} by `1` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeUint8 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value >>>= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 1; - var capacity1 = this.buffer.byteLength; - if (offset > capacity1) - this.resize((capacity1 *= 2) > offset ? capacity1 : offset); - offset -= 1; - this.view.setUint8(offset, value); - if (relative) this.offset += 1; - return this; - }; - - /** - * Reads an 8bit unsigned integer. - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `1` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readUint8 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - var value = this.view.getUint8(offset); - if (relative) this.offset += 1; - return value; - }; - - // types/ints/int16 - - /** - * 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} by `2` if omitted. - * @throws {TypeError} If `offset` or `value` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.writeInt16 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value |= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 2; - var capacity2 = this.buffer.byteLength; - if (offset > capacity2) - this.resize((capacity2 *= 2) > offset ? capacity2 : offset); - offset -= 2; - this.view.setInt16(offset, value, this.littleEndian); - if (relative) this.offset += 2; - return this; - }; - - /** - * Writes a 16bit signed integer. 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} by `2` if omitted. - * @throws {TypeError} If `offset` or `value` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.writeShort = ByteBufferPrototype.writeInt16; - - /** - * Reads a 16bit signed integer. - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. - * @returns {number} Value read - * @throws {TypeError} If `offset` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.readInt16 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 2 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+2+") <= "+this.buffer.byteLength); - } - var value = this.view.getInt16(offset, this.littleEndian); - if (relative) this.offset += 2; - return value; - }; - - /** - * Reads a 16bit signed integer. This is an alias of {@link ByteBuffer#readInt16}. - * @function - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. - * @returns {number} Value read - * @throws {TypeError} If `offset` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.readShort = ByteBufferPrototype.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} by `2` if omitted. - * @throws {TypeError} If `offset` or `value` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.writeUint16 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value >>>= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 2; - var capacity3 = this.buffer.byteLength; - if (offset > capacity3) - this.resize((capacity3 *= 2) > offset ? capacity3 : offset); - offset -= 2; - this.view.setUint16(offset, value, this.littleEndian); - if (relative) this.offset += 2; - return this; - }; - - /** - * Reads a 16bit unsigned integer. - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `2` if omitted. - * @returns {number} Value read - * @throws {TypeError} If `offset` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @expose - */ - ByteBufferPrototype.readUint16 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 2 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+2+") <= "+this.buffer.byteLength); - } - var value = this.view.getUint16(offset, this.littleEndian); - if (relative) this.offset += 2; - return value; - }; - - // types/ints/int32 - - /** - * Writes a 32bit signed integer. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @expose - */ - ByteBufferPrototype.writeInt32 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value |= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 4; - var capacity4 = this.buffer.byteLength; - if (offset > capacity4) - this.resize((capacity4 *= 2) > offset ? capacity4 : offset); - offset -= 4; - this.view.setInt32(offset, value, this.littleEndian); - if (relative) this.offset += 4; - return this; - }; - - /** - * Writes a 32bit signed integer. This is an alias of {@link ByteBuffer#writeInt32}. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @expose - */ - ByteBufferPrototype.writeInt = ByteBufferPrototype.writeInt32; - - /** - * Reads a 32bit signed integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readInt32 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 4 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength); - } - var value = this.view.getInt32(offset, this.littleEndian); - if (relative) this.offset += 4; - return value; - }; - - /** - * Reads a 32bit signed integer. This is an alias of {@link ByteBuffer#readInt32}. - * @param {number=} offset Offset to read from. Will use and advance {@link ByteBuffer#offset} by `4` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readInt = ByteBufferPrototype.readInt32; - - /** - * Writes a 32bit unsigned integer. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @expose - */ - ByteBufferPrototype.writeUint32 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value >>>= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 4; - var capacity5 = this.buffer.byteLength; - if (offset > capacity5) - this.resize((capacity5 *= 2) > offset ? capacity5 : offset); - offset -= 4; - this.view.setUint32(offset, value, this.littleEndian); - if (relative) this.offset += 4; - return this; - }; - - /** - * Reads a 32bit unsigned integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {number} Value read - * @expose - */ - ByteBufferPrototype.readUint32 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 4 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength); - } - var value = this.view.getUint32(offset, this.littleEndian); - if (relative) this.offset += 4; - return value; - }; - - // types/ints/int64 - - if (Long) { - - /** - * Writes a 64bit signed integer. - * @param {number|!Long} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeInt64 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - else if (!(value && value instanceof Long)) - throw TypeError("Illegal value: "+value+" (not an integer or Long)"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - offset += 8; - var capacity6 = this.buffer.byteLength; - if (offset > capacity6) - this.resize((capacity6 *= 2) > offset ? capacity6 : offset); - offset -= 8; - if (this.littleEndian) { - this.view.setInt32(offset , value.low , true); - this.view.setInt32(offset+4, value.high, true); - } else { - this.view.setInt32(offset , value.high, false); - this.view.setInt32(offset+4, value.low , false); - } - if (relative) this.offset += 8; - return this; - }; - - /** - * Writes a 64bit signed integer. This is an alias of {@link ByteBuffer#writeInt64}. - * @param {number|!Long} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeLong = ByteBufferPrototype.writeInt64; - - /** - * Reads a 64bit signed integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!Long} - * @expose - */ - ByteBufferPrototype.readInt64 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 8 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength); - } - var value = this.littleEndian - ? new Long(this.view.getInt32(offset , true ), this.view.getInt32(offset+4, true ), false) - : new Long(this.view.getInt32(offset+4, false), this.view.getInt32(offset , false), false); - if (relative) this.offset += 8; - return value; - }; - - /** - * Reads a 64bit signed integer. This is an alias of {@link ByteBuffer#readInt64}. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!Long} - * @expose - */ - ByteBufferPrototype.readLong = ByteBufferPrototype.readInt64; - - /** - * Writes a 64bit unsigned integer. - * @param {number|!Long} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeUint64 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - else if (!(value && value instanceof Long)) - throw TypeError("Illegal value: "+value+" (not an integer or Long)"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - offset += 8; - var capacity7 = this.buffer.byteLength; - if (offset > capacity7) - this.resize((capacity7 *= 2) > offset ? capacity7 : offset); - offset -= 8; - if (this.littleEndian) { - this.view.setInt32(offset , value.low , true); - this.view.setInt32(offset+4, value.high, true); - } else { - this.view.setInt32(offset , value.high, false); - this.view.setInt32(offset+4, value.low , false); - } - if (relative) this.offset += 8; - return this; - }; - - /** - * Reads a 64bit unsigned integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!Long} - * @expose - */ - ByteBufferPrototype.readUint64 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 8 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength); - } - var value = this.littleEndian - ? new Long(this.view.getInt32(offset , true ), this.view.getInt32(offset+4, true ), true) - : new Long(this.view.getInt32(offset+4, false), this.view.getInt32(offset , false), true); - if (relative) this.offset += 8; - return value; - }; - - } // Long - - - // types/floats/float32 - - /** - * Writes a 32bit float. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeFloat32 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number') - throw TypeError("Illegal value: "+value+" (not a number)"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 4; - var capacity8 = this.buffer.byteLength; - if (offset > capacity8) - this.resize((capacity8 *= 2) > offset ? capacity8 : offset); - offset -= 4; - this.view.setFloat32(offset, value, this.littleEndian); - if (relative) this.offset += 4; - return this; - }; - - /** - * Writes a 32bit 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 increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeFloat = ByteBufferPrototype.writeFloat32; - - /** - * Reads a 32bit float. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {number} - * @expose - */ - ByteBufferPrototype.readFloat32 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 4 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength); - } - var value = this.view.getFloat32(offset, this.littleEndian); - if (relative) this.offset += 4; - return value; - }; - - /** - * Reads a 32bit float. This is an alias of {@link ByteBuffer#readFloat32}. - * @function - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `4` if omitted. - * @returns {number} - * @expose - */ - ByteBufferPrototype.readFloat = ByteBufferPrototype.readFloat32; - - // types/floats/float64 - - /** - * Writes a 64bit float. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeFloat64 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number') - throw TypeError("Illegal value: "+value+" (not a number)"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - offset += 8; - var capacity9 = this.buffer.byteLength; - if (offset > capacity9) - this.resize((capacity9 *= 2) > offset ? capacity9 : offset); - offset -= 8; - this.view.setFloat64(offset, value, this.littleEndian); - if (relative) this.offset += 8; - return this; - }; - - /** - * Writes a 64bit float. 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 increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.writeDouble = ByteBufferPrototype.writeFloat64; - - /** - * Reads a 64bit float. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {number} - * @expose - */ - ByteBufferPrototype.readFloat64 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 8 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+8+") <= "+this.buffer.byteLength); - } - var value = this.view.getFloat64(offset, this.littleEndian); - if (relative) this.offset += 8; - return value; - }; - - /** - * Reads a 64bit float. This is an alias of {@link ByteBuffer#readFloat64}. - * @function - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by `8` if omitted. - * @returns {number} - * @expose - */ - ByteBufferPrototype.readDouble = ByteBufferPrototype.readFloat64; - - - // types/varints/varint32 - - /** - * Maximum number of bytes required to store a 32bit base 128 variable-length integer. - * @type {number} - * @const - * @expose - */ - ByteBuffer.MAX_VARINT32_BYTES = 5; - - /** - * Calculates the actual number of bytes required to store 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 < 1 << 7 ) return 1; - else if (value < 1 << 14) return 2; - else if (value < 1 << 21) return 3; - else if (value < 1 << 28) return 4; - else return 5; - }; - - /** - * Zigzag 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) { - return (((n |= 0) << 1) ^ (n >> 31)) >>> 0; // ref: src/google/protobuf/wire_format_lite.h - }; - - /** - * 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) { - return ((n >>> 1) ^ -(n & 1)) | 0; // // ref: src/google/protobuf/wire_format_lite.h - }; - - /** - * Writes a 32bit base 128 variable-length integer. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written - * @expose - */ - ByteBufferPrototype.writeVarint32 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value |= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - var size = ByteBuffer.calculateVarint32(value), - b; - offset += size; - var capacity10 = this.buffer.byteLength; - if (offset > capacity10) - this.resize((capacity10 *= 2) > offset ? capacity10 : offset); - offset -= size; - // ref: http://code.google.com/searchframe#WTeibokF6gE/trunk/src/google/protobuf/io/coded_stream.cc - this.view.setUint8(offset, b = value | 0x80); - value >>>= 0; - if (value >= 1 << 7) { - b = (value >> 7) | 0x80; - this.view.setUint8(offset+1, b); - if (value >= 1 << 14) { - b = (value >> 14) | 0x80; - this.view.setUint8(offset+2, b); - if (value >= 1 << 21) { - b = (value >> 21) | 0x80; - this.view.setUint8(offset+3, b); - if (value >= 1 << 28) { - this.view.setUint8(offset+4, (value >> 28) & 0x0F); - size = 5; - } else { - this.view.setUint8(offset+3, b & 0x7F); - size = 4; - } - } else { - this.view.setUint8(offset+2, b & 0x7F); - size = 3; - } - } else { - this.view.setUint8(offset+1, b & 0x7F); - size = 2; - } - } else { - this.view.setUint8(offset, b & 0x7F); - size = 1; - } - if (relative) { - this.offset += size; - return this; - } - return size; - }; - - /** - * Writes a zig-zag encoded 32bit base 128 variable-length integer. - * @param {number} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written - * @expose - */ - ByteBufferPrototype.writeVarint32ZigZag = function(value, offset) { - return this.writeVarint32(ByteBuffer.zigZagEncode32(value), offset); - }; - - /** - * Reads a 32bit base 128 variable-length integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written 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. Has a property `truncated = true` if there is not enough data available - * to fully decode the varint. - * @expose - */ - ByteBufferPrototype.readVarint32 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - // ref: src/google/protobuf/io/coded_stream.cc - var size = 0, - value = 0 >>> 0, - temp, - ioffset; - do { - ioffset = offset+size; - if (!this.noAssert && ioffset > this.limit) { - var err = Error("Truncated"); - err['truncated'] = true; - throw err; - } - temp = this.view.getUint8(ioffset); - if (size < 5) - value |= ((temp&0x7F)<<(7*size)) >>> 0; - ++size; - } while ((temp & 0x80) === 0x80); - value = value | 0; // Make sure to discard the higher order bits - if (relative) { - this.offset += size; - return value; - } - return { - "value": value, - "length": size - }; - }; - - /** - * Reads a zig-zag encoded 32bit base 128 variable-length integer. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written 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 - */ - ByteBufferPrototype.readVarint32ZigZag = function(offset) { - var val = this.readVarint32(offset); - if (typeof val === 'object') - val["value"] = ByteBuffer.zigZagDecode32(val["value"]); - else - val = ByteBuffer.zigZagDecode32(val); - return val; - }; - - // types/varints/varint64 - - if (Long) { - - /** - * Maximum number of bytes required to store a 64bit base 128 variable-length integer. - * @type {number} - * @const - * @expose - */ - ByteBuffer.MAX_VARINT64_BYTES = 10; - - /** - * Calculates the actual number of bytes required to store 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) { - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - // ref: src/google/protobuf/io/coded_stream.cc - 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 < 1 << 14) - return part0 < 1 << 7 ? 1 : 2; - else - return part0 < 1 << 21 ? 3 : 4; - } else { - if (part1 < 1 << 14) - return part1 < 1 << 7 ? 5 : 6; - else - return part1 < 1 << 21 ? 7 : 8; - } - } else - return part2 < 1 << 7 ? 9 : 10; - }; - - /** - * Zigzag encodes a signed 64bit integer so that it can be effectively used with varint encoding. - * @param {number|!Long} value Signed long - * @returns {!Long} Unsigned zigzag encoded long - * @expose - */ - ByteBuffer.zigZagEncode64 = function(value) { - if (typeof value === 'number') - value = Long.fromNumber(value, false); - else if (typeof value === 'string') - value = Long.fromString(value, false); - else if (value.unsigned !== false) value = value.toSigned(); - // ref: src/google/protobuf/wire_format_lite.h - return value.shiftLeft(1).xor(value.shiftRight(63)).toUnsigned(); - }; - - /** - * Decodes a zigzag encoded signed 64bit integer. - * @param {!Long|number} value Unsigned zigzag encoded long or JavaScript number - * @returns {!Long} Signed long - * @expose - */ - ByteBuffer.zigZagDecode64 = function(value) { - if (typeof value === 'number') - value = Long.fromNumber(value, false); - else if (typeof value === 'string') - value = Long.fromString(value, false); - else if (value.unsigned !== false) value = value.toSigned(); - // ref: src/google/protobuf/wire_format_lite.h - return value.shiftRightUnsigned(1).xor(value.and(Long.ONE).toSigned().negate()).toSigned(); - }; - - /** - * Writes a 64bit base 128 variable-length integer. - * @param {number|Long} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written. - * @expose - */ - ByteBufferPrototype.writeVarint64 = function(value, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof value === 'number') - value = Long.fromNumber(value); - else if (typeof value === 'string') - value = Long.fromString(value); - else if (!(value && value instanceof Long)) - throw TypeError("Illegal value: "+value+" (not an integer or Long)"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - if (typeof value === 'number') - value = Long.fromNumber(value, false); - else if (typeof value === 'string') - value = Long.fromString(value, false); - else if (value.unsigned !== false) value = value.toSigned(); - var size = ByteBuffer.calculateVarint64(value), - part0 = value.toInt() >>> 0, - part1 = value.shiftRightUnsigned(28).toInt() >>> 0, - part2 = value.shiftRightUnsigned(56).toInt() >>> 0; - offset += size; - var capacity11 = this.buffer.byteLength; - if (offset > capacity11) - this.resize((capacity11 *= 2) > offset ? capacity11 : offset); - offset -= size; - switch (size) { - case 10: this.view.setUint8(offset+9, (part2 >>> 7) & 0x01); - case 9 : this.view.setUint8(offset+8, size !== 9 ? (part2 ) | 0x80 : (part2 ) & 0x7F); - case 8 : this.view.setUint8(offset+7, size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F); - case 7 : this.view.setUint8(offset+6, size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F); - case 6 : this.view.setUint8(offset+5, size !== 6 ? (part1 >>> 7) | 0x80 : (part1 >>> 7) & 0x7F); - case 5 : this.view.setUint8(offset+4, size !== 5 ? (part1 ) | 0x80 : (part1 ) & 0x7F); - case 4 : this.view.setUint8(offset+3, size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F); - case 3 : this.view.setUint8(offset+2, size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F); - case 2 : this.view.setUint8(offset+1, size !== 2 ? (part0 >>> 7) | 0x80 : (part0 >>> 7) & 0x7F); - case 1 : this.view.setUint8(offset , size !== 1 ? (part0 ) | 0x80 : (part0 ) & 0x7F); - } - if (relative) { - this.offset += size; - return this; - } else { - return size; - } - }; - - /** - * Writes a zig-zag encoded 64bit base 128 variable-length integer. - * @param {number|Long} value Value to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} `this` if offset is omitted, else the actual number of bytes written. - * @expose - */ - ByteBufferPrototype.writeVarint64ZigZag = function(value, offset) { - return this.writeVarint64(ByteBuffer.zigZagEncode64(value), offset); - }; - - /** - * Reads a 64bit base 128 variable-length integer. Requires Long.js. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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 - */ - ByteBufferPrototype.readVarint64 = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - // ref: src/google/protobuf/io/coded_stream.cc - var start = offset, - part0 = 0, - part1 = 0, - part2 = 0, - b = 0; - b = this.view.getUint8(offset++); part0 = (b & 0x7F) ; if (b & 0x80) { - b = this.view.getUint8(offset++); part0 |= (b & 0x7F) << 7; if (b & 0x80) { - b = this.view.getUint8(offset++); part0 |= (b & 0x7F) << 14; if (b & 0x80) { - b = this.view.getUint8(offset++); part0 |= (b & 0x7F) << 21; if (b & 0x80) { - b = this.view.getUint8(offset++); part1 = (b & 0x7F) ; if (b & 0x80) { - b = this.view.getUint8(offset++); part1 |= (b & 0x7F) << 7; if (b & 0x80) { - b = this.view.getUint8(offset++); part1 |= (b & 0x7F) << 14; if (b & 0x80) { - b = this.view.getUint8(offset++); part1 |= (b & 0x7F) << 21; if (b & 0x80) { - b = this.view.getUint8(offset++); part2 = (b & 0x7F) ; if (b & 0x80) { - b = this.view.getUint8(offset++); part2 |= (b & 0x7F) << 7; if (b & 0x80) { - throw Error("Buffer overrun"); }}}}}}}}}} - var value = Long.fromBits(part0 | (part1 << 28), (part1 >>> 4) | (part2) << 24, false); - if (relative) { - this.offset = offset; - return value; - } else { - return { - 'value': value, - 'length': offset-start - }; - } - }; - - /** - * Reads a zig-zag encoded 64bit base 128 variable-length integer. Requires Long.js. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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 - */ - ByteBufferPrototype.readVarint64ZigZag = function(offset) { - var val = this.readVarint64(offset); - if (val && val['value'] instanceof Long) - val["value"] = ByteBuffer.zigZagDecode64(val["value"]); - else - val = ByteBuffer.zigZagDecode64(val); - return val; - }; - - } // Long - - - // types/strings/cstring - - /** - * Writes a NULL-terminated UTF8 encoded string. For this to work the specified string must not contain any NULL - * characters itself. - * @param {string} str String to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * contained in `str` + 1 if omitted. - * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written - * @expose - */ - ByteBufferPrototype.writeCString = function(str, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - var i, - k = str.length; - if (!this.noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - for (i=0; i>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - // UTF8 strings do not contain zero bytes in between except for the zero character, so: - k = utfx.calculateUTF16asUTF8(stringSource(str))[1]; - offset += k+1; - var capacity12 = this.buffer.byteLength; - if (offset > capacity12) - this.resize((capacity12 *= 2) > offset ? capacity12 : offset); - offset -= k+1; - utfx.encodeUTF16toUTF8(stringSource(str), function(b) { - this.view.setUint8(offset++, b); - }.bind(this)); - this.view.setUint8(offset++, 0); - if (relative) { - this.offset = offset; - return this; - } - return k; - }; - - /** - * Reads a NULL-terminated UTF8 encoded string. For this to work the string read must not contain any NULL characters - * itself. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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. - * @expose - */ - ByteBufferPrototype.readCString = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - var start = offset, - temp; - // UTF8 strings do not contain zero bytes in between except for the zero character itself, so: - var sd, b = -1; - utfx.decodeUTF8toUTF16(function() { - if (b === 0) return null; - if (offset >= this.limit) - throw RangeError("Illegal range: Truncated data, "+offset+" < "+this.limit); - return (b = this.view.getUint8(offset++)) === 0 ? null : b; - }.bind(this), sd = stringDestination(), true); - if (relative) { - this.offset = offset; - return sd(); - } else { - return { - "string": sd(), - "length": offset - start - }; - } - }; - - // types/strings/istring - - /** - * Writes a length as uint32 prefixed UTF8 encoded string. - * @param {string} str String to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written - * @expose - * @see ByteBuffer#writeVarint32 - */ - ByteBufferPrototype.writeIString = function(str, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - var start = offset, - k; - k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1]; - offset += 4+k; - var capacity13 = this.buffer.byteLength; - if (offset > capacity13) - this.resize((capacity13 *= 2) > offset ? capacity13 : offset); - offset -= 4+k; - this.view.setUint32(offset, k, this.littleEndian); - offset += 4; - utfx.encodeUTF16toUTF8(stringSource(str), function(b) { - this.view.setUint8(offset++, b); - }.bind(this)); - if (offset !== start + 4 + k) - throw RangeError("Illegal range: Truncated data, "+offset+" == "+(offset+4+k)); - if (relative) { - this.offset = offset; - return this; - } - return offset - start; - }; - - /** - * Reads a length as uint32 prefixed UTF8 encoded string. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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. - * @expose - * @see ByteBuffer#readVarint32 - */ - ByteBufferPrototype.readIString = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 4 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+4+") <= "+this.buffer.byteLength); - } - var temp = 0, - start = offset, - str; - temp = this.view.getUint32(offset, this.littleEndian); - offset += 4; - var k = offset + temp, - sd; - utfx.decodeUTF8toUTF16(function() { - return offset < k ? this.view.getUint8(offset++) : null; - }.bind(this), sd = stringDestination(), this.noAssert); - str = sd(); - if (relative) { - this.offset = offset; - return str; - } else { - return { - 'string': str, - 'length': offset - start - }; - } - }; - - // types/strings/utf8string - - /** - * Metrics representing number of UTF8 characters. Evaluates to `c`. - * @type {string} - * @const - * @expose - */ - ByteBuffer.METRICS_CHARS = 'c'; - - /** - * Metrics representing number of bytes. Evaluates to `b`. - * @type {string} - * @const - * @expose - */ - ByteBuffer.METRICS_BYTES = 'b'; - - /** - * Writes an UTF8 encoded string. - * @param {string} str String to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted. - * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. - * @expose - */ - ByteBufferPrototype.writeUTF8String = function(str, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - var k; - var start = offset; - k = utfx.calculateUTF16asUTF8(stringSource(str))[1]; - offset += k; - var capacity14 = this.buffer.byteLength; - if (offset > capacity14) - this.resize((capacity14 *= 2) > offset ? capacity14 : offset); - offset -= k; - utfx.encodeUTF16toUTF8(stringSource(str), function(b) { - this.view.setUint8(offset++, b); - }.bind(this)); - if (relative) { - this.offset = offset; - return this; - } - return offset - start; - }; - - /** - * Writes an UTF8 encoded string. This is an alias of {@link ByteBuffer#writeUTF8String}. - * @function - * @param {string} str String to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} if omitted. - * @returns {!ByteBuffer|number} this if offset is omitted, else the actual number of bytes written. - * @expose - */ - ByteBufferPrototype.writeString = ByteBufferPrototype.writeUTF8String; - - /** - * Calculates the number of UTF8 characters of a string. JavaScript itself uses UTF-16, so that a string's - * `length` property does not reflect its actual UTF8 size if it contains code points larger than 0xFFFF. - * @function - * @param {string} str String to calculate - * @returns {number} Number of UTF8 characters - * @expose - */ - ByteBuffer.calculateUTF8Chars = function(str) { - return utfx.calculateUTF16asUTF8(stringSource(str))[0]; - }; - - /** - * Calculates the number of UTF8 bytes of a string. - * @function - * @param {string} str String to calculate - * @returns {number} Number of UTF8 bytes - * @expose - */ - ByteBuffer.calculateUTF8Bytes = function(str) { - return utfx.calculateUTF16asUTF8(stringSource(str))[1]; - }; - - /** - * Reads an UTF8 encoded string. - * @param {number} length Number of characters or bytes to read. - * @param {string=} metrics Metrics specifying what `length` is meant to count. Defaults to - * {@link ByteBuffer.METRICS_CHARS}. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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. - * @expose - */ - ByteBufferPrototype.readUTF8String = function(length, metrics, offset) { - if (typeof metrics === 'number') { - offset = metrics; - metrics = undefined; - } - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (typeof metrics === 'undefined') metrics = ByteBuffer.METRICS_CHARS; - if (!this.noAssert) { - if (typeof length !== 'number' || length % 1 !== 0) - throw TypeError("Illegal length: "+length+" (not an integer)"); - length |= 0; - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - var i = 0, - start = offset, - sd; - if (metrics === ByteBuffer.METRICS_CHARS) { // The same for node and the browser - sd = stringDestination(); - utfx.decodeUTF8(function() { - return i < length && offset < this.limit ? this.view.getUint8(offset++) : null; - }.bind(this), function(cp) { - ++i; utfx.UTF8toUTF16(cp, sd); - }.bind(this)); - if (i !== length) - throw RangeError("Illegal range: Truncated data, "+i+" == "+length); - if (relative) { - this.offset = offset; - return sd(); - } else { - return { - "string": sd(), - "length": offset - start - }; - } - } else if (metrics === ByteBuffer.METRICS_BYTES) { - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + length > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+length+") <= "+this.buffer.byteLength); - } - var k = offset + length; - utfx.decodeUTF8toUTF16(function() { - return offset < k ? this.view.getUint8(offset++) : null; - }.bind(this), sd = stringDestination(), this.noAssert); - if (offset !== k) - throw RangeError("Illegal range: Truncated data, "+offset+" == "+k); - if (relative) { - this.offset = offset; - return sd(); - } else { - return { - 'string': sd(), - 'length': offset - start - }; - } - } else - throw TypeError("Unsupported metrics: "+metrics); - }; - - /** - * Reads an UTF8 encoded string. This is an alias of {@link ByteBuffer#readUTF8String}. - * @function - * @param {number} length Number of characters or bytes to read - * @param {number=} metrics Metrics specifying what `n` is meant to count. Defaults to - * {@link ByteBuffer.METRICS_CHARS}. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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. - * @expose - */ - ByteBufferPrototype.readString = ByteBufferPrototype.readUTF8String; - - // types/strings/vstring - - /** - * Writes a length as varint32 prefixed UTF8 encoded string. - * @param {string} str String to write - * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. - * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written - * @expose - * @see ByteBuffer#writeVarint32 - */ - ByteBufferPrototype.writeVString = function(str, offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - var start = offset, - k, l; - k = utfx.calculateUTF16asUTF8(stringSource(str), this.noAssert)[1]; - l = ByteBuffer.calculateVarint32(k); - offset += l+k; - var capacity15 = this.buffer.byteLength; - if (offset > capacity15) - this.resize((capacity15 *= 2) > offset ? capacity15 : offset); - offset -= l+k; - offset += this.writeVarint32(k, offset); - utfx.encodeUTF16toUTF8(stringSource(str), function(b) { - this.view.setUint8(offset++, b); - }.bind(this)); - if (offset !== start+k+l) - throw RangeError("Illegal range: Truncated data, "+offset+" == "+(offset+k+l)); - if (relative) { - this.offset = offset; - return this; - } - return offset - start; - }; - - /** - * Reads a length as varint32 prefixed UTF8 encoded string. - * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read 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. - * @expose - * @see ByteBuffer#readVarint32 - */ - ByteBufferPrototype.readVString = function(offset) { - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 1 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+1+") <= "+this.buffer.byteLength); - } - var temp = this.readVarint32(offset), - start = offset, - str; - offset += temp['length']; - temp = temp['value']; - var k = offset + temp, - sd = stringDestination(); - utfx.decodeUTF8toUTF16(function() { - return offset < k ? this.view.getUint8(offset++) : null; - }.bind(this), sd, this.noAssert); - str = sd(); - if (relative) { - this.offset = offset; - return str; - } else { - return { - 'string': str, - 'length': offset - start - }; - } - }; - - - /** - * Appends some data to this ByteBuffer. This will overwrite any contents behind the specified offset up to the appended - * data's length. - * @param {!ByteBuffer|!ArrayBuffer|!Uint8Array|string} source Data to append. If `source` is a ByteBuffer, its offsets - * will be modified according to the performed read operation. - * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8") - * @param {number=} offset Offset to append at. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read if omitted. - * @returns {!ByteBuffer} this - * @expose - * @example A relative `<01 02>03.append(<04 05>)` will result in `<01 02 04 05>, 04 05|` - * @example An absolute `<01 02>03.append(04 05>, 1)` will result in `<01 04>05, 04 05|` - */ - ByteBufferPrototype.append = function(source, encoding, offset) { - if (typeof encoding === 'number' || typeof encoding !== 'string') { - offset = encoding; - encoding = undefined; - } - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - if (!(source instanceof ByteBuffer)) - source = ByteBuffer.wrap(source, encoding); - var length = source.limit - source.offset; - if (length <= 0) return this; // Nothing to append - offset += length; - var capacity16 = this.buffer.byteLength; - if (offset > capacity16) - this.resize((capacity16 *= 2) > offset ? capacity16 : offset); - offset -= length; - new Uint8Array(this.buffer, offset).set(new Uint8Array(source.buffer).subarray(source.offset, source.limit)); - source.offset += length; - if (relative) this.offset += length; - return this; - }; - - /** - * Appends this ByteBuffer's contents to another ByteBuffer. This will overwrite any contents at and after the - specified offset up to the length of this ByteBuffer's data. - * @param {!ByteBuffer} target Target ByteBuffer - * @param {number=} offset Offset to append to. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * read if omitted. - * @returns {!ByteBuffer} this - * @expose - * @see ByteBuffer#append - */ - ByteBufferPrototype.appendTo = function(target, offset) { - target.append(this, offset); - return this; - }; - - /** - * Enables or disables assertions of argument types and offsets. Assertions are enabled by default but you can opt to - * disable them if your code already makes sure that everything is valid. - * @param {boolean} assert `true` to enable assertions, otherwise `false` - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.assert = function(assert) { - this.noAssert = !assert; - return this; - }; - - /** - * Gets the capacity of this ByteBuffer's backing buffer. - * @returns {number} Capacity of the backing buffer - * @expose - */ - ByteBufferPrototype.capacity = function() { - return this.buffer.byteLength; - }; - - /** - * Clears this ByteBuffer's offsets by setting {@link ByteBuffer#offset} to `0` and {@link ByteBuffer#limit} to the - * backing buffer's capacity. Discards {@link ByteBuffer#markedOffset}. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.clear = function() { - this.offset = 0; - this.limit = this.buffer.byteLength; - this.markedOffset = -1; - return this; - }; - - /** - * Creates a cloned instance of this ByteBuffer, preset with this ByteBuffer's values for {@link ByteBuffer#offset}, - * {@link ByteBuffer#markedOffset} and {@link ByteBuffer#limit}. - * @param {boolean=} copy Whether to copy the backing buffer or to return another view on the same, defaults to `false` - * @returns {!ByteBuffer} Cloned instance - * @expose - */ - ByteBufferPrototype.clone = function(copy) { - var bb = new ByteBuffer(0, this.littleEndian, this.noAssert); - if (copy) { - var buffer = new ArrayBuffer(this.buffer.byteLength); - new Uint8Array(buffer).set(this.buffer); - bb.buffer = buffer; - bb.view = new DataView(buffer); - } else { - bb.buffer = this.buffer; - bb.view = this.view; - } - bb.offset = this.offset; - bb.markedOffset = this.markedOffset; - bb.limit = this.limit; - return bb; - }; - - /** - * Compacts this ByteBuffer to be backed by a {@link ByteBuffer#buffer} of its contents' length. Contents are the bytes - * between {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will set `offset = 0` and `limit = capacity` and - * adapt {@link ByteBuffer#markedOffset} to the same relative position if set. - * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset} - * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit} - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.compact = function(begin, end) { - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - if (begin === 0 && end === this.buffer.byteLength) - return this; // Already compacted - var len = end - begin; - if (len === 0) { - this.buffer = EMPTY_BUFFER; - this.view = null; - if (this.markedOffset >= 0) this.markedOffset -= begin; - this.offset = 0; - this.limit = 0; - return this; - } - var buffer = new ArrayBuffer(len); - new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(begin, end)); - this.buffer = buffer; - this.view = new DataView(buffer); - if (this.markedOffset >= 0) this.markedOffset -= begin; - this.offset = 0; - this.limit = len; - return this; - }; - - /** - * Creates a copy of this ByteBuffer's contents. Contents are the bytes between {@link ByteBuffer#offset} and - * {@link ByteBuffer#limit}. - * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}. - * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. - * @returns {!ByteBuffer} Copy - * @expose - */ - ByteBufferPrototype.copy = function(begin, end) { - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - if (begin === end) - return new ByteBuffer(0, this.littleEndian, this.noAssert); - var capacity = end - begin, - bb = new ByteBuffer(capacity, this.littleEndian, this.noAssert); - bb.offset = 0; - bb.limit = capacity; - if (bb.markedOffset >= 0) bb.markedOffset -= begin; - this.copyTo(bb, 0, begin, end); - return bb; - }; - - /** - * Copies this ByteBuffer's contents to another ByteBuffer. Contents are the bytes between {@link ByteBuffer#offset} and - * {@link ByteBuffer#limit}. - * @param {!ByteBuffer} target Target ByteBuffer - * @param {number=} targetOffset Offset to copy to. Will use and increase the target's {@link ByteBuffer#offset} - * by the number of bytes copied if omitted. - * @param {number=} sourceOffset Offset to start copying from. Will use and increase {@link ByteBuffer#offset} by the - * number of bytes copied if omitted. - * @param {number=} sourceLimit Offset to end copying from, defaults to {@link ByteBuffer#limit} - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.copyTo = function(target, targetOffset, sourceOffset, sourceLimit) { - var relative, - targetRelative; - if (!this.noAssert) { - if (!ByteBuffer.isByteBuffer(target)) - throw TypeError("Illegal target: Not a ByteBuffer"); - } - targetOffset = (targetRelative = typeof targetOffset === 'undefined') ? target.offset : targetOffset | 0; - sourceOffset = (relative = typeof sourceOffset === 'undefined') ? this.offset : sourceOffset | 0; - sourceLimit = typeof sourceLimit === 'undefined' ? this.limit : sourceLimit | 0; - - if (targetOffset < 0 || targetOffset > target.buffer.byteLength) - throw RangeError("Illegal target range: 0 <= "+targetOffset+" <= "+target.buffer.byteLength); - if (sourceOffset < 0 || sourceLimit > this.buffer.byteLength) - throw RangeError("Illegal source range: 0 <= "+sourceOffset+" <= "+this.buffer.byteLength); - - var len = sourceLimit - sourceOffset; - if (len === 0) - return target; // Nothing to copy - - target.ensureCapacity(targetOffset + len); - - new Uint8Array(target.buffer).set(new Uint8Array(this.buffer).subarray(sourceOffset, sourceLimit), targetOffset); - - if (relative) this.offset += len; - if (targetRelative) target.offset += len; - - return this; - }; - - /** - * Makes sure that this ByteBuffer is backed by a {@link ByteBuffer#buffer} of at least the specified capacity. If the - * current capacity is exceeded, it will be doubled. If double the current capacity is less than the required capacity, - * the required capacity will be used instead. - * @param {number} capacity Required capacity - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.ensureCapacity = function(capacity) { - var current = this.buffer.byteLength; - if (current < capacity) - return this.resize((current *= 2) > capacity ? current : capacity); - return this; - }; - - /** - * Overwrites this ByteBuffer's contents with the specified value. Contents are the bytes between - * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. - * @param {number|string} value Byte value to fill with. If given as a string, the first character is used. - * @param {number=} begin Begin offset. Will use and increase {@link ByteBuffer#offset} by the number of bytes - * written if omitted. defaults to {@link ByteBuffer#offset}. - * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. - * @returns {!ByteBuffer} this - * @expose - * @example `someByteBuffer.clear().fill(0)` fills the entire backing buffer with zeroes - */ - ByteBufferPrototype.fill = function(value, begin, end) { - var relative = typeof begin === 'undefined'; - if (relative) begin = this.offset; - if (typeof value === 'string' && value.length > 0) - value = value.charCodeAt(0); - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof value !== 'number' || value % 1 !== 0) - throw TypeError("Illegal value: "+value+" (not an integer)"); - value |= 0; - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - if (begin >= end) - return this; // Nothing to fill - while (begin < end) this.view.setUint8(begin++, value); - if (relative) this.offset = begin; - return this; - }; - - /** - * Makes this ByteBuffer ready for a new sequence of write or relative read operations. Sets `limit = offset` and - * `offset = 0`. Make sure always to flip a ByteBuffer when all relative read or write operations are complete. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.flip = function() { - this.limit = this.offset; - this.offset = 0; - return this; - }; - /** - * Marks an offset on this ByteBuffer to be used later. - * @param {number=} offset Offset to mark. Defaults to {@link ByteBuffer#offset}. - * @returns {!ByteBuffer} this - * @throws {TypeError} If `offset` is not a valid number - * @throws {RangeError} If `offset` is out of bounds - * @see ByteBuffer#reset - * @expose - */ - ByteBufferPrototype.mark = function(offset) { - offset = typeof offset === 'undefined' ? this.offset : offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - this.markedOffset = offset; - return this; - }; - /** - * Sets the byte order. - * @param {boolean} littleEndian `true` for little endian byte order, `false` for big endian - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.order = function(littleEndian) { - if (!this.noAssert) { - if (typeof littleEndian !== 'boolean') - throw TypeError("Illegal littleEndian: Not a boolean"); - } - this.littleEndian = !!littleEndian; - return this; - }; - - /** - * Switches (to) little endian byte order. - * @param {boolean=} littleEndian Defaults to `true`, otherwise uses big endian - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.LE = function(littleEndian) { - this.littleEndian = typeof littleEndian !== 'undefined' ? !!littleEndian : true; - return this; - }; - - /** - * Switches (to) big endian byte order. - * @param {boolean=} bigEndian Defaults to `true`, otherwise uses little endian - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.BE = function(bigEndian) { - this.littleEndian = typeof bigEndian !== 'undefined' ? !bigEndian : false; - return this; - }; - /** - * Prepends some data to this ByteBuffer. This will overwrite any contents before the specified offset up to the - * prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer - * will be resized and its contents moved accordingly. - * @param {!ByteBuffer|string|!ArrayBuffer} source Data to prepend. If `source` is a ByteBuffer, its offset will be - * modified according to the performed read operation. - * @param {(string|number)=} encoding Encoding if `data` is a string ("base64", "hex", "binary", defaults to "utf8") - * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes - * prepended if omitted. - * @returns {!ByteBuffer} this - * @expose - * @example A relative `00<01 02 03>.prepend(<04 05>)` results in `<04 05 01 02 03>, 04 05|` - * @example An absolute `00<01 02 03>.prepend(<04 05>, 2)` results in `04<05 02 03>, 04 05|` - */ - ByteBufferPrototype.prepend = function(source, encoding, offset) { - if (typeof encoding === 'number' || typeof encoding !== 'string') { - offset = encoding; - encoding = undefined; - } - var relative = typeof offset === 'undefined'; - if (relative) offset = this.offset; - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: "+offset+" (not an integer)"); - offset >>>= 0; - if (offset < 0 || offset + 0 > this.buffer.byteLength) - throw RangeError("Illegal offset: 0 <= "+offset+" (+"+0+") <= "+this.buffer.byteLength); - } - if (!(source instanceof ByteBuffer)) - source = ByteBuffer.wrap(source, encoding); - var len = source.limit - source.offset; - if (len <= 0) return this; // Nothing to prepend - var diff = len - offset; - var arrayView; - if (diff > 0) { // Not enough space before offset, so resize + move - var buffer = new ArrayBuffer(this.buffer.byteLength + diff); - arrayView = new Uint8Array(buffer); - arrayView.set(new Uint8Array(this.buffer).subarray(offset, this.buffer.byteLength), len); - this.buffer = buffer; - this.view = new DataView(buffer); - this.offset += diff; - if (this.markedOffset >= 0) this.markedOffset += diff; - this.limit += diff; - offset += diff; - } else { - arrayView = new Uint8Array(this.buffer); - } - arrayView.set(new Uint8Array(source.buffer).subarray(source.offset, source.limit), offset - len); - source.offset = source.limit; - if (relative) - this.offset -= len; - return this; - }; - - /** - * Prepends this ByteBuffer to another ByteBuffer. This will overwrite any contents before the specified offset up to the - * prepended data's length. If there is not enough space available before the specified `offset`, the backing buffer - * will be resized and its contents moved accordingly. - * @param {!ByteBuffer} target Target ByteBuffer - * @param {number=} offset Offset to prepend at. Will use and decrease {@link ByteBuffer#offset} by the number of bytes - * prepended if omitted. - * @returns {!ByteBuffer} this - * @expose - * @see ByteBuffer#prepend - */ - ByteBufferPrototype.prependTo = function(target, offset) { - target.prepend(this, offset); - return this; - }; - /** - * Prints debug information about this ByteBuffer's contents. - * @param {function(string)=} out Output function to call, defaults to console.log - * @expose - */ - ByteBufferPrototype.printDebug = function(out) { - if (typeof out !== 'function') out = console.log.bind(console); - out( - this.toString()+"\n"+ - "-------------------------------------------------------------------\n"+ - this.toDebug(/* columns */ true) - ); - }; - - /** - * Gets the number of remaining readable bytes. Contents are the bytes between {@link ByteBuffer#offset} and - * {@link ByteBuffer#limit}, so this returns `limit - offset`. - * @returns {number} Remaining readable bytes. May be negative if `offset > limit`. - * @expose - */ - ByteBufferPrototype.remaining = function() { - return this.limit - this.offset; - }; - /** - * Resets this ByteBuffer's {@link ByteBuffer#offset}. If an offset has been marked through {@link ByteBuffer#mark} - * before, `offset` will be set to {@link ByteBuffer#markedOffset}, which will then be discarded. If no offset has been - * marked, sets `offset = 0`. - * @returns {!ByteBuffer} this - * @see ByteBuffer#mark - * @expose - */ - ByteBufferPrototype.reset = function() { - if (this.markedOffset >= 0) { - this.offset = this.markedOffset; - this.markedOffset = -1; - } else { - this.offset = 0; - } - return this; - }; - /** - * Resizes this ByteBuffer to be backed by a buffer of at least the given capacity. Will do nothing if already that - * large or larger. - * @param {number} capacity Capacity required - * @returns {!ByteBuffer} this - * @throws {TypeError} If `capacity` is not a number - * @throws {RangeError} If `capacity < 0` - * @expose - */ - ByteBufferPrototype.resize = function(capacity) { - if (!this.noAssert) { - if (typeof capacity !== 'number' || capacity % 1 !== 0) - throw TypeError("Illegal capacity: "+capacity+" (not an integer)"); - capacity |= 0; - if (capacity < 0) - throw RangeError("Illegal capacity: 0 <= "+capacity); - } - if (this.buffer.byteLength < capacity) { - var buffer = new ArrayBuffer(capacity); - new Uint8Array(buffer).set(new Uint8Array(this.buffer)); - this.buffer = buffer; - this.view = new DataView(buffer); - } - return this; - }; - /** - * Reverses this ByteBuffer's contents. - * @param {number=} begin Offset to start at, defaults to {@link ByteBuffer#offset} - * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit} - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.reverse = function(begin, end) { - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - if (begin === end) - return this; // Nothing to reverse - Array.prototype.reverse.call(new Uint8Array(this.buffer).subarray(begin, end)); - this.view = new DataView(this.buffer); // FIXME: Why exactly is this necessary? - return this; - }; - /** - * Skips the next `length` bytes. This will just advance - * @param {number} length Number of bytes to skip. May also be negative to move the offset back. - * @returns {!ByteBuffer} this - * @expose - */ - ByteBufferPrototype.skip = function(length) { - if (!this.noAssert) { - if (typeof length !== 'number' || length % 1 !== 0) - throw TypeError("Illegal length: "+length+" (not an integer)"); - length |= 0; - } - var offset = this.offset + length; - if (!this.noAssert) { - if (offset < 0 || offset > this.buffer.byteLength) - throw RangeError("Illegal length: 0 <= "+this.offset+" + "+length+" <= "+this.buffer.byteLength); - } - this.offset = offset; - return this; - }; - - /** - * Slices this ByteBuffer by creating a cloned instance with `offset = begin` and `limit = end`. - * @param {number=} begin Begin offset, defaults to {@link ByteBuffer#offset}. - * @param {number=} end End offset, defaults to {@link ByteBuffer#limit}. - * @returns {!ByteBuffer} Clone of this ByteBuffer with slicing applied, backed by the same {@link ByteBuffer#buffer} - * @expose - */ - ByteBufferPrototype.slice = function(begin, end) { - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - var bb = this.clone(); - bb.offset = begin; - bb.limit = end; - return bb; - }; - /** - * Returns a copy of the backing buffer that contains this ByteBuffer's contents. Contents are the bytes between - * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will transparently {@link ByteBuffer#flip} this - * ByteBuffer if `offset > limit` but the actual offsets remain untouched. - * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory if - * possible. Defaults to `false` - * @returns {!ArrayBuffer} Contents as an ArrayBuffer - * @expose - */ - ByteBufferPrototype.toBuffer = function(forceCopy) { - var offset = this.offset, - limit = this.limit; - if (offset > limit) { - var t = offset; - offset = limit; - limit = t; - } - if (!this.noAssert) { - if (typeof offset !== 'number' || offset % 1 !== 0) - throw TypeError("Illegal offset: Not an integer"); - offset >>>= 0; - if (typeof limit !== 'number' || limit % 1 !== 0) - throw TypeError("Illegal limit: Not an integer"); - limit >>>= 0; - if (offset < 0 || offset > limit || limit > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+offset+" <= "+limit+" <= "+this.buffer.byteLength); - } - // NOTE: It's not possible to have another ArrayBuffer reference the same memory as the backing buffer. This is - // possible with Uint8Array#subarray only, but we have to return an ArrayBuffer by contract. So: - if (!forceCopy && offset === 0 && limit === this.buffer.byteLength) { - return this.buffer; - } - if (offset === limit) { - return EMPTY_BUFFER; - } - var buffer = new ArrayBuffer(limit - offset); - new Uint8Array(buffer).set(new Uint8Array(this.buffer).subarray(offset, limit), 0); - return buffer; - }; - - /** - * Returns a raw buffer compacted to contain this ByteBuffer's contents. Contents are the bytes between - * {@link ByteBuffer#offset} and {@link ByteBuffer#limit}. Will transparently {@link ByteBuffer#flip} this - * ByteBuffer if `offset > limit` but the actual offsets remain untouched. This is an alias of - * {@link ByteBuffer#toBuffer}. - * @function - * @param {boolean=} forceCopy If `true` returns a copy, otherwise returns a view referencing the same memory. - * Defaults to `false` - * @returns {!ArrayBuffer} Contents as an ArrayBuffer - * @expose - */ - ByteBufferPrototype.toArrayBuffer = ByteBufferPrototype.toBuffer; - - - /** - * Converts the ByteBuffer's contents to a string. - * @param {string=} encoding Output encoding. Returns an informative string representation if omitted but also allows - * direct conversion to "utf8", "hex", "base64" and "binary" encoding. "debug" returns a hex representation with - * highlighted offsets. - * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset} - * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit} - * @returns {string} String representation - * @throws {Error} If `encoding` is invalid - * @expose - */ - ByteBufferPrototype.toString = function(encoding, begin, end) { - if (typeof encoding === 'undefined') - return "ByteBufferAB(offset="+this.offset+",markedOffset="+this.markedOffset+",limit="+this.limit+",capacity="+this.capacity()+")"; - if (typeof encoding === 'number') - encoding = "utf8", - begin = encoding, - end = begin; - switch (encoding) { - case "utf8": - return this.toUTF8(begin, end); - case "base64": - return this.toBase64(begin, end); - case "hex": - return this.toHex(begin, end); - case "binary": - return this.toBinary(begin, end); - case "debug": - return this.toDebug(); - case "columns": - return this.toColumns(); - default: - throw Error("Unsupported encoding: "+encoding); - } - }; - - // lxiv-embeddable - - /** - * lxiv-embeddable (c) 2014 Daniel Wirtz - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/lxiv for details - */ - var lxiv = function() { - "use strict"; - - /** - * lxiv namespace. - * @type {!Object.} - * @exports lxiv - */ - var lxiv = {}; - - /** - * Character codes for output. - * @type {!Array.} - * @inner - */ - var aout = [ - 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, - 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, - 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 - ]; - - /** - * Character codes for input. - * @type {!Array.} - * @inner - */ - var ain = []; - for (var i=0, k=aout.length; i>2)&0x3f]); - t = (b&0x3)<<4; - if ((b = src()) !== null) { - t |= (b>>4)&0xf; - dst(aout[(t|((b>>4)&0xf))&0x3f]); - t = (b&0xf)<<2; - if ((b = src()) !== null) - dst(aout[(t|((b>>6)&0x3))&0x3f]), - dst(aout[b&0x3f]); - else - dst(aout[t&0x3f]), - dst(61); - } else - dst(aout[t&0x3f]), - dst(61), - dst(61); - } - }; - - /** - * Decodes base64 char codes to bytes. - * @param {!function():number|null} src Characters source as a function returning the next char code respectively - * `null` if there are no more characters left. - * @param {!function(number)} dst Bytes destination as a function successively called with the next byte. - * @throws {Error} If a character code is invalid - */ - lxiv.decode = function(src, dst) { - var c, t1, t2; - function fail(c) { - throw Error("Illegal character code: "+c); - } - while ((c = src()) !== null) { - t1 = ain[c]; - if (typeof t1 === 'undefined') fail(c); - if ((c = src()) !== null) { - t2 = ain[c]; - if (typeof t2 === 'undefined') fail(c); - dst((t1<<2)>>>0|(t2&0x30)>>4); - if ((c = src()) !== null) { - t1 = ain[c]; - if (typeof t1 === 'undefined') - if (c === 61) break; else fail(c); - dst(((t2&0xf)<<4)>>>0|(t1&0x3c)>>2); - if ((c = src()) !== null) { - t2 = ain[c]; - if (typeof t2 === 'undefined') - if (c === 61) break; else fail(c); - dst(((t1&0x3)<<6)>>>0|t2); - } - } - } - } - }; - - /** - * Tests if a string is valid base64. - * @param {string} str String to test - * @returns {boolean} `true` if valid, otherwise `false` - */ - lxiv.test = function(str) { - return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(str); - }; - - return lxiv; - }(); - - // encodings/base64 - - /** - * Encodes this ByteBuffer's contents to a base64 encoded string. - * @param {number=} begin Offset to begin at, defaults to {@link ByteBuffer#offset}. - * @param {number=} end Offset to end at, defaults to {@link ByteBuffer#limit}. - * @returns {string} Base64 encoded string - * @expose - */ - ByteBufferPrototype.toBase64 = function(begin, end) { - if (typeof begin === 'undefined') - begin = this.offset; - if (typeof end === 'undefined') - end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - var sd; lxiv.encode(function() { - return begin < end ? this.view.getUint8(begin++) : null; - }.bind(this), sd = stringDestination()); - return sd(); - }; - - /** - * Decodes a base64 encoded string to a ByteBuffer. - * @param {string} str String to decode - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} ByteBuffer - * @expose - */ - ByteBuffer.fromBase64 = function(str, littleEndian, noAssert) { - if (!noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - if (str.length % 4 !== 0) - throw TypeError("Illegal str: Length not a multiple of 4"); - } - var bb = new ByteBuffer(str.length/4*3, littleEndian, noAssert), - i = 0; - lxiv.decode(stringSource(str), function(b) { - bb.view.setUint8(i++, b); - }); - bb.limit = i; - return bb; - }; - - /** - * Encodes a binary string to base64 like `window.btoa` does. - * @param {string} str Binary string - * @returns {string} Base64 encoded string - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.btoa - * @expose - */ - ByteBuffer.btoa = function(str) { - return ByteBuffer.fromBinary(str).toBase64(); - }; - - /** - * Decodes a base64 encoded string to binary like `window.atob` does. - * @param {string} b64 Base64 encoded string - * @returns {string} Binary string - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.atob - * @expose - */ - ByteBuffer.atob = function(b64) { - return ByteBuffer.fromBase64(b64).toBinary(); - }; - - // encodings/binary - - /** - * Encodes this ByteBuffer to a binary encoded string, that is using only characters 0x00-0xFF as bytes. - * @param {number=} begin Offset to begin at. Defaults to {@link ByteBuffer#offset}. - * @param {number=} end Offset to end at. Defaults to {@link ByteBuffer#limit}. - * @returns {string} Binary encoded string - * @throws {RangeError} If `offset > limit` - * @expose - */ - ByteBufferPrototype.toBinary = function(begin, end) { - begin = typeof begin === 'undefined' ? this.offset : begin; - end = typeof end === 'undefined' ? this.limit : end; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - if (begin === end) - return ""; - var cc = [], pt = []; - while (begin < end) { - cc.push(this.view.getUint8(begin++)); - if (cc.length >= 1024) - pt.push(String.fromCharCode.apply(String, cc)), - cc = []; - } - return pt.join('') + String.fromCharCode.apply(String, cc); - }; - - /** - * Decodes a binary encoded string, that is using only characters 0x00-0xFF as bytes, to a ByteBuffer. - * @param {string} str String to decode - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} ByteBuffer - * @expose - */ - ByteBuffer.fromBinary = function(str, littleEndian, noAssert) { - if (!noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - } - var i = 0, k = str.length, charCode, - bb = new ByteBuffer(k, littleEndian, noAssert); - while (i 255) - throw RangeError("Illegal charCode at "+i+": 0 <= "+charCode+" <= 255"); - bb.view.setUint8(i++, charCode); - } - bb.limit = k; - return bb; - }; - - // encodings/debug - - /** - * Encodes this ByteBuffer to a hex encoded string with marked offsets. Offset symbols are: - * * `<` : offset, - * * `'` : markedOffset, - * * `>` : limit, - * * `|` : offset and limit, - * * `[` : offset and markedOffset, - * * `]` : markedOffset and limit, - * * `!` : offset, markedOffset and limit - * @param {boolean=} columns If `true` returns two columns hex + ascii, defaults to `false` - * @returns {string|!Array.} Debug string or array of lines if `asArray = true` - * @expose - * @example `>00'01 02<03` contains four bytes with `limit=0, markedOffset=1, offset=3` - * @example `00[01 02 03>` contains four bytes with `offset=markedOffset=1, limit=4` - * @example `00|01 02 03` contains four bytes with `offset=limit=1, markedOffset=-1` - * @example `|` contains zero bytes with `offset=limit=0, markedOffset=-1` - */ - ByteBufferPrototype.toDebug = function(columns) { - var i = -1, - k = this.buffer.byteLength, - b, - hex = "", - asc = "", - out = ""; - while (i 32 && b < 127 ? String.fromCharCode(b) : '.'; - } - } - ++i; - if (columns) { - if (i > 0 && i % 16 === 0 && i !== k) { - while (hex.length < 3*16+3) hex += " "; - out += hex+asc+"\n"; - hex = asc = ""; - } - } - if (i === this.offset && i === this.limit) - hex += i === this.markedOffset ? "!" : "|"; - else if (i === this.offset) - hex += i === this.markedOffset ? "[" : "<"; - else if (i === this.limit) - hex += i === this.markedOffset ? "]" : ">"; - else - hex += i === this.markedOffset ? "'" : (columns || (i !== 0 && i !== k) ? " " : ""); - } - if (columns && hex !== " ") { - while (hex.length < 3*16+3) hex += " "; - out += hex+asc+"\n"; - } - return columns ? out : hex; - }; - - /** - * Decodes a hex encoded string with marked offsets to a ByteBuffer. - * @param {string} str Debug string to decode (not be generated with `columns = true`) - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} ByteBuffer - * @expose - * @see ByteBuffer#toDebug - */ - ByteBuffer.fromDebug = function(str, littleEndian, noAssert) { - var k = str.length, - bb = new ByteBuffer(((k+1)/3)|0, littleEndian, noAssert); - var i = 0, j = 0, ch, b, - rs = false, // Require symbol next - ho = false, hm = false, hl = false, // Already has offset, markedOffset, limit? - fail = false; - while (i': - if (!noAssert) { - if (hl) { - fail = true; break; - } - hl = true; - } - bb.limit = j; - rs = false; - break; - case "'": - if (!noAssert) { - if (hm) { - fail = true; break; - } - hm = true; - } - bb.markedOffset = j; - rs = false; - break; - case ' ': - rs = false; - break; - default: - if (!noAssert) { - if (rs) { - fail = true; break; - } - } - b = parseInt(ch+str.charAt(i++), 16); - if (!noAssert) { - if (isNaN(b) || b < 0 || b > 255) - throw TypeError("Illegal str: Not a debug encoded string"); - } - bb.view.setUint8(j++, b); - rs = true; - } - if (fail) - throw TypeError("Illegal str: Invalid symbol at "+i); - } - if (!noAssert) { - if (!ho || !hl) - throw TypeError("Illegal str: Missing offset or limit"); - if (j>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - var out = new Array(end - begin), - b; - while (begin < end) { - b = this.view.getUint8(begin++); - if (b < 0x10) - out.push("0", b.toString(16)); - else out.push(b.toString(16)); - } - return out.join(''); - }; - - /** - * Decodes a hex encoded string to a ByteBuffer. - * @param {string} str String to decode - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} ByteBuffer - * @expose - */ - ByteBuffer.fromHex = function(str, littleEndian, noAssert) { - if (!noAssert) { - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - if (str.length % 2 !== 0) - throw TypeError("Illegal str: Length not a multiple of 2"); - } - var k = str.length, - bb = new ByteBuffer((k / 2) | 0, littleEndian), - b; - for (var i=0, j=0; i 255) - throw TypeError("Illegal str: Contains non-hex characters"); - bb.view.setUint8(j++, b); - } - bb.limit = j; - return bb; - }; - - // utfx-embeddable - - /** - * utfx-embeddable (c) 2014 Daniel Wirtz - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/utfx for details - */ - var utfx = function() { - "use strict"; - - /** - * utfx namespace. - * @inner - * @type {!Object.} - */ - var utfx = {}; - - /** - * Maximum valid code point. - * @type {number} - * @const - */ - utfx.MAX_CODEPOINT = 0x10FFFF; - - /** - * Encodes UTF8 code points to UTF8 bytes. - * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point - * respectively `null` if there are no more code points left or a single numeric code point. - * @param {!function(number)} dst Bytes destination as a function successively called with the next byte - */ - utfx.encodeUTF8 = function(src, dst) { - var cp = null; - if (typeof src === 'number') - cp = src, - src = function() { return null; }; - while (cp !== null || (cp = src()) !== null) { - if (cp < 0x80) - dst(cp&0x7F); - else if (cp < 0x800) - dst(((cp>>6)&0x1F)|0xC0), - dst((cp&0x3F)|0x80); - else if (cp < 0x10000) - dst(((cp>>12)&0x0F)|0xE0), - dst(((cp>>6)&0x3F)|0x80), - dst((cp&0x3F)|0x80); - else - dst(((cp>>18)&0x07)|0xF0), - dst(((cp>>12)&0x3F)|0x80), - dst(((cp>>6)&0x3F)|0x80), - dst((cp&0x3F)|0x80); - cp = null; - } - }; - - /** - * Decodes UTF8 bytes to UTF8 code points. - * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there - * are no more bytes left. - * @param {!function(number)} dst Code points destination as a function successively called with each decoded code point. - * @throws {RangeError} If a starting byte is invalid in UTF8 - * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the - * remaining bytes. - */ - utfx.decodeUTF8 = function(src, dst) { - var a, b, c, d, fail = function(b) { - b = b.slice(0, b.indexOf(null)); - var err = Error(b.toString()); - err.name = "TruncatedError"; - err['bytes'] = b; - throw err; - }; - while ((a = src()) !== null) { - if ((a&0x80) === 0) - dst(a); - else if ((a&0xE0) === 0xC0) - ((b = src()) === null) && fail([a, b]), - dst(((a&0x1F)<<6) | (b&0x3F)); - else if ((a&0xF0) === 0xE0) - ((b=src()) === null || (c=src()) === null) && fail([a, b, c]), - dst(((a&0x0F)<<12) | ((b&0x3F)<<6) | (c&0x3F)); - else if ((a&0xF8) === 0xF0) - ((b=src()) === null || (c=src()) === null || (d=src()) === null) && fail([a, b, c ,d]), - dst(((a&0x07)<<18) | ((b&0x3F)<<12) | ((c&0x3F)<<6) | (d&0x3F)); - else throw RangeError("Illegal starting byte: "+a); - } - }; - - /** - * Converts UTF16 characters to UTF8 code points. - * @param {!function():number|null} src Characters source as a function returning the next char code respectively - * `null` if there are no more characters left. - * @param {!function(number)} dst Code points destination as a function successively called with each converted code - * point. - */ - utfx.UTF16toUTF8 = function(src, dst) { - var c1, c2 = null; - while (true) { - if ((c1 = c2 !== null ? c2 : src()) === null) - break; - if (c1 >= 0xD800 && c1 <= 0xDFFF) { - if ((c2 = src()) !== null) { - if (c2 >= 0xDC00 && c2 <= 0xDFFF) { - dst((c1-0xD800)*0x400+c2-0xDC00+0x10000); - c2 = null; continue; - } - } - } - dst(c1); - } - if (c2 !== null) dst(c2); - }; - - /** - * Converts UTF8 code points to UTF16 characters. - * @param {(!function():number|null) | number} src Code points source, either as a function returning the next code point - * respectively `null` if there are no more code points left or a single numeric code point. - * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. - * @throws {RangeError} If a code point is out of range - */ - utfx.UTF8toUTF16 = function(src, dst) { - var cp = null; - if (typeof src === 'number') - cp = src, src = function() { return null; }; - while (cp !== null || (cp = src()) !== null) { - if (cp <= 0xFFFF) - dst(cp); - else - cp -= 0x10000, - dst((cp>>10)+0xD800), - dst((cp%0x400)+0xDC00); - cp = null; - } - }; - - /** - * Converts and encodes UTF16 characters to UTF8 bytes. - * @param {!function():number|null} src Characters source as a function returning the next char code respectively `null` - * if there are no more characters left. - * @param {!function(number)} dst Bytes destination as a function successively called with the next byte. - */ - utfx.encodeUTF16toUTF8 = function(src, dst) { - utfx.UTF16toUTF8(src, function(cp) { - utfx.encodeUTF8(cp, dst); - }); - }; - - /** - * Decodes and converts UTF8 bytes to UTF16 characters. - * @param {!function():number|null} src Bytes source as a function returning the next byte respectively `null` if there - * are no more bytes left. - * @param {!function(number)} dst Characters destination as a function successively called with each converted char code. - * @throws {RangeError} If a starting byte is invalid in UTF8 - * @throws {Error} If the last sequence is truncated. Has an array property `bytes` holding the remaining bytes. - */ - utfx.decodeUTF8toUTF16 = function(src, dst) { - utfx.decodeUTF8(src, function(cp) { - utfx.UTF8toUTF16(cp, dst); - }); - }; - - /** - * Calculates the byte length of an UTF8 code point. - * @param {number} cp UTF8 code point - * @returns {number} Byte length - */ - utfx.calculateCodePoint = function(cp) { - return (cp < 0x80) ? 1 : (cp < 0x800) ? 2 : (cp < 0x10000) ? 3 : 4; - }; - - /** - * Calculates the number of UTF8 bytes required to store UTF8 code points. - * @param {(!function():number|null)} src Code points source as a function returning the next code point respectively - * `null` if there are no more code points left. - * @returns {number} The number of UTF8 bytes required - */ - utfx.calculateUTF8 = function(src) { - var cp, l=0; - while ((cp = src()) !== null) - l += utfx.calculateCodePoint(cp); - return l; - }; - - /** - * Calculates the number of UTF8 code points respectively UTF8 bytes required to store UTF16 char codes. - * @param {(!function():number|null)} src Characters source as a function returning the next char code respectively - * `null` if there are no more characters left. - * @returns {!Array.} The number of UTF8 code points at index 0 and the number of UTF8 bytes required at index 1. - */ - utfx.calculateUTF16asUTF8 = function(src) { - var n=0, l=0; - utfx.UTF16toUTF8(src, function(cp) { - ++n; l += utfx.calculateCodePoint(cp); - }); - return [n,l]; - }; - - return utfx; - }(); - - // encodings/utf8 - - /** - * Encodes this ByteBuffer's contents between {@link ByteBuffer#offset} and {@link ByteBuffer#limit} to an UTF8 encoded - * string. - * @returns {string} Hex encoded string - * @throws {RangeError} If `offset > limit` - * @expose - */ - ByteBufferPrototype.toUTF8 = function(begin, end) { - if (typeof begin === 'undefined') begin = this.offset; - if (typeof end === 'undefined') end = this.limit; - if (!this.noAssert) { - if (typeof begin !== 'number' || begin % 1 !== 0) - throw TypeError("Illegal begin: Not an integer"); - begin >>>= 0; - if (typeof end !== 'number' || end % 1 !== 0) - throw TypeError("Illegal end: Not an integer"); - end >>>= 0; - if (begin < 0 || begin > end || end > this.buffer.byteLength) - throw RangeError("Illegal range: 0 <= "+begin+" <= "+end+" <= "+this.buffer.byteLength); - } - var sd; try { - utfx.decodeUTF8toUTF16(function() { - return begin < end ? this.view.getUint8(begin++) : null; - }.bind(this), sd = stringDestination()); - } catch (e) { - if (begin !== end) - throw RangeError("Illegal range: Truncated data, "+begin+" != "+end); - } - return sd(); - }; - - /** - * Decodes an UTF8 encoded string to a ByteBuffer. - * @param {string} str String to decode - * @param {boolean=} littleEndian Whether to use little or big endian byte order. Defaults to - * {@link ByteBuffer.DEFAULT_ENDIAN}. - * @param {boolean=} noAssert Whether to skip assertions of offsets and values. Defaults to - * {@link ByteBuffer.DEFAULT_NOASSERT}. - * @returns {!ByteBuffer} ByteBuffer - * @expose - */ - ByteBuffer.fromUTF8 = function(str, littleEndian, noAssert) { - if (!noAssert) - if (typeof str !== 'string') - throw TypeError("Illegal str: Not a string"); - var bb = new ByteBuffer(utfx.calculateUTF16asUTF8(stringSource(str), true)[1], littleEndian, noAssert), - i = 0; - utfx.encodeUTF16toUTF8(stringSource(str), function(b) { - bb.view.setUint8(i++, b); - }); - bb.limit = i; - return bb; - }; - - - return ByteBuffer; - } - - /* CommonJS */ if (typeof require === 'function' && typeof module === 'object' && module && typeof exports === 'object' && exports) - module['exports'] = (function() { - var Long; try { Long = require("long"); } catch (e) {} - return loadByteBuffer(Long); - })(); - /* AMD */ else if (typeof define === 'function' && define["amd"]) - define("ByteBuffer", ["Long"], function(Long) { return loadByteBuffer(Long); }); - /* Global */ else - (global["dcodeIO"] = global["dcodeIO"] || {})["ByteBuffer"] = loadByteBuffer(global["dcodeIO"]["Long"]); - -})(this); diff --git a/components/long/dist/Long.js b/components/long/dist/Long.js deleted file mode 100644 index 555485a7ed..0000000000 --- a/components/long/dist/Long.js +++ /dev/null @@ -1,1209 +0,0 @@ -/* - Copyright 2013 Daniel Wirtz - Copyright 2009 The Closure Library Authors. All Rights Reserved. - - 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 long.js (c) 2013 Daniel Wirtz - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/long.js for details - */ -(function(global, factory) { - - /* AMD */ if (typeof define === 'function' && define["amd"]) - define([], factory); - /* CommonJS */ else if (typeof require === 'function' && typeof module === "object" && module && module["exports"]) - module["exports"] = factory(); - /* Global */ else - (global["dcodeIO"] = global["dcodeIO"] || {})["Long"] = factory(); - -})(this, function() { - "use strict"; - - /** - * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. - * See the from* functions below for more convenient ways of constructing Longs. - * @exports Long - * @class A Long class for representing a 64 bit two's-complement integer value. - * @param {number} low The low (signed) 32 bits of the long - * @param {number} high The high (signed) 32 bits of the long - * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed - * @constructor - */ - function Long(low, high, unsigned) { - - /** - * The low 32 bits as a signed value. - * @type {number} - */ - this.low = low | 0; - - /** - * The high 32 bits as a signed value. - * @type {number} - */ - this.high = high | 0; - - /** - * Whether unsigned or not. - * @type {boolean} - */ - this.unsigned = !!unsigned; - } - - // The internal representation of a long is the two given signed, 32-bit values. - // We use 32-bit pieces because these are the size of integers on which - // Javascript performs bit-operations. For operations like addition and - // multiplication, we split each number into 16 bit pieces, which can easily be - // multiplied within Javascript's floating-point representation without overflow - // or change in sign. - // - // In the algorithms below, we frequently reduce the negative case to the - // positive case by negating the input(s) and then post-processing the result. - // Note that we must ALWAYS check specially whether those values are MIN_VALUE - // (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as - // a positive number, it overflows back into a negative). Not handling this - // case would often result in infinite recursion. - // - // Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from* - // methods on which they depend. - - /** - * An indicator used to reliably determine if an object is a Long or not. - * @type {boolean} - * @const - * @private - */ - Long.prototype.__isLong__; - - Object.defineProperty(Long.prototype, "__isLong__", { - value: true, - enumerable: false, - configurable: false - }); - - /** - * @function - * @param {*} obj Object - * @returns {boolean} - * @inner - */ - function isLong(obj) { - return (obj && obj["__isLong__"]) === true; - } - - /** - * Tests if the specified object is a Long. - * @function - * @param {*} obj Object - * @returns {boolean} - */ - Long.isLong = isLong; - - /** - * A cache of the Long representations of small integer values. - * @type {!Object} - * @inner - */ - var INT_CACHE = {}; - - /** - * A cache of the Long representations of small unsigned integer values. - * @type {!Object} - * @inner - */ - var UINT_CACHE = {}; - - /** - * @param {number} value - * @param {boolean=} unsigned - * @returns {!Long} - * @inner - */ - function fromInt(value, unsigned) { - var obj, cachedObj, cache; - if (unsigned) { - value >>>= 0; - if (cache = (0 <= value && value < 256)) { - cachedObj = UINT_CACHE[value]; - if (cachedObj) - return cachedObj; - } - obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); - if (cache) - UINT_CACHE[value] = obj; - return obj; - } else { - value |= 0; - if (cache = (-128 <= value && value < 128)) { - cachedObj = INT_CACHE[value]; - if (cachedObj) - return cachedObj; - } - obj = fromBits(value, value < 0 ? -1 : 0, false); - if (cache) - INT_CACHE[value] = obj; - return obj; - } - } - - /** - * Returns a Long representing the given 32 bit integer value. - * @function - * @param {number} value The 32 bit integer in question - * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed - * @returns {!Long} The corresponding Long value - */ - Long.fromInt = fromInt; - - /** - * @param {number} value - * @param {boolean=} unsigned - * @returns {!Long} - * @inner - */ - function fromNumber(value, unsigned) { - if (isNaN(value) || !isFinite(value)) - return unsigned ? UZERO : ZERO; - if (unsigned) { - if (value < 0) - return UZERO; - if (value >= TWO_PWR_64_DBL) - return MAX_UNSIGNED_VALUE; - } else { - if (value <= -TWO_PWR_63_DBL) - return MIN_VALUE; - if (value + 1 >= TWO_PWR_63_DBL) - return MAX_VALUE; - } - if (value < 0) - return fromNumber(-value, unsigned).neg(); - return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); - } - - /** - * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. - * @function - * @param {number} value The number in question - * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed - * @returns {!Long} The corresponding Long value - */ - Long.fromNumber = fromNumber; - - /** - * @param {number} lowBits - * @param {number} highBits - * @param {boolean=} unsigned - * @returns {!Long} - * @inner - */ - function fromBits(lowBits, highBits, unsigned) { - return new Long(lowBits, highBits, unsigned); - } - - /** - * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is - * assumed to use 32 bits. - * @function - * @param {number} lowBits The low 32 bits - * @param {number} highBits The high 32 bits - * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed - * @returns {!Long} The corresponding Long value - */ - Long.fromBits = fromBits; - - /** - * @function - * @param {number} base - * @param {number} exponent - * @returns {number} - * @inner - */ - var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4) - - /** - * @param {string} str - * @param {(boolean|number)=} unsigned - * @param {number=} radix - * @returns {!Long} - * @inner - */ - function fromString(str, unsigned, radix) { - if (str.length === 0) - throw Error('empty string'); - if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") - return ZERO; - if (typeof unsigned === 'number') { - // For goog.math.long compatibility - radix = unsigned, - unsigned = false; - } else { - unsigned = !! unsigned; - } - radix = radix || 10; - if (radix < 2 || 36 < radix) - throw RangeError('radix'); - - var p; - if ((p = str.indexOf('-')) > 0) - throw Error('interior hyphen'); - else if (p === 0) { - return fromString(str.substring(1), unsigned, radix).neg(); - } - - // Do several (8) digits each time through the loop, so as to - // minimize the calls to the very expensive emulated div. - var radixToPower = fromNumber(pow_dbl(radix, 8)); - - var result = ZERO; - for (var i = 0; i < str.length; i += 8) { - var size = Math.min(8, str.length - i), - value = parseInt(str.substring(i, i + size), radix); - if (size < 8) { - var power = fromNumber(pow_dbl(radix, size)); - result = result.mul(power).add(fromNumber(value)); - } else { - result = result.mul(radixToPower); - result = result.add(fromNumber(value)); - } - } - result.unsigned = unsigned; - return result; - } - - /** - * Returns a Long representation of the given string, written using the specified radix. - * @function - * @param {string} str The textual representation of the Long - * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed - * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 - * @returns {!Long} The corresponding Long value - */ - Long.fromString = fromString; - - /** - * @function - * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val - * @returns {!Long} - * @inner - */ - function fromValue(val) { - if (val /* is compatible */ instanceof Long) - return val; - if (typeof val === 'number') - return fromNumber(val); - if (typeof val === 'string') - return fromString(val); - // Throws for non-objects, converts non-instanceof Long: - return fromBits(val.low, val.high, val.unsigned); - } - - /** - * Converts the specified value to a Long. - * @function - * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value - * @returns {!Long} - */ - Long.fromValue = fromValue; - - // NOTE: the compiler should inline these constant values below and then remove these variables, so there should be - // no runtime penalty for these. - - /** - * @type {number} - * @const - * @inner - */ - var TWO_PWR_16_DBL = 1 << 16; - - /** - * @type {number} - * @const - * @inner - */ - var TWO_PWR_24_DBL = 1 << 24; - - /** - * @type {number} - * @const - * @inner - */ - var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL; - - /** - * @type {number} - * @const - * @inner - */ - var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL; - - /** - * @type {number} - * @const - * @inner - */ - var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2; - - /** - * @type {!Long} - * @const - * @inner - */ - var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL); - - /** - * @type {!Long} - * @inner - */ - var ZERO = fromInt(0); - - /** - * Signed zero. - * @type {!Long} - */ - Long.ZERO = ZERO; - - /** - * @type {!Long} - * @inner - */ - var UZERO = fromInt(0, true); - - /** - * Unsigned zero. - * @type {!Long} - */ - Long.UZERO = UZERO; - - /** - * @type {!Long} - * @inner - */ - var ONE = fromInt(1); - - /** - * Signed one. - * @type {!Long} - */ - Long.ONE = ONE; - - /** - * @type {!Long} - * @inner - */ - var UONE = fromInt(1, true); - - /** - * Unsigned one. - * @type {!Long} - */ - Long.UONE = UONE; - - /** - * @type {!Long} - * @inner - */ - var NEG_ONE = fromInt(-1); - - /** - * Signed negative one. - * @type {!Long} - */ - Long.NEG_ONE = NEG_ONE; - - /** - * @type {!Long} - * @inner - */ - var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false); - - /** - * Maximum signed value. - * @type {!Long} - */ - Long.MAX_VALUE = MAX_VALUE; - - /** - * @type {!Long} - * @inner - */ - var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); - - /** - * Maximum unsigned value. - * @type {!Long} - */ - Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; - - /** - * @type {!Long} - * @inner - */ - var MIN_VALUE = fromBits(0, 0x80000000|0, false); - - /** - * Minimum signed value. - * @type {!Long} - */ - Long.MIN_VALUE = MIN_VALUE; - - /** - * @alias Long.prototype - * @inner - */ - var LongPrototype = Long.prototype; - - /** - * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. - * @returns {number} - */ - LongPrototype.toInt = function toInt() { - return this.unsigned ? this.low >>> 0 : this.low; - }; - - /** - * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). - * @returns {number} - */ - LongPrototype.toNumber = function toNumber() { - if (this.unsigned) - return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0); - return this.high * TWO_PWR_32_DBL + (this.low >>> 0); - }; - - /** - * Converts the Long to a string written in the specified radix. - * @param {number=} radix Radix (2-36), defaults to 10 - * @returns {string} - * @override - * @throws {RangeError} If `radix` is out of range - */ - LongPrototype.toString = function toString(radix) { - radix = radix || 10; - if (radix < 2 || 36 < radix) - throw RangeError('radix'); - if (this.isZero()) - return '0'; - if (this.isNegative()) { // Unsigned Longs are never negative - if (this.eq(MIN_VALUE)) { - // We need to change the Long value before it can be negated, so we remove - // the bottom-most digit in this base and then recurse to do the rest. - var radixLong = fromNumber(radix), - div = this.div(radixLong), - rem1 = div.mul(radixLong).sub(this); - return div.toString(radix) + rem1.toInt().toString(radix); - } else - return '-' + this.neg().toString(radix); - } - - // Do several (6) digits each time through the loop, so as to - // minimize the calls to the very expensive emulated div. - var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), - rem = this; - var result = ''; - while (true) { - var remDiv = rem.div(radixToPower), - intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, - digits = intval.toString(radix); - rem = remDiv; - if (rem.isZero()) - return digits + result; - else { - while (digits.length < 6) - digits = '0' + digits; - result = '' + digits + result; - } - } - }; - - /** - * Gets the high 32 bits as a signed integer. - * @returns {number} Signed high bits - */ - LongPrototype.getHighBits = function getHighBits() { - return this.high; - }; - - /** - * Gets the high 32 bits as an unsigned integer. - * @returns {number} Unsigned high bits - */ - LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { - return this.high >>> 0; - }; - - /** - * Gets the low 32 bits as a signed integer. - * @returns {number} Signed low bits - */ - LongPrototype.getLowBits = function getLowBits() { - return this.low; - }; - - /** - * Gets the low 32 bits as an unsigned integer. - * @returns {number} Unsigned low bits - */ - LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { - return this.low >>> 0; - }; - - /** - * Gets the number of bits needed to represent the absolute value of this Long. - * @returns {number} - */ - LongPrototype.getNumBitsAbs = function getNumBitsAbs() { - if (this.isNegative()) // Unsigned Longs are never negative - return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs(); - var val = this.high != 0 ? this.high : this.low; - for (var bit = 31; bit > 0; bit--) - if ((val & (1 << bit)) != 0) - break; - return this.high != 0 ? bit + 33 : bit + 1; - }; - - /** - * Tests if this Long's value equals zero. - * @returns {boolean} - */ - LongPrototype.isZero = function isZero() { - return this.high === 0 && this.low === 0; - }; - - /** - * Tests if this Long's value is negative. - * @returns {boolean} - */ - LongPrototype.isNegative = function isNegative() { - return !this.unsigned && this.high < 0; - }; - - /** - * Tests if this Long's value is positive. - * @returns {boolean} - */ - LongPrototype.isPositive = function isPositive() { - return this.unsigned || this.high >= 0; - }; - - /** - * Tests if this Long's value is odd. - * @returns {boolean} - */ - LongPrototype.isOdd = function isOdd() { - return (this.low & 1) === 1; - }; - - /** - * Tests if this Long's value is even. - * @returns {boolean} - */ - LongPrototype.isEven = function isEven() { - return (this.low & 1) === 0; - }; - - /** - * Tests if this Long's value equals the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.equals = function equals(other) { - if (!isLong(other)) - other = fromValue(other); - if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1) - return false; - return this.high === other.high && this.low === other.low; - }; - - /** - * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.eq = LongPrototype.equals; - - /** - * Tests if this Long's value differs from the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.notEquals = function notEquals(other) { - return !this.eq(/* validates */ other); - }; - - /** - * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.neq = LongPrototype.notEquals; - - /** - * Tests if this Long's value is less than the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.lessThan = function lessThan(other) { - return this.comp(/* validates */ other) < 0; - }; - - /** - * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.lt = LongPrototype.lessThan; - - /** - * Tests if this Long's value is less than or equal the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { - return this.comp(/* validates */ other) <= 0; - }; - - /** - * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.lte = LongPrototype.lessThanOrEqual; - - /** - * Tests if this Long's value is greater than the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.greaterThan = function greaterThan(other) { - return this.comp(/* validates */ other) > 0; - }; - - /** - * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.gt = LongPrototype.greaterThan; - - /** - * Tests if this Long's value is greater than or equal the specified's. - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { - return this.comp(/* validates */ other) >= 0; - }; - - /** - * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. - * @function - * @param {!Long|number|string} other Other value - * @returns {boolean} - */ - LongPrototype.gte = LongPrototype.greaterThanOrEqual; - - /** - * Compares this Long's value with the specified's. - * @param {!Long|number|string} other Other value - * @returns {number} 0 if they are the same, 1 if the this is greater and -1 - * if the given one is greater - */ - LongPrototype.compare = function compare(other) { - if (!isLong(other)) - other = fromValue(other); - if (this.eq(other)) - return 0; - var thisNeg = this.isNegative(), - otherNeg = other.isNegative(); - if (thisNeg && !otherNeg) - return -1; - if (!thisNeg && otherNeg) - return 1; - // At this point the sign bits are the same - if (!this.unsigned) - return this.sub(other).isNegative() ? -1 : 1; - // Both are positive if at least one is unsigned - return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1; - }; - - /** - * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}. - * @function - * @param {!Long|number|string} other Other value - * @returns {number} 0 if they are the same, 1 if the this is greater and -1 - * if the given one is greater - */ - LongPrototype.comp = LongPrototype.compare; - - /** - * Negates this Long's value. - * @returns {!Long} Negated Long - */ - LongPrototype.negate = function negate() { - if (!this.unsigned && this.eq(MIN_VALUE)) - return MIN_VALUE; - return this.not().add(ONE); - }; - - /** - * Negates this Long's value. This is an alias of {@link Long#negate}. - * @function - * @returns {!Long} Negated Long - */ - LongPrototype.neg = LongPrototype.negate; - - /** - * Returns the sum of this and the specified Long. - * @param {!Long|number|string} addend Addend - * @returns {!Long} Sum - */ - LongPrototype.add = function add(addend) { - if (!isLong(addend)) - addend = fromValue(addend); - - // Divide each number into 4 chunks of 16 bits, and then sum the chunks. - - var a48 = this.high >>> 16; - var a32 = this.high & 0xFFFF; - var a16 = this.low >>> 16; - var a00 = this.low & 0xFFFF; - - var b48 = addend.high >>> 16; - var b32 = addend.high & 0xFFFF; - var b16 = addend.low >>> 16; - var b00 = addend.low & 0xFFFF; - - var c48 = 0, c32 = 0, c16 = 0, c00 = 0; - c00 += a00 + b00; - c16 += c00 >>> 16; - c00 &= 0xFFFF; - c16 += a16 + b16; - c32 += c16 >>> 16; - c16 &= 0xFFFF; - c32 += a32 + b32; - c48 += c32 >>> 16; - c32 &= 0xFFFF; - c48 += a48 + b48; - c48 &= 0xFFFF; - return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); - }; - - /** - * Returns the difference of this and the specified Long. - * @param {!Long|number|string} subtrahend Subtrahend - * @returns {!Long} Difference - */ - LongPrototype.subtract = function subtract(subtrahend) { - if (!isLong(subtrahend)) - subtrahend = fromValue(subtrahend); - return this.add(subtrahend.neg()); - }; - - /** - * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}. - * @function - * @param {!Long|number|string} subtrahend Subtrahend - * @returns {!Long} Difference - */ - LongPrototype.sub = LongPrototype.subtract; - - /** - * Returns the product of this and the specified Long. - * @param {!Long|number|string} multiplier Multiplier - * @returns {!Long} Product - */ - LongPrototype.multiply = function multiply(multiplier) { - if (this.isZero()) - return ZERO; - if (!isLong(multiplier)) - multiplier = fromValue(multiplier); - if (multiplier.isZero()) - return ZERO; - if (this.eq(MIN_VALUE)) - return multiplier.isOdd() ? MIN_VALUE : ZERO; - if (multiplier.eq(MIN_VALUE)) - return this.isOdd() ? MIN_VALUE : ZERO; - - if (this.isNegative()) { - if (multiplier.isNegative()) - return this.neg().mul(multiplier.neg()); - else - return this.neg().mul(multiplier).neg(); - } else if (multiplier.isNegative()) - return this.mul(multiplier.neg()).neg(); - - // If both longs are small, use float multiplication - if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24)) - return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned); - - // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. - // We can skip products that would overflow. - - var a48 = this.high >>> 16; - var a32 = this.high & 0xFFFF; - var a16 = this.low >>> 16; - var a00 = this.low & 0xFFFF; - - var b48 = multiplier.high >>> 16; - var b32 = multiplier.high & 0xFFFF; - var b16 = multiplier.low >>> 16; - var b00 = multiplier.low & 0xFFFF; - - var c48 = 0, c32 = 0, c16 = 0, c00 = 0; - c00 += a00 * b00; - c16 += c00 >>> 16; - c00 &= 0xFFFF; - c16 += a16 * b00; - c32 += c16 >>> 16; - c16 &= 0xFFFF; - c16 += a00 * b16; - c32 += c16 >>> 16; - c16 &= 0xFFFF; - c32 += a32 * b00; - c48 += c32 >>> 16; - c32 &= 0xFFFF; - c32 += a16 * b16; - c48 += c32 >>> 16; - c32 &= 0xFFFF; - c32 += a00 * b32; - c48 += c32 >>> 16; - c32 &= 0xFFFF; - c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; - c48 &= 0xFFFF; - return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); - }; - - /** - * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}. - * @function - * @param {!Long|number|string} multiplier Multiplier - * @returns {!Long} Product - */ - LongPrototype.mul = LongPrototype.multiply; - - /** - * Returns this Long divided by the specified. The result is signed if this Long is signed or - * unsigned if this Long is unsigned. - * @param {!Long|number|string} divisor Divisor - * @returns {!Long} Quotient - */ - LongPrototype.divide = function divide(divisor) { - if (!isLong(divisor)) - divisor = fromValue(divisor); - if (divisor.isZero()) - throw Error('division by zero'); - if (this.isZero()) - return this.unsigned ? UZERO : ZERO; - var approx, rem, res; - if (!this.unsigned) { - // This section is only relevant for signed longs and is derived from the - // closure library as a whole. - if (this.eq(MIN_VALUE)) { - if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) - return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE - else if (divisor.eq(MIN_VALUE)) - return ONE; - else { - // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. - var halfThis = this.shr(1); - approx = halfThis.div(divisor).shl(1); - if (approx.eq(ZERO)) { - return divisor.isNegative() ? ONE : NEG_ONE; - } else { - rem = this.sub(divisor.mul(approx)); - res = approx.add(rem.div(divisor)); - return res; - } - } - } else if (divisor.eq(MIN_VALUE)) - return this.unsigned ? UZERO : ZERO; - if (this.isNegative()) { - if (divisor.isNegative()) - return this.neg().div(divisor.neg()); - return this.neg().div(divisor).neg(); - } else if (divisor.isNegative()) - return this.div(divisor.neg()).neg(); - res = ZERO; - } else { - // The algorithm below has not been made for unsigned longs. It's therefore - // required to take special care of the MSB prior to running it. - if (!divisor.unsigned) - divisor = divisor.toUnsigned(); - if (divisor.gt(this)) - return UZERO; - if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true - return UONE; - res = UZERO; - } - - // Repeat the following until the remainder is less than other: find a - // floating-point that approximates remainder / other *from below*, add this - // into the result, and subtract it from the remainder. It is critical that - // the approximate value is less than or equal to the real value so that the - // remainder never becomes negative. - rem = this; - while (rem.gte(divisor)) { - // Approximate the result of division. This may be a little greater or - // smaller than the actual value. - approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber())); - - // We will tweak the approximate result by changing it in the 48-th digit or - // the smallest non-fractional digit, whichever is larger. - var log2 = Math.ceil(Math.log(approx) / Math.LN2), - delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48), - - // Decrease the approximation until it is smaller than the remainder. Note - // that if it is too large, the product overflows and is negative. - approxRes = fromNumber(approx), - approxRem = approxRes.mul(divisor); - while (approxRem.isNegative() || approxRem.gt(rem)) { - approx -= delta; - approxRes = fromNumber(approx, this.unsigned); - approxRem = approxRes.mul(divisor); - } - - // We know the answer can't be zero... and actually, zero would cause - // infinite recursion since we would make no progress. - if (approxRes.isZero()) - approxRes = ONE; - - res = res.add(approxRes); - rem = rem.sub(approxRem); - } - return res; - }; - - /** - * Returns this Long divided by the specified. This is an alias of {@link Long#divide}. - * @function - * @param {!Long|number|string} divisor Divisor - * @returns {!Long} Quotient - */ - LongPrototype.div = LongPrototype.divide; - - /** - * Returns this Long modulo the specified. - * @param {!Long|number|string} divisor Divisor - * @returns {!Long} Remainder - */ - LongPrototype.modulo = function modulo(divisor) { - if (!isLong(divisor)) - divisor = fromValue(divisor); - return this.sub(this.div(divisor).mul(divisor)); - }; - - /** - * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. - * @function - * @param {!Long|number|string} divisor Divisor - * @returns {!Long} Remainder - */ - LongPrototype.mod = LongPrototype.modulo; - - /** - * Returns the bitwise NOT of this Long. - * @returns {!Long} - */ - LongPrototype.not = function not() { - return fromBits(~this.low, ~this.high, this.unsigned); - }; - - /** - * Returns the bitwise AND of this Long and the specified. - * @param {!Long|number|string} other Other Long - * @returns {!Long} - */ - LongPrototype.and = function and(other) { - if (!isLong(other)) - other = fromValue(other); - return fromBits(this.low & other.low, this.high & other.high, this.unsigned); - }; - - /** - * Returns the bitwise OR of this Long and the specified. - * @param {!Long|number|string} other Other Long - * @returns {!Long} - */ - LongPrototype.or = function or(other) { - if (!isLong(other)) - other = fromValue(other); - return fromBits(this.low | other.low, this.high | other.high, this.unsigned); - }; - - /** - * Returns the bitwise XOR of this Long and the given one. - * @param {!Long|number|string} other Other Long - * @returns {!Long} - */ - LongPrototype.xor = function xor(other) { - if (!isLong(other)) - other = fromValue(other); - return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned); - }; - - /** - * Returns this Long with bits shifted to the left by the given amount. - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shiftLeft = function shiftLeft(numBits) { - if (isLong(numBits)) - numBits = numBits.toInt(); - if ((numBits &= 63) === 0) - return this; - else if (numBits < 32) - return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned); - else - return fromBits(0, this.low << (numBits - 32), this.unsigned); - }; - - /** - * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}. - * @function - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shl = LongPrototype.shiftLeft; - - /** - * Returns this Long with bits arithmetically shifted to the right by the given amount. - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shiftRight = function shiftRight(numBits) { - if (isLong(numBits)) - numBits = numBits.toInt(); - if ((numBits &= 63) === 0) - return this; - else if (numBits < 32) - return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned); - else - return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned); - }; - - /** - * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}. - * @function - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shr = LongPrototype.shiftRight; - - /** - * Returns this Long with bits logically shifted to the right by the given amount. - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { - if (isLong(numBits)) - numBits = numBits.toInt(); - numBits &= 63; - if (numBits === 0) - return this; - else { - var high = this.high; - if (numBits < 32) { - var low = this.low; - return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned); - } else if (numBits === 32) - return fromBits(high, 0, this.unsigned); - else - return fromBits(high >>> (numBits - 32), 0, this.unsigned); - } - }; - - /** - * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. - * @function - * @param {number|!Long} numBits Number of bits - * @returns {!Long} Shifted Long - */ - LongPrototype.shru = LongPrototype.shiftRightUnsigned; - - /** - * Converts this Long to signed. - * @returns {!Long} Signed long - */ - LongPrototype.toSigned = function toSigned() { - if (!this.unsigned) - return this; - return fromBits(this.low, this.high, false); - }; - - /** - * Converts this Long to unsigned. - * @returns {!Long} Unsigned long - */ - LongPrototype.toUnsigned = function toUnsigned() { - if (this.unsigned) - return this; - return fromBits(this.low, this.high, true); - }; - - /** - * Converts this Long to its byte representation. - * @param {boolean=} le Whether little or big endian, defaults to big endian - * @returns {!Array.} Byte representation - */ - LongPrototype.toBytes = function(le) { - return le ? this.toBytesLE() : this.toBytesBE(); - } - - /** - * Converts this Long to its little endian byte representation. - * @returns {!Array.} Little endian byte representation - */ - LongPrototype.toBytesLE = function() { - var hi = this.high, - lo = this.low; - return [ - lo & 0xff, - (lo >>> 8) & 0xff, - (lo >>> 16) & 0xff, - (lo >>> 24) & 0xff, - hi & 0xff, - (hi >>> 8) & 0xff, - (hi >>> 16) & 0xff, - (hi >>> 24) & 0xff - ]; - } - - /** - * Converts this Long to its big endian byte representation. - * @returns {!Array.} Big endian byte representation - */ - LongPrototype.toBytesBE = function() { - var hi = this.high, - lo = this.low; - return [ - (hi >>> 24) & 0xff, - (hi >>> 16) & 0xff, - (hi >>> 8) & 0xff, - hi & 0xff, - (lo >>> 24) & 0xff, - (lo >>> 16) & 0xff, - (lo >>> 8) & 0xff, - lo & 0xff - ]; - } - - return Long; -}); diff --git a/components/protobuf/dist/ProtoBuf.js b/components/protobuf/dist/ProtoBuf.js deleted file mode 100644 index 62de9c318b..0000000000 --- a/components/protobuf/dist/ProtoBuf.js +++ /dev/null @@ -1,5250 +0,0 @@ -/* - 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 protobuf.js (c) 2013 Daniel Wirtz - * Released under the Apache License, Version 2.0 - * see: https://github.com/dcodeIO/protobuf.js for details - */ -(function(global, factory) { - - /* AMD */ if (typeof define === 'function' && define["amd"]) - define(["bytebuffer"], factory); - /* CommonJS */ else if (typeof require === "function" && typeof module === "object" && module && module["exports"]) - module["exports"] = factory(require("bytebuffer"), true); - /* Global */ else - (global["dcodeIO"] = global["dcodeIO"] || {})["ProtoBuf"] = factory(global["dcodeIO"]["ByteBuffer"]); - -})(this, function(ByteBuffer, isCommonJS) { - "use strict"; - - /** - * The ProtoBuf namespace. - * @exports ProtoBuf - * @namespace - * @expose - */ - var ProtoBuf = {}; - - /** - * @type {!function(new: ByteBuffer, ...[*])} - * @expose - */ - ProtoBuf.ByteBuffer = ByteBuffer; - - /** - * @type {?function(new: Long, ...[*])} - * @expose - */ - ProtoBuf.Long = ByteBuffer.Long || null; - - /** - * ProtoBuf.js version. - * @type {string} - * @const - * @expose - */ - ProtoBuf.VERSION = "5.0.1"; - - /** - * Wire types. - * @type {Object.} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES = {}; - - /** - * Varint wire type. - * @type {number} - * @expose - */ - ProtoBuf.WIRE_TYPES.VARINT = 0; - - /** - * Fixed 64 bits wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.BITS64 = 1; - - /** - * Length delimited wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.LDELIM = 2; - - /** - * Start group wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.STARTGROUP = 3; - - /** - * End group wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.ENDGROUP = 4; - - /** - * Fixed 32 bits wire type. - * @type {number} - * @const - * @expose - */ - ProtoBuf.WIRE_TYPES.BITS32 = 5; - - /** - * Packable wire types. - * @type {!Array.} - * @const - * @expose - */ - ProtoBuf.PACKABLE_WIRE_TYPES = [ - ProtoBuf.WIRE_TYPES.VARINT, - ProtoBuf.WIRE_TYPES.BITS64, - ProtoBuf.WIRE_TYPES.BITS32 - ]; - - /** - * Types. - * @dict - * @type {!Object.} - * @const - * @expose - */ - ProtoBuf.TYPES = { - // According to the protobuf spec. - "int32": { - name: "int32", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: 0 - }, - "uint32": { - name: "uint32", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: 0 - }, - "sint32": { - name: "sint32", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: 0 - }, - "int64": { - name: "int64", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined - }, - "uint64": { - name: "uint64", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined - }, - "sint64": { - name: "sint64", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined - }, - "bool": { - name: "bool", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: false - }, - "double": { - name: "double", - wireType: ProtoBuf.WIRE_TYPES.BITS64, - defaultValue: 0 - }, - "string": { - name: "string", - wireType: ProtoBuf.WIRE_TYPES.LDELIM, - defaultValue: "" - }, - "bytes": { - name: "bytes", - wireType: ProtoBuf.WIRE_TYPES.LDELIM, - defaultValue: null // overridden in the code, must be a unique instance - }, - "fixed32": { - name: "fixed32", - wireType: ProtoBuf.WIRE_TYPES.BITS32, - defaultValue: 0 - }, - "sfixed32": { - name: "sfixed32", - wireType: ProtoBuf.WIRE_TYPES.BITS32, - defaultValue: 0 - }, - "fixed64": { - name: "fixed64", - wireType: ProtoBuf.WIRE_TYPES.BITS64, - defaultValue: ProtoBuf.Long ? ProtoBuf.Long.UZERO : undefined - }, - "sfixed64": { - name: "sfixed64", - wireType: ProtoBuf.WIRE_TYPES.BITS64, - defaultValue: ProtoBuf.Long ? ProtoBuf.Long.ZERO : undefined - }, - "float": { - name: "float", - wireType: ProtoBuf.WIRE_TYPES.BITS32, - defaultValue: 0 - }, - "enum": { - name: "enum", - wireType: ProtoBuf.WIRE_TYPES.VARINT, - defaultValue: 0 - }, - "message": { - name: "message", - wireType: ProtoBuf.WIRE_TYPES.LDELIM, - defaultValue: null - }, - "group": { - name: "group", - wireType: ProtoBuf.WIRE_TYPES.STARTGROUP, - defaultValue: null - } - }; - - /** - * Valid map key types. - * @type {!Array.>} - * @const - * @expose - */ - ProtoBuf.MAP_KEY_TYPES = [ - ProtoBuf.TYPES["int32"], - ProtoBuf.TYPES["sint32"], - ProtoBuf.TYPES["sfixed32"], - ProtoBuf.TYPES["uint32"], - ProtoBuf.TYPES["fixed32"], - ProtoBuf.TYPES["int64"], - ProtoBuf.TYPES["sint64"], - ProtoBuf.TYPES["sfixed64"], - ProtoBuf.TYPES["uint64"], - ProtoBuf.TYPES["fixed64"], - ProtoBuf.TYPES["bool"], - ProtoBuf.TYPES["string"], - ProtoBuf.TYPES["bytes"] - ]; - - /** - * Minimum field id. - * @type {number} - * @const - * @expose - */ - ProtoBuf.ID_MIN = 1; - - /** - * Maximum field id. - * @type {number} - * @const - * @expose - */ - ProtoBuf.ID_MAX = 0x1FFFFFFF; - - /** - * If set to `true`, field names will be converted from underscore notation to camel case. Defaults to `false`. - * Must be set prior to parsing. - * @type {boolean} - * @expose - */ - ProtoBuf.convertFieldsToCamelCase = false; - - /** - * By default, messages are populated with (setX, set_x) accessors for each field. This can be disabled by - * setting this to `false` prior to building messages. - * @type {boolean} - * @expose - */ - ProtoBuf.populateAccessors = true; - - /** - * By default, messages are populated with default values if a field is not present on the wire. To disable - * this behavior, set this setting to `false`. - * @type {boolean} - * @expose - */ - ProtoBuf.populateDefaults = true; - - /** - * @alias ProtoBuf.Util - * @expose - */ - ProtoBuf.Util = (function() { - "use strict"; - - /** - * ProtoBuf utilities. - * @exports ProtoBuf.Util - * @namespace - */ - var Util = {}; - - /** - * Flag if running in node or not. - * @type {boolean} - * @const - * @expose - */ - Util.IS_NODE = !!( - typeof process === 'object' && process+'' === '[object process]' && !process['browser'] - ); - - /** - * Constructs a XMLHttpRequest object. - * @return {XMLHttpRequest} - * @throws {Error} If XMLHttpRequest is not supported - * @expose - */ - Util.XHR = function() { - // No dependencies please, ref: http://www.quirksmode.org/js/xmlhttp.html - var XMLHttpFactories = [ - function () {return new XMLHttpRequest()}, - function () {return new ActiveXObject("Msxml2.XMLHTTP")}, - function () {return new ActiveXObject("Msxml3.XMLHTTP")}, - function () {return new ActiveXObject("Microsoft.XMLHTTP")} - ]; - /** @type {?XMLHttpRequest} */ - var xhr = null; - for (var i=0;i} - * @expose - */ - ProtoBuf.Lang = { - - // Characters always ending a statement - DELIM: /[\s\{\}=;:\[\],'"\(\)<>]/g, - - // Field rules - RULE: /^(?:required|optional|repeated|map)$/, - - // Field types - TYPE: /^(?:double|float|int32|uint32|sint32|int64|uint64|sint64|fixed32|sfixed32|fixed64|sfixed64|bool|string|bytes)$/, - - // Names - NAME: /^[a-zA-Z_][a-zA-Z_0-9]*$/, - - // Type definitions - TYPEDEF: /^[a-zA-Z][a-zA-Z_0-9]*$/, - - // Type references - TYPEREF: /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/, - - // Fully qualified type references - FQTYPEREF: /^(?:\.[a-zA-Z][a-zA-Z_0-9]*)+$/, - - // All numbers - NUMBER: /^-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+|([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?)|inf|nan)$/, - - // Decimal numbers - NUMBER_DEC: /^(?:[1-9][0-9]*|0)$/, - - // Hexadecimal numbers - NUMBER_HEX: /^0[xX][0-9a-fA-F]+$/, - - // Octal numbers - NUMBER_OCT: /^0[0-7]+$/, - - // Floating point numbers - NUMBER_FLT: /^([0-9]*(\.[0-9]*)?([Ee][+-]?[0-9]+)?|inf|nan)$/, - - // Booleans - BOOL: /^(?:true|false)$/i, - - // Id numbers - ID: /^(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/, - - // Negative id numbers (enum values) - NEGID: /^\-?(?:[1-9][0-9]*|0|0[xX][0-9a-fA-F]+|0[0-7]+)$/, - - // Whitespaces - WHITESPACE: /\s/, - - // All strings - STRING: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")|(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g, - - // Double quoted strings - STRING_DQ: /(?:"([^"\\]*(?:\\.[^"\\]*)*)")/g, - - // Single quoted strings - STRING_SQ: /(?:'([^'\\]*(?:\\.[^'\\]*)*)')/g - }; - - /** - * @alias ProtoBuf.DotProto - * @expose - */ - ProtoBuf.DotProto = (function(ProtoBuf, Lang) { - "use strict"; - - /** - * Utilities to parse .proto files. - * @exports ProtoBuf.DotProto - * @namespace - */ - var DotProto = {}; - - /** - * Constructs a new Tokenizer. - * @exports ProtoBuf.DotProto.Tokenizer - * @class prototype tokenizer - * @param {string} proto Proto to tokenize - * @constructor - */ - var Tokenizer = function(proto) { - - /** - * Source to parse. - * @type {string} - * @expose - */ - this.source = proto+""; - - /** - * Current index. - * @type {number} - * @expose - */ - this.index = 0; - - /** - * Current line. - * @type {number} - * @expose - */ - this.line = 1; - - /** - * Token stack. - * @type {!Array.} - * @expose - */ - this.stack = []; - - /** - * Opening character of the current string read, if any. - * @type {?string} - * @private - */ - this._stringOpen = null; - }; - - /** - * @alias ProtoBuf.DotProto.Tokenizer.prototype - * @inner - */ - var TokenizerPrototype = Tokenizer.prototype; - - /** - * Reads a string beginning at the current index. - * @return {string} - * @private - */ - TokenizerPrototype._readString = function() { - var re = this._stringOpen === '"' - ? Lang.STRING_DQ - : Lang.STRING_SQ; - re.lastIndex = this.index - 1; // Include the open quote - var match = re.exec(this.source); - if (!match) - throw Error("unterminated string"); - this.index = re.lastIndex; - this.stack.push(this._stringOpen); - this._stringOpen = null; - return match[1]; - }; - - /** - * Gets the next token and advances by one. - * @return {?string} Token or `null` on EOF - * @expose - */ - TokenizerPrototype.next = function() { - if (this.stack.length > 0) - return this.stack.shift(); - if (this.index >= this.source.length) - return null; - if (this._stringOpen !== null) - return this._readString(); - - var repeat, - prev, - next; - do { - repeat = false; - - // Strip white spaces - while (Lang.WHITESPACE.test(next = this.source.charAt(this.index))) { - if (next === '\n') - ++this.line; - if (++this.index === this.source.length) - return null; - } - - // Strip comments - if (this.source.charAt(this.index) === '/') { - ++this.index; - if (this.source.charAt(this.index) === '/') { // Line - while (this.source.charAt(++this.index) !== '\n') - if (this.index == this.source.length) - return null; - ++this.index; - ++this.line; - repeat = true; - } else if ((next = this.source.charAt(this.index)) === '*') { /* Block */ - do { - if (next === '\n') - ++this.line; - if (++this.index === this.source.length) - return null; - prev = next; - next = this.source.charAt(this.index); - } while (prev !== '*' || next !== '/'); - ++this.index; - repeat = true; - } else - return '/'; - } - } while (repeat); - - if (this.index === this.source.length) - return null; - - // Read the next token - var end = this.index; - Lang.DELIM.lastIndex = 0; - var delim = Lang.DELIM.test(this.source.charAt(end++)); - if (!delim) - while(end < this.source.length && !Lang.DELIM.test(this.source.charAt(end))) - ++end; - var token = this.source.substring(this.index, this.index = end); - if (token === '"' || token === "'") - this._stringOpen = token; - return token; - }; - - /** - * Peeks for the next token. - * @return {?string} Token or `null` on EOF - * @expose - */ - TokenizerPrototype.peek = function() { - if (this.stack.length === 0) { - var token = this.next(); - if (token === null) - return null; - this.stack.push(token); - } - return this.stack[0]; - }; - - /** - * Skips a specific token and throws if it differs. - * @param {string} expected Expected token - * @throws {Error} If the actual token differs - */ - TokenizerPrototype.skip = function(expected) { - var actual = this.next(); - if (actual !== expected) - throw Error("illegal '"+actual+"', '"+expected+"' expected"); - }; - - /** - * Omits an optional token. - * @param {string} expected Expected optional token - * @returns {boolean} `true` if the token exists - */ - TokenizerPrototype.omit = function(expected) { - if (this.peek() === expected) { - this.next(); - return true; - } - return false; - }; - - /** - * Returns a string representation of this object. - * @return {string} String representation as of "Tokenizer(index/length)" - * @expose - */ - TokenizerPrototype.toString = function() { - return "Tokenizer ("+this.index+"/"+this.source.length+" at line "+this.line+")"; - }; - - /** - * @alias ProtoBuf.DotProto.Tokenizer - * @expose - */ - DotProto.Tokenizer = Tokenizer; - - /** - * Constructs a new Parser. - * @exports ProtoBuf.DotProto.Parser - * @class prototype parser - * @param {string} source Source - * @constructor - */ - var Parser = function(source) { - - /** - * Tokenizer. - * @type {!ProtoBuf.DotProto.Tokenizer} - * @expose - */ - this.tn = new Tokenizer(source); - - /** - * Whether parsing proto3 or not. - * @type {boolean} - */ - this.proto3 = false; - }; - - /** - * @alias ProtoBuf.DotProto.Parser.prototype - * @inner - */ - var ParserPrototype = Parser.prototype; - - /** - * Parses the source. - * @returns {!Object} - * @throws {Error} If the source cannot be parsed - * @expose - */ - ParserPrototype.parse = function() { - var topLevel = { - "name": "[ROOT]", // temporary - "package": null, - "messages": [], - "enums": [], - "imports": [], - "options": {}, - "services": [] - // "syntax": undefined - }; - var token, - head = true, - weak; - try { - while (token = this.tn.next()) { - switch (token) { - case 'package': - if (!head || topLevel["package"] !== null) - throw Error("unexpected 'package'"); - token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - throw Error("illegal package name: " + token); - this.tn.skip(";"); - topLevel["package"] = token; - break; - case 'import': - if (!head) - throw Error("unexpected 'import'"); - token = this.tn.peek(); - if (token === "public" || (weak = token === "weak")) // token ignored - this.tn.next(); - token = this._readString(); - this.tn.skip(";"); - if (!weak) // import ignored - topLevel["imports"].push(token); - break; - case 'syntax': - if (!head) - throw Error("unexpected 'syntax'"); - this.tn.skip("="); - if ((topLevel["syntax"] = this._readString()) === "proto3") - this.proto3 = true; - this.tn.skip(";"); - break; - case 'message': - this._parseMessage(topLevel, null); - head = false; - break; - case 'enum': - this._parseEnum(topLevel); - head = false; - break; - case 'option': - this._parseOption(topLevel); - break; - case 'service': - this._parseService(topLevel); - break; - case 'extend': - this._parseExtend(topLevel); - break; - default: - throw Error("unexpected '" + token + "'"); - } - } - } catch (e) { - e.message = "Parse error at line "+this.tn.line+": " + e.message; - throw e; - } - delete topLevel["name"]; - return topLevel; - }; - - /** - * Parses the specified source. - * @returns {!Object} - * @throws {Error} If the source cannot be parsed - * @expose - */ - Parser.parse = function(source) { - return new Parser(source).parse(); - }; - - // ----- Conversion ------ - - /** - * Converts a numerical string to an id. - * @param {string} value - * @param {boolean=} mayBeNegative - * @returns {number} - * @inner - */ - function mkId(value, mayBeNegative) { - var id = -1, - sign = 1; - if (value.charAt(0) == '-') { - sign = -1; - value = value.substring(1); - } - if (Lang.NUMBER_DEC.test(value)) - id = parseInt(value); - else if (Lang.NUMBER_HEX.test(value)) - id = parseInt(value.substring(2), 16); - else if (Lang.NUMBER_OCT.test(value)) - id = parseInt(value.substring(1), 8); - else - throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value); - id = (sign*id)|0; // Force to 32bit - if (!mayBeNegative && id < 0) - throw Error("illegal id value: " + (sign < 0 ? '-' : '') + value); - return id; - } - - /** - * Converts a numerical string to a number. - * @param {string} val - * @returns {number} - * @inner - */ - function mkNumber(val) { - var sign = 1; - if (val.charAt(0) == '-') { - sign = -1; - val = val.substring(1); - } - if (Lang.NUMBER_DEC.test(val)) - return sign * parseInt(val, 10); - else if (Lang.NUMBER_HEX.test(val)) - return sign * parseInt(val.substring(2), 16); - else if (Lang.NUMBER_OCT.test(val)) - return sign * parseInt(val.substring(1), 8); - else if (val === 'inf') - return sign * Infinity; - else if (val === 'nan') - return NaN; - else if (Lang.NUMBER_FLT.test(val)) - return sign * parseFloat(val); - throw Error("illegal number value: " + (sign < 0 ? '-' : '') + val); - } - - // ----- Reading ------ - - /** - * Reads a string. - * @returns {string} - * @private - */ - ParserPrototype._readString = function() { - var value = "", - token, - delim; - do { - delim = this.tn.next(); - if (delim !== "'" && delim !== '"') - throw Error("illegal string delimiter: "+delim); - value += this.tn.next(); - this.tn.skip(delim); - token = this.tn.peek(); - } while (token === '"' || token === '"'); // multi line? - return value; - }; - - /** - * Reads a value. - * @param {boolean=} mayBeTypeRef - * @returns {number|boolean|string} - * @private - */ - ParserPrototype._readValue = function(mayBeTypeRef) { - var token = this.tn.peek(), - value; - if (token === '"' || token === "'") - return this._readString(); - this.tn.next(); - if (Lang.NUMBER.test(token)) - return mkNumber(token); - if (Lang.BOOL.test(token)) - return (token.toLowerCase() === 'true'); - if (mayBeTypeRef && Lang.TYPEREF.test(token)) - return token; - throw Error("illegal value: "+token); - - }; - - // ----- Parsing constructs ----- - - /** - * Parses a namespace option. - * @param {!Object} parent Parent definition - * @param {boolean=} isList - * @private - */ - ParserPrototype._parseOption = function(parent, isList) { - var token = this.tn.next(), - custom = false; - if (token === '(') { - custom = true; - token = this.tn.next(); - } - if (!Lang.TYPEREF.test(token)) - // we can allow options of the form google.protobuf.* since they will just get ignored anyways - // if (!/google\.protobuf\./.test(token)) // FIXME: Why should that not be a valid typeref? - throw Error("illegal option name: "+token); - var name = token; - if (custom) { // (my_method_option).foo, (my_method_option), some_method_option, (foo.my_option).bar - this.tn.skip(')'); - name = '('+name+')'; - token = this.tn.peek(); - if (Lang.FQTYPEREF.test(token)) { - name += token; - this.tn.next(); - } - } - this.tn.skip('='); - this._parseOptionValue(parent, name); - if (!isList) - this.tn.skip(";"); - }; - - /** - * Sets an option on the specified options object. - * @param {!Object.} options - * @param {string} name - * @param {string|number|boolean} value - * @inner - */ - function setOption(options, name, value) { - if (typeof options[name] === 'undefined') - options[name] = value; - else { - if (!Array.isArray(options[name])) - options[name] = [ options[name] ]; - options[name].push(value); - } - } - - /** - * Parses an option value. - * @param {!Object} parent - * @param {string} name - * @private - */ - ParserPrototype._parseOptionValue = function(parent, name) { - var token = this.tn.peek(); - if (token !== '{') { // Plain value - setOption(parent["options"], name, this._readValue(true)); - } else { // Aggregate options - this.tn.skip("{"); - while ((token = this.tn.next()) !== '}') { - if (!Lang.NAME.test(token)) - throw Error("illegal option name: " + name + "." + token); - if (this.tn.omit(":")) - setOption(parent["options"], name + "." + token, this._readValue(true)); - else - this._parseOptionValue(parent, name + "." + token); - } - } - }; - - /** - * Parses a service definition. - * @param {!Object} parent Parent definition - * @private - */ - ParserPrototype._parseService = function(parent) { - var token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal service name at line "+this.tn.line+": "+token); - var name = token; - var svc = { - "name": name, - "rpc": {}, - "options": {} - }; - this.tn.skip("{"); - while ((token = this.tn.next()) !== '}') { - if (token === "option") - this._parseOption(svc); - else if (token === 'rpc') - this._parseServiceRPC(svc); - else - throw Error("illegal service token: "+token); - } - this.tn.omit(";"); - parent["services"].push(svc); - }; - - /** - * Parses a RPC service definition of the form ['rpc', name, (request), 'returns', (response)]. - * @param {!Object} svc Service definition - * @private - */ - ParserPrototype._parseServiceRPC = function(svc) { - var type = "rpc", - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal rpc service method name: "+token); - var name = token; - var method = { - "request": null, - "response": null, - "request_stream": false, - "response_stream": false, - "options": {} - }; - this.tn.skip("("); - token = this.tn.next(); - if (token.toLowerCase() === "stream") { - method["request_stream"] = true; - token = this.tn.next(); - } - if (!Lang.TYPEREF.test(token)) - throw Error("illegal rpc service request type: "+token); - method["request"] = token; - this.tn.skip(")"); - token = this.tn.next(); - if (token.toLowerCase() !== "returns") - throw Error("illegal rpc service request type delimiter: "+token); - this.tn.skip("("); - token = this.tn.next(); - if (token.toLowerCase() === "stream") { - method["response_stream"] = true; - token = this.tn.next(); - } - method["response"] = token; - this.tn.skip(")"); - token = this.tn.peek(); - if (token === '{') { - this.tn.next(); - while ((token = this.tn.next()) !== '}') { - if (token === 'option') - this._parseOption(method); - else - throw Error("illegal rpc service token: " + token); - } - this.tn.omit(";"); - } else - this.tn.skip(";"); - if (typeof svc[type] === 'undefined') - svc[type] = {}; - svc[type][name] = method; - }; - - /** - * Parses a message definition. - * @param {!Object} parent Parent definition - * @param {!Object=} fld Field definition if this is a group - * @returns {!Object} - * @private - */ - ParserPrototype._parseMessage = function(parent, fld) { - var isGroup = !!fld, - token = this.tn.next(); - var msg = { - "name": "", - "fields": [], - "enums": [], - "messages": [], - "options": {}, - "services": [], - "oneofs": {} - // "extensions": undefined - }; - if (!Lang.NAME.test(token)) - throw Error("illegal "+(isGroup ? "group" : "message")+" name: "+token); - msg["name"] = token; - if (isGroup) { - this.tn.skip("="); - fld["id"] = mkId(this.tn.next()); - msg["isGroup"] = true; - } - token = this.tn.peek(); - if (token === '[' && fld) - this._parseFieldOptions(fld); - this.tn.skip("{"); - while ((token = this.tn.next()) !== '}') { - if (Lang.RULE.test(token)) - this._parseMessageField(msg, token); - else if (token === "oneof") - this._parseMessageOneOf(msg); - else if (token === "enum") - this._parseEnum(msg); - else if (token === "message") - this._parseMessage(msg); - else if (token === "option") - this._parseOption(msg); - else if (token === "service") - this._parseService(msg); - else if (token === "extensions") - msg["extensions"] = this._parseExtensionRanges(); - else if (token === "reserved") - this._parseIgnored(); // TODO - else if (token === "extend") - this._parseExtend(msg); - else if (Lang.TYPEREF.test(token)) { - if (!this.proto3) - throw Error("illegal field rule: "+token); - this._parseMessageField(msg, "optional", token); - } else - throw Error("illegal message token: "+token); - } - this.tn.omit(";"); - parent["messages"].push(msg); - return msg; - }; - - /** - * Parses an ignored statement. - * @private - */ - ParserPrototype._parseIgnored = function() { - while (this.tn.peek() !== ';') - this.tn.next(); - this.tn.skip(";"); - }; - - /** - * Parses a message field. - * @param {!Object} msg Message definition - * @param {string} rule Field rule - * @param {string=} type Field type if already known (never known for maps) - * @returns {!Object} Field descriptor - * @private - */ - ParserPrototype._parseMessageField = function(msg, rule, type) { - if (!Lang.RULE.test(rule)) - throw Error("illegal message field rule: "+rule); - var fld = { - "rule": rule, - "type": "", - "name": "", - "options": {}, - "id": 0 - }; - var token; - if (rule === "map") { - - if (type) - throw Error("illegal type: " + type); - this.tn.skip('<'); - token = this.tn.next(); - if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) - throw Error("illegal message field type: " + token); - fld["keytype"] = token; - this.tn.skip(','); - token = this.tn.next(); - if (!Lang.TYPE.test(token) && !Lang.TYPEREF.test(token)) - throw Error("illegal message field: " + token); - fld["type"] = token; - this.tn.skip('>'); - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal message field name: " + token); - fld["name"] = token; - this.tn.skip("="); - fld["id"] = mkId(this.tn.next()); - token = this.tn.peek(); - if (token === '[') - this._parseFieldOptions(fld); - this.tn.skip(";"); - - } else { - - type = typeof type !== 'undefined' ? type : this.tn.next(); - - if (type === "group") { - - // "A [legacy] group simply combines a nested message type and a field into a single declaration. In your - // code, you can treat this message just as if it had a Result type field called result (the latter name is - // converted to lower-case so that it does not conflict with the former)." - var grp = this._parseMessage(msg, fld); - if (!/^[A-Z]/.test(grp["name"])) - throw Error('illegal group name: '+grp["name"]); - fld["type"] = grp["name"]; - fld["name"] = grp["name"].toLowerCase(); - this.tn.omit(";"); - - } else { - - if (!Lang.TYPE.test(type) && !Lang.TYPEREF.test(type)) - throw Error("illegal message field type: " + type); - fld["type"] = type; - token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal message field name: " + token); - fld["name"] = token; - this.tn.skip("="); - fld["id"] = mkId(this.tn.next()); - token = this.tn.peek(); - if (token === "[") - this._parseFieldOptions(fld); - this.tn.skip(";"); - - } - } - msg["fields"].push(fld); - return fld; - }; - - /** - * Parses a message oneof. - * @param {!Object} msg Message definition - * @private - */ - ParserPrototype._parseMessageOneOf = function(msg) { - var token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal oneof name: "+token); - var name = token, - fld; - var fields = []; - this.tn.skip("{"); - while ((token = this.tn.next()) !== "}") { - fld = this._parseMessageField(msg, "optional", token); - fld["oneof"] = name; - fields.push(fld["id"]); - } - this.tn.omit(";"); - msg["oneofs"][name] = fields; - }; - - /** - * Parses a set of field option definitions. - * @param {!Object} fld Field definition - * @private - */ - ParserPrototype._parseFieldOptions = function(fld) { - this.tn.skip("["); - var token, - first = true; - while ((token = this.tn.peek()) !== ']') { - if (!first) - this.tn.skip(","); - this._parseOption(fld, true); - first = false; - } - this.tn.next(); - }; - - /** - * Parses an enum. - * @param {!Object} msg Message definition - * @private - */ - ParserPrototype._parseEnum = function(msg) { - var enm = { - "name": "", - "values": [], - "options": {} - }; - var token = this.tn.next(); - if (!Lang.NAME.test(token)) - throw Error("illegal name: "+token); - enm["name"] = token; - this.tn.skip("{"); - while ((token = this.tn.next()) !== '}') { - if (token === "option") - this._parseOption(enm); - else { - if (!Lang.NAME.test(token)) - throw Error("illegal name: "+token); - this.tn.skip("="); - var val = { - "name": token, - "id": mkId(this.tn.next(), true) - }; - token = this.tn.peek(); - if (token === "[") - this._parseFieldOptions({ "options": {} }); - this.tn.skip(";"); - enm["values"].push(val); - } - } - this.tn.omit(";"); - msg["enums"].push(enm); - }; - - /** - * Parses extension / reserved ranges. - * @returns {!Array.>} - * @private - */ - ParserPrototype._parseExtensionRanges = function() { - var ranges = []; - var token, - range, - value; - do { - range = []; - while (true) { - token = this.tn.next(); - switch (token) { - case "min": - value = ProtoBuf.ID_MIN; - break; - case "max": - value = ProtoBuf.ID_MAX; - break; - default: - value = mkNumber(token); - break; - } - range.push(value); - if (range.length === 2) - break; - if (this.tn.peek() !== "to") { - range.push(value); - break; - } - this.tn.next(); - } - ranges.push(range); - } while (this.tn.omit(",")); - this.tn.skip(";"); - return ranges; - }; - - /** - * Parses an extend block. - * @param {!Object} parent Parent object - * @private - */ - ParserPrototype._parseExtend = function(parent) { - var token = this.tn.next(); - if (!Lang.TYPEREF.test(token)) - throw Error("illegal extend reference: "+token); - var ext = { - "ref": token, - "fields": [] - }; - this.tn.skip("{"); - while ((token = this.tn.next()) !== '}') { - if (Lang.RULE.test(token)) - this._parseMessageField(ext, token); - else if (Lang.TYPEREF.test(token)) { - if (!this.proto3) - throw Error("illegal field rule: "+token); - this._parseMessageField(ext, "optional", token); - } else - throw Error("illegal extend token: "+token); - } - this.tn.omit(";"); - parent["messages"].push(ext); - return ext; - }; - - // ----- General ----- - - /** - * Returns a string representation of this parser. - * @returns {string} - */ - ParserPrototype.toString = function() { - return "Parser at line "+this.tn.line; - }; - - /** - * @alias ProtoBuf.DotProto.Parser - * @expose - */ - DotProto.Parser = Parser; - - return DotProto; - - })(ProtoBuf, ProtoBuf.Lang); - - /** - * @alias ProtoBuf.Reflect - * @expose - */ - ProtoBuf.Reflect = (function(ProtoBuf) { - "use strict"; - - /** - * Reflection types. - * @exports ProtoBuf.Reflect - * @namespace - */ - var Reflect = {}; - - /** - * Constructs a Reflect base class. - * @exports ProtoBuf.Reflect.T - * @constructor - * @abstract - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {?ProtoBuf.Reflect.T} parent Parent object - * @param {string} name Object name - */ - var T = function(builder, parent, name) { - - /** - * Builder reference. - * @type {!ProtoBuf.Builder} - * @expose - */ - this.builder = builder; - - /** - * Parent object. - * @type {?ProtoBuf.Reflect.T} - * @expose - */ - this.parent = parent; - - /** - * Object name in namespace. - * @type {string} - * @expose - */ - this.name = name; - - /** - * Fully qualified class name - * @type {string} - * @expose - */ - this.className; - }; - - /** - * @alias ProtoBuf.Reflect.T.prototype - * @inner - */ - var TPrototype = T.prototype; - - /** - * Returns the fully qualified name of this object. - * @returns {string} Fully qualified name as of ".PATH.TO.THIS" - * @expose - */ - TPrototype.fqn = function() { - var name = this.name, - ptr = this; - do { - ptr = ptr.parent; - if (ptr == null) - break; - name = ptr.name+"."+name; - } while (true); - return name; - }; - - /** - * Returns a string representation of this Reflect object (its fully qualified name). - * @param {boolean=} includeClass Set to true to include the class name. Defaults to false. - * @return String representation - * @expose - */ - TPrototype.toString = function(includeClass) { - return (includeClass ? this.className + " " : "") + this.fqn(); - }; - - /** - * Builds this type. - * @throws {Error} If this type cannot be built directly - * @expose - */ - TPrototype.build = function() { - throw Error(this.toString(true)+" cannot be built directly"); - }; - - /** - * @alias ProtoBuf.Reflect.T - * @expose - */ - Reflect.T = T; - - /** - * Constructs a new Namespace. - * @exports ProtoBuf.Reflect.Namespace - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {?ProtoBuf.Reflect.Namespace} parent Namespace parent - * @param {string} name Namespace name - * @param {Object.=} options Namespace options - * @param {string?} syntax The syntax level of this definition (e.g., proto3) - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Namespace = function(builder, parent, name, options, syntax) { - T.call(this, builder, parent, name); - - /** - * @override - */ - this.className = "Namespace"; - - /** - * Children inside the namespace. - * @type {!Array.} - */ - this.children = []; - - /** - * Options. - * @type {!Object.} - */ - this.options = options || {}; - - /** - * Syntax level (e.g., proto2 or proto3). - * @type {!string} - */ - this.syntax = syntax || "proto2"; - }; - - /** - * @alias ProtoBuf.Reflect.Namespace.prototype - * @inner - */ - var NamespacePrototype = Namespace.prototype = Object.create(T.prototype); - - /** - * Returns an array of the namespace's children. - * @param {ProtoBuf.Reflect.T=} type Filter type (returns instances of this type only). Defaults to null (all children). - * @return {Array.} - * @expose - */ - NamespacePrototype.getChildren = function(type) { - type = type || null; - if (type == null) - return this.children.slice(); - var children = []; - for (var i=0, k=this.children.length; i} qn Qualified name to resolve - * @param {boolean=} excludeNonNamespace Excludes non-namespace types, defaults to `false` - * @return {?ProtoBuf.Reflect.Namespace} The resolved type or null if not found - * @expose - */ - NamespacePrototype.resolve = function(qn, excludeNonNamespace) { - var part = typeof qn === 'string' ? qn.split(".") : qn, - ptr = this, - i = 0; - if (part[i] === "") { // Fully qualified name, e.g. ".My.Message' - while (ptr.parent !== null) - ptr = ptr.parent; - i++; - } - var child; - do { - do { - if (!(ptr instanceof Reflect.Namespace)) { - ptr = null; - break; - } - child = ptr.getChild(part[i]); - if (!child || !(child instanceof Reflect.T) || (excludeNonNamespace && !(child instanceof Reflect.Namespace))) { - ptr = null; - break; - } - ptr = child; i++; - } while (i < part.length); - if (ptr != null) - break; // Found - // Else search the parent - if (this.parent !== null) - return this.parent.resolve(qn, excludeNonNamespace); - } while (ptr != null); - return ptr; - }; - - /** - * Determines the shortest qualified name of the specified type, if any, relative to this namespace. - * @param {!ProtoBuf.Reflect.T} t Reflection type - * @returns {string} The shortest qualified name or, if there is none, the fqn - * @expose - */ - NamespacePrototype.qn = function(t) { - var part = [], ptr = t; - do { - part.unshift(ptr.name); - ptr = ptr.parent; - } while (ptr !== null); - for (var len=1; len <= part.length; len++) { - var qn = part.slice(part.length-len); - if (t === this.resolve(qn, t instanceof Reflect.Namespace)) - return qn.join("."); - } - return t.fqn(); - }; - - /** - * Builds the namespace and returns the runtime counterpart. - * @return {Object.} Runtime namespace - * @expose - */ - NamespacePrototype.build = function() { - /** @dict */ - var ns = {}; - var children = this.children; - for (var i=0, k=children.length, child; i} - */ - NamespacePrototype.buildOpt = function() { - var opt = {}, - keys = Object.keys(this.options); - for (var i=0, k=keys.length; i}null} Option value or NULL if there is no such option - */ - NamespacePrototype.getOption = function(name) { - if (typeof name === 'undefined') - return this.options; - return typeof this.options[name] !== 'undefined' ? this.options[name] : null; - }; - - /** - * @alias ProtoBuf.Reflect.Namespace - * @expose - */ - Reflect.Namespace = Namespace; - - /** - * Constructs a new Element implementation that checks and converts values for a - * particular field type, as appropriate. - * - * An Element represents a single value: either the value of a singular field, - * or a value contained in one entry of a repeated field or map field. This - * class does not implement these higher-level concepts; it only encapsulates - * the low-level typechecking and conversion. - * - * @exports ProtoBuf.Reflect.Element - * @param {{name: string, wireType: number}} type Resolved data type - * @param {ProtoBuf.Reflect.T|null} resolvedType Resolved type, if relevant - * (e.g. submessage field). - * @param {boolean} isMapKey Is this element a Map key? The value will be - * converted to string form if so. - * @param {string} syntax Syntax level of defining message type, e.g., - * proto2 or proto3. - * @constructor - */ - var Element = function(type, resolvedType, isMapKey, syntax) { - - /** - * Element type, as a string (e.g., int32). - * @type {{name: string, wireType: number}} - */ - this.type = type; - - /** - * Element type reference to submessage or enum definition, if needed. - * @type {ProtoBuf.Reflect.T|null} - */ - this.resolvedType = resolvedType; - - /** - * Element is a map key. - * @type {boolean} - */ - this.isMapKey = isMapKey; - - /** - * Syntax level of defining message type, e.g., proto2 or proto3. - * @type {string} - */ - this.syntax = syntax; - - if (isMapKey && ProtoBuf.MAP_KEY_TYPES.indexOf(type) < 0) - throw Error("Invalid map key type: " + type.name); - }; - - var ElementPrototype = Element.prototype; - - /** - * Obtains a (new) default value for the specified type. - * @param type {string|{name: string, wireType: number}} Field type - * @returns {*} Default value - * @inner - */ - function mkDefault(type) { - if (typeof type === 'string') - type = ProtoBuf.TYPES[type]; - if (typeof type.defaultValue === 'undefined') - throw Error("default value for type "+type.name+" is not supported"); - if (type == ProtoBuf.TYPES["bytes"]) - return new ByteBuffer(0); - return type.defaultValue; - } - - /** - * Returns the default value for this field in proto3. - * @function - * @param type {string|{name: string, wireType: number}} the field type - * @returns {*} Default value - */ - Element.defaultFieldValue = mkDefault; - - /** - * Makes a Long from a value. - * @param {{low: number, high: number, unsigned: boolean}|string|number} value Value - * @param {boolean=} unsigned Whether unsigned or not, defaults to reuse it from Long-like objects or to signed for - * strings and numbers - * @returns {!Long} - * @throws {Error} If the value cannot be converted to a Long - * @inner - */ - function mkLong(value, unsigned) { - if (value && typeof value.low === 'number' && typeof value.high === 'number' && typeof value.unsigned === 'boolean' - && value.low === value.low && value.high === value.high) - return new ProtoBuf.Long(value.low, value.high, typeof unsigned === 'undefined' ? value.unsigned : unsigned); - if (typeof value === 'string') - return ProtoBuf.Long.fromString(value, unsigned || false, 10); - if (typeof value === 'number') - return ProtoBuf.Long.fromNumber(value, unsigned || false); - throw Error("not convertible to Long"); - } - - /** - * Checks if the given value can be set for an element of this type (singular - * field or one element of a repeated field or map). - * @param {*} value Value to check - * @return {*} Verified, maybe adjusted, value - * @throws {Error} If the value cannot be verified for this element slot - * @expose - */ - ElementPrototype.verifyValue = function(value) { - var self = this; - function fail(val, msg) { - throw Error("Illegal value for "+self.toString(true)+" of type "+self.type.name+": "+val+" ("+msg+")"); - } - switch (this.type) { - // Signed 32bit - case ProtoBuf.TYPES["int32"]: - case ProtoBuf.TYPES["sint32"]: - case ProtoBuf.TYPES["sfixed32"]: - // Account for !NaN: value === value - if (typeof value !== 'number' || (value === value && value % 1 !== 0)) - fail(typeof value, "not an integer"); - return value > 4294967295 ? value | 0 : value; - - // Unsigned 32bit - case ProtoBuf.TYPES["uint32"]: - case ProtoBuf.TYPES["fixed32"]: - if (typeof value !== 'number' || (value === value && value % 1 !== 0)) - fail(typeof value, "not an integer"); - return value < 0 ? value >>> 0 : value; - - // Signed 64bit - case ProtoBuf.TYPES["int64"]: - case ProtoBuf.TYPES["sint64"]: - case ProtoBuf.TYPES["sfixed64"]: { - if (ProtoBuf.Long) - try { - return mkLong(value, false); - } catch (e) { - fail(typeof value, e.message); - } - else - fail(typeof value, "requires Long.js"); - } - - // Unsigned 64bit - case ProtoBuf.TYPES["uint64"]: - case ProtoBuf.TYPES["fixed64"]: { - if (ProtoBuf.Long) - try { - return mkLong(value, true); - } catch (e) { - fail(typeof value, e.message); - } - else - fail(typeof value, "requires Long.js"); - } - - // Bool - case ProtoBuf.TYPES["bool"]: - if (typeof value !== 'boolean') - fail(typeof value, "not a boolean"); - return value; - - // Float - case ProtoBuf.TYPES["float"]: - case ProtoBuf.TYPES["double"]: - if (typeof value !== 'number') - fail(typeof value, "not a number"); - return value; - - // Length-delimited string - case ProtoBuf.TYPES["string"]: - if (typeof value !== 'string' && !(value && value instanceof String)) - fail(typeof value, "not a string"); - return ""+value; // Convert String object to string - - // Length-delimited bytes - case ProtoBuf.TYPES["bytes"]: - if (ByteBuffer.isByteBuffer(value)) - return value; - return ByteBuffer.wrap(value, "base64"); - - // Constant enum value - case ProtoBuf.TYPES["enum"]: { - var values = this.resolvedType.getChildren(ProtoBuf.Reflect.Enum.Value); - for (i=0; i 4294967295 || value < 0) - fail(typeof value, "not in range for uint32") - return value; - } else { - // proto2 requires enum values to be valid. - fail(value, "not a valid enum value"); - } - } - // Embedded message - case ProtoBuf.TYPES["group"]: - case ProtoBuf.TYPES["message"]: { - if (!value || typeof value !== 'object') - fail(typeof value, "object expected"); - if (value instanceof this.resolvedType.clazz) - return value; - if (value instanceof ProtoBuf.Builder.Message) { - // Mismatched type: Convert to object (see: https://github.com/dcodeIO/ProtoBuf.js/issues/180) - var obj = {}; - for (var i in value) - if (value.hasOwnProperty(i)) - obj[i] = value[i]; - value = obj; - } - // Else let's try to construct one from a key-value object - return new (this.resolvedType.clazz)(value); // May throw for a hundred of reasons - } - } - - // We should never end here - throw Error("[INTERNAL] Illegal value for "+this.toString(true)+": "+value+" (undefined type "+this.type+")"); - }; - - /** - * Calculates the byte length of an element on the wire. - * @param {number} id Field number - * @param {*} value Field value - * @returns {number} Byte length - * @throws {Error} If the value cannot be calculated - * @expose - */ - ElementPrototype.calculateLength = function(id, value) { - if (value === null) return 0; // Nothing to encode - // Tag has already been written - var n; - switch (this.type) { - case ProtoBuf.TYPES["int32"]: - return value < 0 ? ByteBuffer.calculateVarint64(value) : ByteBuffer.calculateVarint32(value); - case ProtoBuf.TYPES["uint32"]: - return ByteBuffer.calculateVarint32(value); - case ProtoBuf.TYPES["sint32"]: - return ByteBuffer.calculateVarint32(ByteBuffer.zigZagEncode32(value)); - case ProtoBuf.TYPES["fixed32"]: - case ProtoBuf.TYPES["sfixed32"]: - case ProtoBuf.TYPES["float"]: - return 4; - case ProtoBuf.TYPES["int64"]: - case ProtoBuf.TYPES["uint64"]: - return ByteBuffer.calculateVarint64(value); - case ProtoBuf.TYPES["sint64"]: - return ByteBuffer.calculateVarint64(ByteBuffer.zigZagEncode64(value)); - case ProtoBuf.TYPES["fixed64"]: - case ProtoBuf.TYPES["sfixed64"]: - return 8; - case ProtoBuf.TYPES["bool"]: - return 1; - case ProtoBuf.TYPES["enum"]: - return ByteBuffer.calculateVarint32(value); - case ProtoBuf.TYPES["double"]: - return 8; - case ProtoBuf.TYPES["string"]: - n = ByteBuffer.calculateUTF8Bytes(value); - return ByteBuffer.calculateVarint32(n) + n; - case ProtoBuf.TYPES["bytes"]: - if (value.remaining() < 0) - throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining"); - return ByteBuffer.calculateVarint32(value.remaining()) + value.remaining(); - case ProtoBuf.TYPES["message"]: - n = this.resolvedType.calculate(value); - return ByteBuffer.calculateVarint32(n) + n; - case ProtoBuf.TYPES["group"]: - n = this.resolvedType.calculate(value); - return n + ByteBuffer.calculateVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP); - } - // We should never end here - throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)"); - }; - - /** - * Encodes a value to the specified buffer. Does not encode the key. - * @param {number} id Field number - * @param {*} value Field value - * @param {ByteBuffer} buffer ByteBuffer to encode to - * @return {ByteBuffer} The ByteBuffer for chaining - * @throws {Error} If the value cannot be encoded - * @expose - */ - ElementPrototype.encodeValue = function(id, value, buffer) { - if (value === null) return buffer; // Nothing to encode - // Tag has already been written - - switch (this.type) { - // 32bit signed varint - case ProtoBuf.TYPES["int32"]: - // "If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes - // long – it is, effectively, treated like a very large unsigned integer." (see #122) - if (value < 0) - buffer.writeVarint64(value); - else - buffer.writeVarint32(value); - break; - - // 32bit unsigned varint - case ProtoBuf.TYPES["uint32"]: - buffer.writeVarint32(value); - break; - - // 32bit varint zig-zag - case ProtoBuf.TYPES["sint32"]: - buffer.writeVarint32ZigZag(value); - break; - - // Fixed unsigned 32bit - case ProtoBuf.TYPES["fixed32"]: - buffer.writeUint32(value); - break; - - // Fixed signed 32bit - case ProtoBuf.TYPES["sfixed32"]: - buffer.writeInt32(value); - break; - - // 64bit varint as-is - case ProtoBuf.TYPES["int64"]: - case ProtoBuf.TYPES["uint64"]: - buffer.writeVarint64(value); // throws - break; - - // 64bit varint zig-zag - case ProtoBuf.TYPES["sint64"]: - buffer.writeVarint64ZigZag(value); // throws - break; - - // Fixed unsigned 64bit - case ProtoBuf.TYPES["fixed64"]: - buffer.writeUint64(value); // throws - break; - - // Fixed signed 64bit - case ProtoBuf.TYPES["sfixed64"]: - buffer.writeInt64(value); // throws - break; - - // Bool - case ProtoBuf.TYPES["bool"]: - if (typeof value === 'string') - buffer.writeVarint32(value.toLowerCase() === 'false' ? 0 : !!value); - else - buffer.writeVarint32(value ? 1 : 0); - break; - - // Constant enum value - case ProtoBuf.TYPES["enum"]: - buffer.writeVarint32(value); - break; - - // 32bit float - case ProtoBuf.TYPES["float"]: - buffer.writeFloat32(value); - break; - - // 64bit float - case ProtoBuf.TYPES["double"]: - buffer.writeFloat64(value); - break; - - // Length-delimited string - case ProtoBuf.TYPES["string"]: - buffer.writeVString(value); - break; - - // Length-delimited bytes - case ProtoBuf.TYPES["bytes"]: - if (value.remaining() < 0) - throw Error("Illegal value for "+this.toString(true)+": "+value.remaining()+" bytes remaining"); - var prevOffset = value.offset; - buffer.writeVarint32(value.remaining()); - buffer.append(value); - value.offset = prevOffset; - break; - - // Embedded message - case ProtoBuf.TYPES["message"]: - var bb = new ByteBuffer().LE(); - this.resolvedType.encode(value, bb); - buffer.writeVarint32(bb.offset); - buffer.append(bb.flip()); - break; - - // Legacy group - case ProtoBuf.TYPES["group"]: - this.resolvedType.encode(value, buffer); - buffer.writeVarint32((id << 3) | ProtoBuf.WIRE_TYPES.ENDGROUP); - break; - - default: - // We should never end here - throw Error("[INTERNAL] Illegal value to encode in "+this.toString(true)+": "+value+" (unknown type)"); - } - return buffer; - }; - - /** - * Decode one element value from the specified buffer. - * @param {ByteBuffer} buffer ByteBuffer to decode from - * @param {number} wireType The field wire type - * @param {number} id The field number - * @return {*} Decoded value - * @throws {Error} If the field cannot be decoded - * @expose - */ - ElementPrototype.decode = function(buffer, wireType, id) { - if (wireType != this.type.wireType) - throw Error("Unexpected wire type for element"); - - var value, nBytes; - switch (this.type) { - // 32bit signed varint - case ProtoBuf.TYPES["int32"]: - return buffer.readVarint32() | 0; - - // 32bit unsigned varint - case ProtoBuf.TYPES["uint32"]: - return buffer.readVarint32() >>> 0; - - // 32bit signed varint zig-zag - case ProtoBuf.TYPES["sint32"]: - return buffer.readVarint32ZigZag() | 0; - - // Fixed 32bit unsigned - case ProtoBuf.TYPES["fixed32"]: - return buffer.readUint32() >>> 0; - - case ProtoBuf.TYPES["sfixed32"]: - return buffer.readInt32() | 0; - - // 64bit signed varint - case ProtoBuf.TYPES["int64"]: - return buffer.readVarint64(); - - // 64bit unsigned varint - case ProtoBuf.TYPES["uint64"]: - return buffer.readVarint64().toUnsigned(); - - // 64bit signed varint zig-zag - case ProtoBuf.TYPES["sint64"]: - return buffer.readVarint64ZigZag(); - - // Fixed 64bit unsigned - case ProtoBuf.TYPES["fixed64"]: - return buffer.readUint64(); - - // Fixed 64bit signed - case ProtoBuf.TYPES["sfixed64"]: - return buffer.readInt64(); - - // Bool varint - case ProtoBuf.TYPES["bool"]: - return !!buffer.readVarint32(); - - // Constant enum value (varint) - case ProtoBuf.TYPES["enum"]: - // The following Builder.Message#set will already throw - return buffer.readVarint32(); - - // 32bit float - case ProtoBuf.TYPES["float"]: - return buffer.readFloat(); - - // 64bit float - case ProtoBuf.TYPES["double"]: - return buffer.readDouble(); - - // Length-delimited string - case ProtoBuf.TYPES["string"]: - return buffer.readVString(); - - // Length-delimited bytes - case ProtoBuf.TYPES["bytes"]: { - nBytes = buffer.readVarint32(); - if (buffer.remaining() < nBytes) - throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); - value = buffer.clone(); // Offset already set - value.limit = value.offset+nBytes; - buffer.offset += nBytes; - return value; - } - - // Length-delimited embedded message - case ProtoBuf.TYPES["message"]: { - nBytes = buffer.readVarint32(); - return this.resolvedType.decode(buffer, nBytes); - } - - // Legacy group - case ProtoBuf.TYPES["group"]: - return this.resolvedType.decode(buffer, -1, id); - } - - // We should never end here - throw Error("[INTERNAL] Illegal decode type"); - }; - - /** - * Converts a value from a string to the canonical element type. - * - * Legal only when isMapKey is true. - * - * @param {string} str The string value - * @returns {*} The value - */ - ElementPrototype.valueFromString = function(str) { - if (!this.isMapKey) { - throw Error("valueFromString() called on non-map-key element"); - } - - switch (this.type) { - case ProtoBuf.TYPES["int32"]: - case ProtoBuf.TYPES["sint32"]: - case ProtoBuf.TYPES["sfixed32"]: - case ProtoBuf.TYPES["uint32"]: - case ProtoBuf.TYPES["fixed32"]: - return this.verifyValue(parseInt(str)); - - case ProtoBuf.TYPES["int64"]: - case ProtoBuf.TYPES["sint64"]: - case ProtoBuf.TYPES["sfixed64"]: - case ProtoBuf.TYPES["uint64"]: - case ProtoBuf.TYPES["fixed64"]: - // Long-based fields support conversions from string already. - return this.verifyValue(str); - - case ProtoBuf.TYPES["bool"]: - return str === "true"; - - case ProtoBuf.TYPES["string"]: - return this.verifyValue(str); - - case ProtoBuf.TYPES["bytes"]: - return ByteBuffer.fromBinary(str); - } - }; - - /** - * Converts a value from the canonical element type to a string. - * - * It should be the case that `valueFromString(valueToString(val))` returns - * a value equivalent to `verifyValue(val)` for every legal value of `val` - * according to this element type. - * - * This may be used when the element must be stored or used as a string, - * e.g., as a map key on an Object. - * - * Legal only when isMapKey is true. - * - * @param {*} val The value - * @returns {string} The string form of the value. - */ - ElementPrototype.valueToString = function(value) { - if (!this.isMapKey) { - throw Error("valueToString() called on non-map-key element"); - } - - if (this.type === ProtoBuf.TYPES["bytes"]) { - return value.toString("binary"); - } else { - return value.toString(); - } - }; - - /** - * @alias ProtoBuf.Reflect.Element - * @expose - */ - Reflect.Element = Element; - - /** - * Constructs a new Message. - * @exports ProtoBuf.Reflect.Message - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Namespace} parent Parent message or namespace - * @param {string} name Message name - * @param {Object.=} options Message options - * @param {boolean=} isGroup `true` if this is a legacy group - * @param {string?} syntax The syntax level of this definition (e.g., proto3) - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Message = function(builder, parent, name, options, isGroup, syntax) { - Namespace.call(this, builder, parent, name, options, syntax); - - /** - * @override - */ - this.className = "Message"; - - /** - * Extensions range. - * @type {!Array.|undefined} - * @expose - */ - this.extensions = undefined; - - /** - * Runtime message class. - * @type {?function(new:ProtoBuf.Builder.Message)} - * @expose - */ - this.clazz = null; - - /** - * Whether this is a legacy group or not. - * @type {boolean} - * @expose - */ - this.isGroup = !!isGroup; - - // The following cached collections are used to efficiently iterate over or look up fields when decoding. - - /** - * Cached fields. - * @type {?Array.} - * @private - */ - this._fields = null; - - /** - * Cached fields by id. - * @type {?Object.} - * @private - */ - this._fieldsById = null; - - /** - * Cached fields by name. - * @type {?Object.} - * @private - */ - this._fieldsByName = null; - }; - - /** - * @alias ProtoBuf.Reflect.Message.prototype - * @inner - */ - var MessagePrototype = Message.prototype = Object.create(Namespace.prototype); - - /** - * Builds the message and returns the runtime counterpart, which is a fully functional class. - * @see ProtoBuf.Builder.Message - * @param {boolean=} rebuild Whether to rebuild or not, defaults to false - * @return {ProtoBuf.Reflect.Message} Message class - * @throws {Error} If the message cannot be built - * @expose - */ - MessagePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; - - // Create the runtime Message class in its own scope - var clazz = (function(ProtoBuf, T) { - - var fields = T.getChildren(ProtoBuf.Reflect.Message.Field), - oneofs = T.getChildren(ProtoBuf.Reflect.Message.OneOf); - - /** - * Constructs a new runtime Message. - * @name ProtoBuf.Builder.Message - * @class Barebone of all runtime messages. - * @param {!Object.|string} values Preset values - * @param {...string} var_args - * @constructor - * @throws {Error} If the message cannot be created - */ - var Message = function(values, var_args) { - ProtoBuf.Builder.Message.call(this); - - // Create virtual oneof properties - for (var i=0, k=oneofs.length; i 0) { - var value; - // Set field values from a values object - if (arguments.length === 1 && values !== null && typeof values === 'object' && - /* not _another_ Message */ (typeof values.encode !== 'function' || values instanceof Message) && - /* not a repeated field */ !Array.isArray(values) && - /* not a Map */ !(values instanceof ProtoBuf.Map) && - /* not a ByteBuffer */ !ByteBuffer.isByteBuffer(values) && - /* not an ArrayBuffer */ !(values instanceof ArrayBuffer) && - /* not a Long */ !(ProtoBuf.Long && values instanceof ProtoBuf.Long)) { - this.$set(values); - } else // Set field values from arguments, in declaration order - for (i=0, k=arguments.length; i} keyOrObj String key or plain object holding multiple values - * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted - * @param {boolean=} noAssert Whether to not assert for an actual field / proper value type, defaults to `false` - * @returns {!ProtoBuf.Builder.Message} this - * @throws {Error} If the value cannot be set - * @expose - */ - MessagePrototype.set = function(keyOrObj, value, noAssert) { - if (keyOrObj && typeof keyOrObj === 'object') { - noAssert = value; - for (var ikey in keyOrObj) - if (keyOrObj.hasOwnProperty(ikey) && typeof (value = keyOrObj[ikey]) !== 'undefined') - this.$set(ikey, value, noAssert); - return this; - } - var field = T._fieldsByName[keyOrObj]; - if (!noAssert) { - if (!field) - throw Error(this+"#"+keyOrObj+" is not a field: undefined"); - if (!(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+keyOrObj+" is not a field: "+field.toString(true)); - this[field.name] = (value = field.verifyValue(value)); // May throw - } else - this[keyOrObj] = value; - if (field && field.oneof) { // Field is part of an OneOf (not a virtual OneOf field) - var currentField = this[field.oneof.name]; // Virtual field references currently set field - if (value !== null) { - if (currentField !== null && currentField !== field.name) - this[currentField] = null; // Clear currently set field - this[field.oneof.name] = field.name; // Point virtual field at this field - } else if (/* value === null && */currentField === keyOrObj) - this[field.oneof.name] = null; // Clear virtual field (current field explicitly cleared) - } - return this; - }; - - /** - * Sets a field's value. This is an alias for [@link ProtoBuf.Builder.Message#set}. - * @name ProtoBuf.Builder.Message#$set - * @function - * @param {string|!Object.} keyOrObj String key or plain object holding multiple values - * @param {(*|boolean)=} value Value to set if key is a string, otherwise omitted - * @param {boolean=} noAssert Whether to not assert the value, defaults to `false` - * @throws {Error} If the value cannot be set - * @expose - */ - MessagePrototype.$set = MessagePrototype.set; - - /** - * Gets a field's value. - * @name ProtoBuf.Builder.Message#get - * @function - * @param {string} key Key - * @param {boolean=} noAssert Whether to not assert for an actual field, defaults to `false` - * @return {*} Value - * @throws {Error} If there is no such field - * @expose - */ - MessagePrototype.get = function(key, noAssert) { - if (noAssert) - return this[key]; - var field = T._fieldsByName[key]; - if (!field || !(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: undefined"); - if (!(field instanceof ProtoBuf.Reflect.Message.Field)) - throw Error(this+"#"+key+" is not a field: "+field.toString(true)); - return this[field.name]; - }; - - /** - * Gets a field's value. This is an alias for {@link ProtoBuf.Builder.Message#$get}. - * @name ProtoBuf.Builder.Message#$get - * @function - * @param {string} key Key - * @return {*} Value - * @throws {Error} If there is no such field - * @expose - */ - MessagePrototype.$get = MessagePrototype.get; - - // Getters and setters - - for (var i=0; i} data Data payload - * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. - * @param {boolean=} noVerify Whether to not verify field values, defaults to `false` - * @return {!ByteBuffer} Encoded message as a ByteBuffer - * @expose - */ - Message.encode = function(data, buffer, noVerify) { - return new Message(data).encode(buffer, noVerify); - }; - - /** - * Calculates the byte length of the message. - * @name ProtoBuf.Builder.Message#calculate - * @function - * @returns {number} Byte length - * @throws {Error} If the message cannot be calculated or if required fields are missing. - * @expose - */ - MessagePrototype.calculate = function() { - return T.calculate(this); - }; - - /** - * Encodes the varint32 length-delimited message. - * @name ProtoBuf.Builder.Message#encodeDelimited - * @function - * @param {(!ByteBuffer|boolean)=} buffer ByteBuffer to encode to. Will create a new one and flip it if omitted. - * @param {boolean=} noVerify Whether to not verify field values, defaults to `false` - * @return {!ByteBuffer} Encoded message as a ByteBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ByteBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeDelimited = function(buffer, noVerify) { - var isNew = false; - if (!buffer) - buffer = new ByteBuffer(), - isNew = true; - var enc = new ByteBuffer().LE(); - T.encode(this, enc, noVerify).flip(); - buffer.writeVarint32(enc.remaining()); - buffer.append(enc); - return isNew ? buffer.flip() : buffer; - }; - - /** - * Directly encodes the message to an ArrayBuffer. - * @name ProtoBuf.Builder.Message#encodeAB - * @function - * @return {ArrayBuffer} Encoded message as ArrayBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ArrayBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeAB = function() { - try { - return this.encode().toArrayBuffer(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toArrayBuffer(); - throw(e); - } - }; - - /** - * Returns the message as an ArrayBuffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeAB}. - * @name ProtoBuf.Builder.Message#toArrayBuffer - * @function - * @return {ArrayBuffer} Encoded message as ArrayBuffer - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded ArrayBuffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toArrayBuffer = MessagePrototype.encodeAB; - - /** - * Directly encodes the message to a node Buffer. - * @name ProtoBuf.Builder.Message#encodeNB - * @function - * @return {!Buffer} - * @throws {Error} If the message cannot be encoded, not running under node.js or if required fields are - * missing. The later still returns the encoded node Buffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeNB = function() { - try { - return this.encode().toBuffer(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toBuffer(); - throw(e); - } - }; - - /** - * Returns the message as a node Buffer. This is an alias for {@link ProtoBuf.Builder.Message#encodeNB}. - * @name ProtoBuf.Builder.Message#toBuffer - * @function - * @return {!Buffer} - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded node Buffer in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toBuffer = MessagePrototype.encodeNB; - - /** - * Directly encodes the message to a base64 encoded string. - * @name ProtoBuf.Builder.Message#encode64 - * @function - * @return {string} Base64 encoded string - * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later - * still returns the encoded base64 string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encode64 = function() { - try { - return this.encode().toBase64(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toBase64(); - throw(e); - } - }; - - /** - * Returns the message as a base64 encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encode64}. - * @name ProtoBuf.Builder.Message#toBase64 - * @function - * @return {string} Base64 encoded string - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded base64 string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toBase64 = MessagePrototype.encode64; - - /** - * Directly encodes the message to a hex encoded string. - * @name ProtoBuf.Builder.Message#encodeHex - * @function - * @return {string} Hex encoded string - * @throws {Error} If the underlying buffer cannot be encoded or if required fields are missing. The later - * still returns the encoded hex string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.encodeHex = function() { - try { - return this.encode().toHex(); - } catch (e) { - if (e["encoded"]) e["encoded"] = e["encoded"].toHex(); - throw(e); - } - }; - - /** - * Returns the message as a hex encoded string. This is an alias for {@link ProtoBuf.Builder.Message#encodeHex}. - * @name ProtoBuf.Builder.Message#toHex - * @function - * @return {string} Hex encoded string - * @throws {Error} If the message cannot be encoded or if required fields are missing. The later still - * returns the encoded hex string in the `encoded` property on the error. - * @expose - */ - MessagePrototype.toHex = MessagePrototype.encodeHex; - - /** - * Clones a message object or field value to a raw object. - * @param {*} obj Object to clone - * @param {boolean} binaryAsBase64 Whether to include binary data as base64 strings or as a buffer otherwise - * @param {boolean} longsAsStrings Whether to encode longs as strings - * @param {!ProtoBuf.Reflect.T=} resolvedType The resolved field type if a field - * @returns {*} Cloned object - * @inner - */ - function cloneRaw(obj, binaryAsBase64, longsAsStrings, resolvedType) { - if (obj === null || typeof obj !== 'object') { - // Convert enum values to their respective names - if (resolvedType && resolvedType instanceof ProtoBuf.Reflect.Enum) { - var name = ProtoBuf.Reflect.Enum.getName(resolvedType.object, obj); - if (name !== null) - return name; - } - // Pass-through string, number, boolean, null... - return obj; - } - // Convert ByteBuffers to raw buffer or strings - if (ByteBuffer.isByteBuffer(obj)) - return binaryAsBase64 ? obj.toBase64() : obj.toBuffer(); - // Convert Longs to proper objects or strings - if (ProtoBuf.Long.isLong(obj)) - return longsAsStrings ? obj.toString() : ProtoBuf.Long.fromValue(obj); - var clone; - // Clone arrays - if (Array.isArray(obj)) { - clone = []; - obj.forEach(function(v, k) { - clone[k] = cloneRaw(v, binaryAsBase64, longsAsStrings, resolvedType); - }); - return clone; - } - clone = {}; - // Convert maps to objects - if (obj instanceof ProtoBuf.Map) { - var it = obj.entries(); - for (var e = it.next(); !e.done; e = it.next()) - clone[obj.keyElem.valueToString(e.value[0])] = cloneRaw(e.value[1], binaryAsBase64, longsAsStrings, obj.valueElem.resolvedType); - return clone; - } - // Everything else is a non-null object - var type = obj.$type, - field = undefined; - for (var i in obj) - if (obj.hasOwnProperty(i)) { - if (type && (field = type.getChild(i))) - clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings, field.resolvedType); - else - clone[i] = cloneRaw(obj[i], binaryAsBase64, longsAsStrings); - } - return clone; - } - - /** - * Returns the message's raw payload. - * @param {boolean=} binaryAsBase64 Whether to include binary data as base64 strings instead of Buffers, defaults to `false` - * @param {boolean} longsAsStrings Whether to encode longs as strings - * @returns {Object.} Raw payload - * @expose - */ - MessagePrototype.toRaw = function(binaryAsBase64, longsAsStrings) { - return cloneRaw(this, !!binaryAsBase64, !!longsAsStrings, this.$type); - }; - - /** - * Encodes a message to JSON. - * @returns {string} JSON string - * @expose - */ - MessagePrototype.encodeJSON = function() { - return JSON.stringify( - cloneRaw(this, - /* binary-as-base64 */ true, - /* longs-as-strings */ true, - this.$type - ) - ); - }; - - /** - * Decodes a message from the specified buffer or string. - * @name ProtoBuf.Builder.Message.decode - * @function - * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from - * @param {(number|string)=} length Message length. Defaults to decode all the remainig data. - * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - * @see ProtoBuf.Builder.Message.decode64 - * @see ProtoBuf.Builder.Message.decodeHex - */ - Message.decode = function(buffer, length, enc) { - if (typeof length === 'string') - enc = length, - length = -1; - if (typeof buffer === 'string') - buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); - buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw - var le = buffer.littleEndian; - try { - var msg = T.decode(buffer.LE()); - buffer.LE(le); - return msg; - } catch (e) { - buffer.LE(le); - throw(e); - } - }; - - /** - * Decodes a varint32 length-delimited message from the specified buffer or string. - * @name ProtoBuf.Builder.Message.decodeDelimited - * @function - * @param {!ByteBuffer|!ArrayBuffer|!Buffer|string} buffer Buffer to decode from - * @param {string=} enc Encoding if buffer is a string: hex, utf8 (not recommended), defaults to base64 - * @return {ProtoBuf.Builder.Message} Decoded message or `null` if not enough bytes are available yet - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decodeDelimited = function(buffer, enc) { - if (typeof buffer === 'string') - buffer = ByteBuffer.wrap(buffer, enc ? enc : "base64"); - buffer = ByteBuffer.isByteBuffer(buffer) ? buffer : ByteBuffer.wrap(buffer); // May throw - if (buffer.remaining() < 1) - return null; - var off = buffer.offset, - len = buffer.readVarint32(); - if (buffer.remaining() < len) { - buffer.offset = off; - return null; - } - try { - var msg = T.decode(buffer.slice(buffer.offset, buffer.offset + len).LE()); - buffer.offset += len; - return msg; - } catch (err) { - buffer.offset += len; - throw err; - } - }; - - /** - * Decodes the message from the specified base64 encoded string. - * @name ProtoBuf.Builder.Message.decode64 - * @function - * @param {string} str String to decode from - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decode64 = function(str) { - return Message.decode(str, "base64"); - }; - - /** - * Decodes the message from the specified hex encoded string. - * @name ProtoBuf.Builder.Message.decodeHex - * @function - * @param {string} str String to decode from - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are missing. The later still - * returns the decoded message with missing fields in the `decoded` property on the error. - * @expose - */ - Message.decodeHex = function(str) { - return Message.decode(str, "hex"); - }; - - /** - * Decodes the message from a JSON string. - * @name ProtoBuf.Builder.Message.decodeJSON - * @function - * @param {string} str String to decode from - * @return {!ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded or if required fields are - * missing. - * @expose - */ - Message.decodeJSON = function(str) { - return new Message(JSON.parse(str)); - }; - - // Utility - - /** - * Returns a string representation of this Message. - * @name ProtoBuf.Builder.Message#toString - * @function - * @return {string} String representation as of ".Fully.Qualified.MessageName" - * @expose - */ - MessagePrototype.toString = function() { - return T.toString(); - }; - - // Properties - - /** - * Message options. - * @name ProtoBuf.Builder.Message.$options - * @type {Object.} - * @expose - */ - var $optionsS; // cc needs this - - /** - * Message options. - * @name ProtoBuf.Builder.Message#$options - * @type {Object.} - * @expose - */ - var $options; - - /** - * Reflection type. - * @name ProtoBuf.Builder.Message.$type - * @type {!ProtoBuf.Reflect.Message} - * @expose - */ - var $typeS; - - /** - * Reflection type. - * @name ProtoBuf.Builder.Message#$type - * @type {!ProtoBuf.Reflect.Message} - * @expose - */ - var $type; - - if (Object.defineProperty) - Object.defineProperty(Message, '$options', { "value": T.buildOpt() }), - Object.defineProperty(MessagePrototype, "$options", { "value": Message["$options"] }), - Object.defineProperty(Message, "$type", { "value": T }), - Object.defineProperty(MessagePrototype, "$type", { "value": T }); - - return Message; - - })(ProtoBuf, this); - - // Static enums and prototyped sub-messages / cached collections - this._fields = []; - this._fieldsById = {}; - this._fieldsByName = {}; - for (var i=0, k=this.children.length, child; i>> 3; - switch (wireType) { - case ProtoBuf.WIRE_TYPES.VARINT: - do tag = buf.readUint8(); - while ((tag & 0x80) === 0x80); - break; - case ProtoBuf.WIRE_TYPES.BITS64: - buf.offset += 8; - break; - case ProtoBuf.WIRE_TYPES.LDELIM: - tag = buf.readVarint32(); // reads the varint - buf.offset += tag; // skips n bytes - break; - case ProtoBuf.WIRE_TYPES.STARTGROUP: - skipTillGroupEnd(id, buf); - break; - case ProtoBuf.WIRE_TYPES.ENDGROUP: - if (id === expectedId) - return false; - else - throw Error("Illegal GROUPEND after unknown group: "+id+" ("+expectedId+" expected)"); - case ProtoBuf.WIRE_TYPES.BITS32: - buf.offset += 4; - break; - default: - throw Error("Illegal wire type in unknown group "+expectedId+": "+wireType); - } - return true; - } - - /** - * Decodes an encoded message and returns the decoded message. - * @param {ByteBuffer} buffer ByteBuffer to decode from - * @param {number=} length Message length. Defaults to decode all remaining data. - * @param {number=} expectedGroupEndId Expected GROUPEND id if this is a legacy group - * @return {ProtoBuf.Builder.Message} Decoded message - * @throws {Error} If the message cannot be decoded - * @expose - */ - MessagePrototype.decode = function(buffer, length, expectedGroupEndId) { - length = typeof length === 'number' ? length : -1; - var start = buffer.offset, - msg = new (this.clazz)(), - tag, wireType, id, field; - while (buffer.offset < start+length || (length === -1 && buffer.remaining() > 0)) { - tag = buffer.readVarint32(); - wireType = tag & 0x07; - id = tag >>> 3; - if (wireType === ProtoBuf.WIRE_TYPES.ENDGROUP) { - if (id !== expectedGroupEndId) - throw Error("Illegal group end indicator for "+this.toString(true)+": "+id+" ("+(expectedGroupEndId ? expectedGroupEndId+" expected" : "not a group")+")"); - break; - } - // "messages created by your new code can be parsed by your old code: old binaries simply append the buffer to unknownFields when parsing. - if (!(field = this._fieldsById[id])) { - // Finds the starting offset to slice - let start = buffer.offset; - do { - --start; - buffer.offset = start; - } while (buffer.readVarint32() !== tag); - - // Skip the piece in the buffer - switch (wireType) { - case ProtoBuf.WIRE_TYPES.VARINT: - buffer.readVarint32(); - break; - case ProtoBuf.WIRE_TYPES.BITS32: - buffer.offset += 4; - break; - case ProtoBuf.WIRE_TYPES.BITS64: - buffer.offset += 8; - break; - case ProtoBuf.WIRE_TYPES.LDELIM: - var len = buffer.readVarint32(); - buffer.offset += len; - break; - case ProtoBuf.WIRE_TYPES.STARTGROUP: - while (skipTillGroupEnd(id, buffer)) {} - break; - default: - throw Error("Illegal wire type for unknown field "+id+" in "+this.toString(true)+"#decode: "+wireType); - } - - // Slice the part of the buffer we can't parse and add it to unknownFields - const unknownFields = msg.__unknownFields ? msg.__unknownFields : new ByteBuffer(0); - const slicedBuffer = buffer.slice(start, buffer.offset); - msg.__unknownFields = ByteBuffer.concat([ - unknownFields, - slicedBuffer - ]); - continue; - } - if (field.repeated && !field.options["packed"]) { - msg[field.name].push(field.decode(wireType, buffer)); - } else if (field.map) { - var keyval = field.decode(wireType, buffer); - msg[field.name].set(keyval[0], keyval[1]); - } else { - msg[field.name] = field.decode(wireType, buffer); - if (field.oneof) { // Field is part of an OneOf (not a virtual OneOf field) - var currentField = msg[field.oneof.name]; // Virtual field references currently set field - if (currentField !== null && currentField !== field.name) - msg[currentField] = null; // Clear currently set field - msg[field.oneof.name] = field.name; // Point virtual field at this field - } - } - } - - // Check if all required fields are present and set default values for optional fields that are not - for (var i=0, k=this._fields.length; i=} options Options - * @param {!ProtoBuf.Reflect.Message.OneOf=} oneof Enclosing OneOf - * @param {string?} syntax The syntax level of this definition (e.g., proto3) - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Field = function(builder, message, rule, keytype, type, name, id, options, oneof, syntax) { - T.call(this, builder, message, name); - - /** - * @override - */ - this.className = "Message.Field"; - - /** - * Message field required flag. - * @type {boolean} - * @expose - */ - this.required = rule === "required"; - - /** - * Message field repeated flag. - * @type {boolean} - * @expose - */ - this.repeated = rule === "repeated"; - - /** - * Message field map flag. - * @type {boolean} - * @expose - */ - this.map = rule === "map"; - - /** - * Message field key type. Type reference string if unresolved, protobuf - * type if resolved. Valid only if this.map === true, null otherwise. - * @type {string|{name: string, wireType: number}|null} - * @expose - */ - this.keyType = keytype || null; - - /** - * Message field type. Type reference string if unresolved, protobuf type if - * resolved. In a map field, this is the value type. - * @type {string|{name: string, wireType: number}} - * @expose - */ - this.type = type; - - /** - * Resolved type reference inside the global namespace. - * @type {ProtoBuf.Reflect.T|null} - * @expose - */ - this.resolvedType = null; - - /** - * Unique message field id. - * @type {number} - * @expose - */ - this.id = id; - - /** - * Message field options. - * @type {!Object.} - * @dict - * @expose - */ - this.options = options || {}; - - /** - * Default value. - * @type {*} - * @expose - */ - this.defaultValue = null; - - /** - * Enclosing OneOf. - * @type {?ProtoBuf.Reflect.Message.OneOf} - * @expose - */ - this.oneof = oneof || null; - - /** - * Syntax level of this definition (e.g., proto3). - * @type {string} - * @expose - */ - this.syntax = syntax || 'proto2'; - - /** - * Original field name. - * @type {string} - * @expose - */ - this.originalName = this.name; // Used to revert camelcase transformation on naming collisions - - /** - * Element implementation. Created in build() after types are resolved. - * @type {ProtoBuf.Element} - * @expose - */ - this.element = null; - - /** - * Key element implementation, for map fields. Created in build() after - * types are resolved. - * @type {ProtoBuf.Element} - * @expose - */ - this.keyElement = null; - - // Convert field names to camel case notation if the override is set - if (this.builder.options['convertFieldsToCamelCase'] && !(this instanceof Message.ExtensionField)) - this.name = ProtoBuf.Util.toCamelCase(this.name); - }; - - /** - * @alias ProtoBuf.Reflect.Message.Field.prototype - * @inner - */ - var FieldPrototype = Field.prototype = Object.create(T.prototype); - - /** - * Builds the field. - * @override - * @expose - */ - FieldPrototype.build = function() { - this.element = new Element(this.type, this.resolvedType, false, this.syntax); - if (this.map) - this.keyElement = new Element(this.keyType, undefined, true, this.syntax); - - // In proto3, fields do not have field presence, and every field is set to - // its type's default value ("", 0, 0.0, or false). - if (this.syntax === 'proto3' && !this.repeated && !this.map) - this.defaultValue = Element.defaultFieldValue(this.type); - - // Otherwise, default values are present when explicitly specified - else if (typeof this.options['default'] !== 'undefined') - this.defaultValue = this.verifyValue(this.options['default']); - }; - - /** - * Checks if the given value can be set for this field. - * @param {*} value Value to check - * @param {boolean=} skipRepeated Whether to skip the repeated value check or not. Defaults to false. - * @return {*} Verified, maybe adjusted, value - * @throws {Error} If the value cannot be set for this field - * @expose - */ - FieldPrototype.verifyValue = function(value, skipRepeated) { - skipRepeated = skipRepeated || false; - var self = this; - function fail(val, msg) { - throw Error("Illegal value for "+self.toString(true)+" of type "+self.type.name+": "+val+" ("+msg+")"); - } - if (value === null) { // NULL values for optional fields - if (this.required) - fail(typeof value, "required"); - if (this.syntax === 'proto3' && this.type !== ProtoBuf.TYPES["message"]) - fail(typeof value, "proto3 field without field presence cannot be null"); - return null; - } - var i; - if (this.repeated && !skipRepeated) { // Repeated values as arrays - if (!Array.isArray(value)) - value = [value]; - var res = []; - for (i=0; i 0; - - case ProtoBuf.TYPES["bytes"]: - return value.remaining() > 0; - - case ProtoBuf.TYPES["enum"]: - return value !== 0; - - case ProtoBuf.TYPES["message"]: - return value !== null; - default: - return true; - } - }; - - /** - * Encodes the specified field value to the specified buffer. - * @param {*} value Verified field value - * @param {ByteBuffer} buffer ByteBuffer to encode to - * @param {!ProtoBuf.Builder.Message} message Runtime message - * @return {ByteBuffer} The ByteBuffer for chaining - * @throws {Error} If the field cannot be encoded - * @expose - */ - FieldPrototype.encode = function(value, buffer, message) { - if (this.type === null || typeof this.type !== 'object') - throw Error("[INTERNAL] Unresolved type in "+this.toString(true)+": "+this.type); - if (value === null || (this.repeated && value.length == 0)) - return buffer; // Optional omitted - try { - if (this.repeated) { - var i; - // "Only repeated fields of primitive numeric types (types which use the varint, 32-bit, or 64-bit wire - // types) can be declared 'packed'." - if (this.options["packed"] && ProtoBuf.PACKABLE_WIRE_TYPES.indexOf(this.type.wireType) >= 0) { - // "All of the elements of the field are packed into a single key-value pair with wire type 2 - // (length-delimited). Each element is encoded the same way it would be normally, except without a - // tag preceding it." - buffer.writeVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); - buffer.ensureCapacity(buffer.offset += 1); // We do not know the length yet, so let's assume a varint of length 1 - var start = buffer.offset; // Remember where the contents begin - for (i=0; i 1) { // We need to move the contents - var contents = buffer.slice(start, buffer.offset); - start += varintLen-1; - buffer.offset = start; - buffer.append(contents); - } - buffer.writeVarint32(len, start-varintLen); - } else { - // "If your message definition has repeated elements (without the [packed=true] option), the encoded - // message has zero or more key-value pairs with the same tag number" - for (i=0; i= 0) { - n += ByteBuffer.calculateVarint32((this.id << 3) | ProtoBuf.WIRE_TYPES.LDELIM); - ni = 0; - for (i=0; i= 0) { - if (!skipRepeated) { - nBytes = buffer.readVarint32(); - nBytes = buffer.offset + nBytes; // Limit - var values = []; - while (buffer.offset < nBytes) - values.push(this.decode(this.type.wireType, buffer, true)); - return values; - } - // Read the next value otherwise... - } - - // Handle maps. - if (this.map) { - // Read one (key, value) submessage, and return [key, value] - var key = Element.defaultFieldValue(this.keyType); - value = Element.defaultFieldValue(this.type); - - // Read the length - nBytes = buffer.readVarint32(); - if (buffer.remaining() < nBytes) - throw Error("Illegal number of bytes for "+this.toString(true)+": "+nBytes+" required but got only "+buffer.remaining()); - - // Get a sub-buffer of this key/value submessage - var msgbuf = buffer.clone(); - msgbuf.limit = msgbuf.offset + nBytes; - buffer.offset += nBytes; - - while (msgbuf.remaining() > 0) { - var tag = msgbuf.readVarint32(); - wireType = tag & 0x07; - var id = tag >>> 3; - if (id === 1) { - key = this.keyElement.decode(msgbuf, wireType, id); - } else if (id === 2) { - value = this.element.decode(msgbuf, wireType, id); - } else { - throw Error("Unexpected tag in map field key/value submessage"); - } - } - - return [key, value]; - } - - // Handle singular and non-packed repeated field values. - return this.element.decode(buffer, wireType, this.id); - }; - - /** - * @alias ProtoBuf.Reflect.Message.Field - * @expose - */ - Reflect.Message.Field = Field; - - /** - * Constructs a new Message ExtensionField. - * @exports ProtoBuf.Reflect.Message.ExtensionField - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Message} message Message reference - * @param {string} rule Rule, one of requried, optional, repeated - * @param {string} type Data type, e.g. int32 - * @param {string} name Field name - * @param {number} id Unique field id - * @param {!Object.=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Message.Field - */ - var ExtensionField = function(builder, message, rule, type, name, id, options) { - Field.call(this, builder, message, rule, /* keytype = */ null, type, name, id, options); - - /** - * Extension reference. - * @type {!ProtoBuf.Reflect.Extension} - * @expose - */ - this.extension; - }; - - // Extends Field - ExtensionField.prototype = Object.create(Field.prototype); - - /** - * @alias ProtoBuf.Reflect.Message.ExtensionField - * @expose - */ - Reflect.Message.ExtensionField = ExtensionField; - - /** - * Constructs a new Message OneOf. - * @exports ProtoBuf.Reflect.Message.OneOf - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Message} message Message reference - * @param {string} name OneOf name - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var OneOf = function(builder, message, name) { - T.call(this, builder, message, name); - - /** - * Enclosed fields. - * @type {!Array.} - * @expose - */ - this.fields = []; - }; - - /** - * @alias ProtoBuf.Reflect.Message.OneOf - * @expose - */ - Reflect.Message.OneOf = OneOf; - - /** - * Constructs a new Enum. - * @exports ProtoBuf.Reflect.Enum - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.T} parent Parent Reflect object - * @param {string} name Enum name - * @param {Object.=} options Enum options - * @param {string?} syntax The syntax level (e.g., proto3) - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Enum = function(builder, parent, name, options, syntax) { - Namespace.call(this, builder, parent, name, options, syntax); - - /** - * @override - */ - this.className = "Enum"; - - /** - * Runtime enum object. - * @type {Object.|null} - * @expose - */ - this.object = null; - }; - - /** - * Gets the string name of an enum value. - * @param {!ProtoBuf.Builder.Enum} enm Runtime enum - * @param {number} value Enum value - * @returns {?string} Name or `null` if not present - * @expose - */ - Enum.getName = function(enm, value) { - var keys = Object.keys(enm); - for (var i=0, key; i} - * @expose - */ - EnumPrototype.build = function(rebuild) { - if (this.object && !rebuild) - return this.object; - var enm = new ProtoBuf.Builder.Enum(), - values = this.getChildren(Enum.Value); - for (var i=0, k=values.length; i=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Namespace - */ - var Service = function(builder, root, name, options) { - Namespace.call(this, builder, root, name, options); - - /** - * @override - */ - this.className = "Service"; - - /** - * Built runtime service class. - * @type {?function(new:ProtoBuf.Builder.Service)} - */ - this.clazz = null; - }; - - /** - * @alias ProtoBuf.Reflect.Service.prototype - * @inner - */ - var ServicePrototype = Service.prototype = Object.create(Namespace.prototype); - - /** - * Builds the service and returns the runtime counterpart, which is a fully functional class. - * @see ProtoBuf.Builder.Service - * @param {boolean=} rebuild Whether to rebuild or not - * @return {Function} Service class - * @throws {Error} If the message cannot be built - * @expose - */ - ServicePrototype.build = function(rebuild) { - if (this.clazz && !rebuild) - return this.clazz; - - // Create the runtime Service class in its own scope - return this.clazz = (function(ProtoBuf, T) { - - /** - * Constructs a new runtime Service. - * @name ProtoBuf.Builder.Service - * @param {function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))=} rpcImpl RPC implementation receiving the method name and the message - * @class Barebone of all runtime services. - * @constructor - * @throws {Error} If the service cannot be created - */ - var Service = function(rpcImpl) { - ProtoBuf.Builder.Service.call(this); - - /** - * Service implementation. - * @name ProtoBuf.Builder.Service#rpcImpl - * @type {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} - * @expose - */ - this.rpcImpl = rpcImpl || function(name, msg, callback) { - // This is what a user has to implement: A function receiving the method name, the actual message to - // send (type checked) and the callback that's either provided with the error as its first - // argument or null and the actual response message. - setTimeout(callback.bind(this, Error("Not implemented, see: https://github.com/dcodeIO/ProtoBuf.js/wiki/Services")), 0); // Must be async! - }; - }; - - /** - * @alias ProtoBuf.Builder.Service.prototype - * @inner - */ - var ServicePrototype = Service.prototype = Object.create(ProtoBuf.Builder.Service.prototype); - - /** - * Asynchronously performs an RPC call using the given RPC implementation. - * @name ProtoBuf.Builder.Service.[Method] - * @function - * @param {!function(string, ProtoBuf.Builder.Message, function(Error, ProtoBuf.Builder.Message=))} rpcImpl RPC implementation - * @param {ProtoBuf.Builder.Message} req Request - * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving - * the error if any and the response either as a pre-parsed message or as its raw bytes - * @abstract - */ - - /** - * Asynchronously performs an RPC call using the instance's RPC implementation. - * @name ProtoBuf.Builder.Service#[Method] - * @function - * @param {ProtoBuf.Builder.Message} req Request - * @param {function(Error, (ProtoBuf.Builder.Message|ByteBuffer|Buffer|string)=)} callback Callback receiving - * the error if any and the response either as a pre-parsed message or as its raw bytes - * @abstract - */ - - var rpc = T.getChildren(ProtoBuf.Reflect.Service.RPCMethod); - for (var i=0; i} - * @expose - */ - var $optionsS; // cc needs this - - /** - * Service options. - * @name ProtoBuf.Builder.Service#$options - * @type {Object.} - * @expose - */ - var $options; - - /** - * Reflection type. - * @name ProtoBuf.Builder.Service.$type - * @type {!ProtoBuf.Reflect.Service} - * @expose - */ - var $typeS; - - /** - * Reflection type. - * @name ProtoBuf.Builder.Service#$type - * @type {!ProtoBuf.Reflect.Service} - * @expose - */ - var $type; - - if (Object.defineProperty) - Object.defineProperty(Service, "$options", { "value": T.buildOpt() }), - Object.defineProperty(ServicePrototype, "$options", { "value": Service["$options"] }), - Object.defineProperty(Service, "$type", { "value": T }), - Object.defineProperty(ServicePrototype, "$type", { "value": T }); - - return Service; - - })(ProtoBuf, this); - }; - - /** - * @alias ProtoBuf.Reflect.Service - * @expose - */ - Reflect.Service = Service; - - /** - * Abstract service method. - * @exports ProtoBuf.Reflect.Service.Method - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Service} svc Service - * @param {string} name Method name - * @param {Object.=} options Options - * @constructor - * @extends ProtoBuf.Reflect.T - */ - var Method = function(builder, svc, name, options) { - T.call(this, builder, svc, name); - - /** - * @override - */ - this.className = "Service.Method"; - - /** - * Options. - * @type {Object.} - * @expose - */ - this.options = options || {}; - }; - - /** - * @alias ProtoBuf.Reflect.Service.Method.prototype - * @inner - */ - var MethodPrototype = Method.prototype = Object.create(T.prototype); - - /** - * Builds the method's '$options' property. - * @name ProtoBuf.Reflect.Service.Method#buildOpt - * @function - * @return {Object.} - */ - MethodPrototype.buildOpt = NamespacePrototype.buildOpt; - - /** - * @alias ProtoBuf.Reflect.Service.Method - * @expose - */ - Reflect.Service.Method = Method; - - /** - * RPC service method. - * @exports ProtoBuf.Reflect.Service.RPCMethod - * @param {!ProtoBuf.Builder} builder Builder reference - * @param {!ProtoBuf.Reflect.Service} svc Service - * @param {string} name Method name - * @param {string} request Request message name - * @param {string} response Response message name - * @param {boolean} request_stream Whether requests are streamed - * @param {boolean} response_stream Whether responses are streamed - * @param {Object.=} options Options - * @constructor - * @extends ProtoBuf.Reflect.Service.Method - */ - var RPCMethod = function(builder, svc, name, request, response, request_stream, response_stream, options) { - Method.call(this, builder, svc, name, options); - - /** - * @override - */ - this.className = "Service.RPCMethod"; - - /** - * Request message name. - * @type {string} - * @expose - */ - this.requestName = request; - - /** - * Response message name. - * @type {string} - * @expose - */ - this.responseName = response; - - /** - * Whether requests are streamed - * @type {bool} - * @expose - */ - this.requestStream = request_stream; - - /** - * Whether responses are streamed - * @type {bool} - * @expose - */ - this.responseStream = response_stream; - - /** - * Resolved request message type. - * @type {ProtoBuf.Reflect.Message} - * @expose - */ - this.resolvedRequestType = null; - - /** - * Resolved response message type. - * @type {ProtoBuf.Reflect.Message} - * @expose - */ - this.resolvedResponseType = null; - }; - - // Extends Method - RPCMethod.prototype = Object.create(Method.prototype); - - /** - * @alias ProtoBuf.Reflect.Service.RPCMethod - * @expose - */ - Reflect.Service.RPCMethod = RPCMethod; - - return Reflect; - - })(ProtoBuf); - - /** - * @alias ProtoBuf.Builder - * @expose - */ - ProtoBuf.Builder = (function(ProtoBuf, Lang, Reflect) { - "use strict"; - - /** - * Constructs a new Builder. - * @exports ProtoBuf.Builder - * @class Provides the functionality to build protocol messages. - * @param {Object.=} options Options - * @constructor - */ - var Builder = function(options) { - - /** - * Namespace. - * @type {ProtoBuf.Reflect.Namespace} - * @expose - */ - this.ns = new Reflect.Namespace(this, null, ""); // Global namespace - - /** - * Namespace pointer. - * @type {ProtoBuf.Reflect.T} - * @expose - */ - this.ptr = this.ns; - - /** - * Resolved flag. - * @type {boolean} - * @expose - */ - this.resolved = false; - - /** - * The current building result. - * @type {Object.|null} - * @expose - */ - this.result = null; - - /** - * Imported files. - * @type {Array.} - * @expose - */ - this.files = {}; - - /** - * Import root override. - * @type {?string} - * @expose - */ - this.importRoot = null; - - /** - * Options. - * @type {!Object.} - * @expose - */ - this.options = options || {}; - }; - - /** - * @alias ProtoBuf.Builder.prototype - * @inner - */ - var BuilderPrototype = Builder.prototype; - - // ----- Definition tests ----- - - /** - * Tests if a definition most likely describes a message. - * @param {!Object} def - * @returns {boolean} - * @expose - */ - Builder.isMessage = function(def) { - // Messages require a string name - if (typeof def["name"] !== 'string') - return false; - // Messages do not contain values (enum) or rpc methods (service) - if (typeof def["values"] !== 'undefined' || typeof def["rpc"] !== 'undefined') - return false; - return true; - }; - - /** - * Tests if a definition most likely describes a message field. - * @param {!Object} def - * @returns {boolean} - * @expose - */ - Builder.isMessageField = function(def) { - // Message fields require a string rule, name and type and an id - if (typeof def["rule"] !== 'string' || typeof def["name"] !== 'string' || typeof def["type"] !== 'string' || typeof def["id"] === 'undefined') - return false; - return true; - }; - - /** - * Tests if a definition most likely describes an enum. - * @param {!Object} def - * @returns {boolean} - * @expose - */ - Builder.isEnum = function(def) { - // Enums require a string name - if (typeof def["name"] !== 'string') - return false; - // Enums require at least one value - if (typeof def["values"] === 'undefined' || !Array.isArray(def["values"]) || def["values"].length === 0) - return false; - return true; - }; - - /** - * Tests if a definition most likely describes a service. - * @param {!Object} def - * @returns {boolean} - * @expose - */ - Builder.isService = function(def) { - // Services require a string name and an rpc object - if (typeof def["name"] !== 'string' || typeof def["rpc"] !== 'object' || !def["rpc"]) - return false; - return true; - }; - - /** - * Tests if a definition most likely describes an extended message - * @param {!Object} def - * @returns {boolean} - * @expose - */ - Builder.isExtend = function(def) { - // Extends rquire a string ref - if (typeof def["ref"] !== 'string') - return false; - return true; - }; - - // ----- Building ----- - - /** - * Resets the pointer to the root namespace. - * @returns {!ProtoBuf.Builder} this - * @expose - */ - BuilderPrototype.reset = function() { - this.ptr = this.ns; - return this; - }; - - /** - * Defines a namespace on top of the current pointer position and places the pointer on it. - * @param {string} namespace - * @return {!ProtoBuf.Builder} this - * @expose - */ - BuilderPrototype.define = function(namespace) { - if (typeof namespace !== 'string' || !Lang.TYPEREF.test(namespace)) - throw Error("illegal namespace: "+namespace); - namespace.split(".").forEach(function(part) { - var ns = this.ptr.getChild(part); - if (ns === null) // Keep existing - this.ptr.addChild(ns = new Reflect.Namespace(this, this.ptr, part)); - this.ptr = ns; - }, this); - return this; - }; - - /** - * Creates the specified definitions at the current pointer position. - * @param {!Array.} defs Messages, enums or services to create - * @returns {!ProtoBuf.Builder} this - * @throws {Error} If a message definition is invalid - * @expose - */ - BuilderPrototype.create = function(defs) { - if (!defs) - return this; // Nothing to create - if (!Array.isArray(defs)) - defs = [defs]; - else { - if (defs.length === 0) - return this; - defs = defs.slice(); - } - - // It's quite hard to keep track of scopes and memory here, so let's do this iteratively. - var stack = [defs]; - while (stack.length > 0) { - defs = stack.pop(); - - if (!Array.isArray(defs)) // Stack always contains entire namespaces - throw Error("not a valid namespace: "+JSON.stringify(defs)); - - while (defs.length > 0) { - var def = defs.shift(); // Namespaces always contain an array of messages, enums and services - - if (Builder.isMessage(def)) { - var obj = new Reflect.Message(this, this.ptr, def["name"], def["options"], def["isGroup"], def["syntax"]); - - // Create OneOfs - var oneofs = {}; - if (def["oneofs"]) - Object.keys(def["oneofs"]).forEach(function(name) { - obj.addChild(oneofs[name] = new Reflect.Message.OneOf(this, obj, name)); - }, this); - - // Create fields - if (def["fields"]) - def["fields"].forEach(function(fld) { - if (obj.getChild(fld["id"]|0) !== null) - throw Error("duplicate or invalid field id in "+obj.name+": "+fld['id']); - if (fld["options"] && typeof fld["options"] !== 'object') - throw Error("illegal field options in "+obj.name+"#"+fld["name"]); - var oneof = null; - if (typeof fld["oneof"] === 'string' && !(oneof = oneofs[fld["oneof"]])) - throw Error("illegal oneof in "+obj.name+"#"+fld["name"]+": "+fld["oneof"]); - fld = new Reflect.Message.Field(this, obj, fld["rule"], fld["keytype"], fld["type"], fld["name"], fld["id"], fld["options"], oneof, def["syntax"]); - if (oneof) - oneof.fields.push(fld); - obj.addChild(fld); - }, this); - - // Push children to stack - var subObj = []; - if (def["enums"]) - def["enums"].forEach(function(enm) { - subObj.push(enm); - }); - if (def["messages"]) - def["messages"].forEach(function(msg) { - subObj.push(msg); - }); - if (def["services"]) - def["services"].forEach(function(svc) { - subObj.push(svc); - }); - - // Set extension ranges - if (def["extensions"]) { - if (typeof def["extensions"][0] === 'number') // pre 5.0.1 - obj.extensions = [ def["extensions"] ]; - else - obj.extensions = def["extensions"]; - } - - // Create on top of current namespace - this.ptr.addChild(obj); - if (subObj.length > 0) { - stack.push(defs); // Push the current level back - defs = subObj; // Continue processing sub level - subObj = null; - this.ptr = obj; // And move the pointer to this namespace - obj = null; - continue; - } - subObj = null; - - } else if (Builder.isEnum(def)) { - - obj = new Reflect.Enum(this, this.ptr, def["name"], def["options"], def["syntax"]); - def["values"].forEach(function(val) { - obj.addChild(new Reflect.Enum.Value(this, obj, val["name"], val["id"])); - }, this); - this.ptr.addChild(obj); - - } else if (Builder.isService(def)) { - - obj = new Reflect.Service(this, this.ptr, def["name"], def["options"]); - Object.keys(def["rpc"]).forEach(function(name) { - var mtd = def["rpc"][name]; - obj.addChild(new Reflect.Service.RPCMethod(this, obj, name, mtd["request"], mtd["response"], !!mtd["request_stream"], !!mtd["response_stream"], mtd["options"])); - }, this); - this.ptr.addChild(obj); - - } else if (Builder.isExtend(def)) { - - obj = this.ptr.resolve(def["ref"], true); - if (obj) { - def["fields"].forEach(function(fld) { - if (obj.getChild(fld['id']|0) !== null) - throw Error("duplicate extended field id in "+obj.name+": "+fld['id']); - // Check if field id is allowed to be extended - if (obj.extensions) { - var valid = false; - obj.extensions.forEach(function(range) { - if (fld["id"] >= range[0] && fld["id"] <= range[1]) - valid = true; - }); - if (!valid) - throw Error("illegal extended field id in "+obj.name+": "+fld['id']+" (not within valid ranges)"); - } - // Convert extension field names to camel case notation if the override is set - var name = fld["name"]; - if (this.options['convertFieldsToCamelCase']) - name = ProtoBuf.Util.toCamelCase(name); - // see #161: Extensions use their fully qualified name as their runtime key and... - var field = new Reflect.Message.ExtensionField(this, obj, fld["rule"], fld["type"], this.ptr.fqn()+'.'+name, fld["id"], fld["options"]); - // ...are added on top of the current namespace as an extension which is used for - // resolving their type later on (the extension always keeps the original name to - // prevent naming collisions) - var ext = new Reflect.Extension(this, this.ptr, fld["name"], field); - field.extension = ext; - this.ptr.addChild(ext); - obj.addChild(field); - }, this); - - } else if (!/\.?google\.protobuf\./.test(def["ref"])) // Silently skip internal extensions - throw Error("extended message "+def["ref"]+" is not defined"); - - } else - throw Error("not a valid definition: "+JSON.stringify(def)); - - def = null; - obj = null; - } - // Break goes here - defs = null; - this.ptr = this.ptr.parent; // Namespace done, continue at parent - } - this.resolved = false; // Require re-resolve - this.result = null; // Require re-build - return this; - }; - - /** - * Propagates syntax to all children. - * @param {!Object} parent - * @inner - */ - function propagateSyntax(parent) { - if (parent['messages']) { - parent['messages'].forEach(function(child) { - child["syntax"] = parent["syntax"]; - propagateSyntax(child); - }); - } - if (parent['enums']) { - parent['enums'].forEach(function(child) { - child["syntax"] = parent["syntax"]; - }); - } - } - - /** - * Imports another definition into this builder. - * @param {Object.} json Parsed import - * @param {(string|{root: string, file: string})=} filename Imported file name - * @returns {!ProtoBuf.Builder} this - * @throws {Error} If the definition or file cannot be imported - * @expose - */ - BuilderPrototype["import"] = function(json, filename) { - var delim = '/'; - - // Make sure to skip duplicate imports - - if (typeof filename === 'string') { - - if (ProtoBuf.Util.IS_NODE) - filename = require("path")['resolve'](filename); - if (this.files[filename] === true) - return this.reset(); - this.files[filename] = true; - - } else if (typeof filename === 'object') { // Object with root, file. - - var root = filename.root; - if (ProtoBuf.Util.IS_NODE) - root = require("path")['resolve'](root); - if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0) - delim = '\\'; - var fname = root + delim + filename.file; - if (this.files[fname] === true) - return this.reset(); - this.files[fname] = true; - } - - // Import imports - - if (json['imports'] && json['imports'].length > 0) { - var importRoot, - resetRoot = false; - - if (typeof filename === 'object') { // If an import root is specified, override - - this.importRoot = filename["root"]; resetRoot = true; // ... and reset afterwards - importRoot = this.importRoot; - filename = filename["file"]; - if (importRoot.indexOf("\\") >= 0 || filename.indexOf("\\") >= 0) - delim = '\\'; - - } else if (typeof filename === 'string') { - - if (this.importRoot) // If import root is overridden, use it - importRoot = this.importRoot; - else { // Otherwise compute from filename - if (filename.indexOf("/") >= 0) { // Unix - importRoot = filename.replace(/\/[^\/]*$/, ""); - if (/* /file.proto */ importRoot === "") - importRoot = "/"; - } else if (filename.indexOf("\\") >= 0) { // Windows - importRoot = filename.replace(/\\[^\\]*$/, ""); - delim = '\\'; - } else - importRoot = "."; - } - - } else - importRoot = null; - - for (var i=0; i)=} path Specifies what to return. If omitted, the entire namespace will be returned. - * @returns {!ProtoBuf.Builder.Message|!Object.} - * @throws {Error} If a type could not be resolved - * @expose - */ - BuilderPrototype.build = function(path) { - this.reset(); - if (!this.resolved) - this.resolveAll(), - this.resolved = true, - this.result = null; // Require re-build - if (this.result === null) // (Re-)Build - this.result = this.ns.build(); - if (!path) - return this.result; - var part = typeof path === 'string' ? path.split(".") : path, - ptr = this.result; // Build namespace pointer (no hasChild etc.) - for (var i=0; i=} contents Initial contents - * @constructor - */ - var Map = function(field, contents) { - if (!field.map) - throw Error("field is not a map"); - - /** - * The field corresponding to this map. - * @type {!ProtoBuf.Reflect.Field} - */ - this.field = field; - - /** - * Element instance corresponding to key type. - * @type {!ProtoBuf.Reflect.Element} - */ - this.keyElem = new Reflect.Element(field.keyType, null, true, field.syntax); - - /** - * Element instance corresponding to value type. - * @type {!ProtoBuf.Reflect.Element} - */ - this.valueElem = new Reflect.Element(field.type, field.resolvedType, false, field.syntax); - - /** - * Internal map: stores mapping of (string form of key) -> (key, value) - * pair. - * - * We provide map semantics for arbitrary key types, but we build on top - * of an Object, which has only string keys. In order to avoid the need - * to convert a string key back to its native type in many situations, - * we store the native key value alongside the value. Thus, we only need - * a one-way mapping from a key type to its string form that guarantees - * uniqueness and equality (i.e., str(K1) === str(K2) if and only if K1 - * === K2). - * - * @type {!Object} - */ - this.map = {}; - - /** - * Returns the number of elements in the map. - */ - Object.defineProperty(this, "size", { - get: function() { return Object.keys(this.map).length; } - }); - - // Fill initial contents from a raw object. - if (contents) { - var keys = Object.keys(contents); - for (var i = 0; i < keys.length; i++) { - var key = this.keyElem.valueFromString(keys[i]); - var val = this.valueElem.verifyValue(contents[keys[i]]); - this.map[this.keyElem.valueToString(key)] = - { key: key, value: val }; - } - } - }; - - var MapPrototype = Map.prototype; - - /** - * Helper: return an iterator over an array. - * @param {!Array<*>} arr the array - * @returns {!Object} an iterator - * @inner - */ - function arrayIterator(arr) { - var idx = 0; - return { - next: function() { - if (idx < arr.length) - return { done: false, value: arr[idx++] }; - return { done: true }; - } - } - } - - /** - * Clears the map. - */ - MapPrototype.clear = function() { - this.map = {}; - }; - - /** - * Deletes a particular key from the map. - * @returns {boolean} Whether any entry with this key was deleted. - */ - MapPrototype["delete"] = function(key) { - var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); - var hadKey = keyValue in this.map; - delete this.map[keyValue]; - return hadKey; - }; - - /** - * Returns an iterator over [key, value] pairs in the map. - * @returns {Object} The iterator - */ - MapPrototype.entries = function() { - var entries = []; - var strKeys = Object.keys(this.map); - for (var i = 0, entry; i < strKeys.length; i++) - entries.push([(entry=this.map[strKeys[i]]).key, entry.value]); - return arrayIterator(entries); - }; - - /** - * Returns an iterator over keys in the map. - * @returns {Object} The iterator - */ - MapPrototype.keys = function() { - var keys = []; - var strKeys = Object.keys(this.map); - for (var i = 0; i < strKeys.length; i++) - keys.push(this.map[strKeys[i]].key); - return arrayIterator(keys); - }; - - /** - * Returns an iterator over values in the map. - * @returns {!Object} The iterator - */ - MapPrototype.values = function() { - var values = []; - var strKeys = Object.keys(this.map); - for (var i = 0; i < strKeys.length; i++) - values.push(this.map[strKeys[i]].value); - return arrayIterator(values); - }; - - /** - * Iterates over entries in the map, calling a function on each. - * @param {function(this:*, *, *, *)} cb The callback to invoke with value, key, and map arguments. - * @param {Object=} thisArg The `this` value for the callback - */ - MapPrototype.forEach = function(cb, thisArg) { - var strKeys = Object.keys(this.map); - for (var i = 0, entry; i < strKeys.length; i++) - cb.call(thisArg, (entry=this.map[strKeys[i]]).value, entry.key, this); - }; - - /** - * Sets a key in the map to the given value. - * @param {*} key The key - * @param {*} value The value - * @returns {!ProtoBuf.Map} The map instance - */ - MapPrototype.set = function(key, value) { - var keyValue = this.keyElem.verifyValue(key); - var valValue = this.valueElem.verifyValue(value); - this.map[this.keyElem.valueToString(keyValue)] = - { key: keyValue, value: valValue }; - return this; - }; - - /** - * Gets the value corresponding to a key in the map. - * @param {*} key The key - * @returns {*|undefined} The value, or `undefined` if key not present - */ - MapPrototype.get = function(key) { - var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); - if (!(keyValue in this.map)) - return undefined; - return this.map[keyValue].value; - }; - - /** - * Determines whether the given key is present in the map. - * @param {*} key The key - * @returns {boolean} `true` if the key is present - */ - MapPrototype.has = function(key) { - var keyValue = this.keyElem.valueToString(this.keyElem.verifyValue(key)); - return (keyValue in this.map); - }; - - return Map; - })(ProtoBuf, ProtoBuf.Reflect); - - - /** - * Loads a .proto string and returns the Builder. - * @param {string} proto .proto file contents - * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted. - * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. - * @return {ProtoBuf.Builder} Builder to create new messages - * @throws {Error} If the definition cannot be parsed or built - * @expose - */ - ProtoBuf.loadProto = function(proto, builder, filename) { - if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) - filename = builder, - builder = undefined; - return ProtoBuf.loadJson(ProtoBuf.DotProto.Parser.parse(proto), builder, filename); - }; - - /** - * Loads a .proto string and returns the Builder. This is an alias of {@link ProtoBuf.loadProto}. - * @function - * @param {string} proto .proto file contents - * @param {(ProtoBuf.Builder|string)=} builder Builder to append to. Will create a new one if omitted. - * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. - * @return {ProtoBuf.Builder} Builder to create new messages - * @throws {Error} If the definition cannot be parsed or built - * @expose - */ - ProtoBuf.protoFromString = ProtoBuf.loadProto; // Legacy - - /** - * Loads a .proto file and returns the Builder. - * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with - * an overridden 'root' path for all imported files. - * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and - * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the - * file will be read synchronously and this function will return the Builder. - * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. - * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the - * request has failed), else undefined - * @expose - */ - ProtoBuf.loadProtoFile = function(filename, callback, builder) { - if (callback && typeof callback === 'object') - builder = callback, - callback = null; - else if (!callback || typeof callback !== 'function') - callback = null; - if (callback) - return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) { - if (contents === null) { - callback(Error("Failed to fetch file")); - return; - } - try { - callback(null, ProtoBuf.loadProto(contents, builder, filename)); - } catch (e) { - callback(e); - } - }); - var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename); - return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename); - }; - - /** - * Loads a .proto file and returns the Builder. This is an alias of {@link ProtoBuf.loadProtoFile}. - * @function - * @param {string|{root: string, file: string}} filename Path to proto file or an object specifying 'file' with - * an overridden 'root' path for all imported files. - * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and - * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the - * file will be read synchronously and this function will return the Builder. - * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. - * @return {!ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the - * request has failed), else undefined - * @expose - */ - ProtoBuf.protoFromFile = ProtoBuf.loadProtoFile; // Legacy - - - /** - * Constructs a new empty Builder. - * @param {Object.=} options Builder options, defaults to global options set on ProtoBuf - * @return {!ProtoBuf.Builder} Builder - * @expose - */ - ProtoBuf.newBuilder = function(options) { - options = options || {}; - if (typeof options['convertFieldsToCamelCase'] === 'undefined') - options['convertFieldsToCamelCase'] = ProtoBuf.convertFieldsToCamelCase; - if (typeof options['populateAccessors'] === 'undefined') - options['populateAccessors'] = ProtoBuf.populateAccessors; - return new ProtoBuf.Builder(options); - }; - - /** - * Loads a .json definition and returns the Builder. - * @param {!*|string} json JSON definition - * @param {(ProtoBuf.Builder|string|{root: string, file: string})=} builder Builder to append to. Will create a new one if omitted. - * @param {(string|{root: string, file: string})=} filename The corresponding file name if known. Must be specified for imports. - * @return {ProtoBuf.Builder} Builder to create new messages - * @throws {Error} If the definition cannot be parsed or built - * @expose - */ - ProtoBuf.loadJson = function(json, builder, filename) { - if (typeof builder === 'string' || (builder && typeof builder["file"] === 'string' && typeof builder["root"] === 'string')) - filename = builder, - builder = null; - if (!builder || typeof builder !== 'object') - builder = ProtoBuf.newBuilder(); - if (typeof json === 'string') - json = JSON.parse(json); - builder["import"](json, filename); - builder.resolveAll(); - return builder; - }; - - /** - * Loads a .json file and returns the Builder. - * @param {string|!{root: string, file: string}} filename Path to json file or an object specifying 'file' with - * an overridden 'root' path for all imported files. - * @param {function(?Error, !ProtoBuf.Builder=)=} callback Callback that will receive `null` as the first and - * the Builder as its second argument on success, otherwise the error as its first argument. If omitted, the - * file will be read synchronously and this function will return the Builder. - * @param {ProtoBuf.Builder=} builder Builder to append to. Will create a new one if omitted. - * @return {?ProtoBuf.Builder|undefined} The Builder if synchronous (no callback specified, will be NULL if the - * request has failed), else undefined - * @expose - */ - ProtoBuf.loadJsonFile = function(filename, callback, builder) { - if (callback && typeof callback === 'object') - builder = callback, - callback = null; - else if (!callback || typeof callback !== 'function') - callback = null; - if (callback) - return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) { - if (contents === null) { - callback(Error("Failed to fetch file")); - return; - } - try { - callback(null, ProtoBuf.loadJson(JSON.parse(contents), builder, filename)); - } catch (e) { - callback(e); - } - }); - var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename); - return contents === null ? null : ProtoBuf.loadJson(JSON.parse(contents), builder, filename); - }; - - return ProtoBuf; -}); diff --git a/libtextsecure/key_worker.js b/libtextsecure/key_worker.js deleted file mode 100644 index 4390a8aa22..0000000000 --- a/libtextsecure/key_worker.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2015-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global window, postMessage, textsecure, close */ - -/* eslint-disable more/no-then, no-global-assign, no-restricted-globals, no-unused-vars */ - -/* -* Load this script in a Web Worker to generate new prekeys without -* tying up the main thread. -* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API -* -* Because workers don't have access to the window or localStorage, we -* create our own version that proxies back to the caller for actual -* storage. -* -* Example usage: -* - var myWorker = new Worker('/js/key_worker.js'); - myWorker.onmessage = function(e) { - switch(e.data.method) { - case 'set': - localStorage.setItem(e.data.key, e.data.value); - break; - case 'remove': - localStorage.removeItem(e.data.key); - break; - case 'done': - console.error(e.data.keys); - } - }; -*/ -let store = {}; -window.textsecure.storage.impl = { - /** *************************** - *** Override Storage Routines *** - **************************** */ - put(key, value) { - if (value === undefined) throw new Error('Tried to store undefined'); - store[key] = value; - postMessage({ method: 'set', key, value }); - }, - - get(key, defaultValue) { - if (key in store) { - return store[key]; - } - return defaultValue; - }, - - remove(key) { - delete store[key]; - postMessage({ method: 'remove', key }); - }, -}; -// eslint-disable-next-line no-undef -onmessage = e => { - store = e.data; - textsecure.protocol_wrapper.generateKeys().then(keys => { - postMessage({ method: 'done', keys }); - close(); - }); -}; diff --git a/libtextsecure/protobufs.js b/libtextsecure/protobufs.js deleted file mode 100644 index 5b6d57dc54..0000000000 --- a/libtextsecure/protobufs.js +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2015-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global window, dcodeIO, textsecure */ - -// eslint-disable-next-line func-names -(function () { - const FILES_TO_LOAD = [ - 'SignalService.proto', - 'SignalStorage.proto', - 'SubProtocol.proto', - 'DeviceMessages.proto', - 'Stickers.proto', - - // Just for encrypting device names - 'DeviceName.proto', - - // Metadata-specific protos - 'UnidentifiedDelivery.proto', - - // Groups - 'Groups.proto', - ]; - - let remainingFilesToLoad = FILES_TO_LOAD.length; - const hasFinishedLoading = () => remainingFilesToLoad <= 0; - let onLoadCallbacks = []; - - window.textsecure = window.textsecure || {}; - window.textsecure.protobuf = { - onLoad: callback => { - if (hasFinishedLoading()) { - setTimeout(callback, 0); - } else { - onLoadCallbacks.push(callback); - } - }, - }; - - FILES_TO_LOAD.forEach(filename => { - dcodeIO.ProtoBuf.loadProtoFile( - { root: window.PROTO_ROOT, file: filename }, - (error, result) => { - if (error) { - const text = `Error loading protos from ${filename} (root: ${ - window.PROTO_ROOT - }) ${error && error.stack ? error.stack : error}`; - window.log.error(text); - throw error; - } - const protos = result.build('signalservice'); - if (!protos) { - const text = `Error loading protos from ${filename} - no exported types! (root: ${window.PROTO_ROOT})`; - window.log.error(text); - throw new Error(text); - } - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const protoName in protos) { - textsecure.protobuf[protoName] = protos[protoName]; - } - - remainingFilesToLoad -= 1; - if (hasFinishedLoading()) { - onLoadCallbacks.forEach(callback => callback()); - onLoadCallbacks = []; - } - } - ); - }); -})(); diff --git a/libtextsecure/test/.eslintrc.js b/libtextsecure/test/.eslintrc.js index 1e3a1f5e00..6b71e3f3ec 100644 --- a/libtextsecure/test/.eslintrc.js +++ b/libtextsecure/test/.eslintrc.js @@ -17,10 +17,8 @@ module.exports = { globals: { assert: true, assertEqualArrayBuffers: true, - dcodeIO: true, getString: true, hexToArrayBuffer: true, - PROTO_ROOT: true, stringToArrayBuffer: true, }, }; diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index 615242455d..72a59c9753 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -5,7 +5,6 @@ mocha.setup('bdd'); window.assert = chai.assert; -window.PROTO_ROOT = '../../protos'; const OriginalReporter = mocha._reporter; diff --git a/libtextsecure/test/crypto_test.js b/libtextsecure/test/crypto_test.js deleted file mode 100644 index ebe3618c03..0000000000 --- a/libtextsecure/test/crypto_test.js +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2015-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -/* global textsecure */ - -describe('encrypting and decrypting profile data', () => { - const NAME_PADDED_LENGTH = 53; - describe('encrypting and decrypting profile names', () => { - it('pads, encrypts, decrypts, and unpads a short string', () => { - const name = 'Alice'; - const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto - .encryptProfileName(buffer, key) - .then(encrypted => { - assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); - return textsecure.crypto - .decryptProfileName(encrypted, key) - .then(({ given, family }) => { - assert.strictEqual(family, null); - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(given).toString('utf8'), - name - ); - }); - }); - }); - it('handles a given name of the max, 53 characters', () => { - const name = '01234567890123456789012345678901234567890123456789123'; - const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto - .encryptProfileName(buffer, key) - .then(encrypted => { - assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); - return textsecure.crypto - .decryptProfileName(encrypted, key) - .then(({ given, family }) => { - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(given).toString('utf8'), - name - ); - assert.strictEqual(family, null); - }); - }); - }); - it('handles family/given name of the max, 53 characters', () => { - const name = '01234567890123456789\u000001234567890123456789012345678912'; - const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto - .encryptProfileName(buffer, key) - .then(encrypted => { - assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); - return textsecure.crypto - .decryptProfileName(encrypted, key) - .then(({ given, family }) => { - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(given).toString('utf8'), - '01234567890123456789' - ); - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(family).toString('utf8'), - '01234567890123456789012345678912' - ); - }); - }); - }); - it('handles a string with family/given name', () => { - const name = 'Alice\0Jones'; - const buffer = dcodeIO.ByteBuffer.wrap(name).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto - .encryptProfileName(buffer, key) - .then(encrypted => { - assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); - return textsecure.crypto - .decryptProfileName(encrypted, key) - .then(({ given, family }) => { - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(given).toString('utf8'), - 'Alice' - ); - assert.strictEqual( - dcodeIO.ByteBuffer.wrap(family).toString('utf8'), - 'Jones' - ); - }); - }); - }); - it('works for empty string', async () => { - const name = dcodeIO.ByteBuffer.wrap('').toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - const encrypted = await textsecure.crypto.encryptProfileName(name, key); - assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); - - const { given, family } = await textsecure.crypto.decryptProfileName( - encrypted, - key - ); - assert.strictEqual(family, null); - assert.strictEqual(given.byteLength, 0); - assert.strictEqual(dcodeIO.ByteBuffer.wrap(given).toString('utf8'), ''); - }); - }); - describe('encrypting and decrypting profile avatars', () => { - it('encrypts and decrypts', () => { - const buffer = dcodeIO.ByteBuffer.wrap( - 'This is an avatar' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { - assert(encrypted.byteLength === buffer.byteLength + 16 + 12); - return textsecure.crypto - .decryptProfile(encrypted, key) - .then(decrypted => { - assertEqualArrayBuffers(buffer, decrypted); - }); - }); - }); - it('throws when decrypting with the wrong key', () => { - const buffer = dcodeIO.ByteBuffer.wrap( - 'This is an avatar' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - const badKey = window.Signal.Crypto.getRandomBytes(32); - - return textsecure.crypto.encryptProfile(buffer, key).then(encrypted => { - assert(encrypted.byteLength === buffer.byteLength + 16 + 12); - return textsecure.crypto - .decryptProfile(encrypted, badKey) - .catch(error => { - assert.strictEqual(error.name, 'ProfileDecryptError'); - }); - }); - }); - }); -}); diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html index 8614ae33c3..62f255d81f 100644 --- a/libtextsecure/test/index.html +++ b/libtextsecure/test/index.html @@ -20,7 +20,6 @@ > - - @@ -50,10 +48,8 @@ diff --git a/package.json b/package.json index f87d6bdb00..84c2378a13 100644 --- a/package.json +++ b/package.json @@ -268,7 +268,7 @@ "node-sass-import-once": "1.2.0", "npm-run-all": "4.1.5", "nyc": "11.4.1", - "patch-package": "6.1.2", + "patch-package": "6.4.7", "prettier": "^2.2.1", "react-docgen-typescript": "1.2.6", "sass-loader": "7.2.0", diff --git a/patches/protobufjs+6.10.2.patch b/patches/protobufjs+6.10.2.patch new file mode 100644 index 0000000000..571d321e45 --- /dev/null +++ b/patches/protobufjs+6.10.2.patch @@ -0,0 +1,84 @@ +diff --git a/node_modules/protobufjs/cli/lib/tsd-jsdoc/publish.js b/node_modules/protobufjs/cli/lib/tsd-jsdoc/publish.js +index 3846a99..6c5688a 100644 +--- a/node_modules/protobufjs/cli/lib/tsd-jsdoc/publish.js ++++ b/node_modules/protobufjs/cli/lib/tsd-jsdoc/publish.js +@@ -558,6 +558,13 @@ function handleClass(element, parent) { + handleElement(child, element); + }); + ++ writeln(); ++ if (is_interface) { ++ writeln("__unknownFields?: ReadonlyArray;"); ++ } else { ++ writeln("public __unknownFields?: ReadonlyArray;"); ++ } ++ + --indent; + writeln("}"); + +diff --git a/node_modules/protobufjs/src/decoder.js b/node_modules/protobufjs/src/decoder.js +index 491dd30..ec03e9f 100644 +--- a/node_modules/protobufjs/src/decoder.js ++++ b/node_modules/protobufjs/src/decoder.js +@@ -21,6 +21,7 @@ function decoder(mtype) { + ("r=Reader.create(r)") + ("var c=l===undefined?r.len:r.pos+l,m=new this.ctor" + (mtype.fieldsArray.filter(function(field) { return field.map; }).length ? ",k,value" : "")) + ("while(r.pos>>3){"); + ++ var unknownRef = "m" + util.safeProp("__unknownFields"); ++ + var i = 0; + for (; i < /* initializes */ mtype.fieldsArray.length; ++i) { + var field = mtype._fieldsArray[i].resolve(), +@@ -109,6 +112,11 @@ function decoder(mtype) { + } gen + ("default:") + ("r.skipType(t&7)") ++ ("if (!(%s)) {", unknownRef) ++ ("%s = []", unknownRef) ++ ("}") ++ ++ ("%s.push(r.buf.slice(unknownStartPos, r.pos))", unknownRef) + ("break") + + ("}") +diff --git a/node_modules/protobufjs/src/encoder.js b/node_modules/protobufjs/src/encoder.js +index c803e99..d3c6e86 100644 +--- a/node_modules/protobufjs/src/encoder.js ++++ b/node_modules/protobufjs/src/encoder.js +@@ -94,6 +94,13 @@ function encoder(mtype) { + } + } + ++ var unknownRef = "m" + util.safeProp("__unknownFields"); ++ gen ++ ("if (%s) {", unknownRef) ++ ("for (var i=0;i<%s.length;++i)", unknownRef) ++ ("w.__unknownField(%s[i])", unknownRef) ++ ("}") ++ + return gen + ("return w"); + /* eslint-enable no-unexpected-multiline, block-scoped-var, no-redeclare */ +diff --git a/node_modules/protobufjs/src/writer.js b/node_modules/protobufjs/src/writer.js +index cc84a00..3fb6139 100644 +--- a/node_modules/protobufjs/src/writer.js ++++ b/node_modules/protobufjs/src/writer.js +@@ -383,6 +383,10 @@ Writer.prototype.bytes = function write_bytes(value) { + return this.uint32(len)._push(writeBytes, len, value); + }; + ++Writer.prototype.__unknownField = function __unknownField(field) { ++ return this._push(writeBytes, field.length, field); ++}; ++ + /** + * Writes a string. + * @param {string} value Value to write diff --git a/preload.js b/preload.js index 49b823142f..ff8a861e1a 100644 --- a/preload.js +++ b/preload.js @@ -30,7 +30,6 @@ try { window.sqlInitializer = require('./ts/sql/initialize'); - window.PROTO_ROOT = 'protos'; const config = require('url').parse(window.location.toString(), true).query; setEnvironment(parseEnvironment(config.environment)); diff --git a/sticker-creator/index.html b/sticker-creator/index.html index 20d8c06c71..8ebd92d15c 100644 --- a/sticker-creator/index.html +++ b/sticker-creator/index.html @@ -15,9 +15,5 @@ type="text/javascript" src="../../libtextsecure/protocol_wrapper.js" > - diff --git a/sticker-creator/preload.js b/sticker-creator/preload.js index 782b4c11ad..67f35a717e 100644 --- a/sticker-creator/preload.js +++ b/sticker-creator/preload.js @@ -36,7 +36,6 @@ setEnvironment(parseEnvironment(config.environment)); window.sqlInitializer = require('../ts/sql/initialize'); window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/'; -window.PROTO_ROOT = '../../protos'; window.getEnvironment = getEnvironment; window.getVersion = () => config.version; window.getGuid = require('uuid/v4'); diff --git a/test/.eslintrc.js b/test/.eslintrc.js index 909ded8719..c22636780d 100644 --- a/test/.eslintrc.js +++ b/test/.eslintrc.js @@ -12,10 +12,8 @@ module.exports = { globals: { assert: true, assertEqualArrayBuffers: true, - dcodeIO: true, getString: true, hexToArrayBuffer: true, - PROTO_ROOT: true, stringToArrayBuffer: true, }, diff --git a/test/_test.js b/test/_test.js index fef1f84cec..74b180a1ef 100644 --- a/test/_test.js +++ b/test/_test.js @@ -5,7 +5,6 @@ mocha.setup('bdd'); window.assert = chai.assert; -window.PROTO_ROOT = '../protos'; const OriginalReporter = mocha._reporter; diff --git a/test/crypto_test.js b/test/crypto_test.js deleted file mode 100644 index 564b1e17b2..0000000000 --- a/test/crypto_test.js +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2014-2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -'use strict'; - -describe('Crypto', () => { - describe('generateRegistrationId', () => { - it('generates an integer between 0 and 16383 (inclusive)', () => { - for (let i = 0; i < 100; i += 1) { - const id = window.Signal.Crypto.generateRegistrationId(); - assert.isAtLeast(id, 0); - assert.isAtMost(id, 16383); - assert(Number.isInteger(id)); - } - }); - }); - - describe('deriveSecrets', () => { - it('derives key parts via HKDF', () => { - const input = window.Signal.Crypto.getRandomBytes(32); - const salt = window.Signal.Crypto.getRandomBytes(32); - const info = window.Signal.Crypto.bytesFromString('Hello world'); - const result = window.Signal.Crypto.deriveSecrets(input, salt, info); - assert.lengthOf(result, 3); - result.forEach(part => { - // This is a smoke test; HKDF is tested as part of @signalapp/signal-client. - assert.instanceOf(part, ArrayBuffer); - assert.strictEqual(part.byteLength, 32); - }); - }); - }); - - describe('accessKey/profileKey', () => { - it('verification roundtrips', async () => { - const profileKey = await window.Signal.Crypto.getRandomBytes(32); - const accessKey = await window.Signal.Crypto.deriveAccessKey(profileKey); - - const verifier = await window.Signal.Crypto.getAccessKeyVerifier( - accessKey - ); - - const correct = await window.Signal.Crypto.verifyAccessKey( - accessKey, - verifier - ); - - assert.strictEqual(correct, true); - }); - }); - - describe('deriveMasterKeyFromGroupV1', () => { - const vectors = [ - { - gv1: '00000000000000000000000000000000', - masterKey: - 'dbde68f4ee9169081f8814eabc65523fea1359235c8cfca32b69e31dce58b039', - }, - { - gv1: '000102030405060708090a0b0c0d0e0f', - masterKey: - '70884f78f07a94480ee36b67a4b5e975e92e4a774561e3df84c9076e3be4b9bf', - }, - { - gv1: '7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', - masterKey: - 'e69bf7c183b288b4ea5745b7c52b651a61e57769fafde683a6fdf1240f1905f2', - }, - { - gv1: 'ffffffffffffffffffffffffffffffff', - masterKey: - 'dd3a7de23d10f18b64457fbeedc76226c112a730e4b76112e62c36c4432eb37d', - }, - ]; - - vectors.forEach((vector, index) => { - it(`vector ${index}`, async () => { - const gv1 = window.Signal.Crypto.hexToArrayBuffer(vector.gv1); - const expectedHex = vector.masterKey; - - const actual = await window.Signal.Crypto.deriveMasterKeyFromGroupV1( - gv1 - ); - const actualHex = window.Signal.Crypto.arrayBufferToHex(actual); - - assert.strictEqual(actualHex, expectedHex); - }); - }); - }); - - describe('symmetric encryption', () => { - it('roundtrips', async () => { - const message = 'this is my message'; - const plaintext = dcodeIO.ByteBuffer.wrap( - message, - 'binary' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - const encrypted = await window.Signal.Crypto.encryptSymmetric( - key, - plaintext - ); - const decrypted = await window.Signal.Crypto.decryptSymmetric( - key, - encrypted - ); - - const equal = window.Signal.Crypto.constantTimeEqual( - plaintext, - decrypted - ); - if (!equal) { - throw new Error('The output and input did not match!'); - } - }); - - it('roundtrip fails if nonce is modified', async () => { - const message = 'this is my message'; - const plaintext = dcodeIO.ByteBuffer.wrap( - message, - 'binary' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - const encrypted = await window.Signal.Crypto.encryptSymmetric( - key, - plaintext - ); - const uintArray = new Uint8Array(encrypted); - uintArray[2] += 2; - - try { - await window.Signal.Crypto.decryptSymmetric( - key, - window.window.Signal.Crypto.typedArrayToArrayBuffer(uintArray) - ); - } catch (error) { - assert.strictEqual( - error.message, - 'decryptSymmetric: Failed to decrypt; MAC verification failed' - ); - return; - } - - throw new Error('Expected error to be thrown'); - }); - - it('roundtrip fails if mac is modified', async () => { - const message = 'this is my message'; - const plaintext = dcodeIO.ByteBuffer.wrap( - message, - 'binary' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - const encrypted = await window.Signal.Crypto.encryptSymmetric( - key, - plaintext - ); - const uintArray = new Uint8Array(encrypted); - uintArray[uintArray.length - 3] += 2; - - try { - await window.Signal.Crypto.decryptSymmetric( - key, - window.window.Signal.Crypto.typedArrayToArrayBuffer(uintArray) - ); - } catch (error) { - assert.strictEqual( - error.message, - 'decryptSymmetric: Failed to decrypt; MAC verification failed' - ); - return; - } - - throw new Error('Expected error to be thrown'); - }); - - it('roundtrip fails if encrypted contents are modified', async () => { - const message = 'this is my message'; - const plaintext = dcodeIO.ByteBuffer.wrap( - message, - 'binary' - ).toArrayBuffer(); - const key = window.Signal.Crypto.getRandomBytes(32); - - const encrypted = await window.Signal.Crypto.encryptSymmetric( - key, - plaintext - ); - const uintArray = new Uint8Array(encrypted); - uintArray[35] += 9; - - try { - await window.Signal.Crypto.decryptSymmetric( - key, - window.window.Signal.Crypto.typedArrayToArrayBuffer(uintArray) - ); - } catch (error) { - assert.strictEqual( - error.message, - 'decryptSymmetric: Failed to decrypt; MAC verification failed' - ); - return; - } - - throw new Error('Expected error to be thrown'); - }); - }); - - describe('encrypted device name', () => { - it('roundtrips', async () => { - const deviceName = 'v1.19.0 on Windows 10'; - const identityKey = window.Signal.Curve.generateKeyPair(); - - const encrypted = await window.Signal.Crypto.encryptDeviceName( - deviceName, - identityKey.pubKey - ); - const decrypted = await window.Signal.Crypto.decryptDeviceName( - encrypted, - identityKey.privKey - ); - - assert.strictEqual(decrypted, deviceName); - }); - - it('fails if iv is changed', async () => { - const deviceName = 'v1.19.0 on Windows 10'; - const identityKey = window.Signal.Curve.generateKeyPair(); - - const encrypted = await window.Signal.Crypto.encryptDeviceName( - deviceName, - identityKey.pubKey - ); - encrypted.syntheticIv = window.Signal.Crypto.getRandomBytes(16); - try { - await window.Signal.Crypto.decryptDeviceName( - encrypted, - identityKey.privKey - ); - } catch (error) { - assert.strictEqual( - error.message, - 'decryptDeviceName: synthetic IV did not match' - ); - } - }); - }); - - describe('attachment encryption', () => { - it('roundtrips', async () => { - const staticKeyPair = window.Signal.Curve.generateKeyPair(); - const message = 'this is my message'; - const plaintext = window.Signal.Crypto.bytesFromString(message); - const path = - 'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa'; - - const encrypted = await window.Signal.Crypto.encryptAttachment( - staticKeyPair.pubKey.slice(1), - path, - plaintext - ); - const decrypted = await window.Signal.Crypto.decryptAttachment( - staticKeyPair.privKey, - path, - encrypted - ); - - const equal = window.Signal.Crypto.constantTimeEqual( - plaintext, - decrypted - ); - if (!equal) { - throw new Error('The output and input did not match!'); - } - }); - }); - - describe('verifyHmacSha256', () => { - it('rejects if their MAC is too short', async () => { - const key = window.Signal.Crypto.getRandomBytes(32); - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const ourMac = await window.Signal.Crypto.hmacSha256(key, plaintext); - const theirMac = ourMac.slice(0, -1); - let error; - try { - await window.Signal.Crypto.verifyHmacSha256( - plaintext, - key, - theirMac, - ourMac.byteLength - ); - } catch (err) { - error = err; - } - assert.instanceOf(error, Error); - assert.strictEqual(error.message, 'Bad MAC length'); - }); - - it('rejects if their MAC is too long', async () => { - const key = window.Signal.Crypto.getRandomBytes(32); - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const ourMac = await window.Signal.Crypto.hmacSha256(key, plaintext); - const theirMac = window.Signal.Crypto.concatenateBytes( - ourMac, - new Uint8Array([0xff]) - ); - let error; - try { - await window.Signal.Crypto.verifyHmacSha256( - plaintext, - key, - theirMac, - ourMac.byteLength - ); - } catch (err) { - error = err; - } - assert.instanceOf(error, Error); - assert.strictEqual(error.message, 'Bad MAC length'); - }); - - it('rejects if our MAC is shorter than the specified length', async () => { - const key = window.Signal.Crypto.getRandomBytes(32); - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const ourMac = await window.Signal.Crypto.hmacSha256(key, plaintext); - const theirMac = ourMac; - let error; - try { - await window.Signal.Crypto.verifyHmacSha256( - plaintext, - key, - theirMac, - ourMac.byteLength + 1 - ); - } catch (err) { - error = err; - } - assert.instanceOf(error, Error); - assert.strictEqual(error.message, 'Bad MAC length'); - }); - - it("rejects if the MACs don't match", async () => { - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const ourKey = window.Signal.Crypto.getRandomBytes(32); - const ourMac = await window.Signal.Crypto.hmacSha256(ourKey, plaintext); - const theirKey = window.Signal.Crypto.getRandomBytes(32); - const theirMac = await window.Signal.Crypto.hmacSha256( - theirKey, - plaintext - ); - let error; - try { - await window.Signal.Crypto.verifyHmacSha256( - plaintext, - ourKey, - theirMac, - ourMac.byteLength - ); - } catch (err) { - error = err; - } - assert.instanceOf(error, Error); - assert.strictEqual(error.message, 'Bad MAC'); - }); - - it('resolves with undefined if the MACs match exactly', async () => { - const key = window.Signal.Crypto.getRandomBytes(32); - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const theirMac = await window.Signal.Crypto.hmacSha256(key, plaintext); - const result = await window.Signal.Crypto.verifyHmacSha256( - plaintext, - key, - theirMac, - theirMac.byteLength - ); - assert.isUndefined(result); - }); - - it('resolves with undefined if the first `length` bytes of the MACs match', async () => { - const key = window.Signal.Crypto.getRandomBytes(32); - const plaintext = window.Signal.Crypto.bytesFromString('Hello world'); - const theirMac = ( - await window.Signal.Crypto.hmacSha256(key, plaintext) - ).slice(0, -5); - const result = await window.Signal.Crypto.verifyHmacSha256( - plaintext, - key, - theirMac, - theirMac.byteLength - ); - assert.isUndefined(result); - }); - }); - - describe('uuidToArrayBuffer', () => { - const { uuidToArrayBuffer } = window.Signal.Crypto; - - it('converts valid UUIDs to ArrayBuffers', () => { - const expectedResult = window.window.Signal.Crypto.typedArrayToArrayBuffer( - new Uint8Array([ - 0x22, - 0x6e, - 0x44, - 0x02, - 0x7f, - 0xfc, - 0x45, - 0x43, - 0x85, - 0xc9, - 0x46, - 0x22, - 0xc5, - 0x0a, - 0x5b, - 0x14, - ]) - ); - - assert.deepEqual( - uuidToArrayBuffer('226e4402-7ffc-4543-85c9-4622c50a5b14'), - expectedResult - ); - assert.deepEqual( - uuidToArrayBuffer('226E4402-7FFC-4543-85C9-4622C50A5B14'), - expectedResult - ); - }); - - it('returns an empty ArrayBuffer for strings of the wrong length', () => { - assert.deepEqual(uuidToArrayBuffer(''), new ArrayBuffer(0)); - assert.deepEqual(uuidToArrayBuffer('abc'), new ArrayBuffer(0)); - assert.deepEqual( - uuidToArrayBuffer('032deadf0d5e4ee78da28e75b1dfb284'), - new ArrayBuffer(0) - ); - assert.deepEqual( - uuidToArrayBuffer('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'), - new ArrayBuffer(0) - ); - }); - }); - - describe('arrayBufferToUuid', () => { - const { arrayBufferToUuid } = window.Signal.Crypto; - - it('converts valid ArrayBuffers to UUID strings', () => { - const buf = window.window.Signal.Crypto.typedArrayToArrayBuffer( - new Uint8Array([ - 0x22, - 0x6e, - 0x44, - 0x02, - 0x7f, - 0xfc, - 0x45, - 0x43, - 0x85, - 0xc9, - 0x46, - 0x22, - 0xc5, - 0x0a, - 0x5b, - 0x14, - ]) - ); - - assert.deepEqual( - arrayBufferToUuid(buf), - '226e4402-7ffc-4543-85c9-4622c50a5b14' - ); - }); - - it('returns undefined if passed an all-zero buffer', () => { - assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(16))); - }); - - it('returns undefined if passed the wrong number of bytes', () => { - assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(0))); - assert.isUndefined( - arrayBufferToUuid( - window.window.Signal.Crypto.typedArrayToArrayBuffer( - new Uint8Array([0x22]) - ) - ) - ); - assert.isUndefined( - arrayBufferToUuid( - window.window.Signal.Crypto.typedArrayToArrayBuffer( - new Uint8Array(Array(17).fill(0x22)) - ) - ) - ); - }); - }); -}); diff --git a/test/index.html b/test/index.html index 3bd167f4e5..b498b81867 100644 --- a/test/index.html +++ b/test/index.html @@ -332,7 +332,6 @@ type="text/javascript" src="../libtextsecure/protocol_wrapper.js" > - - - @@ -423,15 +420,13 @@ diff --git a/test/protobuf_test.js b/test/protobuf_test.js deleted file mode 100644 index 50ea82ae10..0000000000 --- a/test/protobuf_test.js +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2020 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -describe('ProtoBuf.js', () => { - const { ProtoBuf } = window.dcodeIO; - - const sampleProto = `message Simple_v1 { - optional string knownName = 1; - optional string knownValue = 3; - } - message Simple_v2 { - optional string knownName = 1; - optional int32 unknownFlags = 2; - optional string knownValue = 3; - optional string unknownString = 4; - }`; - - it('retains unknown fields', () => { - const builder = ProtoBuf.loadProto(sampleProto); - const protos = builder.build(); - - const v2 = new protos.Simple_v2(); - v2.knownName = 'version2'; - v2.unknownFlags = 42; - v2.knownValue = 'known value'; - v2.unknownString = 'f'; - - const v1 = protos.Simple_v1.decode(v2.encode()); - - const result = protos.Simple_v2.decode(v1.encode()); - - assert.equal(result.knownName, v2.knownName, 'known fields'); - assert.equal(42, result.unknownFlags, 'unknown flag'); - assert.equal('f', result.unknownString, 'unknown string'); - assert.equal('known value', result.knownValue, 'known value'); - }); - - it('supports nested unknown fields', () => { - const nestedProto = ` - ${sampleProto} - message Container_v1 { - optional Simple_v1 elem = 1; - } - message Container_v2 { - optional Simple_v2 elem = 1; - }`; - - const builder = ProtoBuf.loadProto(nestedProto); - const protos = builder.build(); - - const v2 = new protos.Container_v2(); - v2.elem = { - knownName: 'nested v2', - unknownFlags: 10, - knownValue: 'hello world', - }; - - const v1 = protos.Container_v1.decode(v2.encode()); - - const result = protos.Container_v2.decode(v1.encode()); - - assert.equal( - v2.elem.knownName, - result.elem.knownName, - 'nested: known fields' - ); - assert.equal(10, result.elem.unknownFlags, 'nested: unknown flags'); - assert.equal('hello world', result.elem.knownValue, 'known value'); - }); - - it('allows multi-byte id', () => { - const proto = `message Simple_v1 { - optional string knownName = 1; - optional string knownValue = 3; - } - message Simple_v2 { - optional string knownName = 1; - optional int32 unknownFlags = 296; - optional string knownValue = 3; - }`; - - const builder = ProtoBuf.loadProto(proto); - const protos = builder.build(); - - const v2 = new protos.Simple_v2(); - v2.knownName = 'v2 multibyte'; - v2.unknownFlags = 16; - v2.knownValue = 'foo bar'; - - const v1 = protos.Simple_v1.decode(v2.encode()); - - const result = protos.Simple_v2.decode(v1.encode()); - - assert.equal(result.knownName, v2.knownName, 'multibyte: known fields'); - assert.equal(16, result.unknownFlags, 'multibyte: unknown fields'); - assert.equal('foo bar', result.knownValue, 'multibyte: known value'); - }); - - it('retains fields with 64bit type', () => { - const proto = `message Simple_v1 { - optional string knownName = 1; - optional string knownValue = 3; - } - message Simple_v2 { - optional string knownName = 1; - optional double unknownFlags = 2; - optional string knownValue = 3; - }`; - - const builder = ProtoBuf.loadProto(proto); - const protos = builder.build(); - - const v2 = new protos.Simple_v2(); - v2.knownName = 'v2 double'; - v2.unknownFlags = 0; - v2.knownValue = 'double double'; - - const v1 = protos.Simple_v1.decode(v2.encode()); - - const result = protos.Simple_v2.decode(v1.encode()); - - assert.equal(result.knownName, v2.knownName, 'double: known fields'); - assert.equal(0, result.unknownFlags, 'double: unknown fields'); - assert.equal('double double', result.knownValue, 'double: known value'); - }); -}); diff --git a/test/setup-test-node.js b/test/setup-test-node.js index 70f763e7d0..597354f01b 100644 --- a/test/setup-test-node.js +++ b/test/setup-test-node.js @@ -6,8 +6,6 @@ const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); -const ByteBuffer = require('../components/bytebuffer/dist/ByteBufferAB.js'); -const Long = require('../components/long/dist/Long.js'); const { setEnvironment, Environment } = require('../ts/environment'); const { Context: SignalContext } = require('../ts/context'); const { isValidGuid } = require('../ts/util/isValidGuid'); @@ -27,10 +25,6 @@ global.window = { error: (...args) => console.error(...args), }, i18n: key => `i18n(${key})`, - dcodeIO: { - ByteBuffer, - Long, - }, storage: { get: key => storageMap.get(key), put: async (key, value) => storageMap.set(key, value), diff --git a/ts/Bytes.ts b/ts/Bytes.ts index bee2032fd6..d843e1e4cb 100644 --- a/ts/Bytes.ts +++ b/ts/Bytes.ts @@ -37,7 +37,7 @@ export function toString(data: Uint8Array): string { return bytes.toString(data); } -export function concatenate(list: Array): Uint8Array { +export function concatenate(list: ReadonlyArray): Uint8Array { return bytes.concatenate(list); } @@ -50,3 +50,10 @@ export function isNotEmpty( ): data is Uint8Array { return !bytes.isEmpty(data); } + +export function areEqual( + a: Uint8Array | null | undefined, + b: Uint8Array | null | undefined +): boolean { + return bytes.areEqual(a, b); +} diff --git a/ts/Crypto.ts b/ts/Crypto.ts index 25e166c5ca..291a2023f1 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -1,9 +1,12 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { Buffer } from 'buffer'; import pProps from 'p-props'; import { chunk } from 'lodash'; +import Long from 'long'; import { HKDF } from '@signalapp/signal-client'; + import { calculateAgreement, generateKeyPair } from './Curve'; import { @@ -34,37 +37,43 @@ export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer { } export function arrayBufferToBase64(arrayBuffer: ArrayBuffer): string { - return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); + // NOTE: We can't use `Bytes.toBase64` here because this runs in both + // node and electron contexts. + return Buffer.from(arrayBuffer).toString('base64'); } export function arrayBufferToHex(arrayBuffer: ArrayBuffer): string { - return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('hex'); + return Buffer.from(arrayBuffer).toString('hex'); } export function base64ToArrayBuffer(base64string: string): ArrayBuffer { - return window.dcodeIO.ByteBuffer.wrap(base64string, 'base64').toArrayBuffer(); + return typedArrayToArrayBuffer(Buffer.from(base64string, 'base64')); } export function hexToArrayBuffer(hexString: string): ArrayBuffer { - return window.dcodeIO.ByteBuffer.wrap(hexString, 'hex').toArrayBuffer(); + return typedArrayToArrayBuffer(Buffer.from(hexString, 'hex')); } export function fromEncodedBinaryToArrayBuffer(key: string): ArrayBuffer { - return window.dcodeIO.ByteBuffer.wrap(key, 'binary').toArrayBuffer(); + return typedArrayToArrayBuffer(Buffer.from(key, 'binary')); +} + +export function arrayBufferToEncodedBinary(arrayBuffer: ArrayBuffer): string { + return Buffer.from(arrayBuffer).toString('binary'); } export function bytesFromString(string: string): ArrayBuffer { - return window.dcodeIO.ByteBuffer.wrap(string, 'utf8').toArrayBuffer(); + return typedArrayToArrayBuffer(Buffer.from(string)); } export function stringFromBytes(buffer: ArrayBuffer): string { - return window.dcodeIO.ByteBuffer.wrap(buffer).toString('utf8'); + return Buffer.from(buffer).toString(); } export function hexFromBytes(buffer: ArrayBuffer): string { - return window.dcodeIO.ByteBuffer.wrap(buffer).toString('hex'); + return Buffer.from(buffer).toString('hex'); } export function bytesFromHexString(string: string): ArrayBuffer { - return window.dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer(); + return typedArrayToArrayBuffer(Buffer.from(string, 'hex')); } export async function deriveStickerPackKey( @@ -115,10 +124,16 @@ export async function computeHash(data: ArrayBuffer): Promise { // High-level Operations +export type EncryptedDeviceName = { + ephemeralPublic: ArrayBuffer; + syntheticIv: ArrayBuffer; + ciphertext: ArrayBuffer; +}; + export async function encryptDeviceName( deviceName: string, identityPublic: ArrayBuffer -): Promise> { +): Promise { const plaintext = bytesFromString(deviceName); const ephemeralKeyPair = generateKeyPair(); const masterSecret = calculateAgreement( @@ -143,15 +158,7 @@ export async function encryptDeviceName( } export async function decryptDeviceName( - { - ephemeralPublic, - syntheticIv, - ciphertext, - }: { - ephemeralPublic: ArrayBuffer; - syntheticIv: ArrayBuffer; - ciphertext: ArrayBuffer; - }, + { ephemeralPublic, syntheticIv, ciphertext }: EncryptedDeviceName, identityPrivate: ArrayBuffer ): Promise { const masterSecret = calculateAgreement(ephemeralPublic, identityPrivate); @@ -661,21 +668,18 @@ export async function encryptCdsDiscoveryRequest( phoneNumbers: ReadonlyArray ): Promise> { const nonce = getRandomBytes(32); - const numbersArray = new window.dcodeIO.ByteBuffer( - phoneNumbers.length * 8, - window.dcodeIO.ByteBuffer.BIG_ENDIAN + const numbersArray = Buffer.concat( + phoneNumbers.map(number => { + // Long.fromString handles numbers with or without a leading '+' + return new Uint8Array(Long.fromString(number).toBytesBE()); + }) ); - phoneNumbers.forEach(number => { - // Long.fromString handles numbers with or without a leading '+' - numbersArray.writeLong(window.dcodeIO.ByteBuffer.Long.fromString(number)); - }); // We've written to the array, so offset === byteLength; we need to reset it. Then we'll // have access to everything in the array when we generate an ArrayBuffer from it. - numbersArray.reset(); const queryDataPlaintext = concatenateBytes( nonce, - numbersArray.toArrayBuffer() + typedArrayToArrayBuffer(numbersArray) ); const queryDataKey = getRandomBytes(32); @@ -785,7 +789,5 @@ export function trimForDisplay(arrayBuffer: ArrayBuffer): ArrayBuffer { break; } } - return window.dcodeIO.ByteBuffer.wrap(padded) - .slice(0, paddingEnd) - .toArrayBuffer(); + return typedArrayToArrayBuffer(padded.slice(0, paddingEnd)); } diff --git a/ts/context/Bytes.ts b/ts/context/Bytes.ts index bc0d2ce045..1c8b6a4b75 100644 --- a/ts/context/Bytes.ts +++ b/ts/context/Bytes.ts @@ -54,4 +54,15 @@ export class Bytes { public isNotEmpty(data: Uint8Array | null | undefined): data is Uint8Array { return !this.isEmpty(data); } + + public areEqual( + a: Uint8Array | null | undefined, + b: Uint8Array | null | undefined + ): boolean { + if (!a || !b) { + return !a && !b; + } + + return Buffer.compare(a, b) === 0; + } } diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 560d99e746..fa3955d84a 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -10,11 +10,6 @@ import { CustomColorType } from './types/Colors'; import { DeviceType } from './textsecure/Types'; import { SendOptionsType } from './textsecure/SendMessage'; import { SendMessageChallengeData } from './textsecure/Errors'; -import { - AccessRequiredEnum, - MemberRoleEnum, - SyncMessageClass, -} from './textsecure.d'; import { UserMessage } from './types/Message'; import { MessageModel } from './models/messages'; import { ConversationModel } from './models/conversations'; @@ -24,6 +19,10 @@ import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisi import { ConversationColorType } from './types/Colors'; import { AttachmentType, ThumbnailType } from './types/Attachment'; import { ContactType } from './types/Contact'; +import { SignalService as Proto } from './protobuf'; + +import AccessRequiredEnum = Proto.AccessControl.AccessRequired; +import MemberRoleEnum = Proto.Member.Role; export type WhatIsThis = any; diff --git a/ts/services/storage.ts b/ts/services/storage.ts index d128a0b08b..0989d344d2 100644 --- a/ts/services/storage.ts +++ b/ts/services/storage.ts @@ -6,19 +6,14 @@ import pMap from 'p-map'; import Crypto from '../textsecure/Crypto'; import dataInterface from '../sql/Client'; +import * as Bytes from '../Bytes'; import { arrayBufferToBase64, base64ToArrayBuffer, deriveStorageItemKey, deriveStorageManifestKey, + typedArrayToArrayBuffer, } from '../Crypto'; -import { - ManifestRecordClass, - ManifestRecordIdentifierClass, - StorageItemClass, - StorageManifestClass, - StorageRecordClass, -} from '../textsecure.d'; import { mergeAccountRecord, mergeContactRecord, @@ -30,16 +25,24 @@ import { toGroupV2Record, } from './storageRecordOps'; import { ConversationModel } from '../models/conversations'; +import { strictAssert } from '../util/assert'; import { BackOff } from '../util/BackOff'; import { storageJobQueue } from '../util/JobQueue'; import { sleep } from '../util/sleep'; import { isMoreRecentThan } from '../util/timestamp'; +import { normalizeNumber } from '../util/normalizeNumber'; import { isStorageWriteFeatureEnabled } from '../storage/isFeatureEnabled'; import { ourProfileKeyService } from './ourProfileKey'; import { ConversationTypes, typeofConversation, } from '../util/whatTypeOfConversation'; +import { SignalService as Proto } from '../protobuf'; + +type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier; + +// TODO: remove once we move away from ArrayBuffers +const FIXMEU8 = Uint8Array; const { eraseStorageServiceStateFromConversations, @@ -82,9 +85,9 @@ type UnknownRecord = RemoteRecord; async function encryptRecord( storageID: string | undefined, - storageRecord: StorageRecordClass -): Promise { - const storageItem = new window.textsecure.protobuf.StorageItem(); + storageRecord: Proto.IStorageRecord +): Promise { + const storageItem = new Proto.StorageItem(); const storageKeyBuffer = storageID ? base64ToArrayBuffer(String(storageID)) @@ -101,12 +104,12 @@ async function encryptRecord( ); const encryptedRecord = await Crypto.encryptProfile( - storageRecord.toArrayBuffer(), + typedArrayToArrayBuffer(Proto.StorageRecord.encode(storageRecord).finish()), storageItemKey ); - storageItem.key = storageKeyBuffer; - storageItem.value = encryptedRecord; + storageItem.key = new FIXMEU8(storageKeyBuffer); + storageItem.value = new FIXMEU8(encryptedRecord); return storageItem; } @@ -121,13 +124,13 @@ type GeneratedManifestType = { storageID: string | undefined; }>; deleteKeys: Array; - newItems: Set; - storageManifest: StorageManifestClass; + newItems: Set; + storageManifest: Proto.IStorageManifest; }; async function generateManifest( version: number, - previousManifest?: ManifestRecordClass, + previousManifest?: Proto.IManifestRecord, isNewManifest = false ): Promise { window.log.info( @@ -138,39 +141,39 @@ async function generateManifest( await window.ConversationController.checkForConflicts(); - const ITEM_TYPE = window.textsecure.protobuf.ManifestRecord.Identifier.Type; + const ITEM_TYPE = Proto.ManifestRecord.Identifier.Type; const conversationsToUpdate = []; const insertKeys: Array = []; const deleteKeys: Array = []; - const manifestRecordKeys: Set = new Set(); - const newItems: Set = new Set(); + const manifestRecordKeys: Set = new Set(); + const newItems: Set = new Set(); const conversations = window.getConversations(); for (let i = 0; i < conversations.length; i += 1) { const conversation = conversations.models[i]; - const identifier = new window.textsecure.protobuf.ManifestRecord.Identifier(); + const identifier = new Proto.ManifestRecord.Identifier(); let storageRecord; const conversationType = typeofConversation(conversation.attributes); if (conversationType === ConversationTypes.Me) { - storageRecord = new window.textsecure.protobuf.StorageRecord(); + storageRecord = new Proto.StorageRecord(); // eslint-disable-next-line no-await-in-loop storageRecord.account = await toAccountRecord(conversation); identifier.type = ITEM_TYPE.ACCOUNT; } else if (conversationType === ConversationTypes.Direct) { - storageRecord = new window.textsecure.protobuf.StorageRecord(); + storageRecord = new Proto.StorageRecord(); // eslint-disable-next-line no-await-in-loop storageRecord.contact = await toContactRecord(conversation); identifier.type = ITEM_TYPE.CONTACT; } else if (conversationType === ConversationTypes.GroupV2) { - storageRecord = new window.textsecure.protobuf.StorageRecord(); + storageRecord = new Proto.StorageRecord(); // eslint-disable-next-line no-await-in-loop storageRecord.groupV2 = await toGroupV2Record(conversation); identifier.type = ITEM_TYPE.GROUPV2; } else if (conversationType === ConversationTypes.GroupV1) { - storageRecord = new window.textsecure.protobuf.StorageRecord(); + storageRecord = new Proto.StorageRecord(); // eslint-disable-next-line no-await-in-loop storageRecord.groupV1 = await toGroupV1Record(conversation); identifier.type = ITEM_TYPE.GROUPV1; @@ -256,9 +259,9 @@ async function generateManifest( // When updating the manifest, ensure all "unknown" keys are added to the // new manifest, so we don't inadvertently delete something we don't understand unknownRecordsArray.forEach((record: UnknownRecord) => { - const identifier = new window.textsecure.protobuf.ManifestRecord.Identifier(); + const identifier = new Proto.ManifestRecord.Identifier(); identifier.type = record.itemType; - identifier.raw = base64ToArrayBuffer(record.storageID); + identifier.raw = Bytes.fromBase64(record.storageID); manifestRecordKeys.add(identifier); }); @@ -276,9 +279,9 @@ async function generateManifest( // These records failed to merge in the previous fetchManifest, but we still // need to include them so that the manifest is complete recordsWithErrors.forEach((record: UnknownRecord) => { - const identifier = new window.textsecure.protobuf.ManifestRecord.Identifier(); + const identifier = new Proto.ManifestRecord.Identifier(); identifier.type = record.itemType; - identifier.raw = base64ToArrayBuffer(record.storageID); + identifier.raw = Bytes.fromBase64(record.storageID); manifestRecordKeys.add(identifier); }); @@ -293,7 +296,8 @@ async function generateManifest( // This can be broken down into two parts: // There are no duplicate type+raw pairs // There are no duplicate raw bytes - const storageID = arrayBufferToBase64(identifier.raw); + strictAssert(identifier.raw, 'manifest record key without raw identifier'); + const storageID = Bytes.toBase64(identifier.raw); const typeAndRaw = `${identifier.type}+${storageID}`; if ( rawDuplicates.has(identifier.raw) || @@ -335,11 +339,13 @@ async function generateManifest( rawDuplicates.clear(); typeRawDuplicates.clear(); - const storageKeyDuplicates = new Set(); + const storageKeyDuplicates = new Set(); newItems.forEach(storageItem => { // Ensure there are no duplicate StorageIdentifiers in your list of inserts - const storageID = storageItem.key; + strictAssert(storageItem.key, 'New storage item without key'); + + const storageID = Bytes.toBase64(storageItem.key); if (storageKeyDuplicates.has(storageID)) { window.log.info( 'storageService.generateManifest: removing duplicate identifier from inserts', @@ -360,16 +366,18 @@ async function generateManifest( const pendingDeletes: Set = new Set(); const remoteKeys: Set = new Set(); - previousManifest.keys.forEach( - (identifier: ManifestRecordIdentifierClass) => { - const storageID = arrayBufferToBase64(identifier.raw.toArrayBuffer()); + (previousManifest.keys ?? []).forEach( + (identifier: IManifestRecordIdentifier) => { + strictAssert(identifier.raw, 'Identifier without raw field'); + const storageID = Bytes.toBase64(identifier.raw); remoteKeys.add(storageID); } ); const localKeys: Set = new Set(); - manifestRecordKeys.forEach((identifier: ManifestRecordIdentifierClass) => { - const storageID = arrayBufferToBase64(identifier.raw); + manifestRecordKeys.forEach((identifier: IManifestRecordIdentifier) => { + strictAssert(identifier.raw, 'Identifier without raw field'); + const storageID = Bytes.toBase64(identifier.raw); localKeys.add(storageID); if (!remoteKeys.has(storageID)) { @@ -406,7 +414,7 @@ async function generateManifest( }); } - const manifestRecord = new window.textsecure.protobuf.ManifestRecord(); + const manifestRecord = new Proto.ManifestRecord(); manifestRecord.version = version; manifestRecord.keys = Array.from(manifestRecordKeys); @@ -420,13 +428,15 @@ async function generateManifest( version ); const encryptedManifest = await Crypto.encryptProfile( - manifestRecord.toArrayBuffer(), + typedArrayToArrayBuffer( + Proto.ManifestRecord.encode(manifestRecord).finish() + ), storageManifestKey ); - const storageManifest = new window.textsecure.protobuf.StorageManifest(); + const storageManifest = new Proto.StorageManifest(); storageManifest.version = version; - storageManifest.value = encryptedManifest; + storageManifest.value = new FIXMEU8(encryptedManifest); return { conversationsToUpdate, @@ -462,14 +472,16 @@ async function uploadManifest( deleteKeys.length ); - const writeOperation = new window.textsecure.protobuf.WriteOperation(); + const writeOperation = new Proto.WriteOperation(); writeOperation.manifest = storageManifest; writeOperation.insertItem = Array.from(newItems); - writeOperation.deleteKey = deleteKeys; + writeOperation.deleteKey = deleteKeys.map(key => new FIXMEU8(key)); window.log.info('storageService.uploadManifest: uploading...', version); await window.textsecure.messaging.modifyStorageRecords( - writeOperation.toArrayBuffer(), + typedArrayToArrayBuffer( + Proto.WriteOperation.encode(writeOperation).finish() + ), { credentials, } @@ -565,8 +577,8 @@ async function createNewManifest() { } async function decryptManifest( - encryptedManifest: StorageManifestClass -): Promise { + encryptedManifest: Proto.IStorageManifest +): Promise { const { version, value } = encryptedManifest; const storageKeyBase64 = window.storage.get('storageKey'); @@ -576,20 +588,21 @@ async function decryptManifest( const storageKey = base64ToArrayBuffer(storageKeyBase64); const storageManifestKey = await deriveStorageManifestKey( storageKey, - typeof version === 'number' ? version : version.toNumber() + normalizeNumber(version ?? 0) ); + strictAssert(value, 'StorageManifest has no value field'); const decryptedManifest = await Crypto.decryptProfile( - typeof value.toArrayBuffer === 'function' ? value.toArrayBuffer() : value, + typedArrayToArrayBuffer(value), storageManifestKey ); - return window.textsecure.protobuf.ManifestRecord.decode(decryptedManifest); + return Proto.ManifestRecord.decode(new FIXMEU8(decryptedManifest)); } async function fetchManifest( manifestVersion: number -): Promise { +): Promise { window.log.info('storageService.fetchManifest'); if (!window.textsecure.messaging) { @@ -606,8 +619,8 @@ async function fetchManifest( greaterThanVersion: manifestVersion, } ); - const encryptedManifest = window.textsecure.protobuf.StorageManifest.decode( - manifestBinary + const encryptedManifest = Proto.StorageManifest.decode( + new FIXMEU8(manifestBinary) ); // if we don't get a value we're assuming that there's no newer manifest @@ -645,7 +658,7 @@ async function fetchManifest( type MergeableItemType = { itemType: number; storageID: string; - storageRecord: StorageRecordClass; + storageRecord: Proto.IStorageRecord; }; type MergedRecordType = UnknownRecord & { @@ -659,7 +672,7 @@ async function mergeRecord( ): Promise { const { itemType, storageID, storageRecord } = itemToMerge; - const ITEM_TYPE = window.textsecure.protobuf.ManifestRecord.Identifier.Type; + const ITEM_TYPE = Proto.ManifestRecord.Identifier.Type; let hasConflict = false; let isUnsupported = false; @@ -709,18 +722,16 @@ async function mergeRecord( } async function processManifest( - manifest: ManifestRecordClass + manifest: Proto.IManifestRecord ): Promise { if (!window.textsecure.messaging) { throw new Error('storageService.processManifest: We are offline!'); } const remoteKeysTypeMap = new Map(); - manifest.keys.forEach((identifier: ManifestRecordIdentifierClass) => { - remoteKeysTypeMap.set( - arrayBufferToBase64(identifier.raw.toArrayBuffer()), - identifier.type - ); + (manifest.keys || []).forEach(({ raw, type }: IManifestRecordIdentifier) => { + strictAssert(raw, 'Identifier without raw field'); + remoteKeysTypeMap.set(Bytes.toBase64(raw), type); }); const remoteKeys = new Set(remoteKeysTypeMap.keys()); @@ -820,21 +831,21 @@ async function processRemoteRecords( remoteOnlyRecords.size ); - const readOperation = new window.textsecure.protobuf.ReadOperation(); + const readOperation = new Proto.ReadOperation(); readOperation.readKey = Array.from(remoteOnlyRecords.keys()).map( - base64ToArrayBuffer + Bytes.fromBase64 ); const credentials = window.storage.get('storageCredentials'); const storageItemsBuffer = await window.textsecure.messaging.getStorageRecords( - readOperation.toArrayBuffer(), + typedArrayToArrayBuffer(Proto.ReadOperation.encode(readOperation).finish()), { credentials, } ); - const storageItems = window.textsecure.protobuf.StorageItems.decode( - storageItemsBuffer + const storageItems = Proto.StorageItems.decode( + new FIXMEU8(storageItemsBuffer) ); if (!storageItems.items) { @@ -847,7 +858,7 @@ async function processRemoteRecords( const decryptedStorageItems = await pMap( storageItems.items, async ( - storageRecordWrapper: StorageItemClass + storageRecordWrapper: Proto.IStorageItem ): Promise => { const { key, value: storageItemCiphertext } = storageRecordWrapper; @@ -861,7 +872,7 @@ async function processRemoteRecords( ); } - const base64ItemID = arrayBufferToBase64(key.toArrayBuffer()); + const base64ItemID = Bytes.toBase64(key); const storageItemKey = await deriveStorageItemKey( storageKey, @@ -871,7 +882,7 @@ async function processRemoteRecords( let storageItemPlaintext; try { storageItemPlaintext = await Crypto.decryptProfile( - storageItemCiphertext.toArrayBuffer(), + typedArrayToArrayBuffer(storageItemCiphertext), storageItemKey ); } catch (err) { @@ -882,8 +893,8 @@ async function processRemoteRecords( throw err; } - const storageRecord = window.textsecure.protobuf.StorageRecord.decode( - storageItemPlaintext + const storageRecord = Proto.StorageRecord.decode( + new FIXMEU8(storageItemPlaintext) ); const remoteRecord = remoteOnlyRecords.get(base64ItemID); @@ -906,7 +917,7 @@ async function processRemoteRecords( // Merge Account records last since it contains the pinned conversations // and we need all other records merged first before we can find the pinned // records in our db - const ITEM_TYPE = window.textsecure.protobuf.ManifestRecord.Identifier.Type; + const ITEM_TYPE = Proto.ManifestRecord.Identifier.Type; const sortedStorageItems = decryptedStorageItems.sort((_, b) => b.itemType === ITEM_TYPE.ACCOUNT ? -1 : 1 ); @@ -995,7 +1006,7 @@ async function processRemoteRecords( return 0; } -async function sync(): Promise { +async function sync(): Promise { if (!isStorageWriteFeatureEnabled()) { window.log.info( 'storageService.sync: Not starting desktop.storage is falsey' @@ -1010,7 +1021,7 @@ async function sync(): Promise { window.log.info('storageService.sync: starting...'); - let manifest: ManifestRecordClass | undefined; + let manifest: Proto.ManifestRecord | undefined; try { // If we've previously interacted with strage service, update 'fetchComplete' record const previousFetchComplete = window.storage.get('storageFetchComplete'); @@ -1028,7 +1039,11 @@ async function sync(): Promise { return undefined; } - const version = manifest.version.toNumber(); + strictAssert( + manifest.version !== undefined && manifest.version !== null, + 'Manifest without version' + ); + const version = normalizeNumber(manifest.version); window.log.info( `storageService.sync: manifest versions - previous: ${localManifestVersion}, current: ${version}` @@ -1095,7 +1110,7 @@ async function upload(fromSync = false): Promise { return; } - let previousManifest: ManifestRecordClass | undefined; + let previousManifest: Proto.ManifestRecord | undefined; if (!fromSync) { // Syncing before we upload so that we repair any unknown records and // records with errors as well as ensure that we have the latest up to date diff --git a/ts/services/storageRecordOps.ts b/ts/services/storageRecordOps.ts index f72bb3512d..15cdbde642 100644 --- a/ts/services/storageRecordOps.ts +++ b/ts/services/storageRecordOps.ts @@ -2,22 +2,11 @@ // SPDX-License-Identifier: AGPL-3.0-only import { isEqual, isNumber } from 'lodash'; +import Long from 'long'; -import { - arrayBufferToBase64, - base64ToArrayBuffer, - deriveMasterKeyFromGroupV1, - fromEncodedBinaryToArrayBuffer, -} from '../Crypto'; +import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto'; import * as Bytes from '../Bytes'; import dataInterface from '../sql/Client'; -import { - AccountRecordClass, - ContactRecordClass, - GroupV1RecordClass, - GroupV2RecordClass, - PinnedConversationClass, -} from '../textsecure.d'; import { deriveGroupFields, waitThenMaybeUpdateGroup, @@ -46,6 +35,7 @@ import { } from '../util/universalExpireTimer'; import { ourProfileKeyService } from './ourProfileKey'; import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation'; +import { SignalService as Proto } from '../protobuf'; const { updateConversation } = dataInterface; @@ -53,14 +43,14 @@ const { updateConversation } = dataInterface; const FIXMEU8 = Uint8Array; type RecordClass = - | AccountRecordClass - | ContactRecordClass - | GroupV1RecordClass - | GroupV2RecordClass; + | Proto.IAccountRecord + | Proto.IContactRecord + | Proto.IGroupV1Record + | Proto.IGroupV2Record; -function toRecordVerified(verified: number): number { +function toRecordVerified(verified: number): Proto.ContactRecord.IdentityState { const VERIFIED_ENUM = window.textsecure.storage.protocol.VerifiedStatus; - const STATE_ENUM = window.textsecure.protobuf.ContactRecord.IdentityState; + const STATE_ENUM = Proto.ContactRecord.IdentityState; switch (verified) { case VERIFIED_ENUM.VERIFIED: @@ -82,7 +72,9 @@ function addUnknownFields( conversation.idForLogging() ); conversation.set({ - storageUnknownFields: arrayBufferToBase64(record.__unknownFields), + storageUnknownFields: Bytes.toBase64( + Bytes.concatenate(record.__unknownFields) + ), }); } else if (conversation.get('storageUnknownFields')) { // If the record doesn't have unknown fields attached but we have them @@ -106,41 +98,43 @@ function applyUnknownFields( conversation.get('id') ); // eslint-disable-next-line no-param-reassign - record.__unknownFields = base64ToArrayBuffer(storageUnknownFields); + record.__unknownFields = [Bytes.fromBase64(storageUnknownFields)]; } } export async function toContactRecord( conversation: ConversationModel -): Promise { - const contactRecord = new window.textsecure.protobuf.ContactRecord(); - if (conversation.get('uuid')) { - contactRecord.serviceUuid = conversation.get('uuid'); +): Promise { + const contactRecord = new Proto.ContactRecord(); + const uuid = conversation.get('uuid'); + if (uuid) { + contactRecord.serviceUuid = uuid; } - if (conversation.get('e164')) { - contactRecord.serviceE164 = conversation.get('e164'); + const e164 = conversation.get('e164'); + if (e164) { + contactRecord.serviceE164 = e164; } - if (conversation.get('profileKey')) { - contactRecord.profileKey = base64ToArrayBuffer( - String(conversation.get('profileKey')) - ); + const profileKey = conversation.get('profileKey'); + if (profileKey) { + contactRecord.profileKey = Bytes.fromBase64(String(profileKey)); } const identityKey = await window.textsecure.storage.protocol.loadIdentityKey( conversation.id ); if (identityKey) { - contactRecord.identityKey = identityKey; + contactRecord.identityKey = new FIXMEU8(identityKey); } - if (conversation.get('verified')) { - contactRecord.identityState = toRecordVerified( - Number(conversation.get('verified')) - ); + const verified = conversation.get('verified'); + if (verified) { + contactRecord.identityState = toRecordVerified(Number(verified)); } - if (conversation.get('profileName')) { - contactRecord.givenName = conversation.get('profileName'); + const profileName = conversation.get('profileName'); + if (profileName) { + contactRecord.givenName = profileName; } - if (conversation.get('profileFamilyName')) { - contactRecord.familyName = conversation.get('profileFamilyName'); + const profileFamilyName = conversation.get('profileFamilyName'); + if (profileFamilyName) { + contactRecord.familyName = profileFamilyName; } contactRecord.blocked = conversation.isBlocked(); contactRecord.whitelisted = Boolean(conversation.get('profileSharing')); @@ -157,11 +151,11 @@ export async function toContactRecord( export async function toAccountRecord( conversation: ConversationModel -): Promise { - const accountRecord = new window.textsecure.protobuf.AccountRecord(); +): Promise { + const accountRecord = new Proto.AccountRecord(); if (conversation.get('profileKey')) { - accountRecord.profileKey = base64ToArrayBuffer( + accountRecord.profileKey = Bytes.fromBase64( String(conversation.get('profileKey')) ); } @@ -198,7 +192,7 @@ export async function toAccountRecord( } const PHONE_NUMBER_SHARING_MODE_ENUM = - window.textsecure.protobuf.AccountRecord.PhoneNumberSharingMode; + Proto.AccountRecord.PhoneNumberSharingMode; const phoneNumberSharingMode = parsePhoneNumberSharingMode( window.storage.get('phoneNumberSharingMode') ); @@ -239,7 +233,7 @@ export async function toAccountRecord( const pinnedConversation = window.ConversationController.get(id); if (pinnedConversation) { - const pinnedConversationRecord = new window.textsecure.protobuf.AccountRecord.PinnedConversation(); + const pinnedConversationRecord = new Proto.AccountRecord.PinnedConversation(); if (pinnedConversation.get('type') === 'private') { pinnedConversationRecord.identifier = 'contact'; @@ -255,9 +249,7 @@ export async function toAccountRecord( 'toAccountRecord: trying to pin a v1 Group without groupId' ); } - pinnedConversationRecord.legacyGroupId = fromEncodedBinaryToArrayBuffer( - groupId - ); + pinnedConversationRecord.legacyGroupId = Bytes.fromBinary(groupId); } else if (isGroupV2(pinnedConversation.attributes)) { pinnedConversationRecord.identifier = 'groupMasterKey'; const masterKey = pinnedConversation.get('masterKey'); @@ -266,9 +258,7 @@ export async function toAccountRecord( 'toAccountRecord: trying to pin a v2 Group without masterKey' ); } - pinnedConversationRecord.groupMasterKey = base64ToArrayBuffer( - masterKey - ); + pinnedConversationRecord.groupMasterKey = Bytes.fromBase64(masterKey); } return pinnedConversationRecord; @@ -279,7 +269,7 @@ export async function toAccountRecord( .filter( ( pinnedConversationClass - ): pinnedConversationClass is PinnedConversationClass => + ): pinnedConversationClass is Proto.AccountRecord.PinnedConversation => pinnedConversationClass !== undefined ); @@ -296,12 +286,10 @@ export async function toAccountRecord( export async function toGroupV1Record( conversation: ConversationModel -): Promise { - const groupV1Record = new window.textsecure.protobuf.GroupV1Record(); +): Promise { + const groupV1Record = new Proto.GroupV1Record(); - groupV1Record.id = fromEncodedBinaryToArrayBuffer( - String(conversation.get('groupId')) - ); + groupV1Record.id = Bytes.fromBinary(String(conversation.get('groupId'))); groupV1Record.blocked = conversation.isBlocked(); groupV1Record.whitelisted = Boolean(conversation.get('profileSharing')); groupV1Record.archived = Boolean(conversation.get('isArchived')); @@ -317,12 +305,12 @@ export async function toGroupV1Record( export async function toGroupV2Record( conversation: ConversationModel -): Promise { - const groupV2Record = new window.textsecure.protobuf.GroupV2Record(); +): Promise { + const groupV2Record = new Proto.GroupV2Record(); const masterKey = conversation.get('masterKey'); if (masterKey !== undefined) { - groupV2Record.masterKey = base64ToArrayBuffer(masterKey); + groupV2Record.masterKey = Bytes.fromBase64(masterKey); } groupV2Record.blocked = conversation.isBlocked(); groupV2Record.whitelisted = Boolean(conversation.get('profileSharing')); @@ -337,14 +325,13 @@ export async function toGroupV2Record( return groupV2Record; } -type MessageRequestCapableRecord = ContactRecordClass | GroupV1RecordClass; +type MessageRequestCapableRecord = Proto.IContactRecord | Proto.IGroupV1Record; function applyMessageRequestState( record: MessageRequestCapableRecord, conversation: ConversationModel ): void { - const messageRequestEnum = - window.textsecure.protobuf.SyncMessage.MessageRequestResponse.Type; + const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type; if (record.blocked) { conversation.applyMessageRequestResponse(messageRequestEnum.BLOCK, { @@ -394,15 +381,14 @@ function doRecordsConflict( return true; } - return localKeys.reduce((hasConflict: boolean, key: string): boolean => { + return localKeys.some((key: string): boolean => { const localValue = localRecord[key]; const remoteValue = remoteRecord[key]; // Sometimes we have a ByteBuffer and an ArrayBuffer, this ensures that we // are comparing them both equally by converting them into base64 string. - if (Object.prototype.toString.call(localValue) === '[object ArrayBuffer]') { - const areEqual = - arrayBufferToBase64(localValue) === arrayBufferToBase64(remoteValue); + if (localValue instanceof Uint8Array) { + const areEqual = Bytes.areEqual(localValue, remoteValue); if (!areEqual) { window.log.info( 'storageService.doRecordsConflict: Conflict found for ArrayBuffer', @@ -410,15 +396,18 @@ function doRecordsConflict( idForLogging ); } - return hasConflict || !areEqual; + return !areEqual; } // If both types are Long we can use Long's equals to compare them - if ( - window.dcodeIO.Long.isLong(localValue) && - window.dcodeIO.Long.isLong(remoteValue) - ) { - const areEqual = localValue.equals(remoteValue); + if (localValue instanceof Long || typeof localValue === 'number') { + if (!(remoteValue instanceof Long) || typeof remoteValue !== 'number') { + return true; + } + + const areEqual = Long.fromValue(localValue).equals( + Long.fromValue(remoteValue) + ); if (!areEqual) { window.log.info( 'storageService.doRecordsConflict: Conflict found for Long', @@ -426,7 +415,7 @@ function doRecordsConflict( idForLogging ); } - return hasConflict || !areEqual; + return !areEqual; } if (key === 'pinnedConversations') { @@ -437,11 +426,11 @@ function doRecordsConflict( idForLogging ); } - return hasConflict || !areEqual; + return !areEqual; } if (localValue === remoteValue) { - return hasConflict || false; + return false; } // Sometimes we get `null` values from Protobuf and they should default to @@ -452,9 +441,9 @@ function doRecordsConflict( (localValue === false || localValue === '' || localValue === 0 || - (window.dcodeIO.Long.isLong(localValue) && localValue.toNumber() === 0)) + (Long.isLong(localValue) && localValue.toNumber() === 0)) ) { - return hasConflict || false; + return false; } const areEqual = isEqual(localValue, remoteValue); @@ -468,7 +457,7 @@ function doRecordsConflict( } return !areEqual; - }, false); + }); } function doesRecordHavePendingChanges( @@ -497,13 +486,13 @@ function doesRecordHavePendingChanges( export async function mergeGroupV1Record( storageID: string, - groupV1Record: GroupV1RecordClass + groupV1Record: Proto.IGroupV1Record ): Promise { if (!groupV1Record.id) { throw new Error(`No ID for ${storageID}`); } - const groupId = groupV1Record.id.toBinary(); + const groupId = Bytes.toBinary(groupV1Record.id); // Attempt to fetch an existing group pertaining to the `groupId` or create // a new group and populate it with the attributes from the record. @@ -524,7 +513,9 @@ export async function mergeGroupV1Record( // It's possible this group was migrated to a GV2 if so we attempt to // retrieve the master key and find the conversation locally. If we // are successful then we continue setting and applying state. - const masterKeyBuffer = await deriveMasterKeyFromGroupV1(groupId); + const masterKeyBuffer = await deriveMasterKeyFromGroupV1( + typedArrayToArrayBuffer(groupV1Record.id) + ); const fields = deriveGroupFields(new FIXMEU8(masterKeyBuffer)); const derivedGroupV2Id = Bytes.toBase64(fields.id); @@ -599,12 +590,12 @@ export async function mergeGroupV1Record( } async function getGroupV2Conversation( - masterKeyBuffer: ArrayBuffer + masterKeyBuffer: Uint8Array ): Promise { - const groupFields = deriveGroupFields(new FIXMEU8(masterKeyBuffer)); + const groupFields = deriveGroupFields(masterKeyBuffer); const groupId = Bytes.toBase64(groupFields.id); - const masterKey = arrayBufferToBase64(masterKeyBuffer); + const masterKey = Bytes.toBase64(masterKeyBuffer); const secretParams = Bytes.toBase64(groupFields.secretParams); const publicParams = Bytes.toBase64(groupFields.publicParams); @@ -647,13 +638,13 @@ async function getGroupV2Conversation( export async function mergeGroupV2Record( storageID: string, - groupV2Record: GroupV2RecordClass + groupV2Record: Proto.IGroupV2Record ): Promise { if (!groupV2Record.masterKey) { throw new Error(`No master key for ${storageID}`); } - const masterKeyBuffer = groupV2Record.masterKey.toArrayBuffer(); + const masterKeyBuffer = groupV2Record.masterKey; const conversation = await getGroupV2Conversation(masterKeyBuffer); window.log.info( @@ -720,7 +711,7 @@ export async function mergeGroupV2Record( export async function mergeContactRecord( storageID: string, - originalContactRecord: ContactRecordClass + originalContactRecord: Proto.IContactRecord ): Promise { const contactRecord = { ...originalContactRecord, @@ -757,10 +748,9 @@ export async function mergeContactRecord( ); if (contactRecord.profileKey) { - await conversation.setProfileKey( - arrayBufferToBase64(contactRecord.profileKey.toArrayBuffer()), - { viaStorageServiceSync: true } - ); + await conversation.setProfileKey(Bytes.toBase64(contactRecord.profileKey), { + viaStorageServiceSync: true, + }); } const verified = await conversation.safeGetVerified(); @@ -768,11 +758,11 @@ export async function mergeContactRecord( if (verified !== storageServiceVerified) { const verifiedOptions = { key: contactRecord.identityKey - ? contactRecord.identityKey.toArrayBuffer() + ? typedArrayToArrayBuffer(contactRecord.identityKey) : undefined, viaStorageServiceSync: true, }; - const STATE_ENUM = window.textsecure.protobuf.ContactRecord.IdentityState; + const STATE_ENUM = Proto.ContactRecord.IdentityState; switch (storageServiceVerified) { case STATE_ENUM.VERIFIED: @@ -816,7 +806,7 @@ export async function mergeContactRecord( export async function mergeAccountRecord( storageID: string, - accountRecord: AccountRecordClass + accountRecord: Proto.IAccountRecord ): Promise { const { avatarUrl, @@ -855,7 +845,7 @@ export async function mergeAccountRecord( setUniversalExpireTimer(universalExpireTimer || 0); const PHONE_NUMBER_SHARING_MODE_ENUM = - window.textsecure.protobuf.AccountRecord.PhoneNumberSharingMode; + Proto.AccountRecord.PhoneNumberSharingMode; let phoneNumberSharingModeToStore: PhoneNumberSharingMode; switch (phoneNumberSharingMode) { case undefined: @@ -885,7 +875,7 @@ export async function mergeAccountRecord( window.storage.put('phoneNumberDiscoverability', discoverability); if (profileKey) { - ourProfileKeyService.set(profileKey.toArrayBuffer()); + ourProfileKeyService.set(typedArrayToArrayBuffer(profileKey)); } if (pinnedConversations) { @@ -928,48 +918,29 @@ export async function mergeAccountRecord( ); const remotelyPinnedConversationPromises = pinnedConversations.map( - async pinnedConversation => { - let conversationId; + async ({ contact, legacyGroupId, groupMasterKey }) => { + let conversationId: string | undefined; - switch (pinnedConversation.identifier) { - case 'contact': { - if (!pinnedConversation.contact) { - throw new Error('mergeAccountRecord: no contact found'); - } - conversationId = window.ConversationController.ensureContactIds( - pinnedConversation.contact - ); - break; - } - case 'legacyGroupId': { - if (!pinnedConversation.legacyGroupId) { - throw new Error('mergeAccountRecord: no legacyGroupId found'); - } - conversationId = pinnedConversation.legacyGroupId.toBinary(); - break; - } - case 'groupMasterKey': { - if (!pinnedConversation.groupMasterKey) { - throw new Error('mergeAccountRecord: no groupMasterKey found'); - } - const masterKeyBuffer = pinnedConversation.groupMasterKey.toArrayBuffer(); - const groupFields = deriveGroupFields(masterKeyBuffer); - const groupId = Bytes.toBase64(groupFields.id); + if (contact) { + conversationId = window.ConversationController.ensureContactIds( + contact + ); + } else if (legacyGroupId && legacyGroupId.length) { + conversationId = Bytes.toBinary(legacyGroupId); + } else if (groupMasterKey && groupMasterKey.length) { + const groupFields = deriveGroupFields(groupMasterKey); + const groupId = Bytes.toBase64(groupFields.id); - conversationId = groupId; - break; - } - default: { - window.log.error( - 'storageService.mergeAccountRecord: Invalid identifier received' - ); - } + conversationId = groupId; + } else { + window.log.error( + 'storageService.mergeAccountRecord: Invalid identifier received' + ); } if (!conversationId) { window.log.error( - 'storageService.mergeAccountRecord: missing conversation id. looking based on', - pinnedConversation.identifier + 'storageService.mergeAccountRecord: missing conversation id.' ); return undefined; } @@ -1036,9 +1007,7 @@ export async function mergeAccountRecord( }); if (accountRecord.profileKey) { - await conversation.setProfileKey( - arrayBufferToBase64(accountRecord.profileKey.toArrayBuffer()) - ); + await conversation.setProfileKey(Bytes.toBase64(accountRecord.profileKey)); } if (avatarUrl) { diff --git a/ts/test-both/util/arePinnedConversationsEqual_test.ts b/ts/test-both/util/arePinnedConversationsEqual_test.ts index 3be7ba60f6..69a8e53e7d 100644 --- a/ts/test-both/util/arePinnedConversationsEqual_test.ts +++ b/ts/test-both/util/arePinnedConversationsEqual_test.ts @@ -3,38 +3,32 @@ import { assert } from 'chai'; import { arePinnedConversationsEqual } from '../../util/arePinnedConversationsEqual'; -import { PinnedConversationClass } from '../../textsecure.d'; +import { SignalService as Proto } from '../../protobuf'; + +import PinnedConversation = Proto.AccountRecord.IPinnedConversation; describe('arePinnedConversationsEqual', () => { it('is equal if both have same values at same indices', () => { const localValue = [ { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, { - identifier: 'groupMasterKey' as const, - groupMasterKey: new ArrayBuffer(32), - toArrayBuffer: () => new ArrayBuffer(0), + groupMasterKey: new Uint8Array(32), }, ]; const remoteValue = [ { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, { - identifier: 'groupMasterKey' as const, - groupMasterKey: new ArrayBuffer(32), - toArrayBuffer: () => new ArrayBuffer(0), + groupMasterKey: new Uint8Array(32), }, ]; @@ -44,38 +38,30 @@ describe('arePinnedConversationsEqual', () => { it('is not equal if values are mixed', () => { const localValue = [ { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, { - identifier: 'contact' as const, contact: { uuid: 'f59a9fed-9e91-4bb4-a015-d49e58b47e25', e164: '+17865554321', }, - toArrayBuffer: () => new ArrayBuffer(0), }, ]; const remoteValue = [ { - identifier: 'contact' as const, contact: { uuid: 'f59a9fed-9e91-4bb4-a015-d49e58b47e25', e164: '+17865554321', }, - toArrayBuffer: () => new ArrayBuffer(0), }, { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, ]; @@ -85,34 +71,28 @@ describe('arePinnedConversationsEqual', () => { it('is not equal if lengths are not same', () => { const localValue = [ { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, ]; - const remoteValue: Array = []; + const remoteValue: Array = []; assert.isFalse(arePinnedConversationsEqual(localValue, remoteValue)); }); it('is not equal if content does not match', () => { const localValue = [ { - identifier: 'contact' as const, contact: { uuid: '72313cde-2784-4a6f-a92a-abbe23763a60', e164: '+13055551234', }, - toArrayBuffer: () => new ArrayBuffer(0), }, ]; const remoteValue = [ { - identifier: 'groupMasterKey' as const, - groupMasterKey: new ArrayBuffer(32), - toArrayBuffer: () => new ArrayBuffer(0), + groupMasterKey: new Uint8Array(32), }, ]; assert.isFalse(arePinnedConversationsEqual(localValue, remoteValue)); diff --git a/ts/test-both/util/timestampLongUtils_test.ts b/ts/test-both/util/timestampLongUtils_test.ts index 6285bbb629..435d8b1680 100644 --- a/ts/test-both/util/timestampLongUtils_test.ts +++ b/ts/test-both/util/timestampLongUtils_test.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; +import Long from 'long'; import { getSafeLongFromTimestamp, @@ -9,8 +10,6 @@ import { } from '../../util/timestampLongUtils'; describe('getSafeLongFromTimestamp', () => { - const { Long } = window.dcodeIO; - it('returns zero when passed undefined', () => { assert(getSafeLongFromTimestamp(undefined).isZero()); }); @@ -31,8 +30,6 @@ describe('getSafeLongFromTimestamp', () => { }); describe('getTimestampFromLong', () => { - const { Long } = window.dcodeIO; - it('returns zero when passed 0 Long', () => { assert.equal(getTimestampFromLong(Long.fromNumber(0)), 0); }); diff --git a/ts/test-electron/Crypto_test.ts b/ts/test-electron/Crypto_test.ts new file mode 100644 index 0000000000..4e8cd3c0cd --- /dev/null +++ b/ts/test-electron/Crypto_test.ts @@ -0,0 +1,564 @@ +// Copyright 2015-2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import * as Curve from '../Curve'; +import * as Crypto from '../Crypto'; +import TSCrypto from '../textsecure/Crypto'; + +describe('Crypto', () => { + describe('encrypting and decrypting profile data', () => { + const NAME_PADDED_LENGTH = 53; + describe('encrypting and decrypting profile names', () => { + it('pads, encrypts, decrypts, and unpads a short string', async () => { + const name = 'Alice'; + const buffer = Crypto.bytesFromString(name); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfileName(buffer, key); + assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); + + const { given, family } = await TSCrypto.decryptProfileName( + Crypto.arrayBufferToBase64(encrypted), + key + ); + assert.strictEqual(family, null); + assert.strictEqual(Crypto.stringFromBytes(given), name); + }); + + it('handles a given name of the max, 53 characters', async () => { + const name = '01234567890123456789012345678901234567890123456789123'; + const buffer = Crypto.bytesFromString(name); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfileName(buffer, key); + assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); + const { given, family } = await TSCrypto.decryptProfileName( + Crypto.arrayBufferToBase64(encrypted), + key + ); + + assert.strictEqual(Crypto.stringFromBytes(given), name); + assert.strictEqual(family, null); + }); + + it('handles family/given name of the max, 53 characters', async () => { + const name = + '01234567890123456789\u000001234567890123456789012345678912'; + const buffer = Crypto.bytesFromString(name); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfileName(buffer, key); + assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); + const { given, family } = await TSCrypto.decryptProfileName( + Crypto.arrayBufferToBase64(encrypted), + key + ); + assert.strictEqual( + Crypto.stringFromBytes(given), + '01234567890123456789' + ); + assert.strictEqual( + family && Crypto.stringFromBytes(family), + '01234567890123456789012345678912' + ); + }); + + it('handles a string with family/given name', async () => { + const name = 'Alice\0Jones'; + const buffer = Crypto.bytesFromString(name); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfileName(buffer, key); + assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); + const { given, family } = await TSCrypto.decryptProfileName( + Crypto.arrayBufferToBase64(encrypted), + key + ); + assert.strictEqual(Crypto.stringFromBytes(given), 'Alice'); + assert.strictEqual(family && Crypto.stringFromBytes(family), 'Jones'); + }); + + it('works for empty string', async () => { + const name = Crypto.bytesFromString(''); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfileName(name, key); + assert(encrypted.byteLength === NAME_PADDED_LENGTH + 16 + 12); + + const { given, family } = await TSCrypto.decryptProfileName( + Crypto.arrayBufferToBase64(encrypted), + key + ); + assert.strictEqual(family, null); + assert.strictEqual(given.byteLength, 0); + assert.strictEqual(Crypto.stringFromBytes(given), ''); + }); + }); + + describe('encrypting and decrypting profile avatars', () => { + it('encrypts and decrypts', async () => { + const buffer = Crypto.bytesFromString('This is an avatar'); + const key = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfile(buffer, key); + assert(encrypted.byteLength === buffer.byteLength + 16 + 12); + + const decrypted = await TSCrypto.decryptProfile(encrypted, key); + assert(Crypto.constantTimeEqual(buffer, decrypted)); + }); + + it('throws when decrypting with the wrong key', async () => { + const buffer = Crypto.bytesFromString('This is an avatar'); + const key = Crypto.getRandomBytes(32); + const badKey = Crypto.getRandomBytes(32); + + const encrypted = await TSCrypto.encryptProfile(buffer, key); + assert(encrypted.byteLength === buffer.byteLength + 16 + 12); + await assert.isRejected( + TSCrypto.decryptProfile(encrypted, badKey), + 'Failed to decrypt profile data. Most likely the profile key has changed.' + ); + }); + }); + }); + + describe('generateRegistrationId', () => { + it('generates an integer between 0 and 16383 (inclusive)', () => { + for (let i = 0; i < 100; i += 1) { + const id = Crypto.generateRegistrationId(); + assert.isAtLeast(id, 0); + assert.isAtMost(id, 16383); + assert(Number.isInteger(id)); + } + }); + }); + + describe('deriveSecrets', () => { + it('derives key parts via HKDF', () => { + const input = Crypto.getRandomBytes(32); + const salt = Crypto.getRandomBytes(32); + const info = Crypto.bytesFromString('Hello world'); + const result = Crypto.deriveSecrets(input, salt, info); + assert.lengthOf(result, 3); + result.forEach(part => { + // This is a smoke test; HKDF is tested as part of @signalapp/signal-client. + assert.instanceOf(part, ArrayBuffer); + assert.strictEqual(part.byteLength, 32); + }); + }); + }); + + describe('accessKey/profileKey', () => { + it('verification roundtrips', async () => { + const profileKey = await Crypto.getRandomBytes(32); + const accessKey = await Crypto.deriveAccessKey(profileKey); + + const verifier = await Crypto.getAccessKeyVerifier(accessKey); + + const correct = await Crypto.verifyAccessKey(accessKey, verifier); + + assert.strictEqual(correct, true); + }); + }); + + describe('deriveMasterKeyFromGroupV1', () => { + const vectors = [ + { + gv1: '00000000000000000000000000000000', + masterKey: + 'dbde68f4ee9169081f8814eabc65523fea1359235c8cfca32b69e31dce58b039', + }, + { + gv1: '000102030405060708090a0b0c0d0e0f', + masterKey: + '70884f78f07a94480ee36b67a4b5e975e92e4a774561e3df84c9076e3be4b9bf', + }, + { + gv1: '7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f', + masterKey: + 'e69bf7c183b288b4ea5745b7c52b651a61e57769fafde683a6fdf1240f1905f2', + }, + { + gv1: 'ffffffffffffffffffffffffffffffff', + masterKey: + 'dd3a7de23d10f18b64457fbeedc76226c112a730e4b76112e62c36c4432eb37d', + }, + ]; + + vectors.forEach((vector, index) => { + it(`vector ${index}`, async () => { + const gv1 = Crypto.hexToArrayBuffer(vector.gv1); + const expectedHex = vector.masterKey; + + const actual = await Crypto.deriveMasterKeyFromGroupV1(gv1); + const actualHex = Crypto.arrayBufferToHex(actual); + + assert.strictEqual(actualHex, expectedHex); + }); + }); + }); + + describe('symmetric encryption', () => { + it('roundtrips', async () => { + const message = 'this is my message'; + const plaintext = Crypto.bytesFromString(message); + const key = Crypto.getRandomBytes(32); + + const encrypted = await Crypto.encryptSymmetric(key, plaintext); + const decrypted = await Crypto.decryptSymmetric(key, encrypted); + + const equal = Crypto.constantTimeEqual(plaintext, decrypted); + if (!equal) { + throw new Error('The output and input did not match!'); + } + }); + + it('roundtrip fails if nonce is modified', async () => { + const message = 'this is my message'; + const plaintext = Crypto.bytesFromString(message); + const key = Crypto.getRandomBytes(32); + + const encrypted = await Crypto.encryptSymmetric(key, plaintext); + const uintArray = new Uint8Array(encrypted); + uintArray[2] += 2; + + try { + await Crypto.decryptSymmetric( + key, + Crypto.typedArrayToArrayBuffer(uintArray) + ); + } catch (error) { + assert.strictEqual( + error.message, + 'decryptSymmetric: Failed to decrypt; MAC verification failed' + ); + return; + } + + throw new Error('Expected error to be thrown'); + }); + + it('roundtrip fails if mac is modified', async () => { + const message = 'this is my message'; + const plaintext = Crypto.bytesFromString(message); + const key = Crypto.getRandomBytes(32); + + const encrypted = await Crypto.encryptSymmetric(key, plaintext); + const uintArray = new Uint8Array(encrypted); + uintArray[uintArray.length - 3] += 2; + + try { + await Crypto.decryptSymmetric( + key, + Crypto.typedArrayToArrayBuffer(uintArray) + ); + } catch (error) { + assert.strictEqual( + error.message, + 'decryptSymmetric: Failed to decrypt; MAC verification failed' + ); + return; + } + + throw new Error('Expected error to be thrown'); + }); + + it('roundtrip fails if encrypted contents are modified', async () => { + const message = 'this is my message'; + const plaintext = Crypto.bytesFromString(message); + const key = Crypto.getRandomBytes(32); + + const encrypted = await Crypto.encryptSymmetric(key, plaintext); + const uintArray = new Uint8Array(encrypted); + uintArray[35] += 9; + + try { + await Crypto.decryptSymmetric( + key, + Crypto.typedArrayToArrayBuffer(uintArray) + ); + } catch (error) { + assert.strictEqual( + error.message, + 'decryptSymmetric: Failed to decrypt; MAC verification failed' + ); + return; + } + + throw new Error('Expected error to be thrown'); + }); + }); + + describe('encrypted device name', () => { + it('roundtrips', async () => { + const deviceName = 'v1.19.0 on Windows 10'; + const identityKey = Curve.generateKeyPair(); + + const encrypted = await Crypto.encryptDeviceName( + deviceName, + identityKey.pubKey + ); + const decrypted = await Crypto.decryptDeviceName( + encrypted, + identityKey.privKey + ); + + assert.strictEqual(decrypted, deviceName); + }); + + it('fails if iv is changed', async () => { + const deviceName = 'v1.19.0 on Windows 10'; + const identityKey = Curve.generateKeyPair(); + + const encrypted = await Crypto.encryptDeviceName( + deviceName, + identityKey.pubKey + ); + encrypted.syntheticIv = Crypto.getRandomBytes(16); + try { + await Crypto.decryptDeviceName(encrypted, identityKey.privKey); + } catch (error) { + assert.strictEqual( + error.message, + 'decryptDeviceName: synthetic IV did not match' + ); + } + }); + }); + + describe('attachment encryption', () => { + it('roundtrips', async () => { + const staticKeyPair = Curve.generateKeyPair(); + const message = 'this is my message'; + const plaintext = Crypto.bytesFromString(message); + const path = + 'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa'; + + const encrypted = await Crypto.encryptAttachment( + staticKeyPair.pubKey.slice(1), + path, + plaintext + ); + const decrypted = await Crypto.decryptAttachment( + staticKeyPair.privKey, + path, + encrypted + ); + + const equal = Crypto.constantTimeEqual(plaintext, decrypted); + if (!equal) { + throw new Error('The output and input did not match!'); + } + }); + }); + + describe('verifyHmacSha256', () => { + it('rejects if their MAC is too short', async () => { + const key = Crypto.getRandomBytes(32); + const plaintext = Crypto.bytesFromString('Hello world'); + const ourMac = await Crypto.hmacSha256(key, plaintext); + const theirMac = ourMac.slice(0, -1); + let error; + try { + await Crypto.verifyHmacSha256( + plaintext, + key, + theirMac, + ourMac.byteLength + ); + } catch (err) { + error = err; + } + assert.instanceOf(error, Error); + assert.strictEqual(error.message, 'Bad MAC length'); + }); + + it('rejects if their MAC is too long', async () => { + const key = Crypto.getRandomBytes(32); + const plaintext = Crypto.bytesFromString('Hello world'); + const ourMac = await Crypto.hmacSha256(key, plaintext); + const theirMac = Crypto.concatenateBytes(ourMac, new Uint8Array([0xff])); + let error; + try { + await Crypto.verifyHmacSha256( + plaintext, + key, + theirMac, + ourMac.byteLength + ); + } catch (err) { + error = err; + } + assert.instanceOf(error, Error); + assert.strictEqual(error.message, 'Bad MAC length'); + }); + + it('rejects if our MAC is shorter than the specified length', async () => { + const key = Crypto.getRandomBytes(32); + const plaintext = Crypto.bytesFromString('Hello world'); + const ourMac = await Crypto.hmacSha256(key, plaintext); + const theirMac = ourMac; + let error; + try { + await Crypto.verifyHmacSha256( + plaintext, + key, + theirMac, + ourMac.byteLength + 1 + ); + } catch (err) { + error = err; + } + assert.instanceOf(error, Error); + assert.strictEqual(error.message, 'Bad MAC length'); + }); + + it("rejects if the MACs don't match", async () => { + const plaintext = Crypto.bytesFromString('Hello world'); + const ourKey = Crypto.getRandomBytes(32); + const ourMac = await Crypto.hmacSha256(ourKey, plaintext); + const theirKey = Crypto.getRandomBytes(32); + const theirMac = await Crypto.hmacSha256(theirKey, plaintext); + let error; + try { + await Crypto.verifyHmacSha256( + plaintext, + ourKey, + theirMac, + ourMac.byteLength + ); + } catch (err) { + error = err; + } + assert.instanceOf(error, Error); + assert.strictEqual(error.message, 'Bad MAC'); + }); + + it('resolves with undefined if the MACs match exactly', async () => { + const key = Crypto.getRandomBytes(32); + const plaintext = Crypto.bytesFromString('Hello world'); + const theirMac = await Crypto.hmacSha256(key, plaintext); + const result = await Crypto.verifyHmacSha256( + plaintext, + key, + theirMac, + theirMac.byteLength + ); + assert.isUndefined(result); + }); + + it('resolves with undefined if the first `length` bytes of the MACs match', async () => { + const key = Crypto.getRandomBytes(32); + const plaintext = Crypto.bytesFromString('Hello world'); + const theirMac = (await Crypto.hmacSha256(key, plaintext)).slice(0, -5); + const result = await Crypto.verifyHmacSha256( + plaintext, + key, + theirMac, + theirMac.byteLength + ); + assert.isUndefined(result); + }); + }); + + describe('uuidToArrayBuffer', () => { + const { uuidToArrayBuffer } = Crypto; + + it('converts valid UUIDs to ArrayBuffers', () => { + const expectedResult = Crypto.typedArrayToArrayBuffer( + new Uint8Array([ + 0x22, + 0x6e, + 0x44, + 0x02, + 0x7f, + 0xfc, + 0x45, + 0x43, + 0x85, + 0xc9, + 0x46, + 0x22, + 0xc5, + 0x0a, + 0x5b, + 0x14, + ]) + ); + + assert.deepEqual( + uuidToArrayBuffer('226e4402-7ffc-4543-85c9-4622c50a5b14'), + expectedResult + ); + assert.deepEqual( + uuidToArrayBuffer('226E4402-7FFC-4543-85C9-4622C50A5B14'), + expectedResult + ); + }); + + it('returns an empty ArrayBuffer for strings of the wrong length', () => { + assert.deepEqual(uuidToArrayBuffer(''), new ArrayBuffer(0)); + assert.deepEqual(uuidToArrayBuffer('abc'), new ArrayBuffer(0)); + assert.deepEqual( + uuidToArrayBuffer('032deadf0d5e4ee78da28e75b1dfb284'), + new ArrayBuffer(0) + ); + assert.deepEqual( + uuidToArrayBuffer('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'), + new ArrayBuffer(0) + ); + }); + }); + + describe('arrayBufferToUuid', () => { + const { arrayBufferToUuid } = Crypto; + + it('converts valid ArrayBuffers to UUID strings', () => { + const buf = Crypto.typedArrayToArrayBuffer( + new Uint8Array([ + 0x22, + 0x6e, + 0x44, + 0x02, + 0x7f, + 0xfc, + 0x45, + 0x43, + 0x85, + 0xc9, + 0x46, + 0x22, + 0xc5, + 0x0a, + 0x5b, + 0x14, + ]) + ); + + assert.deepEqual( + arrayBufferToUuid(buf), + '226e4402-7ffc-4543-85c9-4622c50a5b14' + ); + }); + + it('returns undefined if passed an all-zero buffer', () => { + assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(16))); + }); + + it('returns undefined if passed the wrong number of bytes', () => { + assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(0))); + assert.isUndefined( + arrayBufferToUuid( + Crypto.typedArrayToArrayBuffer(new Uint8Array([0x22])) + ) + ); + assert.isUndefined( + arrayBufferToUuid( + Crypto.typedArrayToArrayBuffer(new Uint8Array(Array(17).fill(0x22))) + ) + ); + }); + }); +}); diff --git a/ts/test-electron/services/senderCertificate_test.ts b/ts/test-electron/services/senderCertificate_test.ts index 43c4aae646..1ff9f92710 100644 --- a/ts/test-electron/services/senderCertificate_test.ts +++ b/ts/test-electron/services/senderCertificate_test.ts @@ -7,6 +7,7 @@ import { assert } from 'chai'; import * as sinon from 'sinon'; import { v4 as uuid } from 'uuid'; +import Long from 'long'; import * as Bytes from '../../Bytes'; import { typedArrayToArrayBuffer } from '../../Crypto'; import { SenderCertificateMode } from '../../textsecure/OutgoingMessage'; @@ -42,9 +43,7 @@ describe('SenderCertificateService', () => { fakeValidCertificate = new SenderCertificate(); fakeValidCertificateExpiry = Date.now() + 604800000; const certificate = new SenderCertificate.Certificate(); - certificate.expires = global.window.dcodeIO.Long.fromNumber( - fakeValidCertificateExpiry - ); + certificate.expires = Long.fromNumber(fakeValidCertificateExpiry); fakeValidCertificate.certificate = SenderCertificate.Certificate.encode( certificate ).finish(); @@ -215,9 +214,7 @@ describe('SenderCertificateService', () => { const expiredCertificate = new SenderCertificate(); const certificate = new SenderCertificate.Certificate(); - certificate.expires = global.window.dcodeIO.Long.fromNumber( - Date.now() - 1000 - ); + certificate.expires = Long.fromNumber(Date.now() - 1000); expiredCertificate.certificate = SenderCertificate.Certificate.encode( certificate ).finish(); diff --git a/ts/test-electron/util/isByteBufferEmpty_test.ts b/ts/test-electron/util/isByteBufferEmpty_test.ts deleted file mode 100644 index 8ab17b2e03..0000000000 --- a/ts/test-electron/util/isByteBufferEmpty_test.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { assert } from 'chai'; - -import { isByteBufferEmpty } from '../../util/isByteBufferEmpty'; - -describe('isByteBufferEmpty', () => { - it('returns true for undefined', () => { - assert.isTrue(isByteBufferEmpty(undefined)); - }); - - it('returns true for object missing limit', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const brokenByteBuffer: any = {}; - - assert.isTrue(isByteBufferEmpty(brokenByteBuffer)); - }); - - it('returns true for object limit', () => { - const emptyByteBuffer = new window.dcodeIO.ByteBuffer(0); - - assert.isTrue(isByteBufferEmpty(emptyByteBuffer)); - }); - - it('returns false for object limit', () => { - const byteBuffer = window.dcodeIO.ByteBuffer.wrap('AABBCC', 'hex'); - - assert.isFalse(isByteBufferEmpty(byteBuffer)); - }); -}); diff --git a/ts/test-node/Proto_unknown_field_test.ts b/ts/test-node/Proto_unknown_field_test.ts new file mode 100644 index 0000000000..9cad2ac12e --- /dev/null +++ b/ts/test-node/Proto_unknown_field_test.ts @@ -0,0 +1,95 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import { Root } from 'protobufjs'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const { Partial, Full } = (Root as any).fromJSON({ + nested: { + test: { + nested: { + Partial: { + fields: { + a: { + type: 'string', + id: 1, + }, + c: { + type: 'int32', + id: 3, + }, + }, + }, + Full: { + fields: { + a: { + type: 'string', + id: 1, + }, + b: { + type: 'bool', + id: 2, + }, + c: { + type: 'int32', + id: 3, + }, + d: { + type: 'bytes', + id: 4, + }, + }, + }, + }, + }, + }, +}).nested.test; + +describe('Proto#__unknownFields', () => { + it('should encode and decode with unknown fields', () => { + const full = Full.encode({ + a: 'hello', + b: true, + c: 42, + d: Buffer.from('ohai'), + }).finish(); + + const partial = Partial.decode(full); + assert.strictEqual(partial.a, 'hello'); + assert.strictEqual(partial.c, 42); + assert.strictEqual(partial.__unknownFields.length, 2); + assert.strictEqual( + Buffer.from(partial.__unknownFields[0]).toString('hex'), + '1001' + ); + assert.strictEqual( + Buffer.from(partial.__unknownFields[1]).toString('hex'), + '22046f686169' + ); + + const encoded = Partial.encode({ + a: partial.a, + c: partial.c, + __unknownFields: partial.__unknownFields, + }).finish(); + const decoded = Full.decode(encoded); + + assert.strictEqual(decoded.a, 'hello'); + assert.strictEqual(decoded.b, true); + assert.strictEqual(decoded.c, 42); + assert.strictEqual(Buffer.from(decoded.d).toString(), 'ohai'); + + const concat = Partial.encode({ + a: partial.a, + c: partial.c, + __unknownFields: [Buffer.concat(partial.__unknownFields)], + }).finish(); + const decodedConcat = Full.decode(concat); + + assert.strictEqual(decodedConcat.a, 'hello'); + assert.strictEqual(decodedConcat.b, true); + assert.strictEqual(decodedConcat.c, 42); + assert.strictEqual(Buffer.from(decodedConcat.d).toString(), 'ohai'); + }); +}); diff --git a/ts/textsecure.d.ts b/ts/textsecure.d.ts index c141c99c96..fb9c7af337 100644 --- a/ts/textsecure.d.ts +++ b/ts/textsecure.d.ts @@ -8,7 +8,6 @@ import MessageReceiver from './textsecure/MessageReceiver'; import MessageSender from './textsecure/SendMessage'; import SyncRequest from './textsecure/SyncRequest'; import EventTarget from './textsecure/EventTarget'; -import { ByteBufferClass } from './window.d'; import SendMessage, { SendOptionsType } from './textsecure/SendMessage'; import { WebAPIType } from './textsecure/WebAPI'; import utils from './textsecure/Helpers'; @@ -38,6 +37,13 @@ export type UnprocessedType = { export { StorageServiceCallOptionsType, StorageServiceCredentials }; +export type DownloadAttachmentType = Omit< + ProcessedAttachment, + 'digest' | 'key' +> & { + data: ArrayBuffer; +}; + export type TextSecureType = { createTaskWithTimeout: ( task: () => Promise | any, @@ -49,7 +55,6 @@ export type TextSecureType = { messageReceiver: MessageReceiver; messageSender: MessageSender; messaging: SendMessage; - protobuf: ProtobufCollectionType; utils: typeof utils; EventTarget: typeof EventTarget; @@ -58,1139 +63,3 @@ export type TextSecureType = { MessageSender: typeof MessageSender; SyncRequest: typeof SyncRequest; }; - -// Protobufs - -type DeviceMessagesProtobufTypes = { - ProvisioningUuid: typeof ProvisioningUuidClass; - ProvisionEnvelope: typeof ProvisionEnvelopeClass; - ProvisionMessage: typeof ProvisionMessageClass; -}; - -type DeviceNameProtobufTypes = { - DeviceName: typeof DeviceNameClass; -}; - -type GroupsProtobufTypes = { - AvatarUploadAttributes: typeof AvatarUploadAttributesClass; - MemberPendingProfileKey: typeof MemberPendingProfileKeyClass; - MemberPendingAdminApproval: typeof MemberPendingAdminApprovalClass; - AccessControl: typeof AccessControlClass; - Group: typeof GroupClass; - GroupChange: typeof GroupChangeClass; - GroupChanges: typeof GroupChangesClass; - GroupAttributeBlob: typeof GroupAttributeBlobClass; - GroupExternalCredential: typeof GroupExternalCredentialClass; - GroupInviteLink: typeof GroupInviteLinkClass; - GroupJoinInfo: typeof GroupJoinInfoClass; -}; - -type SignalServiceProtobufTypes = { - AttachmentPointer: typeof AttachmentPointerClass; - Envelope: typeof EnvelopeClass; - GroupContext: typeof GroupContextClass; - GroupContextV2: typeof GroupContextV2Class; - GroupDetails: typeof GroupDetailsClass; - NullMessage: typeof NullMessageClass; - ReceiptMessage: typeof ReceiptMessageClass; - SyncMessage: typeof SyncMessageClass; - TypingMessage: typeof TypingMessageClass; - Verified: typeof VerifiedClass; -}; - -type SignalStorageProtobufTypes = { - AccountRecord: typeof AccountRecordClass; - ContactRecord: typeof ContactRecordClass; - GroupV1Record: typeof GroupV1RecordClass; - GroupV2Record: typeof GroupV2RecordClass; - ManifestRecord: typeof ManifestRecordClass; - ReadOperation: typeof ReadOperationClass; - StorageItem: typeof StorageItemClass; - StorageItems: typeof StorageItemsClass; - StorageManifest: typeof StorageManifestClass; - StorageRecord: typeof StorageRecordClass; - WriteOperation: typeof WriteOperationClass; -}; - -type SubProtocolProtobufTypes = { - WebSocketMessage: typeof WebSocketMessageClass; - WebSocketRequestMessage: typeof WebSocketRequestMessageClass; - WebSocketResponseMessage: typeof WebSocketResponseMessageClass; -}; - -type UnidentifiedDeliveryTypes = { - ServerCertificate: typeof ServerCertificateClass; - SenderCertificate: typeof SenderCertificateClass; - UnidentifiedSenderMessage: typeof UnidentifiedSenderMessageClass; -}; - -type ProtobufCollectionType = { - onLoad: (callback: () => unknown) => void; -} & DeviceMessagesProtobufTypes & - DeviceNameProtobufTypes & - GroupsProtobufTypes & - SignalServiceProtobufTypes & - SignalStorageProtobufTypes & - SubProtocolProtobufTypes & - UnidentifiedDeliveryTypes; - -// Note: there are a lot of places in the code that overwrite a field like this -// with a type that the app can use. Being more rigorous with these -// types would require code changes, out of scope for now. -export type ProtoBinaryType = any; -export type ProtoBigNumberType = any; - -// Groups.proto - -export declare class AvatarUploadAttributesClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => AvatarUploadAttributesClass; - - key?: string; - credential?: string; - acl?: string; - algorithm?: string; - date?: string; - policy?: string; - signature?: string; -} - -export type MemberRoleEnum = number; - -export declare class MemberPendingProfileKeyClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => MemberPendingProfileKeyClass; - - addedByUserId?: ProtoBinaryType; - timestamp?: ProtoBigNumberType; -} - -export declare class MemberPendingAdminApprovalClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => MemberPendingProfileKeyClass; - - userId?: ProtoBinaryType; - profileKey?: ProtoBinaryType; - presentation?: ProtoBinaryType; - timestamp?: ProtoBigNumberType; -} - -export declare class AccessControlClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => AccessControlClass; - - attributes?: AccessRequiredEnum; - members?: AccessRequiredEnum; - addFromInviteLink?: AccessRequiredEnum; -} - -export type AccessRequiredEnum = number; - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace AccessControlClass { - class AccessRequired { - static UNKNOWN: number; - static ANY: number; - static MEMBER: number; - static ADMINISTRATOR: number; - static UNSATISFIABLE: number; - } -} - -export declare class GroupClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupClass; - toArrayBuffer: () => ArrayBuffer; - - publicKey?: ProtoBinaryType; - title?: ProtoBinaryType; - avatar?: string; - disappearingMessagesTimer?: ProtoBinaryType; - accessControl?: AccessControlClass; - version?: number; - membersPendingProfileKey?: Array; - membersPendingAdminApproval?: Array; - inviteLinkPassword?: ProtoBinaryType; - descriptionBytes?: ProtoBinaryType; -} - -export declare class GroupChangeClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupChangeClass; - toArrayBuffer: () => ArrayBuffer; - - actions?: ProtoBinaryType; - serverSignature?: ProtoBinaryType; - changeEpoch?: number; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace GroupChangeClass { - class Actions { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => Actions; - toArrayBuffer: () => ArrayBuffer; - - sourceUuid?: ProtoBinaryType; - version?: number; - addMembers?: Array; - deleteMembers?: Array; - modifyMemberRoles?: Array; - modifyMemberProfileKeys?: Array; - addPendingMembers?: Array; - deletePendingMembers?: Array; - promotePendingMembers?: Array; - modifyTitle?: GroupChangeClass.Actions.ModifyTitleAction; - modifyAvatar?: GroupChangeClass.Actions.ModifyAvatarAction; - modifyDisappearingMessagesTimer?: GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction; - modifyAttributesAccess?: GroupChangeClass.Actions.ModifyAttributesAccessControlAction; - modifyMemberAccess?: GroupChangeClass.Actions.ModifyMembersAccessControlAction; - modifyAddFromInviteLinkAccess?: GroupChangeClass.Actions.ModifyAddFromInviteLinkAccessControlAction; - addMemberPendingAdminApprovals?: Array; - deleteMemberPendingAdminApprovals?: Array; - promoteMemberPendingAdminApprovals?: Array; - modifyInviteLinkPassword?: GroupChangeClass.Actions.ModifyInviteLinkPasswordAction; - modifyDescription?: GroupChangeClass.Actions.ModifyDescriptionAction; - } -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace GroupChangeClass.Actions { - class AddMemberAction { - joinFromInviteLink?: boolean; - } - - class DeleteMemberAction { - deletedUserId?: ProtoBinaryType; - } - - class ModifyMemberRoleAction { - userId?: ProtoBinaryType; - role?: MemberRoleEnum; - } - - class ModifyMemberProfileKeyAction { - presentation?: ProtoBinaryType; - - // The result of decryption - profileKey: ArrayBuffer; - uuid: string; - } - - class AddMemberPendingProfileKeyAction { - added?: MemberPendingProfileKeyClass; - } - - class DeleteMemberPendingProfileKeyAction { - deletedUserId?: ProtoBinaryType; - } - - class PromoteMemberPendingProfileKeyAction { - presentation?: ProtoBinaryType; - - // The result of decryption - profileKey: ArrayBuffer; - uuid: string; - } - - class AddMemberPendingAdminApprovalAction { - added?: MemberPendingAdminApprovalClass; - } - - class DeleteMemberPendingAdminApprovalAction { - deletedUserId?: ProtoBinaryType; - } - - class PromoteMemberPendingAdminApprovalAction { - userId?: ProtoBinaryType; - role?: MemberRoleEnum; - } - - class ModifyTitleAction { - title?: ProtoBinaryType; - } - - class ModifyAvatarAction { - avatar?: string; - } - - class ModifyDisappearingMessagesTimerAction { - timer?: ProtoBinaryType; - } - - class ModifyAttributesAccessControlAction { - attributesAccess?: AccessRequiredEnum; - } - - class ModifyMembersAccessControlAction { - membersAccess?: AccessRequiredEnum; - } - - class ModifyAddFromInviteLinkAccessControlAction { - addFromInviteLinkAccess?: AccessRequiredEnum; - } - - class ModifyInviteLinkPasswordAction { - inviteLinkPassword?: ProtoBinaryType; - } - - class ModifyDescriptionAction { - descriptionBytes?: ProtoBinaryType; - } -} - -export declare class GroupChangesClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupChangesClass; - - groupChanges?: Array; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace GroupChangesClass { - class GroupChangeState { - groupChange?: GroupChangeClass; - groupState?: GroupClass; - } -} - -export declare class GroupAttributeBlobClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupAttributeBlobClass; - toArrayBuffer(): ArrayBuffer; - - title?: string; - avatar?: ProtoBinaryType; - disappearingMessagesDuration?: number; - descriptionText?: string; - - // Note: this isn't part of the proto, but our protobuf library tells us which - // field has been set with this prop. - content: - | 'title' - | 'avatar' - | 'disappearingMessagesDuration' - | 'descriptionText'; -} - -export declare class GroupExternalCredentialClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupExternalCredentialClass; - - token?: string; -} - -export declare class GroupInviteLinkClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupInviteLinkClass; - toArrayBuffer: () => ArrayBuffer; - - v1Contents?: GroupInviteLinkClass.GroupInviteLinkContentsV1; - - // Note: this isn't part of the proto, but our protobuf library tells us which - // field has been set with this prop. - contents?: 'v1Contents'; -} - -export declare namespace GroupInviteLinkClass { - class GroupInviteLinkContentsV1 { - groupMasterKey?: ProtoBinaryType; - inviteLinkPassword?: ProtoBinaryType; - } -} - -export declare class GroupJoinInfoClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupJoinInfoClass; - - publicKey?: ProtoBinaryType; - title?: ProtoBinaryType; - avatar?: string; - memberCount?: number; - addFromInviteLink?: AccessControlClass.AccessRequired; - version?: number; - pendingAdminApproval?: boolean; - descriptionBytes?: ProtoBinaryType; -} - -// Previous protos - -export declare class AttachmentPointerClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => AttachmentPointerClass; - - static Flags: { - VOICE_MESSAGE: number; - BORDERLESS: number; - GIF: number; - }; - - cdnId?: string; - cdnKey?: string; - contentType?: string; - key?: ProtoBinaryType; - size?: number; - thumbnail?: ProtoBinaryType; - digest?: ProtoBinaryType; - fileName?: string; - flags?: number; - width?: number; - height?: number; - caption?: string; - blurHash?: string; - cdnNumber?: number; -} - -export type DownloadAttachmentType = Omit< - ProcessedAttachment, - 'digest' | 'key' -> & { - data: ArrayBuffer; -}; - -declare class DeviceNameClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => DeviceNameClass; - encode: () => DeviceNameClass; - toArrayBuffer: () => ArrayBuffer; - - ephemeralPublic: ProtoBinaryType; - syntheticIv: ProtoBinaryType; - ciphertext: ProtoBinaryType; -} - -export declare class EnvelopeClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => EnvelopeClass; - - type?: number; - source?: string; - sourceUuid?: string; - sourceDevice?: number; - relay?: string; - timestamp?: ProtoBigNumberType; - legacyMessage?: ProtoBinaryType; - content?: ProtoBinaryType; - serverGuid?: string; - serverTimestamp?: ProtoBigNumberType; - - // Note: these additional properties are added in the course of processing - id: string; - receivedAtCounter: number; - receivedAtDate: number; - unidentifiedDeliveryReceived?: boolean; - messageAgeSec?: number; - contentHint?: number; - groupId?: string; - usmc?: UnidentifiedSenderMessageContent; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace EnvelopeClass { - class Type { - static CIPHERTEXT: number; - static PREKEY_BUNDLE: number; - static RECEIPT: number; - static UNIDENTIFIED_SENDER: number; - static PLAINTEXT_CONTENT: number; - } -} - -export declare class GroupContextClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupContextClass; - - id?: ProtoBinaryType; - type?: number; - name?: string | null; - membersE164?: Array; - avatar?: AttachmentPointerClass | null; - - // Note: these additional properties are added in the course of processing - derivedGroupV2Id?: string; -} - -export declare class GroupContextV2Class { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupContextV2Class; - - masterKey?: ProtoBinaryType; - revision?: number; - groupChange?: ProtoBinaryType; - - // Note: these additional properties are added in the course of processing - id?: string; - secretParams?: string; - publicParams?: string; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace GroupContextClass { - class Member { - uuid?: string; - e164?: string; - } - class Type { - static UNKNOWN: number; - static UPDATE: number; - static DELIVER: number; - static QUIT: number; - static REQUEST_INFO: number; - } -} - -export declare class GroupDetailsClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupDetailsClass; - - id?: ProtoBinaryType; - name?: string; - membersE164?: Array; - members?: Array; - avatar?: GroupDetailsClass.Avatar; - active?: boolean; - expireTimer?: number; - color?: string; - blocked?: boolean; - inboxPosition?: number; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace GroupDetailsClass { - class Avatar { - contentType?: string; - length?: string; - } - - class Member { - uuid?: string; - e164?: string; - } -} - -declare enum ManifestType { - UNKNOWN, - CONTACT, - GROUPV1, - GROUPV2, - ACCOUNT, -} - -export declare class ManifestRecordIdentifierClass { - static Type: typeof ManifestType; - raw: ProtoBinaryType; - type: ManifestType; - toArrayBuffer: () => ArrayBuffer; -} - -export declare class ManifestRecordClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ManifestRecordClass; - toArrayBuffer: () => ArrayBuffer; - static Identifier: typeof ManifestRecordIdentifierClass; - - version: ProtoBigNumberType; - keys: ManifestRecordIdentifierClass[]; -} - -export declare class NullMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => NullMessageClass; - - padding?: ProtoBinaryType; -} - -declare class ProvisioningUuidClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ProvisioningUuidClass; - encode: () => ProvisioningUuidClass; - toArrayBuffer: () => ArrayBuffer; - - uuid?: string; -} - -export declare class ProvisionEnvelopeClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ProvisionEnvelopeClass; - encode: () => ProvisionEnvelopeClass; - toArrayBuffer: () => ArrayBuffer; - - publicKey?: ProtoBinaryType; - body?: ProtoBinaryType; -} - -declare class ProvisionMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ProvisionMessageClass; - encode: () => ProvisionMessageClass; - toArrayBuffer: () => ArrayBuffer; - - identityKeyPrivate?: ProtoBinaryType; - number?: string; - uuid?: string; - provisioningCode?: string; - userAgent?: string; - profileKey?: ProtoBinaryType; - readReceipts?: boolean; - ProvisioningVersion?: number; -} - -export declare class ReceiptMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ReceiptMessageClass; - - type?: number; - timestamp?: ProtoBigNumberType; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace ReceiptMessageClass { - class Type { - static DELIVERY: number; - static READ: number; - static VIEWED: number; - } -} - -// Storage Service related types - -export declare class StorageManifestClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => StorageManifestClass; - - version?: ProtoBigNumberType | null; - value?: ProtoBinaryType; -} - -export declare class StorageRecordClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => StorageRecordClass; - toArrayBuffer: () => ArrayBuffer; - - contact?: ContactRecordClass | null; - groupV1?: GroupV1RecordClass | null; - groupV2?: GroupV2RecordClass | null; - account?: AccountRecordClass | null; -} - -export declare class StorageItemClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => StorageItemClass; - - key?: ProtoBinaryType; - value?: ProtoBinaryType; -} - -export declare class StorageItemsClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => StorageItemsClass; - - items?: StorageItemClass[] | null; -} - -export declare enum ContactRecordIdentityState { - DEFAULT = 0, - VERIFIED = 1, - UNVERIFIED = 2, -} - -export declare class ContactRecordClass { - static IdentityState: typeof ContactRecordIdentityState; - - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ContactRecordClass; - toArrayBuffer: () => ArrayBuffer; - - serviceUuid?: string | null; - serviceE164?: string | null; - profileKey?: ProtoBinaryType; - identityKey?: ProtoBinaryType; - identityState?: ContactRecordIdentityState | null; - givenName?: string | null; - familyName?: string | null; - username?: string | null; - blocked?: boolean | null; - whitelisted?: boolean | null; - archived?: boolean | null; - markedUnread?: boolean; - mutedUntilTimestamp?: ProtoBigNumberType; - - __unknownFields?: ArrayBuffer; -} - -export declare class GroupV1RecordClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupV1RecordClass; - toArrayBuffer: () => ArrayBuffer; - - id?: ProtoBinaryType; - blocked?: boolean | null; - whitelisted?: boolean | null; - archived?: boolean | null; - markedUnread?: boolean; - mutedUntilTimestamp?: ProtoBigNumberType; - - __unknownFields?: ArrayBuffer; -} - -export declare class GroupV2RecordClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => GroupV2RecordClass; - toArrayBuffer: () => ArrayBuffer; - - masterKey?: ProtoBinaryType | null; - blocked?: boolean | null; - whitelisted?: boolean | null; - archived?: boolean | null; - markedUnread?: boolean; - mutedUntilTimestamp?: ProtoBigNumberType; - - __unknownFields?: ArrayBuffer; -} - -export declare class PinnedConversationClass { - toArrayBuffer: () => ArrayBuffer; - - // identifier is produced by the oneof field in the PinnedConversation protobuf - // and determined which one of the following optional fields are in use - identifier: 'contact' | 'legacyGroupId' | 'groupMasterKey'; - contact?: { - uuid?: string; - e164?: string; - }; - legacyGroupId?: ProtoBinaryType; - groupMasterKey?: ProtoBinaryType; -} - -declare enum AccountRecordPhoneNumberSharingMode { - EVERYBODY = 0, - CONTACTS_ONLY = 1, - NOBODY = 2, -} - -export declare class AccountRecordClass { - static PhoneNumberSharingMode: typeof AccountRecordPhoneNumberSharingMode; - static PinnedConversation: typeof PinnedConversationClass; - - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => AccountRecordClass; - toArrayBuffer: () => ArrayBuffer; - - profileKey?: ProtoBinaryType; - givenName?: string | null; - familyName?: string | null; - avatarUrl?: string | null; - noteToSelfArchived?: boolean | null; - readReceipts?: boolean | null; - sealedSenderIndicators?: boolean | null; - typingIndicators?: boolean | null; - linkPreviews?: boolean | null; - phoneNumberSharingMode?: AccountRecordPhoneNumberSharingMode; - notDiscoverableByPhoneNumber?: boolean; - pinnedConversations?: PinnedConversationClass[]; - noteToSelfMarkedUnread?: boolean; - universalExpireTimer?: number; - primarySendsSms?: boolean; - - __unknownFields?: ArrayBuffer; -} - -declare class ReadOperationClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ReadOperationClass; - - readKey: ArrayBuffer[] | ByteBufferClass[]; - toArrayBuffer: () => ArrayBuffer; -} - -declare class WriteOperationClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => WriteOperationClass; - toArrayBuffer: () => ArrayBuffer; - - manifest: StorageManifestClass; - insertItem: StorageItemClass[]; - deleteKey: ArrayBuffer[] | ByteBufferClass[]; - clearAll: boolean; -} - -export declare class SyncMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => SyncMessageClass; - - sent?: SyncMessageClass.Sent; - contacts?: SyncMessageClass.Contacts; - groups?: SyncMessageClass.Groups; - request?: SyncMessageClass.Request; - read?: Array; - blocked?: SyncMessageClass.Blocked; - verified?: VerifiedClass; - configuration?: SyncMessageClass.Configuration; - padding?: ProtoBinaryType; - stickerPackOperation?: Array; - viewOnceOpen?: SyncMessageClass.ViewOnceOpen; - messageRequestResponse?: SyncMessageClass.MessageRequestResponse; - fetchLatest?: SyncMessageClass.FetchLatest; - keys?: SyncMessageClass.Keys; - viewed?: Array; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace SyncMessageClass { - class Configuration { - readReceipts?: boolean; - unidentifiedDeliveryIndicators?: boolean; - typingIndicators?: boolean; - linkPreviews?: boolean; - provisioningVersion?: number; - } - class Contacts { - blob?: AttachmentPointerClass; - complete?: boolean; - } - class Groups { - blob?: AttachmentPointerClass; - } - class Blocked { - numbers?: Array; - uuids?: Array; - groupIds?: Array; - } - class Read { - sender: string | null; - senderUuid: string | null; - timestamp?: ProtoBigNumberType; - } - class Viewed { - sender: string | null; - senderUuid: string | null; - timestamp?: ProtoBigNumberType; - } - class Request { - type?: number; - } - class Sent { - destination?: string; - destinationUuid?: string; - timestamp?: ProtoBigNumberType; - expirationStartTimestamp?: ProtoBigNumberType; - unidentifiedStatus?: Array; - isRecipientUpdate?: boolean; - } - class StickerPackOperation { - packId?: ProtoBinaryType; - packKey?: ProtoBinaryType; - type?: number; - } - class ViewOnceOpen { - sender: string | null; - senderUuid: string | null; - timestamp?: ProtoBinaryType | null; - } - class FetchLatest { - static Type: { - UNKNOWN: number; - LOCAL_PROFILE: number; - STORAGE_MANIFEST: number; - }; - type?: number; - } - class Keys { - storageService?: ByteBufferClass; - } - - class MessageRequestResponse { - threadE164: string | null; - threadUuid: string | null; - groupId: ProtoBinaryType | null; - type: number | null; - } -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace SyncMessageClass.Request { - class Type { - static UNKNOWN: number; - static BLOCKED: number; - static CONFIGURATION: number; - static CONTACTS: number; - static GROUPS: number; - static KEYS: number; - } -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace SyncMessageClass.Sent { - class UnidentifiedDeliveryStatus { - destination?: string; - destinationUuid?: string; - unidentified?: boolean; - } -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace SyncMessageClass.StickerPackOperation { - class Type { - static INSTALL: number; - static REMOVE: number; - } -} - -export declare namespace SyncMessageClass.MessageRequestResponse { - class Type { - static UNKNOWN: number; - static ACCEPT: number; - static DELETE: number; - static BLOCK: number; - static BLOCK_AND_DELETE: number; - } -} - -export declare class TypingMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => TypingMessageClass; - - timestamp?: ProtoBigNumberType; - action?: number; - groupId?: ProtoBinaryType; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace TypingMessageClass { - class Action { - static STARTED: number; - static STOPPED: number; - } -} - -export declare class VerifiedClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => VerifiedClass; - static State: WhatIsThis; - - destination?: string; - destinationUuid?: string; - identityKey?: ProtoBinaryType; - state?: number; - nullMessage?: ProtoBinaryType; -} - -export declare class WebSocketMessageClass { - constructor(data: any); - encode: () => WebSocketMessageClass; - toArrayBuffer: () => ArrayBuffer; - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => WebSocketMessageClass; - - type?: number; - request?: WebSocketRequestMessageClass; - response?: WebSocketResponseMessageClass; -} - -// Note: we need to use namespaces to express nested classes in Typescript -export declare namespace WebSocketMessageClass { - class Type { - static UNKNOWN: number; - static REQUEST: number; - static RESPONSE: number; - } -} - -export declare class WebSocketRequestMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => WebSocketRequestMessageClass; - verb?: string; - path?: string; - body?: ProtoBinaryType; - headers?: Array; - id?: ProtoBigNumberType; -} - -export declare class WebSocketResponseMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => WebSocketResponseMessageClass; - id?: ProtoBigNumberType; - status?: number; - message?: string; - headers?: Array; - body?: ProtoBinaryType; -} - -export { CallingMessageClass }; - -// UnidentifiedDelivery.proto - -export declare class ServerCertificateClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => ServerCertificateClass; - toArrayBuffer: () => ArrayBuffer; - - certificate?: ProtoBinaryType; - signature?: ProtoBinaryType; -} - -export declare namespace ServerCertificateClass { - class Certificate { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => Certificate; - toArrayBuffer: () => ArrayBuffer; - - id?: number; - key?: ProtoBinaryType; - } -} - -export declare class SenderCertificateClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => SenderCertificateClass; - toArrayBuffer: () => ArrayBuffer; - - certificate?: ProtoBinaryType; - signature?: ProtoBinaryType; -} - -export declare namespace SenderCertificateClass { - class Certificate { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => Certificate; - toArrayBuffer: () => ArrayBuffer; - - senderE164?: string; - senderUuid?: string; - senderDevice?: number; - expires?: ProtoBigNumberType; - identityKey?: ProtoBinaryType; - signer?: SenderCertificateClass; - } -} - -export declare class UnidentifiedSenderMessageClass { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => UnidentifiedSenderMessageClass; - toArrayBuffer: () => ArrayBuffer; - - ephemeralPublic?: ProtoBinaryType; - encryptedStatic?: ProtoBinaryType; - encryptedMessage?: ProtoBinaryType; -} - -export declare namespace UnidentifiedSenderMessageClass { - class Message { - static decode: ( - data: ArrayBuffer | ByteBufferClass, - encoding?: string - ) => Message; - toArrayBuffer: () => ArrayBuffer; - - type?: number; - senderCertificate?: SenderCertificateClass; - content?: ProtoBinaryType; - contentHint?: number; - groupId?: ProtoBinaryType; - } -} - -export declare namespace UnidentifiedSenderMessageClass.Message { - class Type { - static PREKEY_MESSAGE: number; - static MESSAGE: number; - static SENDERKEY_MESSAGE: number; - static PLAINTEXT_CONTENT: number; - } - - class ContentHint { - static DEFAULT: number; - static RESENDABLE: number; - static IMPLICIT: number; - } -} diff --git a/ts/textsecure/Crypto.ts b/ts/textsecure/Crypto.ts index e26e5c36f5..2c43cea4c6 100644 --- a/ts/textsecure/Crypto.ts +++ b/ts/textsecure/Crypto.ts @@ -11,6 +11,8 @@ import { hmacSha256, sha256, verifyHmacSha256, + base64ToArrayBuffer, + typedArrayToArrayBuffer, } from '../Crypto'; declare global { @@ -335,10 +337,7 @@ const Crypto = { encryptedProfileName: string, key: ArrayBuffer ): Promise<{ given: ArrayBuffer; family: ArrayBuffer | null }> { - const data = window.dcodeIO.ByteBuffer.wrap( - encryptedProfileName, - 'base64' - ).toArrayBuffer(); + const data = base64ToArrayBuffer(encryptedProfileName); return Crypto.decryptProfile(data, key).then(decrypted => { const padded = new Uint8Array(decrypted); @@ -364,13 +363,9 @@ const Crypto = { const foundFamilyName = familyEnd > givenEnd + 1; return { - given: window.dcodeIO.ByteBuffer.wrap(padded) - .slice(0, givenEnd) - .toArrayBuffer(), + given: typedArrayToArrayBuffer(padded.slice(0, givenEnd)), family: foundFamilyName - ? window.dcodeIO.ByteBuffer.wrap(padded) - .slice(givenEnd + 1, familyEnd) - .toArrayBuffer() + ? typedArrayToArrayBuffer(padded.slice(givenEnd + 1, familyEnd)) : null, }; }); diff --git a/ts/textsecure/Helpers.ts b/ts/textsecure/Helpers.ts index 7ad3ea5f18..ccd303729f 100644 --- a/ts/textsecure/Helpers.ts +++ b/ts/textsecure/Helpers.ts @@ -6,27 +6,14 @@ /* eslint-disable no-proto */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ByteBufferClass } from '../window.d'; - -let ByteBuffer: ByteBufferClass | undefined; const arrayBuffer = new ArrayBuffer(0); const uint8Array = new Uint8Array(); -let StaticByteBufferProto: any; const StaticArrayBufferProto = (arrayBuffer as any).__proto__; const StaticUint8ArrayProto = (uint8Array as any).__proto__; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function getString(thing: any): string { - // Note: we must make this at runtime because it's loaded in the browser context - if (!ByteBuffer) { - ByteBuffer = new window.dcodeIO.ByteBuffer(); - } - - if (!StaticByteBufferProto) { - StaticByteBufferProto = (ByteBuffer as any).__proto__; - } - if (thing === Object(thing)) { if (thing.__proto__ === StaticUint8ArrayProto) { return String.fromCharCode.apply(null, thing); @@ -34,9 +21,6 @@ function getString(thing: any): string { if (thing.__proto__ === StaticArrayBufferProto) { return getString(new Uint8Array(thing)); } - if (thing.__proto__ === StaticByteBufferProto) { - return thing.toString('binary'); - } } return thing; } @@ -48,8 +32,7 @@ function getStringable(thing: any): boolean { typeof thing === 'boolean' || (thing === Object(thing) && (thing.__proto__ === StaticArrayBufferProto || - thing.__proto__ === StaticUint8ArrayProto || - thing.__proto__ === StaticByteBufferProto)) + thing.__proto__ === StaticUint8ArrayProto)) ); } diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 7a37bcfeb7..aab6d17add 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -193,7 +193,7 @@ class MessageReceiverInner extends EventTarget { server: WebAPIType; - serverTrustRoot: ArrayBuffer; + serverTrustRoot: Uint8Array; signalingKey: ArrayBuffer; @@ -239,9 +239,7 @@ class MessageReceiverInner extends EventTarget { if (!options.serverTrustRoot) { throw new Error('Server trust root is required!'); } - this.serverTrustRoot = MessageReceiverInner.stringToArrayBufferBase64( - options.serverTrustRoot - ); + this.serverTrustRoot = Bytes.fromBase64(options.serverTrustRoot); this.number_id = oldUsername ? utils.unencodeNumber(oldUsername)[0] @@ -286,18 +284,6 @@ class MessageReceiverInner extends EventTarget { }); } - static stringToArrayBuffer = (string: string): ArrayBuffer => - window.dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer(); - - static arrayBufferToString = (arrayBuffer: ArrayBuffer): string => - window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary'); - - static stringToArrayBufferBase64 = (string: string): ArrayBuffer => - window.dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer(); - - static arrayBufferToStringBase64 = (arrayBuffer: ArrayBuffer): string => - window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); - async connect(socket?: WebSocket): Promise { if (this.calledClose) { return; @@ -2479,8 +2465,8 @@ class MessageReceiverInner extends EventTarget { const paddedData = await Crypto.decryptAttachment( encrypted, - MessageReceiverInner.stringToArrayBufferBase64(key), - MessageReceiverInner.stringToArrayBufferBase64(digest) + typedArrayToArrayBuffer(Bytes.fromBase64(key)), + typedArrayToArrayBuffer(Bytes.fromBase64(digest)) ); if (!isNumber(size)) { @@ -2688,14 +2674,4 @@ export default class MessageReceiver { checkSocket: () => void; getProcessedCount: () => number; - - static stringToArrayBuffer = MessageReceiverInner.stringToArrayBuffer; - - static arrayBufferToString = MessageReceiverInner.arrayBufferToString; - - static stringToArrayBufferBase64 = - MessageReceiverInner.stringToArrayBufferBase64; - - static arrayBufferToStringBase64 = - MessageReceiverInner.arrayBufferToStringBase64; } diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 7289054e8f..3ca3d5df71 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -28,23 +28,22 @@ import PQueue from 'p-queue'; import { v4 as getGuid } from 'uuid'; import { client as WebSocketClient, connection as WebSocket } from 'websocket'; import { z } from 'zod'; +import Long from 'long'; -import { Long } from '../window.d'; import { assert } from '../util/assert'; import { getUserAgent } from '../util/getUserAgent'; import { toWebSafeBase64 } from '../util/webSafeBase64'; import { isPackIdValid, redactPackId } from '../types/Stickers'; +import * as Bytes from '../Bytes'; import { arrayBufferToBase64, base64ToArrayBuffer, - bytesFromHexString, bytesFromString, concatenateBytes, constantTimeEqual, decryptAesGcm, deriveSecrets, encryptCdsDiscoveryRequest, - getBytes, getRandomValue, splitUuids, typedArrayToArrayBuffer, @@ -84,7 +83,7 @@ type SgxConstantsType = { let sgxConstantCache: SgxConstantsType | null = null; function makeLong(value: string): Long { - return window.dcodeIO.Long.fromString(value); + return Long.fromString(value); } function getSgxConstants() { if (sgxConstantCache) { @@ -2434,34 +2433,38 @@ export function initialize({ function validateAttestationQuote({ serverStaticPublic, - quote, + quote: quoteArrayBuffer, }: { serverStaticPublic: ArrayBuffer; quote: ArrayBuffer; }) { const SGX_CONSTANTS = getSgxConstants(); - const byteBuffer = window.dcodeIO.ByteBuffer.wrap( - quote, - 'binary', - window.dcodeIO.ByteBuffer.LITTLE_ENDIAN - ); + const quote = Buffer.from(quoteArrayBuffer); - const quoteVersion = byteBuffer.readShort(0) & 0xffff; + let off = 0; + + const quoteVersion = quote.readInt32LE(off) & 0xffff; + off += 4; if (quoteVersion < 0 || quoteVersion > 2) { throw new Error(`Unknown version ${quoteVersion}`); } - const miscSelect = new Uint8Array(getBytes(quote, 64, 4)); + const miscSelect = quote.slice(off, off + 64); + off += 64; if (!miscSelect.every(byte => byte === 0)) { throw new Error('Quote miscSelect invalid!'); } - const reserved1 = new Uint8Array(getBytes(quote, 68, 28)); + const reserved1 = quote.slice(off, off + 28); + off += 28; if (!reserved1.every(byte => byte === 0)) { throw new Error('Quote reserved1 invalid!'); } - const flags = byteBuffer.readLong(96); + const flags = Long.fromBytesLE( + Array.from(quote.slice(off, off + 8).values()) + ); + off += 8; if ( flags.and(SGX_CONSTANTS.SGX_FLAGS_RESERVED).notEquals(0) || flags.and(SGX_CONSTANTS.SGX_FLAGS_INITTED).equals(0) || @@ -2470,25 +2473,29 @@ export function initialize({ throw new Error(`Quote flags invalid ${flags.toString()}`); } - const xfrm = byteBuffer.readLong(104); + const xfrm = Long.fromBytesLE( + Array.from(quote.slice(off, off + 8).values()) + ); + off += 8; if (xfrm.and(SGX_CONSTANTS.SGX_XFRM_RESERVED).notEquals(0)) { throw new Error(`Quote xfrm invalid ${xfrm}`); } - const mrenclave = new Uint8Array(getBytes(quote, 112, 32)); - const enclaveIdBytes = new Uint8Array( - bytesFromHexString(directoryEnclaveId) - ); - if (!mrenclave.every((byte, index) => byte === enclaveIdBytes[index])) { + const mrenclave = quote.slice(off, off + 32); + off += 32; + const enclaveIdBytes = Bytes.fromHex(directoryEnclaveId); + if (mrenclave.compare(enclaveIdBytes) !== 0) { throw new Error('Quote mrenclave invalid!'); } - const reserved2 = new Uint8Array(getBytes(quote, 144, 32)); + const reserved2 = quote.slice(off, off + 32); + off += 32; if (!reserved2.every(byte => byte === 0)) { throw new Error('Quote reserved2 invalid!'); } - const reportData = new Uint8Array(getBytes(quote, 368, 64)); + const reportData = quote.slice(off, off + 64); + off += 64; const serverStaticPublicBytes = new Uint8Array(serverStaticPublic); if ( !reportData.every((byte, index) => { @@ -2501,22 +2508,26 @@ export function initialize({ throw new Error('Quote report_data invalid!'); } - const reserved3 = new Uint8Array(getBytes(quote, 208, 96)); + const reserved3 = quote.slice(off, off + 96); + off += 96; if (!reserved3.every(byte => byte === 0)) { throw new Error('Quote reserved3 invalid!'); } - const reserved4 = new Uint8Array(getBytes(quote, 308, 60)); + const reserved4 = quote.slice(off, off + 60); + off += 60; if (!reserved4.every(byte => byte === 0)) { throw new Error('Quote reserved4 invalid!'); } - const signatureLength = byteBuffer.readInt(432) & 0xffff_ffff; + const signatureLength = quote.readInt32LE(432) >>> 0; + off += 4; if (signatureLength !== quote.byteLength - 436) { throw new Error(`Bad signatureLength ${signatureLength}`); } - // const signature = Uint8Array.from(getBytes(quote, 436, signatureLength)); + // const signature = quote.slice(off, signatureLength); + // off += signatureLength } function validateAttestationSignatureBody( diff --git a/ts/util/arePinnedConversationsEqual.ts b/ts/util/arePinnedConversationsEqual.ts index 253bf7ce0d..beedbee917 100644 --- a/ts/util/arePinnedConversationsEqual.ts +++ b/ts/util/arePinnedConversationsEqual.ts @@ -1,46 +1,51 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { arrayBufferToBase64 } from '../Crypto'; -import { PinnedConversationClass } from '../textsecure.d'; +import * as Bytes from '../Bytes'; + +import { SignalService as Proto } from '../protobuf'; + +import PinnedConversation = Proto.AccountRecord.IPinnedConversation; export function arePinnedConversationsEqual( - localValue: Array, - remoteValue: Array + localValue: Array, + remoteValue: Array ): boolean { if (localValue.length !== remoteValue.length) { return false; } return localValue.every( - (localPinnedConversation: PinnedConversationClass, index: number) => { + (localPinnedConversation: PinnedConversation, index: number) => { const remotePinnedConversation = remoteValue[index]; - if ( - localPinnedConversation.identifier !== - remotePinnedConversation.identifier - ) { - return false; + + const { + contact, + groupMasterKey, + legacyGroupId, + } = localPinnedConversation; + + if (contact) { + return ( + remotePinnedConversation.contact && + contact.uuid === remotePinnedConversation.contact.uuid + ); } - switch (localPinnedConversation.identifier) { - case 'contact': - return ( - localPinnedConversation.contact && - remotePinnedConversation.contact && - localPinnedConversation.contact.uuid === - remotePinnedConversation.contact.uuid - ); - case 'groupMasterKey': - return ( - arrayBufferToBase64(localPinnedConversation.groupMasterKey) === - arrayBufferToBase64(remotePinnedConversation.groupMasterKey) - ); - case 'legacyGroupId': - return ( - arrayBufferToBase64(localPinnedConversation.legacyGroupId) === - arrayBufferToBase64(remotePinnedConversation.legacyGroupId) - ); - default: - return false; + + if (groupMasterKey && groupMasterKey.length) { + return Bytes.areEqual( + groupMasterKey, + remotePinnedConversation.groupMasterKey + ); } + + if (legacyGroupId && legacyGroupId.length) { + return Bytes.areEqual( + legacyGroupId, + remotePinnedConversation.legacyGroupId + ); + } + + return false; } ); } diff --git a/ts/util/isByteBufferEmpty.ts b/ts/util/isByteBufferEmpty.ts deleted file mode 100644 index 7a345bea0c..0000000000 --- a/ts/util/isByteBufferEmpty.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Signal Messenger, LLC -// SPDX-License-Identifier: AGPL-3.0-only - -import { isNumber } from 'lodash'; - -import { ByteBufferClass } from '../window.d'; - -export function isByteBufferEmpty(data?: ByteBufferClass): boolean { - return !data || !isNumber(data.limit) || data.limit === 0; -} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 5454ef5718..7edbb5ba65 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -8561,13 +8561,6 @@ "reasonCategory": "falseMatch", "updated": "2021-05-20T20:01:50.505Z" }, - { - "rule": "thenify-multiArgs", - "path": "node_modules/make-dir/node_modules/pify/index.js", - "line": "\t\t\t\t} else if (opts.multiArgs) {", - "reasonCategory": "falseMatch", - "updated": "2018-09-19T18:06:35.446Z" - }, { "rule": "jQuery-html(", "path": "node_modules/marked/lib/marked.js", @@ -12995,13 +12988,6 @@ "reasonCategory": "falseMatch", "updated": "2021-05-07T18:18:03.022Z" }, - { - "rule": "jQuery-$(", - "path": "node_modules/update-notifier/index.js", - "line": "\t\t\t\t\tchalk().cyan(format(' sudo chown -R $USER:$(id -gn $USER) %s ', xdgBasedir().config));", - "reasonCategory": "falseMatch", - "updated": "2018-09-19T21:59:32.770Z" - }, { "rule": "jQuery-$(", "path": "node_modules/uri-js/dist/es5/uri.all.min.js", @@ -14164,4 +14150,4 @@ "updated": "2021-03-18T21:41:28.361Z", "reasonDetail": "A generic hook. Typically not to be used with non-DOM values." } -] +] \ No newline at end of file diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts index 4eafd3e4ae..152c5de4ef 100644 --- a/ts/util/lint/linter.ts +++ b/ts/util/lint/linter.ts @@ -244,6 +244,8 @@ const excludedFilesRegexps = [ '^node_modules/xmldom/.+', '^node_modules/yargs-unparser/', '^node_modules/yargs/.+', + '^node_modules/find-yarn-workspace-root/.+', + '^node_modules/update-notifier/.+', // Used by Storybook '^node_modules/@emotion/.+', diff --git a/ts/util/timestampLongUtils.ts b/ts/util/timestampLongUtils.ts index 4c7af086c8..79921821b2 100644 --- a/ts/util/timestampLongUtils.ts +++ b/ts/util/timestampLongUtils.ts @@ -1,22 +1,24 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { Long } from '../window.d'; +import Long from 'long'; + +import { normalizeNumber } from './normalizeNumber'; export function getSafeLongFromTimestamp(timestamp = 0): Long { if (timestamp >= Number.MAX_SAFE_INTEGER) { - return window.dcodeIO.Long.MAX_VALUE; + return Long.MAX_VALUE; } - return window.dcodeIO.Long.fromNumber(timestamp); + return Long.fromNumber(timestamp); } -export function getTimestampFromLong(value: Long | null): number { +export function getTimestampFromLong(value?: Long | number | null): number { if (!value) { return 0; } - const num = value.toNumber(); + const num = normalizeNumber(value); if (num >= Number.MAX_SAFE_INTEGER) { return Number.MAX_SAFE_INTEGER; diff --git a/ts/window.d.ts b/ts/window.d.ts index 5c5e763ee7..a1b6c9de87 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -18,11 +18,7 @@ import { ReactionAttributesType, ReactionModelType, } from './model-types.d'; -import { - ContactRecordIdentityState, - TextSecureType, - DownloadAttachmentType, -} from './textsecure.d'; +import { TextSecureType, DownloadAttachmentType } from './textsecure.d'; import { Storage } from './textsecure/Storage'; import { ChallengeHandler, @@ -177,7 +173,6 @@ declare global { baseAttachmentsPath: string; baseStickersPath: string; baseTempPath: string; - dcodeIO: DCodeIOType; receivedAtCounter: number; enterKeyboardMode: () => void; enterMouseMode: () => void; @@ -553,51 +548,10 @@ declare global { } } -export type DCodeIOType = { - ByteBuffer: typeof ByteBufferClass & { - BIG_ENDIAN: number; - LITTLE_ENDIAN: number; - Long: DCodeIOType['Long']; - }; - Long: Long & { - MAX_VALUE: Long; - equals: (other: Long | number | string) => boolean; - fromBits: (low: number, high: number, unsigned: boolean) => number; - fromNumber: (value: number, unsigned?: boolean) => Long; - fromString: (str: string | null) => Long; - isLong: (obj: unknown) => obj is Long; - }; - ProtoBuf: WhatIsThis; -}; - export class CertificateValidatorType { validate: (cerficate: any, certificateTime: number) => Promise; } -export class ByteBufferClass { - constructor(value?: any, littleEndian?: number); - static wrap: ( - value: any, - encoding?: string, - littleEndian?: number - ) => ByteBufferClass; - buffer: ArrayBuffer; - toString: (type: string) => string; - toArrayBuffer: () => ArrayBuffer; - toBinary: () => string; - slice: (start: number, end?: number) => ByteBufferClass; - append: (data: ArrayBuffer) => void; - limit: number; - offset: 0; - readInt: (offset: number) => number; - readLong: (offset: number) => Long; - readShort: (offset: number) => number; - readVarint32: () => number; - reset: () => void; - writeLong: (l: Long) => void; - skip: (length: number) => void; -} - export class GumVideoCapturer { constructor( maxWidth: number, diff --git a/yarn.lock b/yarn.lock index 0cf0e5b177..724a5072db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4824,7 +4824,7 @@ boom@2.x.x: dependencies: hoek "2.x.x" -boxen@^1.2.1, boxen@^1.3.0: +boxen@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== @@ -5875,18 +5875,6 @@ config@1.28.1: json5 "0.4.0" os-homedir "1.0.2" -configstore@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -6942,13 +6930,6 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dot-prop@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" - integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== - dependencies: - is-obj "^1.0.0" - dot-prop@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" @@ -8492,13 +8473,12 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-yarn-workspace-root@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz#40eb8e6e7c2502ddfaa2577c176f221422f860db" - integrity sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q== +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== dependencies: - fs-extra "^4.0.3" - micromatch "^3.1.4" + micromatch "^4.0.2" findup-sync@^4.0.0: version "4.0.0" @@ -8727,15 +8707,6 @@ fs-extra@^2.0.0: graceful-fs "^4.1.2" jsonfile "^2.1.0" -fs-extra@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -10686,11 +10657,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - is-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" @@ -11317,7 +11283,7 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" -latest-version@^3.0.0, latest-version@^3.1.0: +latest-version@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= @@ -13196,6 +13162,14 @@ open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -13656,24 +13630,24 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" -patch-package@6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.1.2.tgz#9ed0b3defb5c34ecbef3f334ddfb13e01b3d3ff6" - integrity sha512-5GnzR8lEyeleeariG+hGabUnD2b1yL7AIGFjlLo95zMGRWhZCel58IpeKD46wwPb7i+uNhUI8unV56ogk8Bgqg== +patch-package@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== dependencies: "@yarnpkg/lockfile" "^1.1.0" chalk "^2.4.2" cross-spawn "^6.0.5" - find-yarn-workspace-root "^1.2.1" + find-yarn-workspace-root "^2.0.0" fs-extra "^7.0.1" is-ci "^2.0.0" klaw-sync "^6.0.0" minimist "^1.2.0" + open "^7.4.2" rimraf "^2.6.3" semver "^5.6.0" slash "^2.0.0" tmp "^0.0.33" - update-notifier "^2.5.0" path-browserify@0.0.1: version "0.0.1" @@ -18172,22 +18146,6 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== -update-notifier@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - update-notifier@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"