Browse Source

Fix mtproto containers

Fix resending
master
Eduard Kuzmenko 4 years ago
parent
commit
6f4de7e0f0
  1. 4
      src/config/debug.ts
  2. 2
      src/lib/mtproto/dcConfigurator.ts
  3. 176
      src/lib/mtproto/networker.ts
  4. 2
      src/lib/mtproto/transports/http.ts
  5. 36
      src/lib/mtproto/transports/tcpObfuscated.ts

4
src/config/debug.ts

@ -4,7 +4,7 @@ export const DEBUG = process.env.NODE_ENV !== 'production' || Modes.debug;
export const MOUNT_CLASS_TO: any = DEBUG ? (typeof(window) !== 'undefined' ? window : self) : null; export const MOUNT_CLASS_TO: any = DEBUG ? (typeof(window) !== 'undefined' ? window : self) : null;
export default DEBUG; export default DEBUG;
export const superDebug = (object: any, key: string) => { /* export const superDebug = (object: any, key: string) => {
var d = object[key]; var d = object[key];
var beforeStr = '', afterStr = ''; var beforeStr = '', afterStr = '';
for(var r of d) { for(var r of d) {
@ -32,4 +32,4 @@ export const superDebug = (object: any, key: string) => {
dada(key + '_' + 'after', afterStr); dada(key + '_' + 'after', afterStr);
} }
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.superDebug = superDebug); MOUNT_CLASS_TO && (MOUNT_CLASS_TO.superDebug = superDebug); */

2
src/lib/mtproto/dcConfigurator.ts

@ -106,7 +106,7 @@ export class DcConfigurator {
const chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path; const chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path;
const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';
const retryTimeout = connectionType === 'client' ? 15000 : 10000; const retryTimeout = connectionType === 'client' ? 10000 : 10000;
const oooohLetMeLive: MTConnectionConstructable = (isSafari && isWebWorker) /* || true */ ? SocketProxied : Socket; const oooohLetMeLive: MTConnectionConstructable = (isSafari && isWebWorker) /* || true */ ? SocketProxied : Socket;

176
src/lib/mtproto/networker.ts

@ -12,7 +12,6 @@ import { longToBytes } from '../crypto/crypto_utils';
import MTTransport from './transports/transport'; import MTTransport from './transports/transport';
import { convertToUint8Array, bufferConcat, bytesCmp, bytesToHex } from '../../helpers/bytes'; import { convertToUint8Array, bufferConcat, bytesCmp, bytesToHex } from '../../helpers/bytes';
import { nextRandomInt } from '../../helpers/random'; import { nextRandomInt } from '../../helpers/random';
import { CancellablePromise } from '../../helpers/cancellablePromise';
import App from '../../config/app'; import App from '../../config/app';
import DEBUG from '../../config/debug'; import DEBUG from '../../config/debug';
import Modes from '../../config/modes'; import Modes from '../../config/modes';
@ -108,11 +107,13 @@ export default class MTPNetworker {
public isOnline = false; public isOnline = false;
private lastResponseTime = 0; private lastResponseTime = 0;
private disconnectDelay: number;
private pingPromise: CancellablePromise<any>; private schedulePromise: Promise<any>;
//private disconnectDelay: number;
//private pingPromise: CancellablePromise<any>;
//public onConnectionStatusChange: (online: boolean) => void; //public onConnectionStatusChange: (online: boolean) => void;
private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = []; //private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = [];
constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array, constructor(public dcId: number, private authKey: number[], private authKeyId: Uint8Array,
serverSalt: number[], private transport: MTTransport, options: InvokeApiOptions = {}) { serverSalt: number[], private transport: MTTransport, options: InvokeApiOptions = {}) {
@ -641,9 +642,9 @@ export default class MTPNetworker {
this.log.error('timeout', message); this.log.error('timeout', message);
this.setConnectionStatus(false); this.setConnectionStatus(false);
this.getEncryptedOutput(message).then(bytes => { /* this.getEncryptedOutput(message).then(bytes => {
this.log.error('timeout encrypted', bytes); this.log.error('timeout encrypted', bytes);
}); }); */
}, CONNECTION_TIMEOUT); }, CONNECTION_TIMEOUT);
promise.finally(() => { promise.finally(() => {
@ -683,7 +684,7 @@ export default class MTPNetworker {
} */ } */
} }
public pushResend(messageId: string, delay = 0) { public pushResend(messageId: string, delay = 100) {
const value = delay ? Date.now() + delay : 0; const value = delay ? Date.now() + delay : 0;
const sentMessage = this.sentMessages[messageId]; const sentMessage = this.sentMessages[messageId];
if(sentMessage.container) { if(sentMessage.container) {
@ -784,8 +785,8 @@ export default class MTPNetworker {
}; };
} }
let message: MTPNetworker['sentMessages'][keyof MTPNetworker['sentMessages']]; let outMessage: MTPNetworker['sentMessages'][keyof MTPNetworker['sentMessages']];
const messages: typeof message[] = []; const messages: typeof outMessage[] = [];
const currentTime = Date.now(); const currentTime = Date.now();
let messagesByteLen = 0; let messagesByteLen = 0;
@ -796,8 +797,9 @@ export default class MTPNetworker {
for(const messageId in this.pendingMessages) { for(const messageId in this.pendingMessages) {
const value = this.pendingMessages[messageId]; const value = this.pendingMessages[messageId];
if(!value || value >= currentTime) { if(!value || value <= currentTime) {
if(message = this.sentMessages[messageId]) { const message = this.sentMessages[messageId];
if(message) {
/* if(message.fileUpload) { /* if(message.fileUpload) {
this.log('performScheduledRequest message:', message, message.body.length, (message.body as Uint8Array).byteLength, (message.body as Uint8Array).buffer.byteLength); this.log('performScheduledRequest message:', message, message.body.length, (message.body as Uint8Array).byteLength, (message.body as Uint8Array).buffer.byteLength);
} */ } */
@ -810,7 +812,7 @@ export default class MTPNetworker {
if(!message.notContentRelated && if(!message.notContentRelated &&
messagesByteLen && messagesByteLen &&
messagesByteLen + messageByteLength > 655360) { // 640 Kb messagesByteLen + messageByteLength > 655360) { // 640 Kb
this.log.warn('lengthOverflow', message); this.log.warn('lengthOverflow', message, messages);
lengthOverflow = true; lengthOverflow = true;
continue; // maybe break here continue; // maybe break here
} }
@ -822,6 +824,8 @@ export default class MTPNetworker {
} else if(message.longPoll) { } else if(message.longPoll) {
hasHttpWait = true; hasHttpWait = true;
} }
outMessage = message;
} else { } else {
// this.log(message, messageId) // this.log(message, messageId)
} }
@ -855,9 +859,43 @@ export default class MTPNetworker {
return; return;
} }
const noResponseMsgs: Array<string> = []; /// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP
const noResponseMsgs: Array<string> = messages.filter(message => message.noResponse).map(message => message.msg_id);
/// #endif
if(messages.length > 1) { if(messages.length > 1) {
const container = this.generateContainerMessage(messagesByteLen, messages);
outMessage = container.messageWithBody;
this.sentMessages[outMessage.msg_id] = container.message;
} else {
this.sentMessages[outMessage.msg_id] = outMessage;
}
this.pendingAcks = [];
const promise = this.sendEncryptedRequest(outMessage);
/// #if MTPROTO_HTTP_UPLOAD
if(!(this.transport instanceof HTTP)) {
//if(noResponseMsgs.length) this.log.error('noResponseMsgs length!', noResponseMsgs);
this.cleanupSent(); // ! WARNING
} else {
this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs);
}
/// #elif !MTPROTO_HTTP
this.cleanupSent(); // ! WARNING
/// #else
this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs);
//}
/// #endif
if(lengthOverflow) {
this.scheduleRequest();
}
};
private generateContainerMessage(messagesByteLen: number, messages: MTMessage[]) {
const container = new TLSerialization({ const container = new TLSerialization({
mtproto: true, mtproto: true,
startMaxLength: messagesByteLen + 64 startMaxLength: messagesByteLen + 64
@ -868,65 +906,29 @@ export default class MTPNetworker {
const innerMessages: string[] = []; const innerMessages: string[] = [];
messages.forEach((message, i) => { messages.forEach((message, i) => {
container.storeLong(message.msg_id, 'CONTAINER[' + i + '][msg_id]');
innerMessages.push(message.msg_id); innerMessages.push(message.msg_id);
container.storeLong(message.msg_id, 'CONTAINER[' + i + '][msg_id]');
container.storeInt(message.seq_no, 'CONTAINER[' + i + '][seq_no]'); container.storeInt(message.seq_no, 'CONTAINER[' + i + '][seq_no]');
container.storeInt(message.body.length, 'CONTAINER[' + i + '][bytes]'); container.storeInt(message.body.length, 'CONTAINER[' + i + '][bytes]');
container.storeRawBytes(message.body, 'CONTAINER[' + i + '][body]'); container.storeRawBytes(message.body, 'CONTAINER[' + i + '][body]');
if(message.noResponse) {
noResponseMsgs.push(message.msg_id);
}
}); });
const containerSentMessage: MTMessage = { const message: MTMessage = {
msg_id: timeManager.generateId(), msg_id: timeManager.generateId(),
seq_no: this.generateSeqNo(true), seq_no: this.generateSeqNo(true),
container: true, container: true,
inner: innerMessages inner: innerMessages
}; };
message = Object.assign({ if(Modes.debug/* || true */) {
body: container.getBytes(true) this.log.warn('Container', innerMessages, message.msg_id, message.seq_no);
}, containerSentMessage);
this.sentMessages[message.msg_id] = containerSentMessage;
if(Modes.debug) {
this.log('Container', innerMessages, message.msg_id, message.seq_no);
}
} else {
if(message.noResponse) {
noResponseMsgs.push(message.msg_id);
}
this.sentMessages[message.msg_id] = message;
}
this.pendingAcks = [];
const promise = this.sendEncryptedRequest(message);
/// #if MTPROTO_HTTP_UPLOAD
if(!(this.transport instanceof HTTP)) {
//if(noResponseMsgs.length) this.log.error('noResponseMsgs length!', noResponseMsgs);
this.cleanupSent(); // ! WARNING
} else {
this.handleSentEncryptedRequestHTTP(promise, message, noResponseMsgs);
} }
/// #elif !MTPROTO_HTTP
//if(!(this.transport instanceof HTTP)) {
//if(noResponseMsgs.length) this.log.error('noResponseMsgs length!', noResponseMsgs);
this.cleanupSent(); // ! WARNING
//} else {
/// #else
this.handleSentEncryptedRequestHTTP(promise, message, noResponseMsgs);
//}
/// #endif
if(lengthOverflow) { return {
this.scheduleRequest(); message,
} messageWithBody: Object.assign({body: container.getBytes(true)}, message),
}; };
}
public async getEncryptedMessage(dataWithPadding: ArrayBuffer) { public async getEncryptedMessage(dataWithPadding: ArrayBuffer) {
const msgKey = await this.getMsgKey(dataWithPadding, true); const msgKey = await this.getMsgKey(dataWithPadding, true);
@ -1045,6 +1047,8 @@ export default class MTPNetworker {
public sendEncryptedRequest(message: MTMessage) { public sendEncryptedRequest(message: MTMessage) {
return this.getEncryptedOutput(message).then(requestData => { return this.getEncryptedOutput(message).then(requestData => {
//this.log('sendEncryptedRequest: launching message into space:', message);
const promise: Promise<Uint8Array> = this.transport.send(requestData) as any; const promise: Promise<Uint8Array> = this.transport.send(requestData) as any;
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD /// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
return promise; return promise;
@ -1206,27 +1210,20 @@ export default class MTPNetworker {
} }
// ! таймаут очень сильно тормозит скорость работы сокета (даже нулевой) // ! таймаут очень сильно тормозит скорость работы сокета (даже нулевой)
public scheduleRequest(delay = 0) { public scheduleRequest(delay?: number) {
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
/* clearTimeout(this.nextReqTimeout); if(!(this.transport instanceof HTTP)) {
this.nextReqTimeout = self.setTimeout(this.performScheduledRequest.bind(this), delay || 0); this.performScheduledRequest();
return; */ return;
return this.performScheduledRequest(); } else if(this.offline) {
/// #else
if(!(this.transport instanceof HTTP)) return this.performScheduledRequest();
if(this.offline/* && this.transport instanceof HTTP */) {
this.checkConnection('forced schedule'); this.checkConnection('forced schedule');
} }
/// #endif
/* if(delay && !(this.transport instanceof HTTP)) { const nextReq = Date.now() + (delay || 0);
delay = 0; if(this.nextReq && (delay === undefined || this.nextReq <= nextReq)) {
} */
const nextReq = Date.now() + delay;
if(delay && this.nextReq && this.nextReq <= nextReq) {
//this.log('scheduleRequest: nextReq', this.nextReq, nextReq); //this.log('scheduleRequest: nextReq', this.nextReq, nextReq);
return false; return;
} }
//this.log('scheduleRequest: delay', delay); //this.log('scheduleRequest: delay', delay);
@ -1235,33 +1232,43 @@ export default class MTPNetworker {
return; return;
} */ } */
const perf = performance.now(); //const perf = performance.now();
if(this.nextReqTimeout) {
clearTimeout(this.nextReqTimeout); clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = self.setTimeout(() => { }
const cb = () => {
//this.log('scheduleRequest: timeout delay was:', performance.now() - perf); //this.log('scheduleRequest: timeout delay was:', performance.now() - perf);
this.nextReqTimeout = 0; this.nextReqTimeout = 0;
this.nextReq = 0;
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD || true
if(this.offline) { if(this.offline) {
//this.log('Cancel scheduled'); //this.log('Cancel scheduled');
return false; return;
} }
this.nextReq = 0;
/// #endif
this.performScheduledRequest(); this.performScheduledRequest();
}, delay); };
this.nextReq = nextReq; this.nextReq = nextReq;
/// #endif
if(delay) {
this.nextReqTimeout = self.setTimeout(cb, delay);
} else {
cb();
}
} }
public ackMessage(msgId: string) { public ackMessage(msgId: string) {
// this.log('ack message', msgID) // this.log('ack message', msgID)
this.pendingAcks.push(msgId); this.pendingAcks.push(msgId);
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
this.scheduleRequest(30000); this.scheduleRequest(30000);
/// #else
this.scheduleRequest();
/// #endif
} }
public reqResendMessage(msgId: string) { public reqResendMessage(msgId: string) {
@ -1322,7 +1329,8 @@ export default class MTPNetworker {
} }
/** /**
* только для сокета, возможно это будет неправильно работать, но в тесте сработало правильно * * только для сокета
* TODO: consider about containers resend
*/ */
public resend() { public resend() {
for(const id in this.sentMessages) { for(const id in this.sentMessages) {

2
src/lib/mtproto/transports/http.ts

@ -5,7 +5,7 @@ export default class HTTP implements MTTransport {
constructor(protected dcId: number, protected url: string) { constructor(protected dcId: number, protected url: string) {
} }
send = (data: Uint8Array) => { public send(data: Uint8Array) {
return fetch(this.url, {method: 'POST', body: data}).then(response => { return fetch(this.url, {method: 'POST', body: data}).then(response => {
//console.log('http response', response/* , response.arrayBuffer() */); //console.log('http response', response/* , response.arrayBuffer() */);

36
src/lib/mtproto/transports/tcpObfuscated.ts

@ -40,26 +40,24 @@ export default class TcpObfuscated implements MTTransport {
const initPayload = this.obfuscation.init(this.codec); const initPayload = this.obfuscation.init(this.codec);
this.connection.send(initPayload);
if(this.networker) { if(this.networker) {
this.pending.length = 0; // ! clear queue and reformat messages to container, because if sending simultaneously 10+ messages, connection will die
this.networker.setConnectionStatus(true); this.networker.setConnectionStatus(true);
if(this.lastCloseTime) {
this.networker.cleanupSent(); this.networker.cleanupSent();
this.networker.resend(); this.networker.resend();
} } else {
}
for(const pending of this.pending) { for(const pending of this.pending) {
if(pending.encoded && pending.body) { if(pending.encoded && pending.body) {
pending.encoded = this.encodeBody(pending.body); pending.encoded = this.encodeBody(pending.body);
} }
} }
}
setTimeout(() => { setTimeout(() => {
this.releasePending(); this.releasePending();
}, 0); }, 0);
this.connection.send(initPayload);
}; };
private onMessage = (buffer: ArrayBuffer) => { private onMessage = (buffer: ArrayBuffer) => {
@ -101,12 +99,18 @@ export default class TcpObfuscated implements MTTransport {
private onClose = () => { private onClose = () => {
this.connected = false; this.connected = false;
this.connection.removeListener('open', this.onOpen);
this.connection.removeListener('close', this.onClose);
this.connection.removeListener('message', this.onMessage);
this.connection = undefined;
const time = Date.now(); const time = Date.now();
const diff = time - this.lastCloseTime; const diff = time - this.lastCloseTime;
const needTimeout = !isNaN(diff) && diff < this.retryTimeout ? this.retryTimeout - diff : 0; const needTimeout = !isNaN(diff) && diff < this.retryTimeout ? this.retryTimeout - diff : 0;
if(this.networker) { if(this.networker) {
this.networker.setConnectionStatus(false); this.networker.setConnectionStatus(false);
this.pending.length = 0;
} }
this.log('will try to reconnect after timeout:', needTimeout / 1000); this.log('will try to reconnect after timeout:', needTimeout / 1000);
@ -114,30 +118,26 @@ export default class TcpObfuscated implements MTTransport {
this.log('trying to reconnect...'); this.log('trying to reconnect...');
this.lastCloseTime = Date.now(); this.lastCloseTime = Date.now();
if(!this.networker) {
for(const pending of this.pending) { for(const pending of this.pending) {
if(pending.bodySent) { if(pending.bodySent) {
pending.bodySent = false; pending.bodySent = false;
} }
} }
}
this.connect(); this.connect();
}, needTimeout); }, needTimeout);
this.connection.removeListener('open', this.onOpen);
this.connection.removeListener('close', this.onClose);
this.connection.removeListener('message', this.onMessage);
this.connection = undefined;
}; };
private connect() { private connect() {
this.connection = new this.Connection(this.dcId, this.url, this.logSuffix); this.connection = new this.Connection(this.dcId, this.url, this.logSuffix);
this.connection.addListener('open', this.onOpen); this.connection.addListener('open', this.onOpen);
this.connection.addListener('close', this.onClose); this.connection.addListener('close', this.onClose);
this.connection.addListener('message', this.onMessage); this.connection.addListener('message', this.onMessage);
} }
private encodeBody = (body: Uint8Array) => { private encodeBody(body: Uint8Array) {
const toEncode = this.codec.encodePacket(body); const toEncode = this.codec.encodePacket(body);
//this.log('send before obf:', /* body.hex, nonce.hex, */ toEncode.hex); //this.log('send before obf:', /* body.hex, nonce.hex, */ toEncode.hex);
@ -145,9 +145,9 @@ export default class TcpObfuscated implements MTTransport {
//this.log('send after obf:', enc.hex); //this.log('send after obf:', enc.hex);
return encoded; return encoded;
}; }
public send = (body: Uint8Array) => { public send(body: Uint8Array) {
this.debug && this.log.debug('-> body length to pending:', body.length); this.debug && this.log.debug('-> body length to pending:', body.length);
const encoded: typeof body = this.connected ? this.encodeBody(body) : undefined; const encoded: typeof body = this.connected ? this.encodeBody(body) : undefined;
@ -166,7 +166,7 @@ export default class TcpObfuscated implements MTTransport {
return promise; return promise;
} }
}; }
private releasePending(/* tt = false */) { private releasePending(/* tt = false */) {
if(!this.connected) { if(!this.connected) {
@ -206,7 +206,7 @@ export default class TcpObfuscated implements MTTransport {
} */ } */
if(!encoded) { if(!encoded) {
encoded = this.encodeBody(body); encoded = pending.encoded = this.encodeBody(body);
} }
//this.lol.push(body); //this.lol.push(body);

Loading…
Cancel
Save