Преглед изворни кода

Fix a lot of bugs
Major overhaul

painor пре 5 година
родитељ
комит
c05d4056f0
17 измењених фајлова са 496 додато и 240 уклоњено
  1. 1 2
      .gitignore
  2. 1 0
      crypto/AES.js
  3. 2 2
      crypto/AuthKey.js
  4. 20 10
      crypto/RSA.js
  5. 0 2
      errors.js
  6. 5 5
      main.js
  7. 99 74
      network/authenticator.js
  8. 25 12
      network/mtprotoPlainSender.js
  9. 123 50
      network/mtprotoSender.js
  10. 1 0
      network/tcpClient.js
  11. 18 14
      network/tcpTransport.js
  12. 29 0
      package-lock.json
  13. 4 0
      package.json
  14. 5 5
      tl/mtprotoRequest.js
  15. 15 13
      tl/session.js
  16. 25 18
      tl/telegramClient.js
  17. 123 33
      utils/Helpers.js

+ 1 - 2
.gitignore

@@ -1,7 +1,6 @@
 /node_modules/
 /node_modules/
 /.vscode/
 /.vscode/
 /.idea/
 /.idea/
-
 # Generated code
 # Generated code
 /gramjs/
 /gramjs/
 /docs/
 /docs/
@@ -21,4 +20,4 @@ example.js
 # dotenv
 # dotenv
 .env
 .env
 
 
-/api/
+settings

+ 1 - 0
crypto/AES.js

