@telegramv/tl
Version:
Type Language serialization and deserialization.
470 lines (469 loc) • 14.7 kB
JavaScript
"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 };