Pārlūkot izejas kodu

start implmenting network classes

painor 5 gadi atpakaļ
vecāks
revīzija
7703853511

+ 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,

+ 1 - 0
crypto/AuthKey.js

@@ -18,3 +18,4 @@ class AuthKey {
 
 }
 
+exports.AuthKey = AuthKey;

+ 1 - 0
crypto/Factorizator.js

@@ -88,3 +88,4 @@ class Factorizator {
         return {divisor: divisor, divided: Math.floor(pq / divisor)}
     }
 }
+exports.Factorizator = Factorizator;

+ 5 - 0
network/authenticator.js

@@ -0,0 +1,5 @@
+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;

+ 57 - 0
network/mtprotoPlainSender.js

@@ -0,0 +1,57 @@
+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;

+ 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;
+    }
+
+
+}

+ 8 - 0
package.json

@@ -0,0 +1,8 @@
+{
+  "name": "gramjs",
+  "version": "1.0.0",
+  "dependencies": {},
+  "devDependencies": {
+    "crc": "^3.8.0"
+  }
+}

+ 7 - 0
utils/Helpers.js

@@ -125,6 +125,13 @@ 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;