const struct = require('python-struct') const Helpers = require('../Helpers') const AES = require('../crypto/AES') const BinaryReader = require('../extensions/BinaryReader') const GZIPPacked = require('../tl/core/GZIPPacked') const { TLMessage } = require('../tl/core') const { SecurityError, InvalidBufferError } = require('../errors/Common') const { InvokeAfterMsgRequest } = require('../tl/functions') class MTProtoState { /** * `telethon.network.mtprotosender.MTProtoSender` needs to hold a state in order to be able to encrypt and decrypt incoming/outgoing messages, as well as generating the message IDs. Instances of this class hold together all the required information. It doesn't make sense to use `telethon.sessions.abstract.Session` for the sender because the sender should *not* be concerned about storing this information to disk, as one may create as many senders as they desire to any other data center, or some CDN. Using the same session for all these is not a good idea as each need their own authkey, and the concept of "copying" sessions with the unnecessary entities or updates state for these connections doesn't make sense. While it would be possible to have a `MTProtoPlainState` that does no encryption so that it was usable through the `MTProtoLayer` and thus avoid the need for a `MTProtoPlainSender`, the `MTProtoLayer` is more focused to efficiency and this state is also more advanced (since it supports gzipping and invoking after other message IDs). There are too many methods that would be needed to make it convenient to use for the authentication process, at which point the `MTProtoPlainSender` is better * @param authKey * @param loggers */ constructor(authKey, loggers) { this.authKey = authKey this._log = loggers this.timeOffset = 0 this.salt = 0 this.id = this._sequence = this._lastMsgId = null this.reset() } /** * Resets the state */ reset() { // Session IDs can be random on every connection this.id = Helpers.generateRandomLong(true) this._sequence = 0 this._lastMsgId = 0 } /** * Updates the message ID to a new one, * used when the time offset changed. * @param message */ updateMessageId(message) { message.msgId = this._getNewMsgId() } /** * Calculate the key based on Telegram guidelines, specifying whether it's the client or not * @param authKey * @param msgKey * @param client * @returns {{iv: Buffer, key: Buffer}} */ _calcKey(authKey, msgKey, client) { const x = client === true ? 0 : 8 const sha256a = Helpers.sha256(Buffer.concat([msgKey, authKey.slice(x, x + 36)])) const sha256b = Helpers.sha256(Buffer.concat([authKey.slice(x + 40, x + 76), msgKey])) const key = Buffer.concat([sha256a.slice(0, 8), sha256b.slice(8, 24), sha256a.slice(24, 32)]) const iv = Buffer.concat([sha256b.slice(0, 8), sha256a.slice(8, 24), sha256b.slice(24, 32)]) return { key, iv } } /** * Writes a message containing the given data into buffer. * Returns the message id. * @param buffer * @param data * @param contentRelated * @param afterId */ async writeDataAsMessage(buffer, data, contentRelated, afterId) { const msgId = this._getNewMsgId() const seqNo = this._getSeqNo(contentRelated) let body if (!afterId) { body = await GZIPPacked.gzipIfSmaller(contentRelated, data) } else { body = await GZIPPacked.gzipIfSmaller(contentRelated, new InvokeAfterMsgRequest(afterId, data).toBuffer()) } buffer.write(struct.pack('= newMsgId) { newMsgId = this._lastMsgId + 4n } this._lastMsgId = newMsgId return newMsgId } /** * Updates the time offset to the correct * one given a known valid message ID. * @param correctMsgId */ updateTimeOffset(correctMsgId) { const bad = this._getNewMsgId() const old = this.timeOffset const now = Math.floor(new Date().getTime() / 1000) const correct = correctMsgId >> 32n this.timeOffset = correct - now if (this.timeOffset !== old) { this._lastMsgId = 0 this._log.debug( `Updated time offset (old offset ${old}, bad ${bad}, good ${correctMsgId}, new ${this.timeOffset})`, ) } return this.timeOffset } /** * Generates the next sequence number depending on whether * it should be for a content-related query or not. * @param contentRelated * @private */ _getSeqNo(contentRelated) { if (contentRelated) { const result = this._sequence * 2 + 1 this._sequence += 1 return result } else { return this._sequence * 2 } } } module.exports = MTProtoState