Ver código fonte

Merge pull request #5 from gram-js/network

add Network methods
painor 5 anos atrás
pai
commit
55f71e6ae4

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 .idea/
 package-lock.json
+package.json
 node_modules

+ 2 - 0
.idea/.gitignore

@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml

+ 0 - 21
LICENSE

@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2019 gram-js
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# gramjs
-NodeJS MTProto API Telegram client library,

+ 4 - 3
crypto/AuthKey.js

@@ -6,15 +6,16 @@ class AuthKey {
 
         let buffer = Buffer.from(helper.sha1(data));
 
-        this.aux_hash = buffer.slice(0, 8).readBigUInt64LE();
-        this.key_id = buffer.slice(12, 20).readBigUInt64LE();
+        this.auxHash = buffer.slice(0, 8).readBigUInt64LE();
+        this.keyId = buffer.slice(12, 20).readBigUInt64LE();
 
     }
 
     calcNewNonceHash(new_nonce, number) {
-        let buffer = Buffer.concat([new_nonce, number, this.aux_hash]);
+        let buffer = Buffer.concat([new_nonce, number, this.auxHash]);
         return helper.calcMsgKey(buffer);
     }
 
 }
 
+exports.AuthKey = AuthKey;

+ 3 - 1
crypto/Factorizator.js

@@ -48,7 +48,7 @@ class Factorizator {
                 }
 
             }
-            if (g>1){
+            if (g > 1) {
                 break;
             }
         }
@@ -89,3 +89,5 @@ class Factorizator {
         return {divisor: divisor, divided: Math.floor(pq / divisor)}
     }
 }
+
+exports.Factorizator = Factorizator;

+ 15 - 0
crypto/RSA.js

