|
@@ -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
|
|
}
|
|
}
|
|
|
|
|