Fix file download speed for 1 thread

This commit is contained in:
morethanwords 2020-06-06 09:39:35 +03:00
parent d3f1e1e8cb
commit 36839e1486
8 changed files with 158 additions and 165 deletions

View File

@ -183,7 +183,7 @@ export class AppMessagesManager {
public loadSavedState() {
if(this.loaded) return this.loaded;
this.loaded = new Promise((resolve, reject) => {
return this.loaded = new Promise((resolve, reject) => {
AppStorage.get<{
dialogs: Dialog[],
allDialogsLoaded: AppMessagesManager['allDialogsLoaded'],

View File

@ -29,22 +29,22 @@ export class ApiFileManager {
resolve: (...args: any[]) => void,
reject: (...args: any[]) => void
},
activeDelta?: number
activeDelta: number
}>
} = {};
public downloadActives: {[dcID: string]: number} = {};
private log: ReturnType<typeof logger> = logger('AFM');
public downloadRequest(dcID: string | number, cb: () => Promise<unknown>, activeDelta?: number) {
public downloadRequest(dcID: string | number, cb: () => Promise<unknown>, activeDelta: number) {
if(this.downloadPulls[dcID] === undefined) {
this.downloadPulls[dcID] = [];
this.downloadActives[dcID] = 0;
}
var downloadPull = this.downloadPulls[dcID];
const downloadPull = this.downloadPulls[dcID];
let promise = new Promise((resolve, reject) => {
const promise = new Promise((resolve, reject) => {
downloadPull.push({cb: cb, deferred: {resolve, reject}, activeDelta: activeDelta});
})/* .catch(() => {}) */;
@ -56,15 +56,16 @@ export class ApiFileManager {
}
public downloadCheck(dcID: string | number) {
var downloadPull = this.downloadPulls[dcID];
var downloadLimit = dcID == 'upload' ? 11 : 5;
const downloadPull = this.downloadPulls[dcID];
//const downloadLimit = dcID == 'upload' ? 11 : 5;
const downloadLimit = 24;
if(this.downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) {
return false;
}
var data = downloadPull.shift();
var activeDelta = data.activeDelta || 1;
const data = downloadPull.shift();
const activeDelta = data.activeDelta || 1;
this.downloadActives[dcID] += activeDelta;
@ -122,8 +123,8 @@ export class ApiFileManager {
}
public getTempFileName(file: any) {
var size = file.size || -1;
var random = nextRandomInt(0xFFFFFFFF);
const size = file.size || -1;
const random = nextRandomInt(0xFFFFFFFF);
return '_temp' + random + '_' + size;
}
@ -131,12 +132,12 @@ export class ApiFileManager {
if(!location) {
return false;
}
var fileName = this.getFileName(location);
const fileName = this.getFileName(location);
return this.cachedDownloads[fileName] || false;
}
public getFileStorage(): typeof cacheStorage {
public getFileStorage() {
return cacheStorage;
}
@ -419,7 +420,7 @@ export class ApiFileManager {
fileDownload: true/* ,
singleInRequest: 'safari' in window */
});
}, dcID).then((result: any) => {
}, 2).then((result: any) => {
writeFilePromise.then(() => {
if(canceled) {
return Promise.resolve();

View File

@ -13,6 +13,7 @@ import passwordManager from './passwordManager';
/// #if !MTPROTO_WORKER
import { $rootScope } from '../utils';
import { InvokeApiOptions } from '../../types';
/// #endif
//console.error('apiManager included!');
@ -133,10 +134,10 @@ export class ApiManager {
}
// mtpGetNetworker
public async getNetworker(dcID: number, options: any = {}): Promise<MTPNetworker> {
let upload = (options.fileUpload || options.fileDownload)
public async getNetworker(dcID: number, options: InvokeApiOptions): Promise<MTPNetworker> {
const upload = (options.fileUpload || options.fileDownload)
&& (dcConfigurator.chooseServer(dcID, true) instanceof HTTP || Modes.multipleConnections);
let cache = upload ? this.cachedUploadNetworkers : this.cachedNetworkers;
const cache = upload ? this.cachedUploadNetworkers : this.cachedNetworkers;
if(!dcID) {
throw new Error('get Networker without dcID');
@ -146,72 +147,61 @@ export class ApiManager {
return cache[dcID];
}
let getKey = dcID + '-' + +upload;
const getKey = dcID + '-' + +upload;
if(this.gettingNetworkers[getKey]) {
return this.gettingNetworkers[getKey];
}
const ak = 'dc' + dcID + '_auth_key';
const akID = 'dc' + dcID + '_auth_keyID';
const ss = 'dc' + dcID + '_server_salt';
return this.gettingNetworkers[getKey] = new Promise(async(resolve, reject) => {
var ak = 'dc' + dcID + '_auth_key';
var akID = 'dc' + dcID + '_auth_keyID';
var ss = 'dc' + dcID + '_server_salt';
let result = await AppStorage.get<string[]/* |boolean[] */>([ak, akID, ss]);
let [authKeyHex, authKeyIDHex, serverSaltHex] = result;
if(authKeyHex && !authKeyIDHex && serverSaltHex) {
return this.gettingNetworkers[getKey] = AppStorage.get<string[]/* |boolean[] */>([ak, akID, ss])
.then(async([authKeyHex, authKeyIDHex, serverSaltHex]) => {
/* if(authKeyHex && !authKeyIDHex && serverSaltHex) {
this.log.warn('Updating to new version (+akID)');
await AppStorage.remove(ak, akID, ss);
authKeyHex = serverSaltHex = '';
}
} */
let networker: MTPNetworker;
if(authKeyHex && authKeyHex.length == 512) {
if(!serverSaltHex || serverSaltHex.length != 16) {
serverSaltHex = 'AAAAAAAAAAAAAAAA';
}
var authKey = bytesFromHex(authKeyHex);
var authKeyID = new Uint8Array(bytesFromHex(authKeyIDHex));
var serverSalt = bytesFromHex(serverSaltHex);
const authKey = bytesFromHex(authKeyHex);
const authKeyID = new Uint8Array(bytesFromHex(authKeyIDHex));
const serverSalt = bytesFromHex(serverSaltHex);
resolve(cache[dcID] = networkerFactory.getNetworker(dcID, authKey, authKeyID, serverSalt, options));
} else try {
let auth = await authorizer.auth(dcID);
let storeObj = {
[ak]: bytesToHex(auth.authKey),
[akID]: auth.authKeyID.hex,
[ss]: bytesToHex(auth.serverSalt)
};
AppStorage.set(storeObj);
resolve(cache[dcID] = networkerFactory.getNetworker(dcID, auth.authKey, auth.authKeyID, auth.serverSalt, options));
} catch(error) {
this.log('Get networker error', error, error.stack);
reject(error);
networker = networkerFactory.getNetworker(dcID, authKey, authKeyID, serverSalt, options);
} else {
try { // if no saved state
const auth = await authorizer.auth(dcID);
const storeObj = {
[ak]: bytesToHex(auth.authKey),
[akID]: auth.authKeyID.hex,
[ss]: bytesToHex(auth.serverSalt)
};
AppStorage.set(storeObj);
networker = networkerFactory.getNetworker(dcID, auth.authKey, auth.authKeyID, auth.serverSalt, options);
} catch(error) {
this.log('Get networker error', error, error.stack);
delete this.gettingNetworkers[getKey];
throw error;
}
}
delete this.gettingNetworkers[getKey];
return cache[dcID] = networker;
});
}
// mtpInvokeApi
public invokeApi(method: string, params: any = {}, options: Partial<{
dcID: number,
timeout: number,
noErrorBox: boolean,
fileUpload: boolean,
ignoreErrors: boolean,
fileDownload: boolean,
createNetworker: boolean,
singleInRequest: boolean,
startMaxLength: number,
waitTime: number,
stopTime: number,
rawError: any
}> = {}) {
public invokeApi(method: string, params: any = {}, options: InvokeApiOptions = {}) {
///////this.log('Invoke api', method, params, options);
return new Promise((resolve, reject) => {
@ -343,7 +333,6 @@ export class ApiManager {
public checkPassword(value: string): Promise<any> {
return passwordManager.getState()
.then(state => {
console.log(state);
return passwordManager.check(state, value);
});
}

View File

@ -3,19 +3,11 @@ import MTTransport from './transports/transport';
import HTTP from './transports/http';
import { Modes } from './mtproto_config';
type TransportTypes = 'websocket' | 'https' | 'http';
type Servers = {
[transport: string]: {
[dcID: number]: MTTransport
[transportType in TransportTypes]: {
[dcID: number]: MTTransport[]
}
/* websocket: {
[dcID: number]: Socket
},
https: {
[dcID: number]: HTTPTransport
},
http: {
[dcID: number]: HTTPTransport
} */
};
export class DcConfigurator {
@ -47,41 +39,50 @@ export class DcConfigurator {
http: {}
};
public chooseServer(dcID: number, upload?: boolean, transport = 'websocket') {
let servers = upload && (transport != 'websocket' || Modes.multipleConnections)
? this.chosenUploadServers[transport]
: this.chosenServers[transport];
public chooseServer(dcID: number, upload?: boolean, transportType: TransportTypes = 'websocket') {
const servers = upload && (transportType != 'websocket' || Modes.multipleConnections)
? this.chosenUploadServers[transportType]
: this.chosenServers[transportType];
if(!(dcID in servers)) {
let chosenServer = '';
servers[dcID] = [];
}
if(transport == 'websocket') {
let subdomain = this.sslSubdomains[dcID - 1];
let path = Modes.test ? 'apiws_test' : 'apiws';
chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path;
return servers[dcID] = new Socket(dcID, chosenServer);
}
if(Modes.ssl || !Modes.http || transport == 'https') {
let subdomain = this.sslSubdomains[dcID - 1] + (upload ? '-1' : '');
let path = Modes.test ? 'apiw_test1' : 'apiw1';
chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path;
return servers[dcID] = new HTTP(dcID, chosenServer);
}
for(let dcOption of this.dcOptions) {
if(dcOption.id == dcID) {
chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1';
return servers[dcID] = new HTTP(dcID, chosenServer);
const transports = servers[dcID];
if(!transports.length || (upload && transports.length < 1)) {
let transport: MTTransport;
if(transportType == 'websocket') {
const subdomain = this.sslSubdomains[dcID - 1];
const path = Modes.test ? 'apiws_test' : 'apiws';
const chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path;
transport = new Socket(dcID, chosenServer);
} else if(Modes.ssl || !Modes.http || transportType == 'https') {
const subdomain = this.sslSubdomains[dcID - 1] + (upload ? '-1' : '');
const path = Modes.test ? 'apiw_test1' : 'apiw1';
const chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path;
transport = new HTTP(dcID, chosenServer);
} else {
for(let dcOption of this.dcOptions) {
if(dcOption.id == dcID) {
const chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1';
transport = new HTTP(dcID, chosenServer);
break;
}
}
}
console.error('No chosenServer!', dcID);
return null;
if(!transport) {
console.error('No chosenServer!', dcID);
return null;
}
transports.push(transport);
return transport;
}
return servers[dcID];
return transports[0];
}
}

View File

@ -14,6 +14,7 @@ import Socket from './transports/websocket';
import HTTP from './transports/http';
import { logger } from '../polyfill';
import { Modes, App } from './mtproto_config';
import { InvokeApiOptions } from '../../types';
//console.error('networker included!', new Error().stack);
@ -90,14 +91,12 @@ class MTPNetworker {
private log: ReturnType<typeof logger>;
constructor(private dcID: number, private authKey: number[], private authKeyID: Uint8Array,
private serverSalt: number[], private options: any = {}) {
private serverSalt: number[], private options: InvokeApiOptions = {}) {
this.authKeyUint8 = convertToUint8Array(this.authKey);
//this.authKeyID = sha1BytesSync(this.authKey).slice(-8);
this.upload = this.options.fileUpload || this.options.fileDownload || false;
this.log = logger('NET-' + dcID + (this.upload ? '-U' : ''));
this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */);
this.updateSession();
@ -111,7 +110,7 @@ class MTPNetworker {
this.transport = dcConfigurator.chooseServer(this.dcID, this.upload);
if(this.transport instanceof HTTP) {
/* this.longPollInt = */window.setInterval(this.checkLongPoll.bind(this), 10000);
/* this.longPollInt = */setInterval(this.checkLongPoll.bind(this), 10000);
this.checkLongPoll();
} else {
(this.transport as Socket).networker = this;
@ -204,24 +203,7 @@ class MTPNetworker {
return this.pushMessage(message, options);
}
public wrapApiCall(method: string, params: any = {}, options: {
dcID?: number,
timeout?: number,
noErrorBox?: boolean,
fileUpload?: boolean,
ignoreErrors?: boolean,
fileDownload?: boolean,
createNetworker?: boolean,
singleInRequest?: boolean,
startMaxLength?: number,
afterMessageID?: string,
resultType?: boolean,
waitTime?: number,
stopTime?: number,
rawError?: any
} = {}) {
public wrapApiCall(method: string, params: any = {}, options: InvokeApiOptions = {}) {
let serializer = new TLSerialization(options);
if(!this.connectionInited) { // this will call once for each new session
@ -293,7 +275,7 @@ class MTPNetworker {
}
public checkLongPoll() {
var isClean = this.cleanupSent();
const isClean = this.cleanupSent();
//this.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean, this);
if((this.longPollPending && Date.now() < this.longPollPending) ||
this.offline) {
@ -301,18 +283,17 @@ class MTPNetworker {
return false;
}
var self = this;
AppStorage.get<number>('dc').then((baseDcID: number) => {
if(isClean && (
baseDcID != self.dcID ||
self.upload ||
(self.sleepAfter && Date.now() > self.sleepAfter)
baseDcID != this.dcID ||
this.upload ||
(this.sleepAfter && Date.now() > this.sleepAfter)
)) {
//console.warn(dT(), 'Send long-poll for DC is delayed', self.dcID, self.sleepAfter);
//console.warn(dT(), 'Send long-poll for DC is delayed', this.dcID, this.sleepAfter);
return;
}
self.sendLongPoll();
this.sendLongPoll();
});
}
@ -963,7 +944,7 @@ class MTPNetworker {
clearTimeout(this.nextReqTimeout);
this.nextReqTimeout = 0;
if(delay > 0) {
this.nextReqTimeout = window.setTimeout(this.performScheduledRequest.bind(this), delay || 0);
this.nextReqTimeout = setTimeout(this.performScheduledRequest.bind(this), delay || 0);
} else {
setTimeout(this.performScheduledRequest.bind(this), 0);
}
@ -984,26 +965,25 @@ class MTPNetworker {
}
public cleanupSent() {
var self = this;
var notEmpty = false;
let notEmpty = false;
// this.log('clean start', this.dcID/*, this.sentMessages*/)
Object.keys(this.sentMessages).forEach((msgID) => {
let message = this.sentMessages[msgID];
const message = this.sentMessages[msgID];
// this.log('clean iter', msgID, message)
if(message.notContentRelated && self.pendingMessages[msgID] === undefined) {
if(message.notContentRelated && this.pendingMessages[msgID] === undefined) {
// this.log('clean notContentRelated', msgID)
delete self.sentMessages[msgID];
delete this.sentMessages[msgID];
} else if(message.container) {
for(var i = 0; i < message.inner.length; i++) {
if(self.sentMessages[message.inner[i]] !== undefined) {
// this.log('clean failed, found', msgID, message.inner[i], self.sentMessages[message.inner[i]].seq_no)
for(let i = 0; i < message.inner.length; i++) {
if(this.sentMessages[message.inner[i]] !== undefined) {
// this.log('clean failed, found', msgID, message.inner[i], this.sentMessages[message.inner[i]].seq_no)
notEmpty = true;
return;
}
}
// this.log('clean container', msgID)
delete self.sentMessages[msgID];
delete this.sentMessages[msgID];
} else {
notEmpty = true;
}

View File

@ -1,4 +1,5 @@
import { MTPNetworker } from "./networker";
import { InvokeApiOptions } from "../../types";
export class NetworkerFactory {
public updatesProcessor: (obj: any, bool: boolean) => void = null;
@ -7,8 +8,8 @@ export class NetworkerFactory {
this.updatesProcessor = callback;
}
public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: any) {
//console.log(dT(), 'NetworkerFactory: creating new instance of MTPNetworker:', dcID, options);
public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: InvokeApiOptions) {
//console.log('NetworkerFactory: creating new instance of MTPNetworker:', dcID, options);
return new MTPNetworker(dcID, authKey, authKeyID, serverSalt, options);
}
}

View File

@ -6,6 +6,8 @@ import {str2bigInt, greater, isZero,
bigInt2str, powMod, int2bigInt, mult, mod, sub, bitSize, negative, mult, add} from 'leemon/es/index/';
export class PasswordManager {
private log = (...args: any[]) => {};
public getState(options: any = {}) {
return apiManager.invokeApi('account.getPassword', {}, options).then((result) => {
return result
@ -89,19 +91,19 @@ export class PasswordManager {
let buffer = bufferConcats(client_salt, passwordBuffer, client_salt);
return CryptoWorker.sha256Hash(buffer).then((buffer: any) => {
console.log('encoded 1', bytesToHex(new Uint8Array(buffer)));
this.log('encoded 1', bytesToHex(new Uint8Array(buffer)));
buffer = bufferConcats(server_salt, buffer, server_salt);
return CryptoWorker.sha256Hash(buffer).then((buffer: any) => {
console.log('encoded 2', buffer, bytesToHex(new Uint8Array(buffer)));
this.log('encoded 2', buffer, bytesToHex(new Uint8Array(buffer)));
return CryptoWorker.pbkdf2(new Uint8Array(buffer), client_salt, 100000).then((hash: any) => {
console.log('encoded 3', hash, bytesToHex(new Uint8Array(hash)));
this.log('encoded 3', hash, bytesToHex(new Uint8Array(hash)));
hash = bufferConcats(server_salt, hash, server_salt);
return CryptoWorker.sha256Hash(hash).then((buffer: any) => {
console.log('got password hash:', buffer, bytesToHex(new Uint8Array(buffer)));
this.log('got password hash:', buffer, bytesToHex(new Uint8Array(buffer)));
return buffer;
});
@ -117,8 +119,8 @@ export class PasswordManager {
let B = str2bigInt(bytesToHex(state.srp_B), 16);
let g = int2bigInt(algo.g, 32, 256);
console.log('p', bigInt2str(p, 16));
console.log('B', bigInt2str(B, 16));
this.log('p', bigInt2str(p, 16));
this.log('B', bigInt2str(B, 16));
/* if(B.compareTo(BigInteger.ZERO) < 0) {
console.error('srp_B < 0')
@ -144,7 +146,7 @@ export class PasswordManager {
new Uint8Array(algo.salt2)) as ArrayBuffer;
let x = str2bigInt(bytesToHex(new Uint8Array(pw_hash)), 16);
console.warn('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash)));
this.log('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash)));
var padArray = function(arr: any[], len: number, fill = 0) {
@ -155,25 +157,25 @@ export class PasswordManager {
let gForHash = padArray(bytesFromHex(bigInt2str(g, 16)), 256); // like uint8array
let b_for_hash = padArray(bytesFromHex(bigInt2str(B, 16)), 256);
console.log(bytesToHex(pForHash));
console.log(bytesToHex(gForHash));
console.log(bytesToHex(b_for_hash));
this.log(bytesToHex(pForHash));
this.log(bytesToHex(gForHash));
this.log(bytesToHex(b_for_hash));
let g_x = powMod(g, x, p);
console.log('g_x', bigInt2str(g_x, 16));
this.log('g_x', bigInt2str(g_x, 16));
let k: any = await CryptoWorker.sha256Hash(bufferConcat(pForHash, gForHash));
k = str2bigInt(bytesToHex(new Uint8Array(k)), 16);
console.log('k', bigInt2str(k, 16));
this.log('k', bigInt2str(k, 16));
// kg_x = (k * g_x) % p
let kg_x = mod(mult(k, g_x), p);
// good
console.log('kg_x', bigInt2str(kg_x, 16));
this.log('kg_x', bigInt2str(kg_x, 16));
let is_good_mod_exp_first = (modexp: any, prime: any) => {
let diff = sub(prime, modexp);
@ -217,12 +219,12 @@ export class PasswordManager {
let {a, a_for_hash, u} = await generate_and_check_random();
console.log('a', bigInt2str(a, 16));
console.log('a_for_hash', bytesToHex(a_for_hash));
console.log('u', bigInt2str(u, 16));
this.log('a', bigInt2str(a, 16));
this.log('a_for_hash', bytesToHex(a_for_hash));
this.log('u', bigInt2str(u, 16));
// g_b = (B - kg_x) % p
console.log('B - kg_x', bigInt2str(sub(B, kg_x), 16));
this.log('B - kg_x', bigInt2str(sub(B, kg_x), 16));
//let g_b = mod(sub(B, kg_x), p);
/* let g_b = sub(B, kg_x);
if(negative(g_b)) g_b = add(g_b, p);
@ -231,15 +233,15 @@ export class PasswordManager {
if(!negative(sub(B, kg_x))) g_b = sub(mod(B, p), kg_x);
else g_b = mod(sub(B, kg_x), p); */
/* let lol = trim(sub(B, kg_x), 10);
console.log('llalala', bigInt2str(lol, 16)); */
this.log('llalala', bigInt2str(lol, 16)); */
let g_b;
if(!greater(B, kg_x)) {
console.log('negative');
this.log('negative');
g_b = add(B, p);
} else g_b = B;
g_b = mod(sub(g_b, kg_x), p);
//g_b = mod(g_b, p);
//console.log('g_b', bigInt2str(g_b, 16));
//this.log('g_b', bigInt2str(g_b, 16));
/* if(!is_good_mod_exp_first(g_b, p))
throw new Error('bad g_b'); */
@ -277,7 +279,7 @@ export class PasswordManager {
};
console.log('out', bytesToHex(out.A), bytesToHex(out.M1));
this.log('out', bytesToHex(out.A), bytesToHex(out.M1));
return out;
/* console.log(gForHash, pForHash, bForHash); */

21
src/types.d.ts vendored
View File

@ -42,4 +42,23 @@ export type MTPhotoSize = {
type?: string, // i, m, x, y, w by asc
location?: any,
bytes?: Uint8Array // if type == 'i'
};
};
export type InvokeApiOptions = Partial<{
dcID: number,
timeout: number,
noErrorBox: boolean,
fileUpload: boolean,
ignoreErrors: boolean,
fileDownload: boolean,
createNetworker: boolean,
singleInRequest: boolean,
startMaxLength: number,
afterMessageID: string,
resultType: boolean,
waitTime: number,
stopTime: number,
rawError: any
}>;