@@ -46,6 +46,7 @@ class AES {
      * @returns {Buffer}
      * @returns {Buffer}
      */
      */
     static encryptIge(plainText, key, iv) {
     static encryptIge(plainText, key, iv) {
+
         if (plainText.length % 16 !== 0) {
         if (plainText.length % 16 !== 0) {
             let padding = new Uint8Array(16 - plainText.length % 16);
             let padding = new Uint8Array(16 - plainText.length % 16);
             plainText = new Uint8Array([
             plainText = new Uint8Array([

+ 2 - 2
crypto/AuthKey.js

@@ -2,11 +2,11 @@ const Helpers = require("../utils/Helpers");
 
 
 class AuthKey {
 class AuthKey {
     constructor(data) {
     constructor(data) {
-        this.data = data;
+        this.key = data;
         let offset = 0;
         let offset = 0;
         let buffer = Helpers.sha1(data);
         let buffer = Helpers.sha1(data);
         this.auxHash = buffer.readBigUInt64LE(offset);
         this.auxHash = buffer.readBigUInt64LE(offset);
-        offset += 8 + 4;
+        offset = 8 + 4;
         this.keyId = buffer.readBigUInt64LE(offset);
         this.keyId = buffer.readBigUInt64LE(offset);
 
 
     }
     }

+ 20 - 10
crypto/RSA.js

@@ -1,4 +1,5 @@
 const Helpers = require("../utils/Helpers");
 const Helpers = require("../utils/Helpers");
+const BigIntBuffer = require("bigint-buffer");
 
 
 
 
 class RSAServerKey {
 class RSAServerKey {
@@ -22,33 +23,42 @@ class RSAServerKey {
         if (length === undefined) {
         if (length === undefined) {
             length = data.length;
             length = data.length;
         }
         }
-        let dataToWrite = data.split(offset, offset + length);
-        let sha1Data = helpers.sha1(dataToWrite);
+
+        let dataToWrite = data.slice(offset, offset + length);
+        let sha1Data = Helpers.sha1(dataToWrite);
         let writer = Buffer.concat([sha1Data, dataToWrite]);
         let writer = Buffer.concat([sha1Data, dataToWrite]);
+        let rnd = Helpers.generateRandomBytes(235 - length);
 
 
         if (length < 235) {
         if (length < 235) {
-            writer = Buffer.concat([writer, Helpers.generateRandomBytes(235 - length)]);
+            writer = Buffer.concat([writer, rnd]);
 
 
         }
         }
-        let result = writer.readIntBE(0, writer.byteLength);
-        result = (result ** this.e) % this.m;
-        let buffer = Buffer.alloc(256);
-        buffer.writeUIntBE(result, 0, 256);
-        return buffer;
+
+        let result = BigIntBuffer.toBigIntBE(writer);
+
+        result = Helpers.modExp(result, this.e, this.m);
+
+        /**
+         *  If the result byte count is less than 256, since the byte order is big,
+         *  the non-used bytes on the left will be 0 and act as padding,
+         *  without need of any additional checks
+         */
+        result = BigInt(result);
+        return BigIntBuffer.toBufferBE(result, 256);
     }
     }
 }
 }
 
 
 
 
 class RSA {
 class RSA {
     static _server_keys = {
     static _server_keys = {
-        '216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
+        '216be86c022bb4c3': new RSAServerKey("216be86c022bb4c3", BigInt('0xC150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
             '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
             '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
             '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
             '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
             '9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934' +
             '9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934' +
             'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F' +
             'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F' +
             '81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
             '81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
             '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
             '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
-            '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 16), parseInt('010001', 16)),
+            '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F'), BigInt('0x010001')),
 // -4344800451088585951
 // -4344800451088585951
         '-4344800451088585951': new RSAServerKey(  // Telegram servers //1
         '-4344800451088585951': new RSAServerKey(  // Telegram servers //1
 // -----BEGIN RSA PUBLIC KEY-----
 // -----BEGIN RSA PUBLIC KEY-----

+ 0 - 2
errors.js

@@ -175,10 +175,8 @@ class RPCError extends Error {
             let match = message.match(key);
             let match = message.match(key);
 
 
             if (match) {
             if (match) {
-                console.log(match[1]);
                 // Get additionalData if any
                 // Get additionalData if any
                 if (match.length === 2) {
                 if (match.length === 2) {
-                    console.log(errorMsg);
                     let additionalData = parseInt(match[1]);
                     let additionalData = parseInt(match[1]);
                     super(errorMsg.replace("{}", additionalData));
                     super(errorMsg.replace("{}", additionalData));
 
 

+ 5 - 5
main.js

@@ -1,10 +1,10 @@
-const {Helpers} = require("./utils/Helpers");
-const {TelegramClient} = require("./tl/TelegramClient");
+const Helpers = require("./utils/Helpers");
+const TelegramClient = require("./tl/TelegramClient");
 
 
 (async function () {
 (async function () {
     console.log("Loading interactive example...");
     console.log("Loading interactive example...");
-    let settings = Helpers.loadSettings();
-    let client = TelegramClient(settings["session_name"], 105, settings["api_id"], settings["api_hash"]);
+    let settings = await Helpers.loadSettings();
+    let client = new TelegramClient(settings["session_name"], 73, settings["api_id"], settings["api_hash"]);
     await client.connect();
     await client.connect();
     console.log("You should now be connected.");
     console.log("You should now be connected.");
-})();
+})();

+ 99 - 74
network/authenticator.js

@@ -4,56 +4,72 @@ const Factorizator = require("../crypto/Factorizator");
 const RSA = require("../crypto/RSA");
 const RSA = require("../crypto/RSA");
 const MtProtoPlainSender = require("./MTProtoPlainSender");
 const MtProtoPlainSender = require("./MTProtoPlainSender");
 const Helpers = require("../utils/Helpers");
 const Helpers = require("../utils/Helpers");
+const BigIntBuffer = require("bigint-buffer");
 
 
-function doAuthentication(transport) {
-    let sender = MtProtoPlainSender(transport);
+/**
+ *
+ * @param transport
+ * @returns {Promise<{authKey: AuthKey, timeOffset: BigInt}>}
+ */
+async function doAuthentication(transport) {
+    let sender = new MtProtoPlainSender(transport);
 
 
     // Step 1 sending: PQ request
     // Step 1 sending: PQ request
     let nonce = Helpers.generateRandomBytes(16);
     let nonce = Helpers.generateRandomBytes(16);
-    let buffer = Buffer.alloc(32);
+    let buffer = Buffer.alloc(4);
     buffer.writeUInt32LE(0x60469778, 0);
     buffer.writeUInt32LE(0x60469778, 0);
     buffer = Buffer.concat([buffer, nonce]);
     buffer = Buffer.concat([buffer, nonce]);
-    sender.send(buffer);
-
+    await sender.send(buffer);
+    let offset = 0;
     // Step 1 response: PQ request
     // Step 1 response: PQ request
-    let pq = null;
-    let serverNonce = null;
+    let pq;
+    let serverNonce;
     let fingerprints = Array();
     let fingerprints = Array();
-    buffer = sender.receive();
-    let responseCode = buffer.readUInt32LE(0);
+    buffer = await sender.receive();
+
+    let responseCode = buffer.readUInt32LE(offset);
+    offset += 4;
     if (responseCode !== 0x05162463) {
     if (responseCode !== 0x05162463) {
         throw Error("invalid response code");
         throw Error("invalid response code");
     }
     }
-    let nonceFromServer = buffer.read(16, 8);
-    if (nonce !== nonceFromServer) {
+
+    let nonceFromServer = buffer.slice(offset, offset + 16);
+    offset += 16;
+    if (!nonce.equals(nonceFromServer)) {
         throw Error("Invalid nonce from server");
         throw Error("Invalid nonce from server");
     }
     }
-    serverNonce = buffer.read(16, 12);
+    serverNonce = buffer.slice(offset, offset + 16);
+    offset += 16;
+    let res = Helpers.tgReadByte(buffer, offset);
+    let pqBytes = res.data;
+
+    let newOffset = res.offset;
+    pq = BigIntBuffer.toBigIntBE(pqBytes);
 
 
-    let {pqBytes, newOffset} = Helpers.tgReadByte(buffer, 12);
-    pq = buffer.readBigInt64BE(newOffset);
-    newOffset += 8;
-    let vectorId = buffer.readInt8(newOffset);
-    newOffset += 1;
+    let vectorId = buffer.readInt32LE(newOffset);
+    newOffset += 4;
     if (vectorId !== 0x1cb5c415) {
     if (vectorId !== 0x1cb5c415) {
         throw Error("vector error");
         throw Error("vector error");
     }
     }
-    let fingerprints_count = buffer.readInt8(newOffset);
+    let fingerprints_count = buffer.readInt32LE(newOffset);
+    newOffset += 4;
     for (let i = 0; i < fingerprints_count; i++) {
     for (let i = 0; i < fingerprints_count; i++) {
-        fingerprints.push(buffer.readInt32LE(newOffset));
+        fingerprints.push(buffer.slice(newOffset, newOffset + 8));
         newOffset += 8;
         newOffset += 8;
     }
     }
-
     // Step 2 sending: DH Exchange
     // Step 2 sending: DH Exchange
     let newNonce = Helpers.generateRandomBytes(32);
     let newNonce = Helpers.generateRandomBytes(32);
     let {p, q} = Factorizator.factorize(pq);
     let {p, q} = Factorizator.factorize(pq);
-    let tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0x83c95aec, 0, 8);
+    let min = p < q ? p : q;
+    let max = p > q ? p : q;
+
+    let tempBuffer = Buffer.alloc(4);
+    tempBuffer.writeUInt32LE(0x83c95aec, 0);
     let pqInnerData = Buffer.concat([
     let pqInnerData = Buffer.concat([
         tempBuffer,
         tempBuffer,
         Helpers.tgWriteBytes(getByteArray(pq, false)),
         Helpers.tgWriteBytes(getByteArray(pq, false)),
-        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
-        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
+        Helpers.tgWriteBytes(getByteArray(min, false)),
+        Helpers.tgWriteBytes(getByteArray(max, false)),
         nonce,
         nonce,
         serverNonce,
         serverNonce,
         newNonce,
         newNonce,
@@ -69,23 +85,26 @@ function doAuthentication(transport) {
     if (cipherText === undefined) {
     if (cipherText === undefined) {
         throw Error("Could not find a valid key for fingerprints");
         throw Error("Could not find a valid key for fingerprints");
     }
     }
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0xd712e4be, 0, 8);
+
+    tempBuffer = Buffer.alloc(4);
+    tempBuffer.writeUInt32LE(0xd712e4be, 0);
+
 
 
     let reqDhParams = Buffer.concat([
     let reqDhParams = Buffer.concat([
         tempBuffer,
         tempBuffer,
         nonce,
         nonce,
         serverNonce,
         serverNonce,
-        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
-        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
+        Helpers.tgWriteBytes(getByteArray(min, false)),
+        Helpers.tgWriteBytes(getByteArray(max, false)),
         targetFingerprint,
         targetFingerprint,
         Helpers.tgWriteBytes(cipherText)
         Helpers.tgWriteBytes(cipherText)
     ]);
     ]);
-    sender.send(reqDhParams);
+
+    await sender.send(reqDhParams);
     // Step 2 response: DH Exchange
     // Step 2 response: DH Exchange
     newOffset = 0;
     newOffset = 0;
-    let reader = sender.receive();
-    responseCode = reader.readInt32LE(newOffset);
+    let reader = await sender.receive();
+    responseCode = reader.readUInt32LE(newOffset);
     newOffset += 4;
     newOffset += 4;
     if (responseCode === 0x79cb045d) {
     if (responseCode === 0x79cb045d) {
         throw Error("Server DH params fail: TODO ");
         throw Error("Server DH params fail: TODO ");
@@ -93,59 +112,65 @@ function doAuthentication(transport) {
     if (responseCode !== 0xd0e8075c) {
     if (responseCode !== 0xd0e8075c) {
         throw Error("Invalid response code: TODO ");
         throw Error("Invalid response code: TODO ");
     }
     }
-    nonceFromServer = reader.readIntLE(newOffset, 16);
+    nonceFromServer = reader.slice(newOffset, newOffset + 16);
     newOffset += 16;
     newOffset += 16;
-    if (nonceFromServer !== nonce) {
+    if (!nonceFromServer.equals(nonce)) {
         throw Error("Invalid nonce from server");
         throw Error("Invalid nonce from server");
     }
     }
-    let serverNonceFromServer = reader.readIntLE(newOffset, 16);
-    if (serverNonceFromServer !== nonceFromServer) {
+    let serverNonceFromServer = reader.slice(newOffset, newOffset + 16);
+
+    if (!serverNonceFromServer.equals(serverNonce)) {
         throw Error("Invalid server nonce from server");
         throw Error("Invalid server nonce from server");
     }
     }
+
     newOffset += 16;
     newOffset += 16;
-    let encryptedAnswer = Helpers.tgReadByte(reader, newOffset).data;
 
 
+    let encryptedAnswer = Helpers.tgReadByte(reader, newOffset).data;
     // Step 3 sending: Complete DH Exchange
     // Step 3 sending: Complete DH Exchange
 
 
-    let {key, iv} = Helpers.generateKeyDataFromNonces(serverNonce, newNonce);
+    res = Helpers.generateKeyDataFromNonces(serverNonce, newNonce);
+    let key = res.keyBuffer;
+    let iv = res.ivBuffer;
     let plainTextAnswer = AES.decryptIge(encryptedAnswer, key, iv);
     let plainTextAnswer = AES.decryptIge(encryptedAnswer, key, iv);
     let g, dhPrime, ga, timeOffset;
     let g, dhPrime, ga, timeOffset;
     let dhInnerData = plainTextAnswer;
     let dhInnerData = plainTextAnswer;
     newOffset = 20;
     newOffset = 20;
-    let code = dhInnerData.readUInt32BE(newOffset);
+    let code = dhInnerData.readUInt32LE(newOffset);
     if (code !== 0xb5890dba) {
     if (code !== 0xb5890dba) {
         throw Error("Invalid DH Inner Data code:")
         throw Error("Invalid DH Inner Data code:")
     }
     }
     newOffset += 4;
     newOffset += 4;
-    let nonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
-    if (nonceFromServer1 !== nonceFromServer) {
+    let nonceFromServer1 = dhInnerData.slice(newOffset, newOffset + 16);
+    if (!nonceFromServer1.equals(nonceFromServer)) {
         throw Error("Invalid nonce in encrypted answer");
         throw Error("Invalid nonce in encrypted answer");
     }
     }
     newOffset += 16;
     newOffset += 16;
-    let serverNonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
-    if (serverNonceFromServer1 !== serverNonce) {
+    let serverNonceFromServer1 = dhInnerData.slice(newOffset, newOffset + 16);
+    if (!serverNonceFromServer1.equals(serverNonce)) {
         throw Error("Invalid server nonce in encrypted answer");
         throw Error("Invalid server nonce in encrypted answer");
     }
     }
     newOffset += 16;
     newOffset += 16;
-    g = dhInnerData.readInt32LE(newOffset);
+    g = BigInt(dhInnerData.readInt32LE(newOffset));
     newOffset += 4;
     newOffset += 4;
+
     let temp = Helpers.tgReadByte(dhInnerData, newOffset);
     let temp = Helpers.tgReadByte(dhInnerData, newOffset);
-    newOffset += temp.offset;
+    newOffset = temp.offset;
 
 
-    dhPrime = temp.data.readUInt32BE(0);
+    dhPrime = BigIntBuffer.toBigIntBE(temp.data);
     temp = Helpers.tgReadByte(dhInnerData, newOffset);
     temp = Helpers.tgReadByte(dhInnerData, newOffset);
-    newOffset += temp.offset;
-    ga = temp.data.readUInt32BE(0);
+
+    newOffset = temp.offset;
+    ga = BigIntBuffer.toBigIntBE(temp.data);
     let serverTime = dhInnerData.readInt32LE(newOffset);
     let serverTime = dhInnerData.readInt32LE(newOffset);
     timeOffset = serverTime - Math.floor(new Date().getTime() / 1000);
     timeOffset = serverTime - Math.floor(new Date().getTime() / 1000);
-    let b = Helpers.generateRandomBytes(2048).readUInt32BE(0);
-    let gb = (g ** b) % dhPrime;
-    let gab = (ga * b) % dhPrime;
+    let b = BigIntBuffer.toBigIntBE(Helpers.generateRandomBytes(2048));
+    let gb = Helpers.modExp(g, b, dhPrime);
+    let gab = Helpers.modExp(ga, b, dhPrime);
 
 
     // Prepare client DH Inner Data
     // Prepare client DH Inner Data
 
 
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0x6643b654, 0, 8);
+    tempBuffer = Buffer.alloc(4);
+    tempBuffer.writeUInt32LE(0x6643b654, 0);
     let clientDHInnerData = Buffer.concat([
     let clientDHInnerData = Buffer.concat([
         tempBuffer,
         tempBuffer,
         nonce,
         nonce,
@@ -162,38 +187,39 @@ function doAuthentication(transport) {
     let clientDhInnerDataEncrypted = AES.encryptIge(clientDhInnerData, key, iv);
     let clientDhInnerDataEncrypted = AES.encryptIge(clientDhInnerData, key, iv);
 
 
     // Prepare Set client DH params
     // Prepare Set client DH params
-    tempBuffer = Buffer.alloc(8);
-    tempBuffer.writeUIntLE(0xf5045f1f, 0, 8);
+    tempBuffer = Buffer.alloc(4);
+    tempBuffer.writeUInt32LE(0xf5045f1f, 0);
     let setClientDhParams = Buffer.concat([
     let setClientDhParams = Buffer.concat([
         tempBuffer,
         tempBuffer,
         nonce,
         nonce,
         serverNonce,
         serverNonce,
         Helpers.tgWriteBytes(clientDhInnerDataEncrypted),
         Helpers.tgWriteBytes(clientDhInnerDataEncrypted),
     ]);
     ]);
-    sender.send(setClientDhParams);
+    await sender.send(setClientDhParams);
 
 
     // Step 3 response: Complete DH Exchange
     // Step 3 response: Complete DH Exchange
-    reader = sender.receive();
+    reader = await sender.receive();
     newOffset = 0;
     newOffset = 0;
     code = reader.readUInt32LE(newOffset);
     code = reader.readUInt32LE(newOffset);
     newOffset += 4;
     newOffset += 4;
     if (code === 0x3bcbf734) { //  DH Gen OK
     if (code === 0x3bcbf734) { //  DH Gen OK
-        nonceFromServer = reader.readIntLE(newOffset, 16);
+        nonceFromServer = reader.slice(newOffset, newOffset + 16);
         newOffset += 16;
         newOffset += 16;
-        if (nonceFromServer !== nonce) {
+        if (!nonceFromServer.equals(nonce)) {
             throw Error("Invalid nonce from server");
             throw Error("Invalid nonce from server");
         }
         }
-        serverNonceFromServer = reader.readIntLE(newOffset, 16);
+        serverNonceFromServer = reader.slice(newOffset, newOffset + 16);
         newOffset += 16;
         newOffset += 16;
-        if (serverNonceFromServer !== serverNonce) {
+        if (!serverNonceFromServer.equals(serverNonce)) {
             throw Error("Invalid server nonce from server");
             throw Error("Invalid server nonce from server");
         }
         }
-        let newNonceHash1 = reader.readIntLE(newOffset, 16);
-        let authKey = AuthKey(getByteArray(gab, false));
+        let newNonceHash1 = reader.slice(newOffset, newOffset + 16);
+        let authKey = new AuthKey(getByteArray(gab, false));
         let newNonceHashCalculated = authKey.calcNewNonceHash(newNonce, 1);
         let newNonceHashCalculated = authKey.calcNewNonceHash(newNonce, 1);
-        if (newNonceHash1 !== newNonceHashCalculated) {
+        if (!newNonceHash1.equals(newNonceHashCalculated)) {
             throw Error("Invalid new nonce hash");
             throw Error("Invalid new nonce hash");
         }
         }
+        timeOffset = BigInt(timeOffset);
         return {authKey, timeOffset};
         return {authKey, timeOffset};
     } else if (code === 0x46dc1fb9) {
     } else if (code === 0x46dc1fb9) {
         throw Error("dh_gen_retry");
         throw Error("dh_gen_retry");
@@ -222,29 +248,28 @@ function rightJustify(string, length, char) {
  * @returns {string}
  * @returns {string}
  */
  */
 function getFingerprintText(fingerprint) {
 function getFingerprintText(fingerprint) {
-    let res = "";
-    for (let b of fingerprint) {
-        res += rightJustify(b.toString(16), 2, '0').toUpperCase();
-    }
-    return res;
+    return fingerprint.toString("hex");
+
 }
 }
 
 
+
 /**
 /**
- *
+ * Gets the arbitrary-length byte array corresponding to the given integer
  * @param integer {number,BigInt}
  * @param integer {number,BigInt}
  * @param signed {boolean}
  * @param signed {boolean}
- * @returns {number}
+ * @returns {Buffer}
  */
  */
 function getByteArray(integer, signed) {
 function getByteArray(integer, signed) {
     let bits = integer.toString(2).length;
     let bits = integer.toString(2).length;
     let byteLength = Math.floor((bits + 8 - 1) / 8);
     let byteLength = Math.floor((bits + 8 - 1) / 8);
-    let buffer = Buffer.alloc(byteLength);
+    let f;
     if (signed) {
     if (signed) {
-        return buffer.readIntLE(0, byteLength);
+        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
 
 
     } else {
     } else {
-        return buffer.readUIntLE(0, byteLength);
-
+        f = BigIntBuffer.toBufferBE(BigInt(integer), byteLength);
     }
     }
+    return f;
 }
 }
+
 module.exports = doAuthentication;
 module.exports = doAuthentication;

+ 25 - 12
network/mtprotoPlainSender.js

@@ -1,4 +1,5 @@
 const Helpers = require("../utils/Helpers");
 const Helpers = require("../utils/Helpers");
+const BigIntBuffer = require('bigint-buffer');
 
 
 /**
 /**
  * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
  * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
@@ -15,22 +16,34 @@ class MTProtoPlainSender {
      * Sends a plain packet (auth_key_id = 0) containing the given message body (data)
      * Sends a plain packet (auth_key_id = 0) containing the given message body (data)
      * @param data
      * @param data
      */
      */
-    send(data) {
-        let packet = Buffer.alloc(8, 0);
-        packet.writeBigInt64LE(this.getNewMsgId(), packet.byteLength);
-        packet.writeInt32LE(data.length, packet.byteLength);
-        packet.write(data, packet.byteLength);
-        this._transport.send(packet);
+    async send(data) {
+        let packet = Buffer.alloc(20);
+        let offset = 0;
+        packet.writeBigInt64LE(0n, offset);
+        offset += 8;
+        packet.writeBigInt64LE(this.getNewMsgId(), offset);
+        offset += 8;
+        packet.writeInt32LE(data.length, offset);
+        await this._transport.send(Buffer.concat([
+            packet,
+            data,
+        ]));
     }
     }
 
 
     /**
     /**
      * Receives a plain packet, returning the body of the response
      * Receives a plain packet, returning the body of the response
-     * @returns {Buffer}
+     * @returns {number}
      */
      */
-    receive() {
-        let {seq, body} = this._transport.receive();
-        let message_length = body.readInt32LE(16);
-        return body.slice(20, message_length);
+    async receive() {
+        let {seq, body} = await this._transport.receive();
+        let offset = 0;
+        let authKeyId = body.readBigInt64LE(0);
+        offset += 8;
+        let msgId = body.readBigInt64LE(offset);
+        offset += 8;
+        let messageLength = body.readInt32LE(offset);
+        offset += 4;
+        return body.slice(offset);
 
 
     }
     }
 
 
@@ -46,7 +59,7 @@ class MTProtoPlainSender {
             BigInt(Helpers.getRandomInt(0, 524288)) << BigInt(2));// "message identifiers are divisible by 4"
             BigInt(Helpers.getRandomInt(0, 524288)) << BigInt(2));// "message identifiers are divisible by 4"
         //Ensure that we always return a message ID which is higher than the previous one
         //Ensure that we always return a message ID which is higher than the previous one
         if (this._lastMsgId >= newMsgId) {
         if (this._lastMsgId >= newMsgId) {
-            newMsgId = this._lastMsgId + 4
+            newMsgId = this._lastMsgId + 4n
         }
         }
         this._lastMsgId = newMsgId;
         this._lastMsgId = newMsgId;
         return BigInt(newMsgId);
         return BigInt(newMsgId);

+ 123 - 50
network/mtprotoSender.js

@@ -1,10 +1,24 @@
 const MtProtoPlainSender = require("./MTProtoPlainSender");
 const MtProtoPlainSender = require("./MTProtoPlainSender");
 const Helpers = require("../utils/Helpers");
 const Helpers = require("../utils/Helpers");
+const {MsgsAck} = require("../gramjs/tl/types");
+const AES = require("../crypto/AES");
+const {RPCError} = require("../errors");
+const format = require('string-format');
+const {BadMessageError} = require("../errors");
+const {InvalidDCError} = require("../errors");
+const {gzip, ungzip} = require('node-gzip');
+//const {tlobjects} = require("../gramjs/tl/alltlobjects");
+format.extend(String.prototype, {});
 
 
 /**
 /**
  * MTProto Mobile Protocol sender (https://core.telegram.org/mtproto/description)
  * MTProto Mobile Protocol sender (https://core.telegram.org/mtproto/description)
  */
  */
 class MTProtoSender {
 class MTProtoSender {
+    /**
+     *
+     * @param transport
+     * @param session
+     */
     constructor(transport, session) {
     constructor(transport, session) {
         this.transport = transport;
         this.transport = transport;
         this.session = session;
         this.session = session;
@@ -68,38 +82,49 @@ class MTProtoSender {
     /**
     /**
      * Sends the specified MTProtoRequest, previously sending any message
      * Sends the specified MTProtoRequest, previously sending any message
      * which needed confirmation. This also pauses the updates thread
      * which needed confirmation. This also pauses the updates thread
-     * @param request {MtProtoPlainSender}
+     * @param request {MTProtoRequest}
      * @param resend
      * @param resend
      */
      */
-    send(request, resend = false) {
+    async send(request, resend = false) {
         let buffer;
         let buffer;
         //If any message needs confirmation send an AckRequest first
         //If any message needs confirmation send an AckRequest first
-        if (Boolean(this.needConfirmation.length)) {
-            let msgsAck = MsgsAck(this.needConfirmation);
+        if (this.needConfirmation.length) {
+            let msgsAck = new MsgsAck(
+                {
+                    msgIds:
+                    this.needConfirmation
+                });
 
 
             buffer = msgsAck.onSend();
             buffer = msgsAck.onSend();
-            this.sendPacket(buffer, msgsAck);
+            await this.sendPacket(buffer, msgsAck);
             this.needConfirmation.length = 0;
             this.needConfirmation.length = 0;
         }
         }
         //Finally send our packed request
         //Finally send our packed request
-        buffer = request.on_send();
-        this.sendPacket(buffer, request);
+
+        buffer = request.onSend();
+        await this.sendPacket(buffer, request);
 
 
         //And update the saved session
         //And update the saved session
         this.session.save();
         this.session.save();
 
 
     }
     }
 
 
-    receive(request) {
+    /**
+     *
+     * @param request
+     */
+    async receive(request) {
         try {
         try {
             //Try until we get an update
             //Try until we get an update
-            while (!request.confirmReceive()) {
-                let {seq, body} = this.transport.receive();
+            while (!request.confirmReceived) {
+                let {seq, body} = await this.transport.receive();
                 let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
                 let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
-                this.processMsg(remoteMsgId, remoteSequence, message, request);
+                console.log("processing msg");
+                await this.processMsg(remoteMsgId, remoteSequence, message, 0, request);
+                console.log("finished processing msg");
             }
             }
-        } catch (e) {
-
+        } finally {
+            // Todo
         }
         }
     }
     }
 
 
@@ -110,9 +135,8 @@ class MTProtoSender {
      * @param packet
      * @param packet
      * @param request
      * @param request
      */
      */
-    sendPacket(packet, request) {
+    async sendPacket(packet, request) {
         request.msgId = this.session.getNewMsgId();
         request.msgId = this.session.getNewMsgId();
-
         // First Calculate plainText to encrypt it
         // First Calculate plainText to encrypt it
         let first = Buffer.alloc(8);
         let first = Buffer.alloc(8);
         let second = Buffer.alloc(8);
         let second = Buffer.alloc(8);
@@ -134,73 +158,87 @@ class MTProtoSender {
         ]);
         ]);
         let msgKey = Helpers.calcMsgKey(plain);
         let msgKey = Helpers.calcMsgKey(plain);
         let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, true);
         let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, true);
+
         let cipherText = AES.encryptIge(plain, key, iv);
         let cipherText = AES.encryptIge(plain, key, iv);
 
 
         //And then finally send the encrypted packet
         //And then finally send the encrypted packet
 
 
         first = Buffer.alloc(8);
         first = Buffer.alloc(8);
-        first.writeUInt32LE(this.session.authKey.keyId, 0);
+        first.writeBigUInt64LE(this.session.authKey.keyId, 0);
         let cipher = Buffer.concat([
         let cipher = Buffer.concat([
             first,
             first,
             msgKey,
             msgKey,
             cipherText,
             cipherText,
         ]);
         ]);
-        this.transport.send(cipher);
+        await this.transport.send(cipher);
     }
     }
 
 
+    /**
+     *
+     * @param body {Buffer}
+     * @returns {{remoteMsgId: number, remoteSequence: BigInt, message: Buffer}}
+     */
     decodeMsg(body) {
     decodeMsg(body) {
         if (body.length < 8) {
         if (body.length < 8) {
             throw Error("Can't decode packet");
             throw Error("Can't decode packet");
         }
         }
+        let remoteAuthKeyId = body.readBigInt64LE(0);
         let offset = 8;
         let offset = 8;
-        let msgKey = body.readIntLE(offset, 16);
+        let msgKey = body.slice(offset, offset + 16);
         offset += 16;
         offset += 16;
         let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, false);
         let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, false);
-        let plainText = AES.decryptIge(body.readIntLE(offset, body.length - offset), key, iv);
+        let plainText = AES.decryptIge(body.slice(offset, body.length), key, iv);
         offset = 0;
         offset = 0;
         let remoteSalt = plainText.readBigInt64LE(offset);
         let remoteSalt = plainText.readBigInt64LE(offset);
         offset += 8;
         offset += 8;
         let remoteSessionId = plainText.readBigInt64LE(offset);
         let remoteSessionId = plainText.readBigInt64LE(offset);
         offset += 8;
         offset += 8;
-        let remoteSequence = plainText.readBigInt64LE(offset);
+        let remoteMsgId = plainText.readBigInt64LE(offset);
         offset += 8;
         offset += 8;
-        let remoteMsgId = plainText.readInt32LE(offset);
+        let remoteSequence = plainText.readInt32LE(offset);
         offset += 4;
         offset += 4;
         let msgLen = plainText.readInt32LE(offset);
         let msgLen = plainText.readInt32LE(offset);
         offset += 4;
         offset += 4;
-        let message = plainText.readIntLE(offset, msgLen);
+        let message = plainText.slice(offset, offset + msgLen);
         return {message, remoteMsgId, remoteSequence}
         return {message, remoteMsgId, remoteSequence}
     }
     }
 
 
-    processMsg(msgId, sequence, reader, offset, request = undefined) {
+    async processMsg(msgId, sequence, reader, offset, request = undefined) {
         this.needConfirmation.push(msgId);
         this.needConfirmation.push(msgId);
         let code = reader.readUInt32LE(offset);
         let code = reader.readUInt32LE(offset);
-        offset -= 4;
-
+        console.log("code is ", code);
         // The following codes are "parsed manually"
         // The following codes are "parsed manually"
         if (code === 0xf35c6d01) {  //rpc_result, (response of an RPC call, i.e., we sent a request)
         if (code === 0xf35c6d01) {  //rpc_result, (response of an RPC call, i.e., we sent a request)
-            return this.handleRpcResult(msgId, sequence, reader, request);
+            console.log("got rpc result");
+            return await this.handleRpcResult(msgId, sequence, reader, offset, request);
         }
         }
 
 
         if (code === 0x73f1f8dc) {  //msg_container
         if (code === 0x73f1f8dc) {  //msg_container
-            return this.handlerContainer(msgId, sequence, reader, request);
+            return this.handleContainer(msgId, sequence, reader, offset, request);
         }
         }
         if (code === 0x3072cfa1) {  //gzip_packed
         if (code === 0x3072cfa1) {  //gzip_packed
-            return this.handlerGzipPacked(msgId, sequence, reader, request);
+            return this.handleGzipPacked(msgId, sequence, reader, offset, request);
         }
         }
         if (code === 0xedab447b) {  //bad_server_salt
         if (code === 0xedab447b) {  //bad_server_salt
-            return this.handleBadServerSalt(msgId, sequence, reader, request);
+            return await this.handleBadServerSalt(msgId, sequence, reader, offset, request);
         }
         }
         if (code === 0xa7eff811) {  //bad_msg_notification
         if (code === 0xa7eff811) {  //bad_msg_notification
-            return this.handleBadMsgNotification(msgId, sequence, reader);
+            console.log("bad msg notification");
+            return this.handleBadMsgNotification(msgId, sequence, reader, offset);
         }
         }
         /**
         /**
          * If the code is not parsed manually, then it was parsed by the code generator!
          * If the code is not parsed manually, then it was parsed by the code generator!
          * In this case, we will simply treat the incoming TLObject as an Update,
          * In this case, we will simply treat the incoming TLObject as an Update,
          * if we can first find a matching TLObject
          * if we can first find a matching TLObject
          */
          */
-        if (tlobjects.contains(code)) {
-            return this.handleUpdate(msgId, sequence, reader);
+        console.log("code", code);
+        if (code === 0x9ec20908) {
+            return this.handleUpdate(msgId, sequence, reader, offset);
+        } else {
+
+            if (tlobjects.contains(code)) {
+                return this.handleUpdate(msgId, sequence, reader);
+            }
         }
         }
         console.log("Unknown message");
         console.log("Unknown message");
         return false;
         return false;
@@ -208,15 +246,15 @@ class MTProtoSender {
 
 
     // region Message handling
     // region Message handling
 
 
-    handleUpdate(msgId, sequence, reader) {
-        let tlobject = Helpers.tgReadObject(reader);
+    handleUpdate(msgId, sequence, reader, offset = 0) {
+        let tlobject = Helpers.tgReadObject(reader,offset);
         for (let handler of this.onUpdateHandlers) {
         for (let handler of this.onUpdateHandlers) {
             handler(tlobject);
             handler(tlobject);
         }
         }
         return Float32Array
         return Float32Array
     }
     }
 
 
-    handleContainer(msgId, sequence, reader, offset, request) {
+    async handleContainer(msgId, sequence, reader, offset, request) {
         let code = reader.readUInt32LE(offset);
         let code = reader.readUInt32LE(offset);
         offset += 4;
         offset += 4;
         let size = reader.readInt32LE(offset);
         let size = reader.readInt32LE(offset);
@@ -224,28 +262,28 @@ class MTProtoSender {
         for (let i = 0; i < size; i++) {
         for (let i = 0; i < size; i++) {
             let innerMsgId = reader.readBigUInt64LE(offset);
             let innerMsgId = reader.readBigUInt64LE(offset);
             offset += 8;
             offset += 8;
-            let innerSequence = reader.readBigInt64LE(offset);
-            offset += 8;
+            let innerSequence = reader.readInt32LE(offset);
+            offset += 4;
             let innerLength = reader.readInt32LE(offset);
             let innerLength = reader.readInt32LE(offset);
             offset += 4;
             offset += 4;
-            if (!this.processMsg(innerMsgId, sequence, reader, request)) {
+            if (!(await this.processMsg(innerMsgId, sequence, reader, offset, request))) {
                 offset += innerLength;
                 offset += innerLength;
             }
             }
         }
         }
         return false;
         return false;
     }
     }
 
 
-    handleBadServerSalt(msgId, sequence, reader, offset, request) {
+    async handleBadServerSalt(msgId, sequence, reader, offset, request) {
         let code = reader.readUInt32LE(offset);
         let code = reader.readUInt32LE(offset);
         offset += 4;
         offset += 4;
-        let badMsgId = reader.readUInt32LE(offset);
-        offset += 4;
+        let badMsgId = reader.readBigUInt64LE(offset);
+        offset += 8;
         let badMsgSeqNo = reader.readInt32LE(offset);
         let badMsgSeqNo = reader.readInt32LE(offset);
         offset += 4;
         offset += 4;
         let errorCode = reader.readInt32LE(offset);
         let errorCode = reader.readInt32LE(offset);
         offset += 4;
         offset += 4;
-        let newSalt = reader.readUInt32LE(offset);
-        offset += 4;
+        let newSalt = reader.readBigUInt64LE(offset);
+        offset += 8;
         this.session.salt = newSalt;
         this.session.salt = newSalt;
 
 
         if (!request) {
         if (!request) {
@@ -253,7 +291,7 @@ class MTProtoSender {
         }
         }
 
 
         //Resend
         //Resend
-        this.send(request, true);
+        await this.send(request, true);
         return true;
         return true;
     }
     }
 
 
@@ -265,14 +303,14 @@ class MTProtoSender {
         let requestSequence = reader.readInt32LE(offset);
         let requestSequence = reader.readInt32LE(offset);
         offset += 4;
         offset += 4;
         let errorCode = reader.readInt32LE(offset);
         let errorCode = reader.readInt32LE(offset);
-        return BadMessageError(errorCode);
+        return new BadMessageError(errorCode);
     }
     }
 
 
-    handleRpcResult(msgId, sequence, reader, offset, request) {
+    async handleRpcResult(msgId, sequence, reader, offset, request) {
         if (!request) {
         if (!request) {
             throw Error("RPC results should only happen after a request was sent");
             throw Error("RPC results should only happen after a request was sent");
         }
         }
-
+        let buffer = Buffer.alloc(0);
         let code = reader.readUInt32LE(offset);
         let code = reader.readUInt32LE(offset);
         offset += 4;
         offset += 4;
         let requestId = reader.readUInt32LE(offset);
         let requestId = reader.readUInt32LE(offset);
@@ -284,14 +322,49 @@ class MTProtoSender {
         }
         }
 
 
         if (innerCode === 0x2144ca19) {  // RPC Error
         if (innerCode === 0x2144ca19) {  // RPC Error
-            // TODO add rpc logic
-            throw Error("error");
+            console.log("Got an error");
+            let errorCode = reader.readInt32LE(offset);
+            offset += 4;
+            let errorMessage = Helpers.tgReadString(reader, offset);
+            offset = errorMessage.offset;
+            errorMessage = errorMessage.data;
+            let error = new RPCError(errorCode, errorMessage);
+            if (error.mustResend) {
+                request.confirmReceived = false;
+            }
+            if (error.message.startsWith("FLOOD_WAIT_")) {
+                console.log("Should wait {}s. Sleeping until then.".format(error.additionalData));
+                await Helpers.sleep();
+            } else if (error.message.startsWith("PHONE_MIGRATE_")) {
+                throw new InvalidDCError(error.additionalData);
+            } else {
+                throw error;
+            }
+
         } else {
         } else {
-            // TODO
+            console.log("no errors");
+            if (innerCode === 0x3072cfa1) { //GZip packed
+                console.log("Gzipped data");
+                let res = Helpers.tgReadByte(reader, offset);
+                let unpackedData = await ungzip(res.data);
+                offset = res.offset;
+                res = request.onResponse(unpackedData, offset);
+                buffer = res.data;
+                offset = res.offset;
+            } else {
+                console.log("plain data");
+                offset -= 4;
+                let res = request.onResponse(reader, offset);
+                buffer = res.data;
+                offset = res.offset;
+            }
         }
         }
+        return {buffer, offset}
+
     }
     }
 
 
     handleGzipPacked(msgId, sequence, reader, offset, request) {
     handleGzipPacked(msgId, sequence, reader, offset, request) {
+        throw Error("not implemented");
         // TODO
         // TODO
     }
     }
 
 

+ 1 - 0
network/tcpClient.js

@@ -50,6 +50,7 @@ class TcpClient {
             let leftCount = bufferSize - writtenCount;
             let leftCount = bufferSize - writtenCount;
             let partial = this.socket.read(leftCount);
             let partial = this.socket.read(leftCount);
             if (partial == null) {
             if (partial == null) {
+                console.log("sleeping");
                 await sleep(this.delay);
                 await sleep(this.delay);
                 continue;
                 continue;
             }
             }

+ 18 - 14
network/tcpTransport.js

@@ -7,9 +7,12 @@ class TcpTransport {
         this.sendCounter = 0;
         this.sendCounter = 0;
         this.ipAddress = ipAddress;
         this.ipAddress = ipAddress;
         this.port = port;
         this.port = port;
+
     }
     }
 
 
     async connect() {
     async connect() {
+
+
         await this.tcpClient.connect(this.ipAddress, this.port);
         await this.tcpClient.connect(this.ipAddress, this.port);
     }
     }
 
 
@@ -19,9 +22,9 @@ class TcpTransport {
      * The packets are encoded as: total length, sequence number, packet and checksum (CRC32)
      * The packets are encoded as: total length, sequence number, packet and checksum (CRC32)
      * @param packet
      * @param packet
      */
      */
-    send(packet) {
-        if (this.tcpClient.connected) {
-            throw Error("not connected");
+    async send(packet) {
+        if (!this.tcpClient.connected) {
+            throw Error("Client not connected to server.");
         }
         }
         let buffer = Buffer.alloc(4 + 4);
         let buffer = Buffer.alloc(4 + 4);
         buffer.writeInt32LE(packet.length + 12, 0);
         buffer.writeInt32LE(packet.length + 12, 0);
@@ -30,22 +33,22 @@ class TcpTransport {
         let tempBuffer = Buffer.alloc(4);
         let tempBuffer = Buffer.alloc(4);
         tempBuffer.writeUInt32LE(crc.crc32(buffer), 0);
         tempBuffer.writeUInt32LE(crc.crc32(buffer), 0);
         buffer = Buffer.concat([buffer, tempBuffer]);
         buffer = Buffer.concat([buffer, tempBuffer]);
-        this.tcpClient.write(buffer);
+        await this.tcpClient.write(buffer);
         this.sendCounter++;
         this.sendCounter++;
     }
     }
 
 
     /**
     /**
      * Receives a TCP message (tuple(sequence number, body)) from the connected peer
      * Receives a TCP message (tuple(sequence number, body)) from the connected peer
-     * @returns {{body: {Buffer}, seq: {Buffer}}}
+     * @returns {{body: Buffer, seq: number}}
      */
      */
-    receive() {
+    async receive() {
         /**First read everything we need**/
         /**First read everything we need**/
-        let packetLengthBytes = this.tcpClient.read(4);
-        let packetLength = Buffer.from(packetLengthBytes).readInt32LE(0);
-        let seqBytes = this.tcpClient.read(4);
-        let seq = Buffer.from(seqBytes).readInt32LE(0);
-        let body = this.tcpClient.read(packetLength - 12);
-        let checksum = Buffer.from(this.tcpClient.read(4)).readUInt32LE(0);
+        let packetLengthBytes = await this.tcpClient.read(4);
+        let packetLength = packetLengthBytes.readInt32LE(0);
+        let seqBytes = await this.tcpClient.read(4);
+        let seq = seqBytes.readInt32LE(0);
+        let body = await this.tcpClient.read(packetLength - 12);
+        let checksum = (await this.tcpClient.read(4)).readUInt32LE(0);
         /**Then perform the checks**/
         /**Then perform the checks**/
         let rv = Buffer.concat([packetLengthBytes, seqBytes, body]);
         let rv = Buffer.concat([packetLengthBytes, seqBytes, body]);
         let validChecksum = crc.crc32(rv);
         let validChecksum = crc.crc32(rv);
@@ -57,9 +60,9 @@ class TcpTransport {
         return {seq, body}
         return {seq, body}
     }
     }
 
 
-    close() {
+    async close() {
         if (this.tcpClient.connected) {
         if (this.tcpClient.connected) {
-            this.tcpClient.close();
+            await this.tcpClient.close();
         }
         }
     }
     }
 
 
@@ -81,4 +84,5 @@ class TcpTransport {
 
 
 
 
 }
 }
+
 module.exports = TcpTransport;
 module.exports = TcpTransport;

+ 29 - 0
package-lock.json

@@ -104,6 +104,24 @@
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
       "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
       "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
     },
     },
+    "bigint-buffer": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/bigint-buffer/-/bigint-buffer-1.1.2.tgz",
+      "integrity": "sha512-gof9NgbodUgCXD2aUHfUKk5KUDk6/zPlwE+YYpRM2t7zS0K/6WipJFWfziIY+EavxFDuGUnuuQQzzvNcckPKNA==",
+      "dev": true,
+      "requires": {
+        "bindings": "^1.3.0"
+      }
+    },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "dev": true,
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
     "brace-expansion": {
     "brace-expansion": {
       "version": "1.1.11",
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -479,6 +497,12 @@
         "flat-cache": "^2.0.1"
         "flat-cache": "^2.0.1"
       }
       }
     },
     },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "dev": true
+    },
     "find-up": {
     "find-up": {
       "version": "4.1.0",
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -804,6 +828,11 @@
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
       "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
     },
     },
+    "node-gzip": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz",
+      "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw=="
+    },
     "normalize-package-data": {
     "normalize-package-data": {
       "version": "2.5.0",
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",

+ 4 - 0
package.json

@@ -18,7 +18,11 @@
     "csv-parse": "^4.4.6",
     "csv-parse": "^4.4.6",
     "fast-csv": "^3.4.0",
     "fast-csv": "^3.4.0",
     "glob": "^7.1.4",
     "glob": "^7.1.4",
+    "node-gzip": "^1.1.2",
     "python-struct": "^1.1.1",
     "python-struct": "^1.1.1",
     "string-format": "^2.0.0"
     "string-format": "^2.0.0"
+  },
+  "devDependencies": {
+    "bigint-buffer": "^1.1.2"
   }
   }
 }
 }

+ 5 - 5
tl/mtprotoRequest.js

@@ -26,20 +26,20 @@ class MTProtoRequest {
         this.confirmReceived = true;
         this.confirmReceived = true;
     }
     }
 
 
-    needResend(){
+    needResend() {
         return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000);
         return this.dirty || (this.confirmed && !this.confirmReceived && new Date().getTime() - this.sendTime > 3000);
     }
     }
 
 
     // These should be overrode
     // These should be overrode
-    onSend(buffer){
-
+    onSend() {
+        throw Error("Not overload " + this.constructor.name);
     }
     }
 
 
-    onResponse(buffer){
+    onResponse(buffer) {
 
 
     }
     }
 
 
-    onException(exception){
+    onException(exception) {
 
 
     }
     }
 }
 }

+ 15 - 13
tl/session.js

@@ -1,5 +1,6 @@
 const Helpers = require("../utils/Helpers");
 const Helpers = require("../utils/Helpers");
-const fs = require("fs");
+const fs = require("fs").promises;
+const {existsSync,readFileSync} = require("fs");
 
 
 class Session {
 class Session {
     constructor(sessionUserId) {
     constructor(sessionUserId) {
@@ -9,9 +10,9 @@ class Session {
         this.authKey = undefined;
         this.authKey = undefined;
         this.id = Helpers.generateRandomLong(false);
         this.id = Helpers.generateRandomLong(false);
         this.sequence = 0;
         this.sequence = 0;
-        this.salt = 0; // Unsigned long
-        this.timeOffset = 0;
-        this.lastMessageId = 0;
+        this.salt = 0n; // Unsigned long
+        this.timeOffset = 0n;
+        this.lastMessageId = 0n;
         this.user = undefined;
         this.user = undefined;
     }
     }
 
 
@@ -20,7 +21,7 @@ class Session {
      */
      */
     async save() {
     async save() {
         if (this.sessionUserId) {
         if (this.sessionUserId) {
-            await fs.writeFile(`${this.sessionUserId}.session`, JSON.stringify(this));
+            //await fs.writeFile(`${this.sessionUserId}.session`, JSON.stringify(this));
         }
         }
     }
     }
 
 
@@ -29,25 +30,26 @@ class Session {
             return new Session();
             return new Session();
         }
         }
         let filepath = `${sessionUserId}.session`;
         let filepath = `${sessionUserId}.session`;
-        if (fs.existsSync(filepath)) {
-            return JSON.parse(fs.readFileSync(filepath, "utf-8"));
+        if (existsSync(filepath)) {
+            return JSON.parse(readFileSync(filepath, "utf-8"));
         } else {
         } else {
-            return Session(sessionUserId);
+            return new Session(sessionUserId);
         }
         }
     }
     }
 
 
     getNewMsgId() {
     getNewMsgId() {
         let msTime = new Date().getTime();
         let msTime = new Date().getTime();
-        let newMessageId = (BigInt(Math.floor(msTime / 1000) + this.timeOffset) << BigInt(32)) |
-            ((msTime % 1000) << 22) |
-            (Helpers.getRandomInt(0, 524288) << 2); // 2^19
+        let newMessageId = (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << 32n) |
+            (BigInt(msTime % 1000) << 22n) |
+            (BigInt(Helpers.getRandomInt(0, 524288)) << 2n); // 2^19
 
 
         if (this.lastMessageId >= newMessageId) {
         if (this.lastMessageId >= newMessageId) {
-            newMessageId = this.lastMessageId + 4;
+            newMessageId = this.lastMessageId + 4n;
         }
         }
         this.lastMessageId = newMessageId;
         this.lastMessageId = newMessageId;
         return newMessageId;
         return newMessageId;
     }
     }
 }
 }
 
 
-module.exports = Session;
+module.exports = Session;
+

+ 25 - 18
tl/telegramClient.js

@@ -1,7 +1,9 @@
-const {Session} = require("./Session");
-const {doAuthentication} = require("../network/authenticator");
-const {MtProtoSender} = require("../network/mtprotoSender");
-const {TcpTransport} = require("../network/tcpTransport");
+const Session = require("./Session");
+const doAuthentication = require("../network/authenticator");
+const MtProtoSender = require("../network/mtprotoSender");
+const MTProtoRequest = require("../tl/MTProtoRequest");
+const TcpTransport = require("../network/tcpTransport");
+
 const {InvokeWithLayerRequest, InitConnectionRequest} = require("../gramjs/tl/functions/index");
 const {InvokeWithLayerRequest, InitConnectionRequest} = require("../gramjs/tl/functions/index");
 const {GetConfigRequest} = require("../gramjs/tl/functions/help");
 const {GetConfigRequest} = require("../gramjs/tl/functions/help");
 
 
@@ -17,11 +19,10 @@ class TelegramClient {
         this.layer = layer;
         this.layer = layer;
 
 
         this.session = Session.tryLoadOrCreateNew(sessionUserId);
         this.session = Session.tryLoadOrCreateNew(sessionUserId);
-        this.transport = TcpTransport(this.session.serverAddress, this.session.port);
-
+        this.transport = new TcpTransport(this.session.serverAddress, this.session.port);
         //These will be set later
         //These will be set later
-        this.dcOptions = undefined;
-        this.sender = undefined;
+        this.dcOptions = null;
+        this.sender = null;
         this.phoneCodeHashes = Array();
         this.phoneCodeHashes = Array();
 
 
     }
     }
@@ -34,36 +35,40 @@ class TelegramClient {
      * @returns {Promise<Boolean>}
      * @returns {Promise<Boolean>}
      */
      */
     async connect(reconnect = false) {
     async connect(reconnect = false) {
+        await this.transport.connect();
         try {
         try {
             if (!this.session.authKey || reconnect) {
             if (!this.session.authKey || reconnect) {
-                let res = doAuthentication(this.transport);
+                let res = await doAuthentication(this.transport);
+                console.log("authenticated");
                 this.session.authKey = res.authKey;
                 this.session.authKey = res.authKey;
                 this.session.timeOffset = res.timeOffset;
                 this.session.timeOffset = res.timeOffset;
                 this.session.save();
                 this.session.save();
             }
             }
-            this.sender = MtProtoSender(this.transport, this.session);
-
+            this.sender = new MtProtoSender(this.transport, this.session);
+            let r = await this.invoke(new GetConfigRequest());
+            console.log(r);
+            process.exit(0)
             // Now it's time to send an InitConnectionRequest
             // Now it's time to send an InitConnectionRequest
             // This must always be invoked with the layer we'll be using
             // This must always be invoked with the layer we'll be using
-            let query = InitConnectionRequest({
+            let query = new InitConnectionRequest({
                 apiId: this.apiId,
                 apiId: this.apiId,
                 deviceModel: "PlaceHolder",
                 deviceModel: "PlaceHolder",
                 systemVersion: "PlaceHolder",
                 systemVersion: "PlaceHolder",
                 appVersion: "0.0.1",
                 appVersion: "0.0.1",
                 langCode: "en",
                 langCode: "en",
-                query: GetConfigRequest()
+                query: new GetConfigRequest()
             });
             });
-            let result = await this.invoke(InvokeWithLayerRequest({
+            let result = await this.invoke(new InvokeWithLayerRequest({
                 layer: this.layer,
                 layer: this.layer,
                 query: query
                 query: query
             }));
             }));
-
             // We're only interested in the DC options
             // We're only interested in the DC options
             // although many other options are available!
             // although many other options are available!
             this.dcOptions = result.dcOptions;
             this.dcOptions = result.dcOptions;
             return true;
             return true;
         } catch (error) {
         } catch (error) {
             console.log('Could not stabilise initial connection: {}'.replace("{}", error));
             console.log('Could not stabilise initial connection: {}'.replace("{}", error));
+            console.log(error.stack);
             return false;
             return false;
         }
         }
     }
     }
@@ -83,8 +88,9 @@ class TelegramClient {
                 break;
                 break;
             }
             }
         }
         }
-        this.transport.close();
+        await this.transport.close();
         this.transport = new TcpTransport(dc.ipAddress, dc.port);
         this.transport = new TcpTransport(dc.ipAddress, dc.port);
+        await this.transport.connect();
         this.session.server_address = dc.ipAddress;
         this.session.server_address = dc.ipAddress;
         this.session.port = dc.port;
         this.session.port = dc.port;
         this.session.save();
         this.session.save();
@@ -107,10 +113,11 @@ class TelegramClient {
      * @returns {Promise}
      * @returns {Promise}
      */
      */
     async invoke(request) {
     async invoke(request) {
-        if (!MTProtoRequest.prototype.isPrototypeOf(Object.getPrototypeOf(request).prototype)) {
-            throw new Error("You can only invoke MtProtoRequests");
+        if (!(request instanceof MTProtoRequest)) {
+            throw new Error("You can only invoke MTProtoRequests");
         }
         }
         await this.sender.send(request);
         await this.sender.send(request);
+
         await this.sender.receive(request);
         await this.sender.receive(request);
         return request.result;
         return request.result;
     }
     }

+ 123 - 33
utils/Helpers.js

@@ -6,14 +6,14 @@ class Helpers {
 
 
     /**
     /**
      * Generates a random long integer (8 bytes), which is optionally signed
      * Generates a random long integer (8 bytes), which is optionally signed
-     * @returns {number}
+     * @returns {BigInt}
      */
      */
     static generateRandomLong(signed) {
     static generateRandomLong(signed) {
-        let buf = Buffer.from(this.generateRandomBytes(8)); // 0x12345678 = 305419896
+        let buf = Buffer.from(Helpers.generateRandomBytes(8)); // 0x12345678 = 305419896
         if (signed)
         if (signed)
-            return buf.readInt32BE(0);
+            return buf.readBigInt64LE(0);
         else
         else
-            return buf.readUInt32LE(0);
+            return buf.readBigUInt64LE(0);
     }
     }
 
 
     /**
     /**
@@ -30,7 +30,7 @@ class Helpers {
      * @param path
      * @param path
      * @returns {Promise<void>}
      * @returns {Promise<void>}
      */
      */
-    static async loadSettings(path = "../api/settings") {
+    static async loadSettings(path = "api/settings") {
         let settings = {};
         let settings = {};
         let left, right, value_pair;
         let left, right, value_pair;
 
 
@@ -61,20 +61,39 @@ class Helpers {
      * @param shared_key
      * @param shared_key
      * @param msg_key
      * @param msg_key
      * @param client
      * @param client
-     * @returns {[*, *]}
+     * @returns {{iv: Buffer, key: Buffer}}
      */
      */
 
 
     static calcKey(shared_key, msg_key, client) {
     static calcKey(shared_key, msg_key, client) {
-        let x = client !== null ? 0 : 8;
+        let x = client === true ? 0 : 8;
         let iv, key, sha1a, sha1b, sha1c, sha1d;
         let iv, key, sha1a, sha1b, sha1c, sha1d;
-        sha1a = this.sha1((msg_key + shared_key.slice(x, (x + 32))));
-        sha1b = this.sha1(((shared_key.slice((x + 32), (x + 48)) + msg_key) + shared_key.slice((x + 48), (x + 64))));
-        sha1c = this.sha1((shared_key.slice((x + 64), (x + 96)) + msg_key));
-        sha1d = this.sha1((msg_key + shared_key.slice((x + 96), (x + 128))));
-        key = ((sha1a.slice(0, 8) + sha1b.slice(8, 20)) + sha1c.slice(4, 16));
-        iv = (((sha1a.slice(8, 20) + sha1b.slice(0, 8)) + sha1c.slice(16, 20)) + sha1d.slice(0, 8));
-        return [key, iv];
-
+        sha1a = Helpers.sha1(Buffer.concat([
+            msg_key,
+            shared_key.slice(x, (x + 32))
+        ]));
+        sha1b = Helpers.sha1(Buffer.concat([
+            shared_key.slice(x + 32, x + 48),
+            msg_key,
+            shared_key.slice(x + 48, x + 64)
+        ]));
+        sha1c = Helpers.sha1(Buffer.concat([
+            shared_key.slice(x + 64, x + 96),
+            msg_key
+        ]));
+        sha1d = Helpers.sha1(Buffer.concat([
+            msg_key,
+            shared_key.slice((x + 96), (x + 128))
+        ]));
+        key = Buffer.concat([
+            sha1a.slice(0, 8),
+            sha1b.slice(8, 20),
+            sha1c.slice(4, 16)]);
+        iv = Buffer.concat([
+            sha1a.slice(8, 20),
+            sha1b.slice(0, 8),
+            sha1c.slice(16, 20),
+            sha1d.slice(0, 8)]);
+        return {key, iv}
 
 
     }
     }
 
 
@@ -84,7 +103,7 @@ class Helpers {
      * @returns {Buffer}
      * @returns {Buffer}
      */
      */
     static calcMsgKey(data) {
     static calcMsgKey(data) {
-        return this.sha1(data).slice(4, 20);
+        return Helpers.sha1(data).slice(4, 20);
 
 
 
 
     }
     }
@@ -96,10 +115,10 @@ class Helpers {
      * @returns {{ivBuffer: Buffer, keyBuffer: Buffer}}
      * @returns {{ivBuffer: Buffer, keyBuffer: Buffer}}
      */
      */
     static generateKeyDataFromNonces(serverNonce, newNonce) {
     static generateKeyDataFromNonces(serverNonce, newNonce) {
-        let hash1 = this.sha1(Buffer.concat([newNonce, serverNonce]));
-        let hash2 = this.sha1(Buffer.concat([serverNonce, newNonce]));
-        let hash3 = this.sha1(Buffer.concat([newNonce, newNonce]));
-        let keyBuffer = Buffer.concat([hash1, hash1.slice(0, 12)]);
+        let hash1 = Helpers.sha1(Buffer.concat([newNonce, serverNonce]));
+        let hash2 = Helpers.sha1(Buffer.concat([serverNonce, newNonce]));
+        let hash3 = Helpers.sha1(Buffer.concat([newNonce, newNonce]));
+        let keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
         let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
         let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
         return {keyBuffer: keyBuffer, ivBuffer: ivBuffer}
         return {keyBuffer: keyBuffer, ivBuffer: ivBuffer}
     }
     }
@@ -116,32 +135,78 @@ class Helpers {
 
 
     }
     }
 
 
+    /**
+     * Reads a Telegram-encoded string
+     * @param buffer {Buffer}
+     * @param offset {number}
+     * @returns {{string: string, offset: number}}
+     */
+    static tgReadString(buffer, offset) {
+        let res = Helpers.tgReadByte(buffer, offset);
+        offset = res.offset;
+        let string = res.data.toString("utf8");
+        return {string, offset}
+    }
+
+    /**
+     *
+     * @param reader {Buffer}
+     * @param offset {number}
+     */
+    static tgReadObject(reader, offset) {
+        let constructorId = reader.readUInt32LE(offset);
+        offset += 4;
+        let clazz = tlobjects[constructorId];
+        if (clazz === undefined) {
+            /**
+             * The class was None, but there's still a
+             *  chance of it being a manually parsed value like bool!
+             */
+            if (constructorId === 0x997275b5) {
+                return true
+            } else if (constructorId === 0xbc799737) {
+                return false
+            }
+            throw Error("type not found "+ constructorId);
+        }
+        return undefined;
+    }
+
 
 
     /**
     /**
      *
      *
      * @param buffer {Buffer}
      * @param buffer {Buffer}
      * @param offset {Number}
      * @param offset {Number}
-     * @returns {{data: {Buffer}, offset: {Number}}}
+     * @returns {{data: Buffer, offset: Number}}
      */
      */
     static tgReadByte(buffer, offset) {
     static tgReadByte(buffer, offset) {
-        let firstByte = buffer.readInt8(offset);
+        let firstByte = buffer[offset];
         offset += 1;
         offset += 1;
         let padding, length;
         let padding, length;
-        if (firstByte === 255) {
-            length = buffer.readInt8(offset) | (buffer.readInt8(offset) << 8) | (buffer.readInt8(offset) << 16);
-            offset += 1;
+        if (firstByte === 254) {
+            length = buffer.readInt8(offset) | (buffer.readInt8(offset + 1) << 8) | (buffer.readInt8(offset + 2) << 16);
+            offset += 3;
             padding = length % 4;
             padding = length % 4;
         } else {
         } else {
             length = firstByte;
             length = firstByte;
             padding = (length + 1) % 4;
             padding = (length + 1) % 4;
         }
         }
-        let data = buffer.readInt8(offset);
-        offset += 1;
+
+        let data = buffer.slice(offset, offset + length);
+
+        offset += length;
+
         if (padding > 0) {
         if (padding > 0) {
             padding = 4 - padding;
             padding = 4 - padding;
             offset += padding;
             offset += padding;
         }
         }
-        return {data, offset}
+
+        return {data: data, offset: offset}
+    }
+
+
+    static tgWriteString(string) {
+        return Helpers.tgWriteBytes(Buffer.from(string, "utf8"));
     }
     }
 
 
     static tgWriteBytes(data) {
     static tgWriteBytes(data) {
@@ -153,26 +218,51 @@ class Helpers {
             if (padding !== 0) {
             if (padding !== 0) {
                 padding = 4 - padding;
                 padding = 4 - padding;
             }
             }
-            buffer = Buffer.from([data.length, data]);
+            buffer = Buffer.concat([Buffer.from([data.length]), data]);
         } else {
         } else {
             padding = data.length % 4;
             padding = data.length % 4;
             if (padding !== 0) {
             if (padding !== 0) {
                 padding = 4 - padding;
                 padding = 4 - padding;
             }
             }
-            buffer = Buffer.concat([Buffer.from([254]),
+            buffer = Buffer.concat([
+                Buffer.from([254]),
                 Buffer.from([data.length % 256]),
                 Buffer.from([data.length % 256]),
                 Buffer.from([(data.length >> 8) % 256]),
                 Buffer.from([(data.length >> 8) % 256]),
                 Buffer.from([(data.length >> 16) % 256]),
                 Buffer.from([(data.length >> 16) % 256]),
-                Buffer.from([data]),
-                Buffer.from([padding])
+                data,
             ]);
             ]);
 
 
         }
         }
-        return buffer;
+
+        return Buffer.concat([buffer, Buffer.alloc(padding).fill(0)]);
 
 
 
 
     }
     }
 
 
+    /**
+     * Fast mod pow for RSA calculation. a^b % n
+     * @param a
+     * @param b
+     * @param n
+     * @returns {bigint}
+     */
+    static modExp(a, b, n) {
+        a = a % n;
+        let result = 1n;
+        let x = a;
+        while (b > 0) {
+            let leastSignificantBit = b % 2n;
+            b = b / 2n;
+            if (leastSignificantBit === 1n) {
+                result = result * x;
+                result = result % n;
+            }
+            x = x * x;
+            x = x % n;
+        }
+        return result;
+    };
+
     /**
     /**
      * returns a random int from min (inclusive) and max (inclusive)
      * returns a random int from min (inclusive) and max (inclusive)
      * @param min
      * @param min