tests(core): added e2e and fuzzing tests
This commit is contained in:
parent
0a45f5e71a
commit
77022e29c9
21 changed files with 625 additions and 80 deletions
|
@ -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/*"
|
||||
|
|
61
packages/client/tests/buffer-utils.spec.ts
Normal file
61
packages/client/tests/buffer-utils.spec.ts
Normal file
|
@ -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
|
||||
})
|
||||
})
|
16
packages/core/.env
Normal file
16
packages/core/.env
Normal file
|
@ -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=
|
1
packages/core/.gitignore
vendored
Normal file
1
packages/core/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.env.local
|
61
packages/core/scripts/get-user-token-for-e2e.ts
Normal file
61
packages/core/scripts/get-user-token-for-e2e.ts
Normal file
|
@ -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)
|
|
@ -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(
|
||||
|
|
|
@ -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<Buffer>
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
62
packages/core/tests/e2e/idle-connection.spec.ts
Normal file
62
packages/core/tests/e2e/idle-connection.spec.ts
Normal file
|
@ -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)
|
||||
})
|
||||
})
|
173
packages/core/tests/e2e/receiving-updates.spec.ts
Normal file
173
packages/core/tests/e2e/receiving-updates.spec.ts
Normal file
|
@ -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()
|
||||
})
|
||||
})
|
101
packages/core/tests/fuzz/fuzz-packet.spec.ts
Normal file
101
packages/core/tests/fuzz/fuzz-packet.spec.ts
Normal file
|
@ -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<Buffer> => {
|
||||
// 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)
|
||||
})
|
||||
})
|
113
packages/core/tests/fuzz/fuzz-transport.spec.ts
Normal file
113
packages/core/tests/fuzz/fuzz-transport.spec.ts
Normal file
|
@ -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<void> {
|
||||
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
|
||||
})
|
||||
})
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
68
yarn.lock
68
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"
|
||||
|
|
Loading…
Reference in a new issue