UNPKG

telegram-mtproto

Version:
581 lines (500 loc) 16.7 kB
//@flow import EventEmitter from 'eventemitter2' import { dTime } from 'mtproto-shared' import { uintToInt, intToUint, bytesToHex, gzipUncompress, bytesToArrayBuffer, } from 'Bin' import { readLong, readInt, readBytes, readString, readDouble } from './reader' import { writeInt, writeIntBytes, writeBytes, writeDouble, writeBool, writeLong } from './writer' import Layout, { getFlags, isSimpleType, getTypeProps } from '../layout' import { TypeBuffer, TypeWriter, getNakedType, getTypeConstruct } from './type-buffer' import Config, { getConfig } from 'ConfigProvider' import type { MsgGetter } from './index.h' import { NetMessage } from '../service/networker/net-message' // const storeMethodLog = writer('storeMethod') // const fetchObjectLog = writer('fetchObject') import Logger from 'mtproto-logger' const logr = Logger`tl,read` const PACKED = 0x3072cfa1 type SerialConstruct = { mtproto?: boolean, startMaxLength?: number } type DConfig = { mtproto?: boolean, override?: *, getter?: MsgGetter } export class Serialization { writer: TypeWriter = new TypeWriter() uid: string mtproto: boolean apiLayer: Layout mtLayer: Layout constructor( { mtproto = false, startMaxLength = 2048 /* 2Kb */ }: SerialConstruct, uid: string) { this.uid = uid this.writer.maxLength = startMaxLength this.writer.reset() this.mtproto = mtproto } getBytes: () => number[] getBytes: (typed: false) => number[] getBytes: (typed: true) => Uint8Array getBytes(typed?: boolean) { if (typed) return this.writer.getBytesTyped() else return this.writer.getBytesPlain() } getBytesPlain() { return this.writer.getBytesPlain() } storeMethod(methodName: string, params: { [key: string]: * }) { // const logId = storeMethodLog.input({ // methodName, // params // }) const layer = this.mtproto ? Config.layer.mtLayer(this.uid) : Config.layer.apiLayer(this.uid) const pred = layer.funcs.get(methodName) if (!pred) throw new Error(`No method name ${methodName} found`) writeInt(this.writer, intToUint(`${pred.id}`), `${methodName}[id]`) if (pred.hasFlags) { const flags = getFlags(pred)(params) this.storeObject(flags, '#', `f ${methodName} #flags ${flags}`) } for (const param of pred.params) { const paramName = param.name const typeClass = param.typeClass let fieldObj if (typeof params[paramName] === 'undefined') { if (param.isFlag) continue else if (layer.typeDefaults.has(typeClass)) fieldObj = layer.typeDefaults.get(typeClass) else if (isSimpleType(typeClass)) { switch (typeClass) { case 'int': fieldObj = 0; break // case 'long': fieldObj = 0; break case 'string': fieldObj = ' '; break // case 'double': fieldObj = 0; break case 'true': fieldObj = true; break // case 'bytes': fieldObj = [0]; break } } else throw new Error(`Method ${methodName} did not receive required argument ${paramName}`) } else { fieldObj = params[paramName] } if (param.isVector) { if (!Array.isArray(fieldObj)) throw new TypeError(`Vector argument ${paramName} in ${methodName} required Array,` + //$FlowIssue ` got ${fieldObj} ${typeof fieldObj}`) writeInt(this.writer, 0x1cb5c415, `${paramName}[id]`) writeInt(this.writer, fieldObj.length, `${paramName}[count]`) for (const [ i, elem ] of fieldObj.entries()) this.storeObject(elem, param.typeClass, `${paramName}[${i}]`) } else this.storeObject(fieldObj, param.typeClass, `f ${methodName}(${paramName})`) } /*let condType let fieldBit for (const param of methodData.params) { let 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] } const paramName = param.name const stored = params[paramName] if (!stored) stored = this.emptyOfType(type, schema) if (!stored) throw new Error(`Method ${methodName}.`+ ` No value of field ${ param.name } recieved and no Empty of type ${ param.type }`) this.storeObject(stored, type, `f ${methodName}(${paramName})`) }*/ // storeMethodLog.output(logId, { // pred, // writer: this.writer // }) return pred.returns } /*emptyOfType(ofType, schema: TLSchema) { const resultConstruct = schema.constructors.find( ({ type, predicate }: TLConstruct) => type === ofType && predicate.indexOf('Empty') !== -1) return resultConstruct ? { _: resultConstruct.predicate } : null }*/ storeObject(obj: *, type: string, field: string) { switch (type) { case '#': case 'int': return writeInt(this.writer, obj, field) case 'long': return writeLong(this.writer, obj, field) case 'int128': return writeIntBytes(this.writer, obj, 128) case 'int256': return writeIntBytes(this.writer, obj, 256) case 'int512': return writeIntBytes(this.writer, obj, 512) case 'string': return writeBytes(this.writer, obj) case 'bytes': return writeBytes(this.writer, obj) case 'double': return writeDouble(this.writer, obj, field) case 'Bool': return writeBool(this.writer, obj, field) case 'true': return } if (Array.isArray(obj)) { if (type.substr(0, 6) == 'Vector') writeInt(this.writer, 0x1cb5c415, `${field}[id]`) else if (type.substr(0, 6) != 'vector') { throw new Error(`Invalid vector type ${ type}`) } const itemType = type.substr(7, type.length - 8) // for "Vector<itemType>" writeInt(this.writer, obj.length, `${field}[count]`) for (let 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 (typeof obj !== 'object') throw new Error(`Invalid object for type ${ type}`) const schema = this.mtproto ? Config.schema.mtSchema(this.uid) : Config.schema.apiSchema(this.uid) const predicate = obj['_'] let isBare = false let constructorData = false isBare = type.charAt(0) == '%' if (isBare) type = type.substr(1) for (const tlConst of schema.constructors) { if (tlConst.predicate == predicate) { constructorData = tlConst break } } if (!constructorData) throw new Error(`No predicate ${predicate} found`) if (predicate == type) isBare = true if (!isBare) writeInt(this.writer, intToUint(constructorData.id), `${field}.${predicate}[id]`) let condType let fieldBit for (const param of constructorData.params) { type = param.type if (type.indexOf('?') !== -1) { condType = type.split('?') fieldBit = condType[0].split('.') const flagIndex = parseInt(fieldBit[1], 10) if (!(obj.fiags & 1 << flagIndex)) { continue } type = condType[1] } this.storeObject(obj[param.name], type, `${field}.${ predicate }.${ param.name }`) } if (typeof constructorData === 'boolean') return constructorData return constructorData.type } } const emitter = new EventEmitter({ wildcard: true }) export class Deserialization { typeBuffer: TypeBuffer /*:: override: * */ mtproto: boolean uid: string emitter: EventEmitter getter: ?MsgGetter constructor( buffer: Buffer | ArrayBuffer, { mtproto = false, override = {}, getter }: DConfig, uid: string) { this.getter = getter this.uid = uid this.override = override this.typeBuffer = new TypeBuffer(buffer) this.mtproto = mtproto this.emitter = emitter // const fetchObject = this.fetchObject.bind(this) // const mock = (type, field) => { // const logId = fetchObjectLog.input({ // type, // typeBuffer: this.typeBuffer, // field // }) // const result = fetchObject(type, field) // fetchObjectLog.output(logId, { // typeBuffer: this.typeBuffer, // result // }) // return result // } // this.fetchObject = mock } // log('int')(field, i.toString(16), i) readInt = (field: string) => readInt(this.typeBuffer, field) fetchInt(field: string = ''): number { return this.readInt(`${ field }:int`) } fetchBool(field: string = '') { const i = this.readInt(`${ field }:bool`) switch (i) { case 0x997275b5: return true case 0xbc799737: return false default: { this.typeBuffer.offset -= 4 return this.fetchObject('Object', field) } } } fetchIntBytes(bitss: number, field: string = ''): Uint8Array { let bits = bitss if (Array.isArray(bits)) { console.trace() bits = bitss[0] } if (bits % 32) { console.error(bits, typeof bits, Array.isArray(bits)) throw new Error(`Invalid bits: ${bits}`) } const len = bits / 8 const bytes = this.typeBuffer.next(len) logr(`int bytes`)(bytesToHex(bytes), `${ field }:int${ bits}`) return bytes } fetchRawBytes(len: number | false, field: string = ''): Uint8Array { let ln: number if (typeof len === 'number') ln = len else if (typeof len === 'boolean' && len === false) { ln = this.readInt(`${ field }_length`) if (ln > this.typeBuffer.byteView.byteLength) throw new Error(`Invalid raw bytes length: ${ln}, buffer len: ${this.typeBuffer.byteView.byteLength}`) } else throw new TypeError(`[fetchRawBytes] len must be number or false, get ${typeof len}`) const bytes = this.typeBuffer.next(ln) logr(`raw bytes`)(bytesToHex(bytes), field) return bytes } fetchPacked(type: string, field: string = '') { const compressed = readBytes( this.typeBuffer, `${field}[packed_string]`) const uncompressed = gzipUncompress(compressed) const buffer = bytesToArrayBuffer(uncompressed) const newDeserializer = new Deserialization( buffer, { mtproto : this.mtproto, override: this.override }, this.uid) return newDeserializer.fetchObject(type, field) } fetchVector(type: string, field: string = '') { // const typeProps = getTypeProps(type) if (type.charAt(0) === 'V') { const constructor = this.readInt(`${field}[id]`) const constructorCmp = uintToInt(constructor) if (constructorCmp === PACKED) return this.fetchPacked(type, field) if (constructorCmp !== 0x1cb5c415) throw new Error(`Invalid vector constructor ${constructor}`) } const len = this.readInt(`${field}[count]`) const result = [] if (len > 0) { const itemType = type.substr(7, type.length - 8) // for "Vector<itemType>" for (let i = 0; i < len; i++) result.push(this.fetchObject(itemType, `${field}[${i}]`)) } return result } fetchObject(type: string, field: string = '') { switch (type) { case '#': case 'int': return this.fetchInt(field) case 'long': return readLong(this.typeBuffer, field) case 'int128': return this.fetchIntBytes(128, field) case 'int256': return this.fetchIntBytes(256, field) case 'int512': return this.fetchIntBytes(512, field) case 'string': return readString(this.typeBuffer, field) case 'bytes': return readBytes(this.typeBuffer, field) case 'double': return readDouble(this.typeBuffer, field) case 'Bool': return this.fetchBool(field) case 'true': return true } let fallback field = field || type || 'Object' // const layer = this.mtproto // ? mtLayer // : apiLayer const typeProps = getTypeProps(type) // layer.typesById if (typeProps.isVector) return this.fetchVector(type, field) const { apiSchema, mtSchema } = Config.schema.get(this.uid) const schema = this.mtproto ? mtSchema : apiSchema let predicate = false let constructorData = false if (typeProps.isBare) constructorData = getNakedType(type, schema) else { const constructor = this.readInt(`${field}[id]`) const constructorCmp = uintToInt(constructor) if (constructorCmp === PACKED) return this.fetchPacked(type, field) let index = schema.constructorsIndex if (!index) { schema.constructorsIndex = index = {} for (let i = 0; i < schema.constructors.length; i++) index[schema.constructors[i].id] = i } const i = index[constructorCmp] if (i) constructorData = schema.constructors[i] fallback = false if (!constructorData && this.mtproto) { const schemaFallback = apiSchema const finded = getTypeConstruct(constructorCmp, schemaFallback) if (finded) { constructorData = finded delete this.mtproto fallback = true } } if (!constructorData) { throw new Error(`Constructor not found: ${constructor} ${this.fetchInt()} ${this.fetchInt()}`) } } predicate = constructorData.predicate const result = { '_': predicate } const isOverrided = predicate === 'rpc_result' || predicate === 'message' if (this.mtproto && isOverrided) { switch (predicate) { case 'rpc_result': { this.rpc_result(result, `${field}[${predicate}]`) break } case 'message': { this.message(result, `${field}[${predicate}]`) break } } } else { for (const param of constructorData.params) { type = param.type // if (type === '#' && isNil(result.pFlags)) // result.pFlags = {} if (type.indexOf('?') !== -1) { const condType = type.split('?') const fieldBit = condType[0].split('.') const fieldName = fieldBit[0] const bit: any = fieldBit[1] if (!(result[fieldName] & 1 << bit)) continue type = condType[1] } const paramName = param.name const value = this.fetchObject(type, `${field}[${predicate}][${paramName}]`) result[paramName] = value } } if (fallback) this.mtproto = true const { layer: { apiLayer } } = getConfig(this.uid) if (apiLayer.seqSet.has(predicate)) { this.emitter.emit('seq', result) } return result } getOffset() { return this.typeBuffer.offset } fetchEnd() { if (!this.typeBuffer.isEnd()) throw new Error('Fetch end with non-empty buffer') return true } rpc_result(result: { [key: string]: * }, field: string) { result.req_msg_id = readLong(this.typeBuffer, `${ field }[req_msg_id]`) if (this.getter == null) return result const sentMessage: NetMessage = this.getter(result) const type = sentMessage && sentMessage.resultType || 'Object' if (result.req_msg_id && !sentMessage) { // console.warn(dTime(), 'Result for unknown message', result) return } result.result = this.fetchObject(type, `${ field }[result]`) // console.log(dTime(), 'override rpc_result', sentMessage, type, result) } message(result: { [key: string]: * }, field: string) { result.msg_id = readLong(this.typeBuffer, `${ field }[msg_id]`) result.seqno = readInt(this.typeBuffer, `${ field }[seqno]`) result.bytes = readInt(this.typeBuffer, `${ field }[bytes]`) const offset = this.getOffset() try { result.body = this.fetchObject('Object', `${ field }[body]`) } catch (e) { console.error(dTime(), 'parse error', e.message, e.stack) result.body = { _: 'parse_error', error: e } } if (this.typeBuffer.offset != offset + result.bytes) { // console.warn(dTime(), 'set offset', this.offset, offset, result.bytes) // console.log(dTime(), result) this.typeBuffer.offset = offset + result.bytes } // console.log(dTime(), 'override message', result) } } export { TypeWriter } from './type-buffer'