/*! * Webogram v0.7 - 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 var 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) } } if (typeof sLong != 'string') { sLong = sLong ? sLong.toString() : '0' } 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, field) { 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') if (s === undefined) { s = '' } 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) } else if (bytes === undefined) { 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 var 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 param, type var i, condType var fieldBit var len = methodData.params.length for (i = 0; i < len; i++) { param = methodData.params[i] type = param.type if (type.indexOf('?') !== -1) { condType = type.split('?') fieldBit = condType[0].split('.') if (!(params[fieldBit[0]] & (1 << fieldBit[1]))) { continue } type = condType[1] } this.storeObject(params[param.name], type, methodName + '[' + param.name + ']') } return methodData.type } TLSerialization.prototype.storeObject = function (obj, type, field) { switch (type) { case '#': 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) case 'true': return } 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 var predicate = obj['_'] var isBare = false var 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 param, type var i, condType var fieldBit var len = constructorData.params.length for (i = 0; i < len; i++) { param = constructorData.params[i] type = param.type if (type.indexOf('?') !== -1) { condType = type.split('?') fieldBit = condType[0].split('.') if (!(obj[fieldBit[0]] & (1 << fieldBit[1]))) { continue } type = condType[1] } this.storeObject(obj[param.name], 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]') var 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) { 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 (len > this.byteView.byteLength) { throw new Error('Invalid raw bytes length: ' + len + ', buffer len: ' + this.byteView.byteLength) } } 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 '#': 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) case 'true': return true } 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]') var constructorCmp = uintToInt(constructor) if (constructorCmp == 0x3072cfa1) { // Gzip packed var compressed = this.fetchBytes(field + '[packed_string]') var uncompressed = gzipUncompress(compressed) var buffer = bytesToArrayBuffer(uncompressed) var newDeserializer = (new TLDeserialization(buffer)) return newDeserializer.fetchObject(type, field) } if (constructorCmp != 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 var predicate = false var constructorData = false if (type.charAt(0) == '%') { var checkType = type.substr(1) for (var 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 (var 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]') var constructorCmp = uintToInt(constructor) if (constructorCmp == 0x3072cfa1) { // Gzip packed var compressed = this.fetchBytes(field + '[packed_string]') var uncompressed = gzipUncompress(compressed) var buffer = bytesToArrayBuffer(uncompressed) var newDeserializer = (new TLDeserialization(buffer)) return newDeserializer.fetchObject(type, field) } var index = schema.constructorsIndex if (!index) { schema.constructorsIndex = index = {} for (var i = 0; i < schema.constructors.length; i++) { index[schema.constructors[i].id] = i } } var i = index[constructorCmp] if (i) { constructorData = schema.constructors[i] } 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 + ' ' + this.fetchInt() + ' ' + this.fetchInt()) } } predicate = constructorData.predicate var result = {'_': predicate} var overrideKey = (this.mtproto ? 'mt_' : '') + predicate var self = this if (this.override[overrideKey]) { this.override[overrideKey].apply(this, [result, field + '[' + predicate + ']']) } else { var i, param var type, isCond var condType, fieldBit var value var len = constructorData.params.length for (i = 0; i < len; i++) { param = constructorData.params[i] type = param.type if (type == '#' && result.pFlags === undefined) { result.pFlags = {} } if (isCond = (type.indexOf('?') !== -1)) { condType = type.split('?') fieldBit = condType[0].split('.') if (!(result[fieldBit[0]] & (1 << fieldBit[1]))) { continue } type = condType[1] } value = self.fetchObject(type, field + '[' + predicate + '][' + param.name + ']') if (isCond && type === 'true') { result.pFlags[param.name] = value } else { result[param.name] = value } } } 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 }