dograma
Version:
NodeJS/Browser MTProto API Telegram client library,
243 lines (242 loc) • 7.15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinaryReader = void 0;
const errors_1 = require("../errors");
const core_1 = require("../tl/core");
const AllTLObjects_1 = require("../tl/AllTLObjects");
const Helpers_1 = require("../Helpers");
class BinaryReader {
/**
* Small utility class to read binary data.
* @param data {Buffer}
*/
constructor(data) {
this.stream = data;
this._last = undefined;
this.offset = 0;
}
// region Reading
// "All numbers are written as little endian."
// https://core.telegram.org/mtproto
/**
* Reads a single byte value.
*/
readByte() {
return this.read(1)[0];
}
/**
* Reads an integer (4 bytes or 32 bits) value.
* @param signed {Boolean}
*/
readInt(signed = true) {
let res;
if (signed) {
res = this.stream.readInt32LE(this.offset);
}
else {
res = this.stream.readUInt32LE(this.offset);
}
this.offset += 4;
return res;
}
/**
* Reads a long integer (8 bytes or 64 bits) value.
* @param signed
* @returns {BigInteger}
*/
readLong(signed = true) {
return this.readLargeInt(64, signed);
}
/**
* Reads a real floating point (4 bytes) value.
* @returns {number}
*/
readFloat() {
return this.read(4).readFloatLE(0);
}
/**
* Reads a real floating point (8 bytes) value.
* @returns {BigInteger}
*/
readDouble() {
// was this a bug ? it should have been <d
return this.read(8).readDoubleLE(0);
}
/**
* Reads a n-bits long integer value.
* @param bits
* @param signed {Boolean}
*/
readLargeInt(bits, signed = true) {
const buffer = this.read(Math.floor(bits / 8));
return (0, Helpers_1.readBigIntFromBuffer)(buffer, true, signed);
}
/**
* Read the given amount of bytes, or -1 to read all remaining.
* @param length {number}
* @param checkLength {boolean} whether to check if the length overflows or not.
*/
read(length = -1, checkLength = true) {
if (length === -1) {
length = this.stream.length - this.offset;
}
const result = this.stream.slice(this.offset, this.offset + length);
this.offset += length;
if (checkLength && result.length !== length) {
throw Error(`No more data left to read (need ${length}, got ${result.length}: ${result}); last read ${this._last}`);
}
this._last = result;
return result;
}
/**
* Gets the byte array representing the current buffer as a whole.
* @returns {Buffer}
*/
getBuffer() {
return this.stream;
}
// endregion
// region Telegram custom reading
/**
* Reads a Telegram-encoded byte array, without the need of
* specifying its length.
* @returns {Buffer}
*/
tgReadBytes() {
const firstByte = this.readByte();
let padding;
let length;
if (firstByte === 254) {
length =
this.readByte() |
(this.readByte() << 8) |
(this.readByte() << 16);
padding = length % 4;
}
else {
length = firstByte;
padding = (length + 1) % 4;
}
const data = this.read(length);
if (padding > 0) {
padding = 4 - padding;
this.read(padding);
}
return data;
}
/**
* Reads a Telegram-encoded string.
* @returns {string}
*/
tgReadString() {
return this.tgReadBytes().toString("utf-8");
}
/**
* Reads a Telegram boolean value.
* @returns {boolean}
*/
tgReadBool() {
const value = this.readInt(false);
if (value === 0x997275b5) {
// boolTrue
return true;
}
else if (value === 0xbc799737) {
// boolFalse
return false;
}
else {
throw new Error(`Invalid boolean code ${value.toString(16)}`);
}
}
/**
* Reads and converts Unix time (used by Telegram)
* into a Javascript {Date} object.
* @returns {Date}
*/
tgReadDate() {
const value = this.readInt();
return new Date(value * 1000);
}
/**
* Reads a Telegram object.
*/
tgReadObject() {
const constructorId = this.readInt(false);
let clazz = AllTLObjects_1.tlobjects[constructorId];
if (clazz === undefined) {
/**
* The class was undefined, but there's still a
* chance of it being a manually parsed value like bool!
*/
const value = constructorId;
if (value === 0x997275b5) {
// boolTrue
return true;
}
else if (value === 0xbc799737) {
// boolFalse
return false;
}
else if (value === 0x1cb5c415) {
// Vector
const temp = [];
const length = this.readInt();
for (let i = 0; i < length; i++) {
temp.push(this.tgReadObject());
}
return temp;
}
clazz = core_1.coreObjects.get(constructorId);
if (clazz === undefined) {
// If there was still no luck, give up
this.seek(-4); // Go back
const pos = this.tellPosition();
const error = new errors_1.TypeNotFoundError(constructorId, this.read());
this.setPosition(pos);
throw error;
}
}
return clazz.fromReader(this);
}
/**
* Reads a vector (a list) of Telegram objects.
* @returns {[Buffer]}
*/
tgReadVector() {
if (this.readInt(false) !== 0x1cb5c415) {
throw new Error("Invalid constructor code, vector was expected");
}
const count = this.readInt();
const temp = [];
for (let i = 0; i < count; i++) {
temp.push(this.tgReadObject());
}
return temp;
}
// endregion
// region Position related
/**
* Tells the current position on the stream.
* @returns {number}
*/
tellPosition() {
return this.offset;
}
/**
* Sets the current position on the stream.
* @param position
*/
setPosition(position) {
this.offset = position;
}
/**
* Seeks the stream position given an offset from the current position.
* The offset may be negative.
* @param offset
*/
seek(offset) {
this.offset += offset;
}
}
exports.BinaryReader = BinaryReader;