/*! * Webogram v0.3.8 - messaging web application for MTProto * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ function TLSerialization (options) { options = options || {}; this.maxLength = options.startMaxLength || 2048; // 2Kb this.offset = 0; // in bytes this.createBuffer(); // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug; this.mtproto = options.mtproto || false; return this; } TLSerialization.prototype.createBuffer = function () { this.buffer = new ArrayBuffer(this.maxLength); this.intView = new Int32Array(this.buffer); this.byteView = new Uint8Array(this.buffer); }; TLSerialization.prototype.getArray = function () { var resultBuffer = new ArrayBuffer(this.offset); var resultArray = new Int32Array(resultBuffer); resultArray.set(this.intView.subarray(0, this.offset / 4)); return resultArray; }; TLSerialization.prototype.getBuffer = function () { return this.getArray().buffer; }; TLSerialization.prototype.getBytes = function (typed) { if (typed) { var resultBuffer = new ArrayBuffer(this.offset); var resultArray = new Uint8Array(resultBuffer); resultArray.set(this.byteView.subarray(0, this.offset)); return resultArray; } var bytes = []; for (var i = 0; i < this.offset; i++) { bytes.push(this.byteView[i]); } return bytes; }; TLSerialization.prototype.checkLength = function (needBytes) { if (this.offset + needBytes < this.maxLength) { return; } console.trace('Increase buffer', this.offset, needBytes, this.maxLength); this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4; var previousBuffer = this.buffer, previousArray = new Int32Array(previousBuffer); this.createBuffer(); new Int32Array(this.buffer).set(previousArray); }; TLSerialization.prototype.writeInt = function (i, field) { this.debug && console.log('>>>', i.toString(16), i, field); this.checkLength(4); this.intView[this.offset / 4] = i; this.offset += 4; }; TLSerialization.prototype.storeInt = function (i, field) { this.writeInt(i, (field || '') + ':int'); }; TLSerialization.prototype.storeBool = function (i, field) { if (i) { this.writeInt(0x997275b5, (field || '') + ':bool'); } else { this.writeInt(0xbc799737, (field || '') + ':bool'); } }; TLSerialization.prototype.storeLongP = function (iHigh, iLow, field) { this.writeInt(iLow, (field || '') + ':long[low]'); this.writeInt(iHigh, (field || '') + ':long[high]'); }; TLSerialization.prototype.storeLong = function (sLong, field) { if (angular.isArray(sLong)) { if (sLong.length == 2) { return this.storeLongP(sLong[0], sLong[1], field); } else { return this.storeIntBytes(sLong, 64, field); } } var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]'); this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]'); }; TLSerialization.prototype.storeDouble = function (f) { var buffer = new ArrayBuffer(8); var intView = new Int32Array(buffer); var doubleView = new Float64Array(buffer); doubleView[0] = f; this.writeInt(intView[0], (field || '') + ':double[low]'); this.writeInt(intView[1], (field || '') + ':double[high]'); }; TLSerialization.prototype.storeString = function (s, field) { this.debug && console.log('>>>', s, (field || '') + ':string'); var sUTF8 = unescape(encodeURIComponent(s)); this.checkLength(sUTF8.length + 8); var len = sUTF8.length; if (len <= 253) { this.byteView[this.offset++] = len; } else { this.byteView[this.offset++] = 254; this.byteView[this.offset++] = len & 0xFF; this.byteView[this.offset++] = (len & 0xFF00) >> 8; this.byteView[this.offset++] = (len & 0xFF0000) >> 16; } for (var i = 0; i < len; i++) { this.byteView[this.offset++] = sUTF8.charCodeAt(i); } // Padding while (this.offset % 4) { this.byteView[this.offset++] = 0; } } TLSerialization.prototype.storeBytes = function (bytes, field) { if (bytes instanceof ArrayBuffer) { bytes = new Uint8Array(bytes); } this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes'); var len = bytes.byteLength || bytes.length; this.checkLength(len + 8); if (len <= 253) { this.byteView[this.offset++] = len; } else { this.byteView[this.offset++] = 254; this.byteView[this.offset++] = len & 0xFF; this.byteView[this.offset++] = (len & 0xFF00) >> 8; this.byteView[this.offset++] = (len & 0xFF0000) >> 16; } this.byteView.set(bytes, this.offset); this.offset += len; // Padding while (this.offset % 4) { this.byteView[this.offset++] = 0; } } TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { if (bytes instanceof ArrayBuffer) { bytes = new Uint8Array(bytes); } var len = bytes.length; if ((bits % 32) || (len * 8) != bits) { throw new Error('Invalid bits: ' + bits + ', ' + bytes.length); } this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits); this.checkLength(len); this.byteView.set(bytes, this.offset); this.offset += len; }; TLSerialization.prototype.storeRawBytes = function (bytes, field) { if (bytes instanceof ArrayBuffer) { bytes = new Uint8Array(bytes); } var len = bytes.length; this.debug && console.log('>>>', bytesToHex(bytes), (field || '')); this.checkLength(len); this.byteView.set(bytes, this.offset); this.offset += len; }; TLSerialization.prototype.storeMethod = function (methodName, params) { var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, methodData = false, i; for (i = 0; i < schema.methods.length; i++) { if (schema.methods[i].method == methodName) { methodData = schema.methods[i]; break } } if (!methodData) { throw new Error('No method ' + methodName + ' found'); } this.storeInt(intToUint(methodData.id), methodName + '[id]'); var self = this; angular.forEach(methodData.params, function (param) { self.storeObject(params[param.name], param.type, methodName + '[' + param.name + ']'); }); return methodData.type; }; TLSerialization.prototype.storeObject = function (obj, type, field) { switch (type) { case 'int': return this.storeInt(obj, field); case 'long': return this.storeLong(obj, field); case 'int128': return this.storeIntBytes(obj, 128, field); case 'int256': return this.storeIntBytes(obj, 256, field); case 'int512': return this.storeIntBytes(obj, 512, field); case 'string': return this.storeString(obj, field); case 'bytes': return this.storeBytes(obj, field); case 'double': return this.storeDouble(obj, field); case 'Bool': return this.storeBool(obj, field); } if (angular.isArray(obj)) { if (type.substr(0, 6) == 'Vector') { this.writeInt(0x1cb5c415, field + '[id]'); } else if (type.substr(0, 6) != 'vector') { throw new Error('Invalid vector type ' + type); } var itemType = type.substr(7, type.length - 8); // for "Vector" this.writeInt(obj.length, field + '[count]'); for (var i = 0; i < obj.length; i++) { this.storeObject(obj[i], itemType, field + '[' + i + ']'); } return true; } else if (type.substr(0, 6).toLowerCase() == 'vector') { throw new Error('Invalid vector object'); } if (!angular.isObject(obj)) { throw new Error('Invalid object for type ' + type); } var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, predicate = obj['_'], isBare = false, constructorData = false, i; if (isBare = (type.charAt(0) == '%')) { type = type.substr(1); } for (i = 0; i < schema.constructors.length; i++) { if (schema.constructors[i].predicate == predicate) { constructorData = schema.constructors[i]; break } } if (!constructorData) { throw new Error('No predicate ' + predicate + ' found'); } if (predicate == type) { isBare = true; } if (!isBare) { this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]'); } var self = this; angular.forEach(constructorData.params, function (param) { self.storeObject(obj[param.name], param.type, field + '[' + predicate + '][' + param.name + ']'); }); return constructorData.type; }; function TLDeserialization (buffer, options) { options = options || {}; this.offset = 0; // in bytes this.override = options.override || {}; this.buffer = buffer; this.intView = new Uint32Array(this.buffer); this.byteView = new Uint8Array(this.buffer); // this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug; this.mtproto = options.mtproto || false; return this; } TLDeserialization.prototype.readInt = function (field) { if (this.offset >= this.intView.length * 4) { throw new Error('Nothing to fetch: ' + field); } var i = this.intView[this.offset / 4]; this.debug && console.log('<<<', i.toString(16), i, field); this.offset += 4; return i; }; TLDeserialization.prototype.fetchInt = function (field) { return this.readInt((field || '') + ':int'); } TLDeserialization.prototype.fetchDouble = function (field) { var buffer = new ArrayBuffer(8); var intView = new Int32Array(buffer); var doubleView = new Float64Array(buffer); intView[0] = this.readInt((field || '') + ':double[low]'), intView[1] = this.readInt((field || '') + ':double[high]'); return doubleView[0]; }; TLDeserialization.prototype.fetchLong = function (field) { var iLow = this.readInt((field || '') + ':long[low]'), iHigh = this.readInt((field || '') + ':long[high]'); var longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString(); return longDec; } TLDeserialization.prototype.fetchBool = function (field) { var i = this.readInt((field || '') + ':bool'); if (i == 0x997275b5) { return true; } else if (i == 0xbc799737) { return false } this.offset -= 4; return this.fetchObject('Object', field); } TLDeserialization.prototype.fetchString = function (field) { var len = this.byteView[this.offset++]; if (len == 254) { var len = this.byteView[this.offset++] | (this.byteView[this.offset++] << 8) | (this.byteView[this.offset++] << 16); } var sUTF8 = ''; for (var i = 0; i < len; i++) { sUTF8 += String.fromCharCode(this.byteView[this.offset++]); } // Padding while (this.offset % 4) { this.offset++; } try { var s = decodeURIComponent(escape(sUTF8)); } catch (e) { var s = sUTF8; } this.debug && console.log('<<<', s, (field || '') + ':string'); return s; } TLDeserialization.prototype.fetchBytes = function (field) { var len = this.byteView[this.offset++]; if (len == 254) { var len = this.byteView[this.offset++] | (this.byteView[this.offset++] << 8) | (this.byteView[this.offset++] << 16); } var bytes = this.byteView.subarray(this.offset, this.offset + len); this.offset += len; // Padding while (this.offset % 4) { this.offset++; } this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':bytes'); return bytes; } TLDeserialization.prototype.fetchIntBytes = function (bits, typed, field) { if (bits % 32) { throw new Error('Invalid bits: ' + bits); } var len = bits / 8; if (typed) { var result = this.byteView.subarray(this.offset, this.offset + len); this.offset += len; return result; } var bytes = []; for (var i = 0; i < len; i++) { bytes.push(this.byteView[this.offset++]); } this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':int' + bits); return bytes; }; TLDeserialization.prototype.fetchRawBytes = function (len, typed, field) { if (len === false) { len = this.readInt((field || '') + '_length'); } if (typed) { var bytes = new Uint8Array(len); bytes.set(this.byteView.subarray(this.offset, this.offset + len)); this.offset += len; return bytes; } var bytes = []; for (var i = 0; i < len; i++) { bytes.push(this.byteView[this.offset++]); } this.debug && console.log('<<<', bytesToHex(bytes), (field || '')); return bytes; }; TLDeserialization.prototype.fetchObject = function (type, field) { switch (type) { case 'int': return this.fetchInt(field); case 'long': return this.fetchLong(field); case 'int128': return this.fetchIntBytes(128, false, field); case 'int256': return this.fetchIntBytes(256, false, field); case 'int512': return this.fetchIntBytes(512, false, field); case 'string': return this.fetchString(field); case 'bytes': return this.fetchBytes(field); case 'double': return this.fetchDouble(field); case 'Bool': return this.fetchBool(field); } field = field || type || 'Object'; if (type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') { if (type.charAt(0) == 'V') { var constructor = this.readInt(field + '[id]'); if (constructor != 0x1cb5c415) { throw new Error('Invalid vector constructor ' + constructor); } } var len = this.readInt(field + '[count]'); var result = []; if (len > 0) { var itemType = type.substr(7, type.length - 8); // for "Vector" for (var i = 0; i < len; i++) { result.push(this.fetchObject(itemType, field + '[' + i + ']')) } } return result; } var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, predicate = false, constructorData = false; if (type.charAt(0) == '%') { var checkType = type.substr(1); for (i = 0; i < schema.constructors.length; i++) { if (schema.constructors[i].type == checkType) { constructorData = schema.constructors[i]; break } } if (!constructorData) { throw new Error('Constructor not found for type: ' + type); } } else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) { for (i = 0; i < schema.constructors.length; i++) { if (schema.constructors[i].predicate == type) { constructorData = schema.constructors[i]; break } } if (!constructorData) { throw new Error('Constructor not found for predicate: ' + type); } } else { var constructor = this.readInt(field + '[id]'), constructorCmp = uintToInt(constructor); if (constructorCmp == 0x3072cfa1) { // Gzip packed var compressed = this.fetchBytes(field + '[packed_string]'), uncompressed = gzipUncompress(compressed), buffer = bytesToArrayBuffer(uncompressed), newDeserializer = (new TLDeserialization(buffer)); return newDeserializer.fetchObject(type, field); } for (i = 0; i < schema.constructors.length; i++) { if (schema.constructors[i].id == constructorCmp) { constructorData = schema.constructors[i]; break; } } var fallback = false; if (!constructorData && this.mtproto) { var schemaFallback = Config.Schema.API; for (i = 0; i < schemaFallback.constructors.length; i++) { if (schemaFallback.constructors[i].id == constructorCmp) { constructorData = schemaFallback.constructors[i]; delete this.mtproto; fallback = true; break; } } } if (!constructorData) { throw new Error('Constructor not found: ' + constructor); } } predicate = constructorData.predicate; var result = {'_': predicate}, overrideKey = (this.mtproto ? 'mt_' : '') + predicate, self = this; if (this.override[overrideKey]) { this.override[overrideKey].apply(this, [result, field + '[' + predicate + ']']); } else { angular.forEach(constructorData.params, function (param) { result[param.name] = self.fetchObject(param.type, field + '[' + predicate + '][' + param.name + ']'); }); } if (fallback) { this.mtproto = true; } return result; }; TLDeserialization.prototype.getOffset = function () { return this.offset; }; TLDeserialization.prototype.fetchEnd = function () { if (this.offset != this.byteView.length) { throw new Error('Fetch end with non-empty buffer'); } return true; };