UNPKG

@telegramv/tl

Version:

Type Language serialization and deserialization.

470 lines (469 loc) 14.7 kB
"use strict"; /* * Telegram V * Copyright (C) 2020 Davyd Kohut * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const buffer_1 = require("buffer/"); const decodeText_1 = __importDefault(require("./decodeText")); // --- implementation --- // function isGzipped(predicate) { return predicate === 0x3072cfa1 || predicate === "gzip_packed"; } class JsonSchema { constructor(raw, options = {}) { this.indexes = { constructors: { ids: {}, predicates: {}, }, methods: { ids: {}, names: {}, }, bare: { types: {} } }; this.raw = raw; if (options.bareTypes) { this.bareTypes = options.bareTypes; } else { this.bareTypes = [ "Message" ]; } this.index(); } index() { for (let i = 0; i < this.raw.constructors.length; i++) { const { id, type, predicate } = this.raw.constructors[i]; this.indexes.constructors.ids[id] = i; if (this.bareTypes.indexOf(type) !== -1 && this.indexes.bare.types[type] == null) { this.indexes.bare.types[type] = i; } this.indexes.constructors.predicates[predicate] = i; } for (let i = 0; i < this.raw.methods.length; i++) { const { id, method } = this.raw.methods[i]; this.indexes.methods.ids[id] = i; this.indexes.methods.names[method] = i; } } getMethodById(id) { return this.raw.methods[this.indexes.methods.ids[id]]; } getMethodByName(name) { return this.raw.methods[this.indexes.methods.names[name]]; } getConstructorById(id) { return this.raw.constructors[this.indexes.constructors.ids[id]]; } getConstructorByPredicate(predicate) { return this.raw.constructors[this.indexes.constructors.predicates[predicate]]; } getConstructorByBareType(type) { return this.raw.constructors[this.indexes.bare.types[type]]; } } exports.JsonSchema = JsonSchema; class Serializer { constructor(schema, options = {}) { this.size = 2048; this.size = options.size || 2048; this.buffer = new buffer_1.Buffer(this.size); this.schema = schema; // @ts-ignore this.gzip = options.gzip; this.offset = 0; } resizeIfNeeded(plusSize = 1024) { const newSize = this.size + plusSize; if (this.size < newSize) { const newBuffer = new buffer_1.Buffer(newSize + 1024); newBuffer.set(this.buffer, 0); this.buffer = newBuffer; this.size = newSize + 1024; } } id(value) { return this.int(value); } int(value) { this.resizeIfNeeded(4); this.buffer.writeInt32LE(value, this.offset); this.offset += 4; return this; } int128(value) { this.resizeIfNeeded(16); this.buffer.set(value.slice(0, 16), this.offset); this.offset += 16; return this; } int64(value) { this.resizeIfNeeded(8); this.buffer.set(value.slice(0, 8), this.offset); this.offset += 8; return this; } long(value) { this.resizeIfNeeded(8); this.buffer.set(value.slice(0, 8), this.offset); this.offset += 8; return this; } int256(value) { this.resizeIfNeeded(32); this.buffer.set(value.slice(0, 32), this.offset); this.offset += 32; return this; } int512(value) { this.resizeIfNeeded(64); this.buffer.set(value.slice(0, 64), this.offset); this.offset += 64; return this; } double(value) { this.resizeIfNeeded(8); this.buffer.writeDoubleLE(value, this.offset); this.offset += 8; return this; } bool(value) { if (value) { this.id(-1720552011); } else { this.id(-1132882121); } return this; } bytes(value, length) { length = length || value.byteLength || value.length; this.resizeIfNeeded(length); if (length <= 253) { this.buffer.writeUInt8(length, this.offset++); } else { this.buffer.writeUInt8(254, this.offset++); this.buffer.writeUInt8(length & 0xFF, this.offset++); this.buffer.writeUInt8((length & 0xFF00) >> 8, this.offset++); this.buffer.writeUInt8((length & 0xFF0000) >> 16, this.offset++); } this.buffer.set(value, this.offset); this.offset += length; return this; } string(value) { const strBuffer = new buffer_1.Buffer(value); this.bytes(strBuffer); this.addPadd(); return this; } params(params, schemaParams) { for (let i = 0; i < schemaParams.length; i++) { let { name, type } = schemaParams[i]; if (type === "#") { const hashParams = schemaParams.filter(param => param.type.substr(0, name.length + 1) === `${name}.`); for (const param of hashParams) { const [cond] = param.type.split("?"); const [field, bit] = cond.split("."); // @ts-ignore if (!(params[field] & (1 << bit)) && params[param.name]) { // @ts-ignore params[field] |= 1 << bit; } } } if (type.indexOf("?") !== -1) { const [cond, condType] = type.split("?"); const [field, bit] = cond.split("."); // @ts-ignore if (!(params[field] & (1 << bit))) { continue; } type = condType; } this.store(type, params[name]); } } vector(type, vector) { if (type.toLowerCase().substr(0, 6) === "vector") { this.id(0x1cb5c415); } const itemType = type.substr(7, type.length - 8); this.int(vector.length); for (let i = 0; i < vector.length; i++) { this.store(vector[i], itemType); } return this; } method(name, params) { const method = this.schema.getMethodByName(name); if (!method) { throw new Error(`No method found: ${name}`); } this.id(method.id); this.params(params, method.params); return this; } object(constructor) { const predicate = constructor._; const schemaConstructor = this.schema.getConstructorByPredicate(predicate); if (!schemaConstructor) { throw new Error(`No constructor found: ${predicate}`); } this.id(schemaConstructor.id); this.params(constructor, schemaConstructor.params); return this; } store(type, value) { if (type.charAt(0) === "%") { type = type.substr(1); } if (value instanceof Array) { return this.vector(type, value); } switch (type) { case "int": return this.int(value); case "long": return this.long(value); case "int128": return this.int128(value); case "int256": return this.int256(value); case "int512": return this.int512(value); case "string": return this.string(value); case "bytes": return this.bytes(value); case "double": return this.double(value); case "Bool" || "bool": return this.bool(value); case "true": return this; } if (typeof value === "object") { return this.object(value); } throw new Error("Invalid input."); } addPadd() { while (this.offset % 4) { this.buffer.writeUInt8(0, this.offset++); } } getBytes(size) { size = size == null ? this.offset : size; const bytes = new Uint8Array(size); bytes.set(this.buffer.slice(0, this.offset)); // zero padding for (let i = this.offset; i < size; i++) { bytes[i] = 0; } return bytes; } } exports.Serializer = Serializer; class Deserializer { constructor(schema, data, options = {}) { this.schema = schema; this.buffer = buffer_1.Buffer.from(data); this.offset = 0; // @ts-ignore this.gzip = options.gzip; } bool() { const id = this.id(); if (id === -1720552011) { return true; } else if (id === -1132882121) { return false; } this.offset -= 4; return this.object(); } bytes() { let length = this.buffer.readUInt8(this.offset++); if (length === 254) { length = this.buffer.readUInt8(this.offset++) | (this.buffer.readUInt8(this.offset++) << 8) | (this.buffer.readUInt8(this.offset++) << 16); } const bytes = this.subarray(length); this.skipPad(); return bytes; } double() { const double = this.buffer.readDoubleLE(this.offset); this.offset += 8; return double; } id() { return this.int(); } int() { const int = this.buffer.readInt32LE(this.offset); this.offset += 4; return int; } int128() { return this.subarray(16); } int64() { return this.subarray(8); } int256() { return this.subarray(32); } int512() { return this.subarray(64); } long() { return this.subarray(8); } string() { return decodeText_1.default(this.bytes()); } object() { let schemaConstructor; const id = this.id(); if (isGzipped(id)) { return this.uncompress(); } schemaConstructor = this.schema.getConstructorById(id); if (!schemaConstructor) { throw new Error("No constructor found: " + id); } return this.objectByConstructor(schemaConstructor); } bareObject(type) { const schemaConstructor = this.schema.getConstructorByBareType(type); if (!schemaConstructor) { throw new Error("No bare type found: " + type); } return this.objectByConstructor(schemaConstructor); } objectByConstructor(constructor) { const predicate = constructor.predicate; const result = { _: predicate }; for (let i = 0; i < constructor.params.length; i++) { let { name, type } = constructor.params[i]; if (type === "#") { result[name] = this.int(); continue; } if (type.indexOf("?") !== -1) { const conditional = this.conditional(result, name, type); if (conditional !== undefined) { result[name] = conditional; } } else { result[name] = this.read(type); } } return result; } conditional(result, name, type) { const [cond, condType] = type.split("?"); const [field, bit] = cond.split("."); // ["field", 0] // @ts-ignore if (!(result[field] & (1 << bit))) { return undefined; } return this.read(condType); } read(type) { switch (type) { case "int": return this.int(); case "long": return this.long(); case "double": return this.double(); case "int128": return this.int128(); case "int256": return this.int256(); case "int512": return this.int512(); case "string": return this.string(); case "bytes": return this.bytes(); case "bool" || "Bool": return this.bool(); case "true": return true; case "gzip_packed": return this.uncompress(); } if (type) { if (type.slice(0, 1) === "%") { return this.bareObject(type.slice(1)); } if (type === "vector") { return this.vector("vector<T>"); } if (type.slice(0, 6) === "Vector" || type.slice(0, 6) === "vector") { return this.vector(type); } } return this.object(); } vector(type) { const size = this.int(); if (isGzipped(size)) { return this.uncompress(type); } let result = new Array(size); if (size > 0) { const vectorType = type.substr(7, type.length - 8); for (let i = 0; i < size; i++) { result[i] = this.read(vectorType); } } return result; } uncompress(type) { const bytes = this.gzip.ungzip(this.bytes()); return new Deserializer(this.schema, bytes, { gzip: this.gzip }).read(type); } subarray(length) { return this.buffer.subarray(this.offset, this.offset += length); } skipPad() { while (this.offset % 4) { this.offset++; } } } exports.Deserializer = Deserializer; exports.default = { Serializer, Deserializer };