From 77022e29c9f639d5e012cb5225bab880780da517 Mon Sep 17 00:00:00 2001 From: teidesu Date: Tue, 15 Jun 2021 03:12:22 +0300 Subject: [PATCH] tests(core): added e2e and fuzzing tests --- package.json | 3 +- packages/client/tests/buffer-utils.spec.ts | 61 ++++++ .../tests/stream-utils.spec.ts | 0 packages/core/.env | 16 ++ packages/core/.gitignore | 1 + .../core/scripts/get-user-token-for-e2e.ts | 61 ++++++ .../core/src/network/telegram-connection.ts | 8 +- .../core/src/network/transports/abstract.ts | 2 +- .../src/network/transports/intermediate.ts | 4 +- .../core/src/network/transports/obfuscated.ts | 6 +- packages/core/src/network/transports/tcp.ts | 4 +- .../core/src/network/transports/websocket.ts | 4 +- .../core/src/network/transports/wrapped.ts | 6 +- .../core/tests/e2e/idle-connection.spec.ts | 62 +++++++ .../core/tests/e2e/receiving-updates.spec.ts | 173 ++++++++++++++++++ packages/core/tests/fuzz/fuzz-packet.spec.ts | 101 ++++++++++ .../core/tests/fuzz/fuzz-transport.spec.ts | 113 ++++++++++++ .../intermediate-codec.spec.ts | 2 +- packages/mtproxy/fake-tls.ts | 4 +- packages/mtproxy/index.ts | 6 +- yarn.lock | 68 ++----- 21 files changed, 625 insertions(+), 80 deletions(-) create mode 100644 packages/client/tests/buffer-utils.spec.ts rename packages/{core => client}/tests/stream-utils.spec.ts (100%) create mode 100644 packages/core/.env create mode 100644 packages/core/.gitignore create mode 100644 packages/core/scripts/get-user-token-for-e2e.ts create mode 100644 packages/core/tests/e2e/idle-connection.spec.ts create mode 100644 packages/core/tests/e2e/receiving-updates.spec.ts create mode 100644 packages/core/tests/fuzz/fuzz-packet.spec.ts create mode 100644 packages/core/tests/fuzz/fuzz-transport.spec.ts diff --git a/package.json b/package.json index d05cab7c..23450c75 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "typescript": "^4.1.3", "nyc": "^15.1.0", "glob": "^7.1.7", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "dotenv-flow": "^3.2.0" }, "workspaces": [ "packages/*" diff --git a/packages/client/tests/buffer-utils.spec.ts b/packages/client/tests/buffer-utils.spec.ts new file mode 100644 index 00000000..cc53a90f --- /dev/null +++ b/packages/client/tests/buffer-utils.spec.ts @@ -0,0 +1,61 @@ +import { describe, it } from 'mocha' +import { expect } from 'chai' +import { isProbablyPlainText } from '../src/utils/file-utils' + +describe('isProbablyPlainText', () => { + it('should return true for buffers only containing printable ascii', () => { + expect( + isProbablyPlainText(Buffer.from('hello this is some ascii text')) + ).to.be.true + expect( + isProbablyPlainText( + Buffer.from( + 'hello this is some ascii text\nwith unix new lines' + ) + ) + ).to.be.true + expect( + isProbablyPlainText( + Buffer.from( + 'hello this is some ascii text\r\nwith windows new lines' + ) + ) + ).to.be.true + expect( + isProbablyPlainText( + Buffer.from( + 'hello this is some ascii text\n\twith unix new lines and tabs' + ) + ) + ).to.be.true + expect( + isProbablyPlainText( + Buffer.from( + 'hello this is some ascii text\r\n\twith windows new lines and tabs' + ) + ) + ).to.be.true + }) + + it('should return false for buffers containing some binary data', () => { + expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to + .be.false + expect( + isProbablyPlainText( + Buffer.from('hello this is some ascii text with emojis 🌸') + ) + ).to.be.false + + // random strings of 16 bytes + expect( + isProbablyPlainText( + Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex') + ) + ).to.be.false + expect( + isProbablyPlainText( + Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex') + ) + ).to.be.false + }) +}) diff --git a/packages/core/tests/stream-utils.spec.ts b/packages/client/tests/stream-utils.spec.ts similarity index 100% rename from packages/core/tests/stream-utils.spec.ts rename to packages/client/tests/stream-utils.spec.ts diff --git a/packages/core/.env b/packages/core/.env new file mode 100644 index 00000000..c70e516c --- /dev/null +++ b/packages/core/.env @@ -0,0 +1,16 @@ +# These env variables are only used in e2e tests + +# API ID and Hash (obviously) +API_ID= +API_HASH= + +# User (test DC 2) username and session string +USER_USERNAME= +USER_SESSION= +# User (test DC != 2) username and session string +USER_OTHER_DC_USERNAME= +USER_OTHER_DC_SESSION= + +# Bot token (from test DC!) +BOT_USERNAME= +BOT_TOKEN= diff --git a/packages/core/.gitignore b/packages/core/.gitignore new file mode 100644 index 00000000..11ee7581 --- /dev/null +++ b/packages/core/.gitignore @@ -0,0 +1 @@ +.env.local diff --git a/packages/core/scripts/get-user-token-for-e2e.ts b/packages/core/scripts/get-user-token-for-e2e.ts new file mode 100644 index 00000000..17344287 --- /dev/null +++ b/packages/core/scripts/get-user-token-for-e2e.ts @@ -0,0 +1,61 @@ +import { BaseTelegramClient, defaultDcs } from '../src' + +require('dotenv-flow').config({ path: __dirname + '/../' }) +require('debug').enable('mtcute:*') + +if (!process.env.API_ID || !process.env.API_HASH) { + console.warn('Set API_ID and API_HASH env variables') + process.exit(0) +} + +const dcId = process.argv[2] ?? '2' + +const tg = new BaseTelegramClient({ + apiId: process.env.API_ID, + apiHash: process.env.API_HASH, + primaryDc: defaultDcs.defaultTestDc, +}) + +;(async () => { + await tg.connect() + + let numbers = Math.floor(Math.random() * 9999).toString() + while (numbers.length !== 4) numbers += '0' + + const phone = `99966${dcId}${numbers}` + const code = `${dcId}${dcId}${dcId}${dcId}${dcId}${dcId}` + + const res = await tg.call({ + _: 'auth.sendCode', + phoneNumber: phone, + apiId: tg['_initConnectionParams'].apiId, + apiHash: tg['_apiHash'], + settings: { _: 'codeSettings' }, + }) + + const res1 = await tg.call({ + _: 'auth.signIn', + phoneNumber: phone, + phoneCodeHash: res.phoneCodeHash, + phoneCode: code, + }) + + if (res1._ === 'auth.authorizationSignUpRequired') { + await tg.call({ + _: 'auth.signUp', + phoneNumber: phone, + phoneCodeHash: res.phoneCodeHash, + firstName: 'MTCute E2E', + lastName: '', + }) + } + + const username = `mtcute_e2e_${numbers}` + + await tg.call({ + _: 'account.updateUsername', + username, + }) + + console.log('User %s, DC %d: %s', username, dcId, await tg.exportSession()) +})().catch(console.error) diff --git a/packages/core/src/network/telegram-connection.ts b/packages/core/src/network/telegram-connection.ts index c1867ac6..303a6776 100644 --- a/packages/core/src/network/telegram-connection.ts +++ b/packages/core/src/network/telegram-connection.ts @@ -185,15 +185,15 @@ export class TelegramConnection extends PersistentConnection { protected onConnectionUsable(): void { super.onConnectionUsable() + Object.entries(this._pendingRpcCalls).forEach(([id, it]) => + this._resend(it, id) + ) + const sendOnceUsable = this._sendOnceUsable // this way in case connection is still invalid (somehow??) messages aren't lost this._sendOnceUsable = [] sendOnceUsable.forEach((it) => this._resend(it)) - Object.entries(this._pendingRpcCalls).forEach(([id, it]) => - this._resend(it, id) - ) - this._pingInterval = setInterval(() => { if (this._pendingPing === null) { this._pendingPing = ulongToLong( diff --git a/packages/core/src/network/transports/abstract.ts b/packages/core/src/network/transports/abstract.ts index 08c74c23..9e7ca80e 100644 --- a/packages/core/src/network/transports/abstract.ts +++ b/packages/core/src/network/transports/abstract.ts @@ -64,7 +64,7 @@ export type TransportFactory = () => ICuteTransport * When receiving a packet, its content is sent to feed(), * and codec is supposed to emit `packet` or `error` event when packet is parsed. */ -export interface PacketCodec { +export interface IPacketCodec { /** Initial tag of the codec. Will be sent immediately once connected. */ tag(): MaybeAsync diff --git a/packages/core/src/network/transports/intermediate.ts b/packages/core/src/network/transports/intermediate.ts index ec490ef7..37326c68 100644 --- a/packages/core/src/network/transports/intermediate.ts +++ b/packages/core/src/network/transports/intermediate.ts @@ -1,4 +1,4 @@ -import { PacketCodec, TransportError } from './abstract' +import { IPacketCodec, TransportError } from './abstract' import { StreamedCodec } from './streamed' import { randomBytes } from '../../utils/buffer-utils' @@ -11,7 +11,7 @@ const PADDED_TAG = Buffer.from([0xdd, 0xdd, 0xdd, 0xdd]) */ export class IntermediatePacketCodec extends StreamedCodec - implements PacketCodec { + implements IPacketCodec { tag(): Buffer { return TAG } diff --git a/packages/core/src/network/transports/obfuscated.ts b/packages/core/src/network/transports/obfuscated.ts index 3387c947..799d83ca 100644 --- a/packages/core/src/network/transports/obfuscated.ts +++ b/packages/core/src/network/transports/obfuscated.ts @@ -1,4 +1,4 @@ -import { PacketCodec } from './abstract' +import { IPacketCodec } from './abstract' import { IEncryptionScheme } from '../../utils/crypto' import { buffersEqual, randomBytes } from '../../utils/buffer-utils' import { WrappedCodec } from './wrapped' @@ -19,13 +19,13 @@ interface MtProxyInfo { media: boolean } -export class ObfuscatedPacketCodec extends WrappedCodec implements PacketCodec { +export class ObfuscatedPacketCodec extends WrappedCodec implements IPacketCodec { private _encryptor?: IEncryptionScheme private _decryptor?: IEncryptionScheme private _proxy?: MtProxyInfo - constructor(inner: PacketCodec, proxy?: MtProxyInfo) { + constructor(inner: IPacketCodec, proxy?: MtProxyInfo) { super(inner) this._proxy = proxy } diff --git a/packages/core/src/network/transports/tcp.ts b/packages/core/src/network/transports/tcp.ts index 3fd32746..046653bd 100644 --- a/packages/core/src/network/transports/tcp.ts +++ b/packages/core/src/network/transports/tcp.ts @@ -1,4 +1,4 @@ -import { ICuteTransport, PacketCodec, TransportState } from './abstract' +import { ICuteTransport, IPacketCodec, TransportState } from './abstract' import { tl } from '@mtcute/tl' import { Socket, connect } from 'net' import EventEmitter from 'events' @@ -18,7 +18,7 @@ export abstract class TcpTransport protected _state: TransportState = TransportState.Idle protected _socket: Socket | null = null - abstract _packetCodec: PacketCodec + abstract _packetCodec: IPacketCodec protected _crypto: ICryptoProvider packetCodecInitialized = false diff --git a/packages/core/src/network/transports/websocket.ts b/packages/core/src/network/transports/websocket.ts index 8e157d31..7a25c53e 100644 --- a/packages/core/src/network/transports/websocket.ts +++ b/packages/core/src/network/transports/websocket.ts @@ -1,4 +1,4 @@ -import { ICuteTransport, PacketCodec, TransportState } from './abstract' +import { ICuteTransport, IPacketCodec, TransportState } from './abstract' import { tl } from '@mtcute/tl' import EventEmitter from 'events' import { typedArrayToBuffer } from '../../utils/buffer-utils' @@ -42,7 +42,7 @@ export abstract class WebSocketTransport private _socket: WebSocket | null = null private _crypto: ICryptoProvider - abstract _packetCodec: PacketCodec + abstract _packetCodec: IPacketCodec packetCodecInitialized = false private _baseDomain: string diff --git a/packages/core/src/network/transports/wrapped.ts b/packages/core/src/network/transports/wrapped.ts index 31674a89..a5bf795e 100644 --- a/packages/core/src/network/transports/wrapped.ts +++ b/packages/core/src/network/transports/wrapped.ts @@ -1,12 +1,12 @@ import EventEmitter from 'events' -import { PacketCodec } from './abstract' +import { IPacketCodec } from './abstract' import { ICryptoProvider } from '../../utils/crypto' export abstract class WrappedCodec extends EventEmitter { protected _crypto: ICryptoProvider - protected _inner: PacketCodec + protected _inner: IPacketCodec - constructor(inner: PacketCodec) { + constructor(inner: IPacketCodec) { super() this._inner = inner this._inner.on('error', (err) => this.emit('error', err)) diff --git a/packages/core/tests/e2e/idle-connection.spec.ts b/packages/core/tests/e2e/idle-connection.spec.ts new file mode 100644 index 00000000..fa2cbb10 --- /dev/null +++ b/packages/core/tests/e2e/idle-connection.spec.ts @@ -0,0 +1,62 @@ +import { describe, it } from 'mocha' +import { expect } from 'chai' +import { BaseTelegramClient, defaultDcs, TransportState } from '../../src' +import { sleep } from '../../src/utils/misc-utils' + +require('dotenv-flow').config() + +describe('e2e : idle connection', function () { + if (!process.env.API_ID || !process.env.API_HASH) { + console.warn('Warning: skipping e2e idle connection test (no API_ID or API_HASH)') + return + } + + this.timeout(120000) + + // 75s is to make sure ping is sent + + it('75s idle to test dc', async () => { + const client = new BaseTelegramClient({ + apiId: process.env.API_ID!, + apiHash: process.env.API_HASH!, + primaryDc: defaultDcs.defaultTestDc + }) + + await client.connect() + await sleep(75000) + + expect(client.primaryConnection['_transport'].state()).eq(TransportState.Ready) + + const config = await client.call({ _: 'help.getConfig' }) + expect(config._).eql('config') + + await client.close() + expect(client.primaryConnection['_transport'].state()).eq(TransportState.Idle) + }) + + if (!process.env.USER_SESSION) { + console.warn('Warning: skipping e2e idle connection test with auth (no USER_SESSION)') + return + } + + it('75s idle to test dc with auth', async () => { + const client = new BaseTelegramClient({ + apiId: process.env.API_ID!, + apiHash: process.env.API_HASH!, + primaryDc: defaultDcs.defaultTestDc + }) + + client.importSession(process.env.USER_SESSION!) + + await client.connect() + await sleep(75000) + + expect(client.primaryConnection['_transport'].state()).eq(TransportState.Ready) + + const config = await client.call({ _: 'help.getConfig' }) + expect(config._).eql('config') + + await client.close() + expect(client.primaryConnection['_transport'].state()).eq(TransportState.Idle) + }) +}) diff --git a/packages/core/tests/e2e/receiving-updates.spec.ts b/packages/core/tests/e2e/receiving-updates.spec.ts new file mode 100644 index 00000000..b74164ec --- /dev/null +++ b/packages/core/tests/e2e/receiving-updates.spec.ts @@ -0,0 +1,173 @@ +import { describe, it } from 'mocha' +import { expect } from 'chai' +import { BaseTelegramClient, bufferToBigInt, defaultDcs, randomBytes, tl } from '../../src' +import { sleep } from '../../src/utils/misc-utils' + +require('dotenv-flow').config() + +async function createClientPair( + session1: string, + session2: string +): Promise<[BaseTelegramClient, BaseTelegramClient]> { + const client1 = new BaseTelegramClient({ + apiId: process.env.API_ID!, + apiHash: process.env.API_HASH!, + primaryDc: defaultDcs.defaultTestDc, + }) + + if (session1.indexOf(':') > -1) { + // bot token + await client1.call({ + _: 'auth.importBotAuthorization', + apiId: parseInt(process.env.API_ID!), + apiHash: process.env.API_HASH!, + botAuthToken: session1, + flags: 0 + }) + } else { + client1.importSession(session1) + } + + const client2 = new BaseTelegramClient({ + apiId: process.env.API_ID!, + apiHash: process.env.API_HASH!, + primaryDc: defaultDcs.defaultTestDc, + }) + + if (session2.indexOf(':') > -1) { + // bot token + await client2.call({ + _: 'auth.importBotAuthorization', + apiId: parseInt(process.env.API_ID!), + apiHash: process.env.API_HASH!, + botAuthToken: session2, + flags: 0 + }) + } else { + client2.importSession(session2) + } + + return [client1, client2] +} + +describe('e2e : receiving updates', function () { + if (!process.env.API_ID || !process.env.API_HASH) { + console.warn('Warning: skipping e2e updates test (no API_ID or API_HASH)') + return + } + if (!process.env.BOT_TOKEN || !process.env.USER_SESSION) { + console.warn('Warning: skipping e2e updates test (no API_ID or API_HASH)') + return + } + + this.timeout(60000) + + it('receiving from user by bot', async () => { + const [actor, observer] = await createClientPair( + process.env.USER_SESSION!, + process.env.BOT_TOKEN! + ) + + await observer.connect() + await observer.waitUntilUsable() + + let updatesCount = 0 + + observer['_handleUpdate'] = function() { + updatesCount += 1 + } + + const bot = await actor.call({ + _: 'contacts.resolveUsername', + username: process.env.BOT_USERNAME! + }).then((res) => res.users[0]) + + expect(bot._).eq('user') + + const inputPeer: tl.TypeInputPeer = { + _: 'inputPeerUser', + userId: bot.id, + accessHash: (bot as tl.RawUser).accessHash! + } + + await actor.call({ + _: 'messages.startBot', + bot: { + _: 'inputUser', + userId: bot.id, + accessHash: (bot as tl.RawUser).accessHash! + }, + peer: inputPeer, + randomId: bufferToBigInt(randomBytes(8)), + startParam: '123' + }) + + for (let i = 0; i < 5; i++) { + await actor.call({ + _: 'messages.sendMessage', + peer: inputPeer, + randomId: bufferToBigInt(randomBytes(8)), + message: `Test ${i}` + }) + await sleep(1000) + } + + // to make sure the updates were delivered + await sleep(5000) + + // 5 messages + /start = 6 + // it is assumed that there were no gaps + expect(updatesCount).gte(6) + + await actor.close() + await observer.close() + }) + + it('receiving from user by user', async () => { + const [actor, observer] = await createClientPair( + process.env.USER_SESSION!, + process.env.USER_OTHER_DC_SESSION! + ) + + await observer.connect() + await observer.waitUntilUsable() + + let updatesCount = 0 + + observer['_handleUpdate'] = function() { + updatesCount += 1 + } + + const user = await actor.call({ + _: 'contacts.resolveUsername', + username: process.env.USER_OTHER_DC_USERNAME! + }).then((res) => res.users[0]) + + expect(user._).eq('user') + + const inputPeer: tl.TypeInputPeer = { + _: 'inputPeerUser', + userId: user.id, + accessHash: (user as tl.RawUser).accessHash! + } + for (let i = 0; i < 5; i++) { + await sleep(1000) + await actor.call({ + _: 'messages.sendMessage', + peer: inputPeer, + randomId: bufferToBigInt(randomBytes(8)), + message: `Test ${i}` + }) + } + + // to make sure the updates were delivered + await sleep(5000) + + // 5 messages + // it is assumed that there were no gaps + expect(updatesCount).gte(5) + + await actor.close() + await observer.close() + }) +}) diff --git a/packages/core/tests/fuzz/fuzz-packet.spec.ts b/packages/core/tests/fuzz/fuzz-packet.spec.ts new file mode 100644 index 00000000..dbb494d2 --- /dev/null +++ b/packages/core/tests/fuzz/fuzz-packet.spec.ts @@ -0,0 +1,101 @@ +import { describe, after, it } from 'mocha' +import { expect } from 'chai' +import { + BaseTelegramClient, BinaryReader, + BinaryWriter, + defaultDcs, + randomBytes, +} from '../../src' +import { sleep } from '../../src/utils/misc-utils' +import { createAesIgeForMessage } from '../../src/utils/crypto/mtproto' + +require('dotenv-flow').config() + +describe('fuzz : packet', async function () { + if (!process.env.API_ID || !process.env.API_HASH) { + console.warn( + 'Warning: skipping fuzz packet test (no API_ID or API_HASH)' + ) + return + } + + this.timeout(45000) + + it('random packet', async () => { + const client = new BaseTelegramClient({ + apiId: process.env.API_ID!, + apiHash: process.env.API_HASH!, + primaryDc: defaultDcs.defaultTestDc, + }) + + await client.connect() + await client.waitUntilUsable() + + const errors: Error[] = [] + + const errorHandler = (err: Error) => { + errors.push(err) + } + + client.onError(errorHandler) + + const conn = client.primaryConnection + const mtproto = conn['_mtproto'] + + const createFakeMtprotoPacket = async (payload: Buffer): Promise => { + // create a fake mtproto packet + const messageId = conn['_getMessageId']().minus(1) // must be odd + + const innerWriter = BinaryWriter.alloc(payload.length + 32) + innerWriter.raw(mtproto.serverSalt) + innerWriter.raw(mtproto._sessionId) + innerWriter.long(messageId) + innerWriter.int32(0) // seqno + innerWriter.int32(payload.length) + innerWriter.raw(payload) + + const innerData = innerWriter.result() + + const messageKey = ( + await mtproto._crypto.sha256( + Buffer.concat([mtproto._authKeyServerSalt!, innerData]) + ) + ).slice(8, 24) + const ige = await createAesIgeForMessage( + mtproto._crypto, + mtproto._authKey!, + messageKey, + false + ) + const encryptedData = await ige.encrypt(innerData) + + const writer = BinaryWriter.alloc(24 + encryptedData.length) + writer.raw(mtproto._authKeyId!) + writer.raw(messageKey) + + return Buffer.concat([mtproto._authKeyId!, messageKey, encryptedData]) + } + + for (let i = 0; i < 100; i++) { + const payload = randomBytes(Math.round(Math.random() * 16) * 16) + + await conn['onMessage'](await createFakeMtprotoPacket(payload)) + await sleep(100) + } + + // similar test, but this time only using object ids that do exist + const objectIds = Object.keys(new BinaryReader(Buffer.alloc(0))._objectsMap) + for (let i = 0; i < 100; i++) { + const payload = randomBytes((Math.round(Math.random() * 16) + 1) * 16) + const objectId = parseInt(objectIds[Math.round(Math.random() * objectIds.length)]) + payload.writeUInt32LE(objectId, 0) + + await conn['onMessage'](await createFakeMtprotoPacket(payload)) + await sleep(100) + } + + await client.close() + + expect(errors.length).gt(0) + }) +}) diff --git a/packages/core/tests/fuzz/fuzz-transport.spec.ts b/packages/core/tests/fuzz/fuzz-transport.spec.ts new file mode 100644 index 00000000..9846a508 --- /dev/null +++ b/packages/core/tests/fuzz/fuzz-transport.spec.ts @@ -0,0 +1,113 @@ +import { describe, it } from 'mocha' +import { expect } from 'chai' +import { + BaseTelegramClient, + defaultDcs, + ICuteTransport, + randomBytes, + tl, + TransportState, +} from '../../src' +import { EventEmitter } from 'events' +import { sleep } from '../../src/utils/misc-utils' + +require('dotenv-flow').config() + +class RandomBytesTransport extends EventEmitter implements ICuteTransport { + dc: tl.RawDcOption + + interval: NodeJS.Timeout | null + + close(): void { + clearInterval(this.interval!) + this.emit('close') + this.interval = null + } + + connect(dc: tl.RawDcOption): void { + this.dc = dc + + setTimeout(() => this.emit('ready'), 0) + + this.interval = setInterval(() => { + this.emit('message', randomBytes(64)) + }, 100) + } + + currentDc(): tl.RawDcOption | null { + return this.dc + } + + send(data: Buffer): Promise { + return Promise.resolve() + } + + state(): TransportState { + return this.interval ? TransportState.Ready : TransportState.Idle + } +} + +describe('fuzz : transport', function () { + this.timeout(30000) + + it('RandomBytesTransport (no auth)', async () => { + const client = new BaseTelegramClient({ + transport: () => new RandomBytesTransport(), + apiId: 0, + apiHash: '', + primaryDc: defaultDcs.defaultTestDc, + }) + + const errors: Error[] = [] + + client.onError((err) => { + errors.push(err) + }) + + await client.connect() + await sleep(15000) + await client.close() + + expect(errors.length).gt(0) + errors.forEach((err) => { + expect(err.message).match(/unknown object id/i) + }) + }) + + it('RandomBytesTransport (with auth)', async () => { + const client = new BaseTelegramClient({ + transport: () => new RandomBytesTransport(), + apiId: 0, + apiHash: '', + primaryDc: defaultDcs.defaultTestDc, + }) + // random key just to make it think it already has one + await client.storage.setAuthKeyFor(2, randomBytes(256)) + + // in this case, there will be no actual errors, only + // warnings like 'received message with unknown authKey' + // + // to test for that, we hook into `decryptMessage` and make + // sure that it returns `null` + + await client.connect() + + let hadNonNull = false + + const decryptMessage = + client.primaryConnection['_mtproto'].decryptMessage + client.primaryConnection[ + '_mtproto' + ].decryptMessage = async function () { + const res = await decryptMessage.apply(this, arguments) + if (res !== null) hadNonNull = true + + return res + } + + await sleep(15000) + await client.close() + + expect(hadNonNull).false + }) +}) diff --git a/packages/core/tests/transport-codecs/intermediate-codec.spec.ts b/packages/core/tests/transport-codecs/intermediate-codec.spec.ts index 68eac033..968672ba 100644 --- a/packages/core/tests/transport-codecs/intermediate-codec.spec.ts +++ b/packages/core/tests/transport-codecs/intermediate-codec.spec.ts @@ -1,6 +1,6 @@ import { describe, it } from 'mocha' import { expect } from 'chai' -import { IntermediatePacketCodec } from '../../src/network/transports/tcp-intermediate' +import { IntermediatePacketCodec } from '../../src/network/transports/intermediate' import { TransportError } from '../../src/network/transports/abstract' describe('IntermediatePacketCodec', () => { diff --git a/packages/mtproxy/fake-tls.ts b/packages/mtproxy/fake-tls.ts index a8a33966..01c494aa 100644 --- a/packages/mtproxy/fake-tls.ts +++ b/packages/mtproxy/fake-tls.ts @@ -2,7 +2,7 @@ import { bigIntToBuffer, bufferToBigInt, ICryptoProvider, - PacketCodec, + IPacketCodec, WrappedCodec, randomBytes, } from '@mtcute/core' @@ -285,7 +285,7 @@ export async function generateFakeTlsHeader( * Must only be used inside {@link MtProxyTcpTransport} * @internal */ -export class FakeTlsPacketCodec extends WrappedCodec implements PacketCodec { +export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec { protected _stream = Buffer.alloc(0) private _header: Buffer diff --git a/packages/mtproxy/index.ts b/packages/mtproxy/index.ts index a4611153..0c13fa80 100644 --- a/packages/mtproxy/index.ts +++ b/packages/mtproxy/index.ts @@ -1,7 +1,7 @@ import { IntermediatePacketCodec, ObfuscatedPacketCodec, - PacketCodec, + IPacketCodec, PaddedIntermediatePacketCodec, parseUrlSafeBase64, TcpTransport, @@ -86,7 +86,7 @@ export class MtProxyTcpTransport extends TcpTransport { } } - _packetCodec!: PacketCodec + _packetCodec!: IPacketCodec connect(dc: tl.RawDcOption): void { if (this._state !== TransportState.Idle) @@ -110,7 +110,7 @@ export class MtProxyTcpTransport extends TcpTransport { } if (!this._fakeTlsDomain) { - let inner: PacketCodec + let inner: IPacketCodec if (this._randomPadding) { inner = new PaddedIntermediatePacketCodec() } else { diff --git a/yarn.lock b/yarn.lock index 681a91a0..05699ec3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2161,14 +2161,6 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - dargs@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" @@ -2384,6 +2376,18 @@ dot-prop@^6.0.1: dependencies: is-obj "^2.0.0" +dotenv-flow@^3.2.0: + version "3.2.0" + resolved "http://localhost:4873/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7" + integrity sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg== + dependencies: + dotenv "^8.0.0" + +dotenv@^8.0.0: + version "8.6.0" + resolved "http://localhost:4873/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -2501,37 +2505,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - es6-error@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2693,13 +2671,6 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -4369,11 +4340,6 @@ neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - node-abi@^2.21.0: version "2.26.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.26.0.tgz#355d5d4bc603e856f74197adbf3f5117a396ba40" @@ -6222,16 +6188,6 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d" - integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw== - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"