@@ -11,6 +11,21 @@ class RSA {
             '81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
             '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
             '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 16), parseInt('010001', 16)),
+        'c3b42b026ce86b21': new RSAServerKey("c3b42b026ce86b21", parseInt('MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6' +
+            'lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS' +
+            'an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw' +
+            'Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+' +
+            '8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n' +
+            'Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB', 64), parseInt('010001', 16)),
+        '9a996a1db11c729b': new RSAServerKey("9a996a1db11c729b", parseInt('C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9' +
+            '1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E' +
+            '580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F' +
+            '9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934' +
+            'EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F' +
+            '81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F' +
+            '6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1' +
+            '5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F', 64), parseInt('010001', 16)),
+
 // -4344800451088585951
         '-4344800451088585951': new RSAServerKey(  // Telegram servers //1
 // -----BEGIN RSA PUBLIC KEY-----

+ 251 - 0
network/authenticator.js

@@ -0,0 +1,251 @@
+const AES = require("../crypto/AES").AES;
+const AuthKey = require("../crypto/AuthKey").AuthKey;
+const Factorizator = require("../crypto/Factorizator").Factorizator;
+const RSA = require("../crypto/RSA").RSA;
+const MtProtoPlainSender = require("./mtprotoPlainSender").MtProtoPlainSender;
+const Helpers = require("../utils/Helpers").helpers;
+
+function doAuthentication(transport) {
+    let sender = MtProtoPlainSender(transport);
+
+    // Step 1 sending: PQ request
+    let nonce = Helpers.generateRandomBytes(16);
+    let buffer = Buffer.alloc(32);
+    buffer.writeUInt32LE(0x60469778, 0);
+    buffer = Buffer.concat([buffer, nonce]);
+    sender.send(buffer);
+
+    // Step 1 response: PQ request
+    let pq = null;
+    let serverNonce = null;
+    let fingerprints = Array();
+    buffer = sender.receive();
+    let responseCode = buffer.readUInt32LE(0);
+    if (responseCode !== 0x05162463) {
+        throw Error("invalid response code");
+    }
+    let nonceFromServer = buffer.read(16, 8);
+    if (nonce !== nonceFromServer) {
+        throw Error("Invalid nonce from server");
+    }
+    serverNonce = buffer.read(16, 12);
+
+    let {pqBytes, newOffset} = Helpers.tgReadByte(buffer, 12);
+    pq = buffer.readBigInt64BE(newOffset);
+    newOffset += 8;
+    let vectorId = buffer.readInt8(newOffset);
+    newOffset += 1;
+    if (vectorId !== 0x1cb5c415) {
+        throw Error("vector error");
+    }
+    let fingerprints_count = buffer.readInt8(newOffset);
+    for (let i = 0; i < fingerprints_count; i++) {
+        fingerprints.push(buffer.readInt32LE(newOffset));
+        newOffset += 8;
+    }
+
+    // Step 2 sending: DH Exchange
+    let newNonce = Helpers.generateRandomBytes(32);
+    let {p, q} = Factorizator.factorize(pq);
+    let tempBuffer = Buffer.alloc(8);
+    tempBuffer.writeUIntLE(0x83c95aec, 0, 8);
+    let pqInnerData = Buffer.concat([
+        tempBuffer,
+        Helpers.tgWriteBytes(getByteArray(pq, false)),
+        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
+        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
+        nonce,
+        serverNonce,
+        newNonce,
+    ]);
+    let cipherText, targetFingerprint;
+    for (let fingerprint of fingerprints) {
+        cipherText = RSA.encrypt(getFingerprintText(fingerprint), pqInnerData);
+        if (cipherText !== undefined) {
+            targetFingerprint = fingerprint;
+            break;
+        }
+    }
+    if (cipherText === undefined) {
+        throw Error("Could not find a valid key for fingerprints");
+    }
+    tempBuffer = Buffer.alloc(8);
+    tempBuffer.writeUIntLE(0xd712e4be, 0, 8);
+
+    let reqDhParams = Buffer.concat([
+        tempBuffer,
+        nonce,
+        serverNonce,
+        Helpers.tgWriteBytes(getByteArray(Math.min(p, q), false)),
+        Helpers.tgWriteBytes(getByteArray(Math.max(p, q), false)),
+        targetFingerprint,
+        Helpers.tgWriteBytes(cipherText)
+    ]);
+    sender.send(reqDhParams);
+    // Step 2 response: DH Exchange
+    newOffset = 0;
+    let reader = sender.receive();
+    responseCode = reader.readInt32LE(newOffset);
+    newOffset += 4;
+    if (responseCode === 0x79cb045d) {
+        throw Error("Server DH params fail: TODO ");
+    }
+    if (responseCode !== 0xd0e8075c) {
+        throw Error("Invalid response code: TODO ");
+    }
+    nonceFromServer = reader.readIntLE(newOffset, 16);
+    newOffset += 16;
+    if (nonceFromServer !== nonce) {
+        throw Error("Invalid nonce from server");
+    }
+    let serverNonceFromServer = reader.readIntLE(newOffset, 16);
+    if (serverNonceFromServer !== nonceFromServer) {
+        throw Error("Invalid server nonce from server");
+    }
+    newOffset += 16;
+    let encryptedAnswer = Helpers.tgReadByte(reader, newOffset).data;
+
+    // Step 3 sending: Complete DH Exchange
+
+    let {key, iv} = Helpers.generateKeyDataFromNonces(serverNonce, newNonce);
+    let plainTextAnswer = AES.decryptIge(encryptedAnswer, key, iv);
+    let g, dhPrime, ga, timeOffset;
+    let dhInnerData = plainTextAnswer;
+    newOffset = 20;
+    let code = dhInnerData.readUInt32BE(newOffset);
+    if (code !== 0xb5890dba) {
+        throw Error("Invalid DH Inner Data code:")
+    }
+    newOffset += 4;
+    let nonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
+    if (nonceFromServer1 !== nonceFromServer) {
+        throw Error("Invalid nonce in encrypted answer");
+    }
+    newOffset += 16;
+    let serverNonceFromServer1 = dhInnerData.readIntLE(newOffset, 16);
+    if (serverNonceFromServer1 !== serverNonce) {
+        throw Error("Invalid server nonce in encrypted answer");
+    }
+    newOffset += 16;
+    g = dhInnerData.readInt32LE(newOffset);
+    newOffset += 4;
+    let temp = Helpers.tgReadByte(dhInnerData, newOffset);
+    newOffset += temp.offset;
+
+    dhPrime = temp.data.readUInt32BE(0);
+    temp = Helpers.tgReadByte(dhInnerData, newOffset);
+    newOffset += temp.offset;
+    ga = temp.data.readUInt32BE(0);
+    let serverTime = dhInnerData.readInt32LE(newOffset);
+    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;
+
+    // Prepare client DH Inner Data
+
+    tempBuffer = Buffer.alloc(8);
+    tempBuffer.writeUIntLE(0x6643b654, 0, 8);
+    let clientDHInnerData = Buffer.concat([
+        tempBuffer,
+        nonce,
+        serverNonce,
+        Buffer.alloc(8).fill(0),
+        Helpers.tgWriteBytes(getByteArray(gb, false)),
+    ]);
+    let clientDhInnerData = Buffer.concat([
+        Helpers.sha1(clientDHInnerData),
+        clientDHInnerData
+    ]);
+
+    // Encryption
+    let clientDhInnerDataEncrypted = AES.encryptIge(clientDhInnerData, key, iv);
+
+    // Prepare Set client DH params
+    tempBuffer = Buffer.alloc(8);
+    tempBuffer.writeUIntLE(0xf5045f1f, 0, 8);
+    let setClientDhParams = Buffer.concat([
+        tempBuffer,
+        nonce,
+        serverNonce,
+        Helpers.tgWriteBytes(clientDhInnerDataEncrypted),
+    ]);
+    sender.send(setClientDhParams);
+
+    // Step 3 response: Complete DH Exchange
+    reader = sender.receive();
+    newOffset = 0;
+    code = reader.readUInt32LE(newOffset);
+    newOffset += 4;
+    if (code === 0x3bcbf734) { //  DH Gen OK
+        nonceFromServer = reader.readIntLE(newOffset, 16);
+        newOffset += 16;
+        if (nonceFromServer !== nonce) {
+            throw Error("Invalid nonce from server");
+        }
+        serverNonceFromServer = reader.readIntLE(newOffset, 16);
+        newOffset += 16;
+        if (serverNonceFromServer !== serverNonce) {
+            throw Error("Invalid server nonce from server");
+        }
+        let newNonceHash1 = reader.readIntLE(newOffset, 16);
+        let authKey = AuthKey(getByteArray(gab, false));
+        let newNonceHashCalculated = authKey.calcNewNonceHash(newNonce, 1);
+        if (newNonceHash1 !== newNonceHashCalculated) {
+            throw Error("Invalid new nonce hash");
+        }
+        return {authKey,timeOffset};
+    }
+    else if (code===0x46dc1fb9){
+        throw Error("dh_gen_retry");
+
+    }
+    else if (code===0x46dc1fb9){
+        throw Error("dh_gen_fail");
+
+    }else{
+        throw Error("DH Gen unknown");
+
+    }
+
+}
+
+function rightJustify(string, length, char) {
+    let fill = [];
+    while (fill.length + string.length < length) {
+        fill[fill.length] = char;
+    }
+    return fill.join('') + string;
+}
+
+/**
+ * Gets a fingerprint text in 01-23-45-67-89-AB-CD-EF format (no hyphens)
+ * @param fingerprint {Array}
+ * @returns {string}
+ */
+function getFingerprintText(fingerprint) {
+    let res = "";
+    for (let b of fingerprint) {
+        res += rightJustify(b.toString(16), 2, '0').toUpperCase();
+    }
+    return res;
+}
+
+/**
+ *
+ * @param integer {number,BigInt}
+ * @param signed {boolean}
+ * @returns {number}
+ */
+function getByteArray(integer, signed) {
+    let bits = integer.toString(2).length;
+    let byteLength = Math.floor((bits + 8 - 1) / 8);
+    let buffer = Buffer.alloc(byteLength);
+    if (signed) {
+        return buffer.readIntLE(0, byteLength);
+
+    } else {
+        return buffer.readUIntLE(0, byteLength);
+
+    }
+}

+ 58 - 0
network/mtprotoPlainSender.js

@@ -0,0 +1,58 @@
+const helpers = require("../utils/Helpers").helpers;
+
+/**
+ * MTProto Mobile Protocol plain sender (https://core.telegram.org/mtproto/description#unencrypted-messages)
+ */
+class MtProtoPlainSender {
+    constructor(transport) {
+        this._sequence = 0;
+        this._timeOffset = 0;
+        this._lastMsgId = 0;
+        this._transport = transport;
+    }
+
+    /**
+     * Sends a plain packet (auth_key_id = 0) containing the given message body (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);
+    }
+
+    /**
+     * Receives a plain packet, returning the body of the response
+     * @returns {Buffer}
+     */
+    receive() {
+        let {seq, body} = this._transport.receive();
+        let message_length = body.readInt32LE(16);
+        return body.slice(20, message_length);
+
+    }
+
+    /**
+     * Generates a new message ID based on the current time (in ms) since epoch
+     * @returns {BigInt}
+     */
+    getNewMsgId() {
+        //See https://core.telegram.org/mtproto/description#message-identifier-msg-id
+        let msTime = Date.now();
+        let newMsgId = ((BigInt(Math.floor(msTime / 1000)) << BigInt(32)) | // "must approximately equal unixtime*2^32"
+            (BigInt(msTime % 1000) << BigInt(32)) | // "approximate moment in time the message was created"
+            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
+        if (this._lastMsgId >= newMsgId) {
+            newMsgId = this._lastMsgId + 4
+        }
+        this._lastMsgId = newMsgId;
+        return BigInt(newMsgId);
+
+    }
+
+}
+
+exports.MtProtoPlainSender = MtProtoPlainSender;

+ 315 - 0
network/mtprotoSender.js

@@ -0,0 +1,315 @@
+const MtProtoPlainSender = require("./mtprotoPlainSender").MtProtoPlainSender;
+const Helpers = require("../utils/Helpers");
+
+/**
+ * MTProto Mobile Protocol sender (https://core.telegram.org/mtproto/description)
+ */
+class MtProtoSender {
+    constructor(transport, session) {
+        this.transport = transport;
+        this.session = session;
+        this.needConfirmation = Array(); // Message IDs that need confirmation
+        this.onUpdateHandlers = Array();
+
+    }
+
+    /**
+     * Disconnects
+     */
+    disconnect() {
+        this.setListenForUpdates(false);
+        this.transport.close();
+    }
+
+    /**
+     * Adds an update handler (a method with one argument, the received
+     * TLObject) that is fired when there are updates available
+     * @param handler {function}
+     */
+    addUpdateHandler(handler) {
+        let firstHandler = Boolean(this.onUpdateHandlers.length);
+        this.onUpdateHandlers.push(handler);
+        // If this is the first added handler,
+        // we must start receiving updates
+        if (firstHandler) {
+            this.setListenForUpdates(true);
+        }
+    }
+
+    /**
+     * Removes an update handler (a method with one argument, the received
+     * TLObject) that is fired when there are updates available
+     * @param handler {function}
+     */
+    removeUpdateHandler(handler) {
+        let index = this.onUpdateHandlers.indexOf(handler);
+        if (index !== -1) this.onUpdateHandlers.splice(index, 1);
+        if (!Boolean(this.onUpdateHandlers.length)) {
+            this.setListenForUpdates(false);
+
+        }
+    }
+
+    /**
+     *
+     * @param confirmed {boolean}
+     * @returns {number}
+     */
+    generateSequence(confirmed) {
+        if (confirmed) {
+            let result = this.session.sequence * 2 + 1;
+            this.session.sequence += 1;
+            return result;
+        } else {
+            return this.session.sequence * 2;
+        }
+    }
+
+    /**
+     * Sends the specified MTProtoRequest, previously sending any message
+     * which needed confirmation. This also pauses the updates thread
+     * @param request {MtProtoPlainSender}
+     * @param resend
+     */
+    send(request, resend = false) {
+        let buffer;
+        //If any message needs confirmation send an AckRequest first
+        if (Boolean(this.needConfirmation.length)) {
+            let msgsAck = MsgsAck(this.needConfirmation);
+
+            buffer = msgsAck.onSend();
+            this.sendPacket(buffer, msgsAck);
+            this.needConfirmation.length = 0;
+        }
+        //Finally send our packed request
+        buffer = request.on_send();
+        this.sendPacket(buffer, request);
+
+        //And update the saved session
+        this.session.save();
+
+    }
+
+    receive(request) {
+        try {
+            //Try until we get an update
+            while (!request.confirmReceive()) {
+                let {seq, body} = this.transport.receive();
+                let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
+                this.processMsg(remoteMsgId, remoteSequence, message, request);
+            }
+        } catch (e) {
+
+        }
+    }
+
+    // region Low level processing
+    /**
+     * Sends the given packet bytes with the additional
+     * information of the original request.
+     * @param packet
+     * @param request
+     */
+    sendPacket(packet, request) {
+        request.msgId = this.session.getNewMsgId();
+
+        // First Calculate plainText to encrypt it
+        let first = Buffer.alloc(8);
+        let second = Buffer.alloc(8);
+        let third = Buffer.alloc(8);
+        let forth = Buffer.alloc(4);
+        let fifth = Buffer.alloc(4);
+        first.writeBigUInt64LE(this.session.salt, 0);
+        second.writeBigUInt64LE(this.session.id, 0);
+        third.writeBigUInt64LE(request.msgId, 0);
+        forth.writeInt32LE(this.generateSequence(request.confirmed), 0);
+        fifth.writeInt32LE(packet.length, 0);
+        let plain = Buffer.concat([
+            first,
+            second,
+            third,
+            forth,
+            fifth,
+            packet
+        ]);
+        let msgKey = Helpers.calcMsgKey(plain);
+        let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, true);
+        let cipherText = AES.encryptIge(plain, key, iv);
+
+        //And then finally send the encrypted packet
+
+        first = Buffer.alloc(8);
+        first.writeUInt32LE(this.session.authKey.keyId, 0);
+        let cipher = Buffer.concat([
+            first,
+            msgKey,
+            cipherText,
+        ]);
+        this.transport.send(cipher);
+    }
+
+    decodeMsg(body) {
+        if (body.length < 8) {
+            throw Error("Can't decode packet");
+        }
+        let offset = 8;
+        let msgKey = body.readIntLE(offset, 16);
+        offset += 16;
+        let {key, iv} = Helpers.calcKey(this.session.authKey.key, msgKey, false);
+        let plainText = AES.decryptIge(body.readIntLE(offset, body.length - offset), key, iv);
+        offset = 0;
+        let remoteSalt = plainText.readBigInt64LE(offset);
+        offset += 8;
+        let remoteSessionId = plainText.readBigInt64LE(offset);
+        offset += 8;
+        let remoteSequence = plainText.readBigInt64LE(offset);
+        offset += 8;
+        let remoteMsgId = plainText.readInt32LE(offset);
+        offset += 4;
+        let msgLen = plainText.readInt32LE(offset);
+        offset += 4;
+        let message = plainText.readIntLE(offset, msgLen);
+        return {message, remoteMsgId, remoteSequence}
+    }
+
+    processMsg(msgId, sequence, reader, offset, request = undefined) {
+        this.needConfirmation.push(msgId);
+        let code = reader.readUInt32LE(offset);
+        offset -= 4;
+
+        // The following codes are "parsed manually"
+        if (code === 0xf35c6d01) {  //rpc_result, (response of an RPC call, i.e., we sent a request)
+            return this.handleRpcResult(msgId, sequence, reader, request);
+        }
+
+        if (code === 0x73f1f8dc) {  //msg_container
+            return this.handlerContainer(msgId, sequence, reader, request);
+        }
+        if (code === 0x3072cfa1) {  //gzip_packed
+            return this.handlerGzipPacked(msgId, sequence, reader, request);
+        }
+        if (code === 0xedab447b) {  //bad_server_salt
+            return this.handleBadServerSalt(msgId, sequence, reader, request);
+        }
+        if (code === 0xa7eff811) {  //bad_msg_notification
+            return this.handleBadMsgNotification(msgId, sequence, reader);
+        }
+        /**
+         * 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,
+         * if we can first find a matching TLObject
+         */
+        if (tlobjects.contains(code)) {
+            return this.handleUpdate(msgId, sequence, reader);
+        }
+        console.log("Unknown message");
+        return false;
+    }
+
+    // region Message handling
+
+    handleUpdate(msgId, sequence, reader) {
+        let tlobject = Helpers.tgReadObject(reader);
+        for (let handler of this.onUpdateHandlers) {
+            handler(tlobject);
+        }
+        return Float32Array
+    }
+
+    handleContainer(msgId, sequence, reader, offset, request) {
+        let code = reader.readUInt32LE(offset);
+        offset += 4;
+        let size = reader.readInt32LE(offset);
+        offset += 4;
+        for (let i = 0; i < size; i++) {
+            let innerMsgId = reader.readBigUInt64LE(offset);
+            offset += 8;
+            let innerSequence = reader.readBigInt64LE(offset);
+            offset += 8;
+            let innerLength = reader.readInt32LE(offset);
+            offset += 4;
+            if (!this.processMsg(innerMsgId, sequence, reader, request)) {
+                offset += innerLength;
+            }
+        }
+        return false;
+    }
+
+    handleBadServerSalt(msgId, sequence, reader, offset, request) {
+        let code = reader.readUInt32LE(offset);
+        offset += 4;
+        let badMsgId = reader.readUInt32LE(offset);
+        offset += 4;
+        let badMsgSeqNo = reader.readInt32LE(offset);
+        offset += 4;
+        let errorCode = reader.readInt32LE(offset);
+        offset += 4;
+        let newSalt = reader.readUInt32LE(offset);
+        offset += 4;
+        this.session.salt = newSalt;
+
+        if (!request) {
+            throw Error("Tried to handle a bad server salt with no request specified");
+        }
+
+        //Resend
+        this.send(request, true);
+        return true;
+    }
+
+    handleBadMsgNotification(msgId, sequence, reader, offset) {
+        let code = reader.readUInt32LE(offset);
+        offset += 4;
+        let requestId = reader.readUInt32LE(offset);
+        offset += 4;
+        let requestSequence = reader.readInt32LE(offset);
+        offset += 4;
+        let errorCode = reader.readInt32LE(offset);
+        return BadMessageError(errorCode);
+    }
+
+    handleRpcResult(msgId, sequence, reader, offset, request) {
+        if (!request) {
+            throw Error("RPC results should only happen after a request was sent");
+        }
+
+        let code = reader.readUInt32LE(offset);
+        offset += 4;
+        let requestId = reader.readUInt32LE(offset);
+        offset += 4;
+        let innerCode = reader.readUInt32LE(offset);
+        offset += 4;
+        if (requestId === request.msgId) {
+            request.confirmReceived = true;
+        }
+
+        if (innerCode === 0x2144ca19) {  // RPC Error
+            // TODO add rpc logic
+            throw Error("error");
+        } else {
+            // TODO
+        }
+    }
+
+    handleGzipPacked(msgId, sequence, reader, offset, request) {
+        // TODO
+    }
+
+    setListenForUpdates(enabled) {
+
+        if (enabled) {
+            console.log("Enabled updates");
+        } else {
+            console.log("Disabled updates");
+        }
+    }
+
+    updatesListenMethod() {
+        while (true) {
+            let {seq, body} = this.transport.receive();
+            let {message, remoteMsgId, remoteSequence} = this.decodeMsg(body);
+            this.processMsg(remoteMsgId, remoteSequence, message);
+
+        }
+    }
+}

+ 74 - 0
network/tcpClient.js

@@ -0,0 +1,74 @@
+const Socket = require("net").Socket;
+const TextEncoder = require("util").TextEncoder;
+const sleep = require("../utils/Helpers").helpers.sleep;
+
+class TcpClient {
+    constructor() {
+        this.connected = false;
+        this.socket = new Socket();
+        this.canceled = false;
+        this.delay = 100;
+    }
+
+    /**
+     * Connects to the specified IP and port number
+     * @param ip
+     * @param port
+     */
+    async connect(ip, port) {
+        this.socket.connect({host: ip, port: port});
+        this.connected = true;
+
+    }
+
+    /**
+     * Closes the connection
+     */
+    async close() {
+        this.socket.destroy();
+        this.connected = true;
+    }
+
+    /**
+     * Writes (sends) the specified bytes to the connected peer
+     * @param data
+     */
+    async write(data) {
+        this.socket.write(data);
+    }
+
+    /**
+     * Reads (receives) the specified bytes from the connected peer
+     * @param bufferSize
+     * @returns {Buffer}
+     */
+    async read(bufferSize) {
+        this.canceled = false;
+        let buffer = Buffer.alloc(0);
+
+        let writtenCount = 0;
+        while (writtenCount < bufferSize) {
+            let leftCount = bufferSize - writtenCount;
+            let partial = this.socket.read(leftCount);
+            if (partial == null) {
+                await sleep(this.delay);
+                continue;
+            }
+            buffer = Buffer.concat([buffer, partial]);
+            writtenCount += buffer.byteLength;
+        }
+
+        return buffer;
+    }
+
+    /**
+     * Cancels the read operation IF it hasn't yet
+     * started, raising a ReadCancelledError
+     */
+    cancelRead() {
+        this.canceled = true;
+    }
+
+}
+
+exports.TcpClient = TcpClient;

+ 83 - 0
network/tcpTransport.js

@@ -0,0 +1,83 @@
+const TcpClient = require("./tcpClient").TcpClient;
+const crc = require('crc');
+
+class TcpTransport {
+    constructor(ipAddress, port) {
+        this.tcpClient = new TcpClient();
+        this.sendCounter = 0;
+        this.ipAddress = ipAddress;
+        this.port = port;
+    }
+
+    async connect() {
+        await this.tcpClient.connect(this.ipAddress, this.port);
+    }
+
+    /**
+     * Sends the given packet (bytes array) to the connected peer
+     * Original reference: https://core.telegram.org/mtproto#tcp-transport
+     * The packets are encoded as: total length, sequence number, packet and checksum (CRC32)
+     * @param packet
+     */
+    send(packet) {
+        if (this.tcpClient.connected) {
+            throw Error("not connected");
+        }
+        let buffer = Buffer.alloc(4 + 4);
+        buffer.writeInt32LE(packet.length + 12, 0);
+        buffer.writeInt32LE(this.sendCounter, 4);
+        buffer = Buffer.concat([buffer, packet]);
+        let tempBuffer = Buffer.alloc(4);
+        tempBuffer.writeUInt32LE(crc.crc32(buffer), 0);
+        buffer = Buffer.concat([buffer, tempBuffer]);
+        this.tcpClient.write(buffer);
+        this.sendCounter++;
+    }
+
+    /**
+     * Receives a TCP message (tuple(sequence number, body)) from the connected peer
+     * @returns {{body: {Buffer}, seq: {Buffer}}}
+     */
+    receive() {
+        /**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);
+        /**Then perform the checks**/
+        let rv = Buffer.concat([packetLengthBytes, seqBytes, body]);
+        let validChecksum = crc.crc32(rv);
+        if (checksum !== validChecksum) {
+            throw Error("invalid checksum");
+        }
+        /** If we passed the tests, we can then return a valid TCP message**/
+
+        return {seq, body}
+    }
+
+    close() {
+        if (this.tcpClient.connected) {
+            this.tcpClient.close();
+        }
+    }
+
+    /**
+     * Cancels (stops) trying to receive from the
+     * remote peer and raises an {Error}
+     */
+    cancelReceive() {
+        this.tcpClient.cancelRead();
+    }
+
+    /**
+     * Gets the client read delay
+     * @returns {number}
+     */
+    getClientDelay() {
+        return this.tcpClient.delay;
+    }
+
+
+}

+ 9 - 0
package.json

@@ -0,0 +1,9 @@
+{
+  "name": "gramjs",
+  "version": "1.0.0",
+  "dependencies": {},
+  "devDependencies": {
+    "aes-js": "^3.1.2",
+    "crc": "^3.8.0"
+  }
+}

+ 67 - 1
utils/Helpers.js

@@ -116,6 +116,63 @@ class Helpers {
 
     }
 
+
+    /**
+     *
+     * @param buffer {Buffer}
+     * @param offset {Number}
+     * @returns {{data: {Buffer}, offset: {Number}}}
+     */
+    static tgReadByte(buffer, offset) {
+        let firstByte = buffer.readInt8(offset);
+        offset += 1;
+        let padding, length;
+        if (firstByte === 255) {
+            length = buffer.readInt8(offset) | (buffer.readInt8(offset) << 8) | (buffer.readInt8(offset) << 16);
+            offset += 1;
+            padding = length % 4;
+        } else {
+            length = firstByte;
+            padding = (length + 1) % 4;
+        }
+        let data = buffer.readInt8(offset);
+        offset += 1;
+        if (padding > 0) {
+            padding = 4 - padding;
+            offset += padding;
+        }
+        return {data, offset}
+    }
+
+    static tgWriteBytes(data) {
+        let buffer;
+        let padding;
+
+        if (data.length < 254) {
+            padding = (data.length + 1) % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            buffer = Buffer.from([data.length, data]);
+        } else {
+            padding = data.length % 4;
+            if (padding !== 0) {
+                padding = 4 - padding;
+            }
+            buffer = Buffer.concat([Buffer.from([254]),
+                Buffer.from([data.length % 256]),
+                Buffer.from([(data.length >> 8) % 256]),
+                Buffer.from([(data.length >> 16) % 256]),
+                Buffer.from([data]),
+                Buffer.from([padding])
+            ]);
+
+        }
+        return buffer;
+
+
+    }
+
     /**
      * returns a random int from min (inclusive) and max (inclusive)
      * @param min
@@ -128,6 +185,15 @@ class Helpers {
         return Math.floor(Math.random() * (max - min + 1)) + min;
     }
 
+    /**
+     * Sleeps a specified amount of time
+     * @param ms time in milliseconds
+     * @returns {Promise}
+     */
+    static sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
 }
 
-exports.helpers = Helpers;
+let l = Buffer.from(0x83c95aec);
+console.log(l.length);
+