tweb/src/lib/mtproto/transports/websocket.ts

271 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import MTTransport from './transport';
//import aesjs from 'aes-js';
import {CTR} from '@cryptography/aes';
//import abridgetPacketCodec from './abridged';
import intermediatePacketCodec from './intermediate';
import {MTPNetworker} from '../networker';
import { logger, LogLevels } from '../../polyfill';
import { bytesFromWordss } from '../../bin_utils';
import { Codec } from './codec';
/*
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/
export class Obfuscation {
/* public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */
public encNew: CTR;
public decNew: CTR;
public init(codec: Codec) {
const initPayload = new Uint8Array(64);
initPayload.randomize();
while(true) {
let val = (initPayload[3] << 24) | (initPayload[2] << 16) | (initPayload[1] << 8) | (initPayload[0]);
let val2 = (initPayload[7] << 24) | (initPayload[6] << 16) | (initPayload[5] << 8) | (initPayload[4]);
if(initPayload[0] != 0xef &&
val != 0x44414548 &&
val != 0x54534f50 &&
val != 0x20544547 &&
val != 0x4954504f &&
val != 0xeeeeeeee &&
val != 0xdddddddd &&
val2 != 0x00000000) {
//initPayload[56] = initPayload[57] = initPayload[58] = initPayload[59] = transport;
break;
}
initPayload.randomize();
}
////////////////////////initPayload.subarray(60, 62).hex = dcID;
const reversedPayload = initPayload.slice().reverse();
let encKey = initPayload.slice(8, 40);
let encIv = initPayload.slice(40, 56);
let decKey = reversedPayload.slice(8, 40);
let decIv = reversedPayload.slice(40, 56);
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */
this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv);
initPayload.set(codec.obfuscateTag, 56);
const encrypted = this.encode(initPayload);
initPayload.set(encrypted.slice(56, 64), 56);
return initPayload;
}
/* public encode(payload: Uint8Array) {
let res = this.enc.encrypt(payload);
try {
let arr = this.encNew.encrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: encode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
}
public decode(payload: Uint8Array) {
let res = this.dec.encrypt(payload);
try {
let arr = this.decNew.decrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: decode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
} */
public encode(payload: Uint8Array) {
let res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
public decode(payload: Uint8Array) {
let res = this.decNew.decrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
}
export default class Socket extends MTTransport {
ws: WebSocket;
pending: Array<Partial<{
resolve: any,
reject: any,
body: Uint8Array,
bodySent: boolean
}>> = [];
connected = false;
transport = 'websocket';
obfuscation = new Obfuscation();
networker: MTPNetworker;
log: ReturnType<typeof logger>;
codec = intermediatePacketCodec;
constructor(dcID: number, url: string) {
super(dcID, url);
this.log = logger(`WS-${dcID}`, LogLevels.log/* | LogLevels.error | LogLevels.debug */);
this.log('constructor');
this.connect();
}
connect = () => {
if(this.ws) {
this.ws.removeEventListener('open', this.handleOpen);
this.ws.removeEventListener('close', this.handleClose);
this.ws.removeEventListener('message', this.handleMessage);
this.ws.close(1000);
}
this.ws = new WebSocket(this.url, 'binary');
this.ws.binaryType = 'arraybuffer';
this.ws.addEventListener('open', this.handleOpen);
this.ws.addEventListener('close', this.handleClose);
this.ws.addEventListener('message', this.handleMessage);
};
handleOpen = () => {
this.log('opened');
this.log.debug('sending init packet');
this.ws.send(this.obfuscation.init(this.codec));
//setTimeout(() => {
this.connected = true;
this.releasePending();
//}, 3e3);
};
handleClose = (event: CloseEvent) => {
this.log('closed', event, this.pending);
this.connected = false;
//this.pending.length = 0;
/* if(this.networker) {
this.networker.resend();
this.networker.cleanupSent();
} */
this.log('trying to reconnect...');
this.connect();
for(let pending of this.pending) {
if(pending.bodySent) {
pending.bodySent = false;
}
}
if(this.networker) {
this.ws.addEventListener('open', () => {
this.networker.resend();
this.networker.cleanupSent();
}, {once: true});
}
};
handleMessage = (event: MessageEvent) => {
this.log.debug('<-', 'handleMessage', event);
let data = this.obfuscation.decode(new Uint8Array(event.data));
data = this.codec.readPacket(data);
if(this.networker) { // authenticated!
//this.pending = this.pending.filter(p => p.body); // clear pending
this.log.debug('redirecting to networker');
return this.networker.parseResponse(data).then(response => {
this.log.debug('redirecting to networker response:', response);
this.networker.processMessage(response.response, response.messageID, response.sessionID);
});
}
//console.log('got hex:', data.hex);
let pending = this.pending.shift();
if(!pending) {
return this.log.debug('no pending for res:', data.hex);
}
pending.resolve(data);
};
send = (body: Uint8Array) => {
this.log.debug('-> body length to pending:', body.length);
//return;
if(this.networker) {
this.pending.push({body});
this.releasePending();
} else {
let promise = new Promise<Uint8Array>((resolve, reject) => {
this.pending.push({resolve, reject, body});
});
this.releasePending();
return promise;
}
}
releasePending() {
if(!this.connected) {
//this.connect();
return;
}
//this.log.error('Pending length:', this.pending.length);
const length = this.pending.length;
for(let i = length - 1; i >= 0; --i) {
const pending = this.pending[i];
const {body, bodySent} = pending;
if(body && !bodySent) {
const toEncode = this.codec.encodePacket(body);
//this.log('send before obf:', /* body.hex, nonce.hex, */ toEncode.hex);
const enc = this.obfuscation.encode(toEncode);
//this.log('send after obf:', enc.hex);
this.log.debug('-> body length to send:', enc.length);
this.ws.send(enc);
if(!pending.resolve) { // remove if no response needed
this.pending.splice(i, 1);
} else {
pending.bodySent = true;
}
//delete pending.body;
}
}
}
}