From eebf95c6ecb2ef86d99e67752257d22901a9f5c4 Mon Sep 17 00:00:00 2001 From: alina sireneva Date: Thu, 5 Sep 2024 22:06:01 +0300 Subject: [PATCH] chore!: moved to @fuman/utils for common stuff breaking: some utils were removed from @mtcute/core/utils.js, use them from @fuman/utils instead --- .config/vite-utils/test-setup.ts | 4 +- package.json | 1 + packages/convert/src/gramjs/parse.ts | 5 +- packages/convert/src/gramjs/serialize.ts | 5 +- packages/convert/src/pyrogram/parse.ts | 6 +- packages/convert/src/pyrogram/serialize.ts | 7 +- packages/convert/src/telethon/parse.ts | 5 +- packages/convert/src/telethon/serialize.ts | 5 +- .../src/highlevel/methods/auth/sign-in-qr.ts | 11 +- .../methods/files/download-buffer.ts | 5 +- .../methods/files/download-iterable.ts | 5 +- .../methods/users/resolve-peer-many.ts | 2 +- .../src/highlevel/storage/service/updates.ts | 11 +- .../core/src/highlevel/types/conversation.ts | 19 +- .../core/src/highlevel/updates/manager.ts | 4 +- packages/core/src/highlevel/updates/parsed.ts | 3 +- packages/core/src/highlevel/updates/types.ts | 3 +- .../core/src/highlevel/utils/file-utils.ts | 5 +- .../core/src/highlevel/utils/stream-utils.ts | 9 +- .../core/src/highlevel/utils/voice-utils.ts | 6 +- packages/core/src/highlevel/worker/invoker.ts | 9 +- packages/core/src/network/auth-key.ts | 14 +- packages/core/src/network/authorization.ts | 68 +++--- packages/core/src/network/mtproto-session.ts | 6 +- .../core/src/network/mtproxy/_fake-tls.ts | 19 +- packages/core/src/network/mtproxy/index.ts | 12 +- .../src/network/multi-session-connection.ts | 6 +- packages/core/src/network/network-manager.ts | 14 +- .../core/src/network/persistent-connection.ts | 2 +- packages/core/src/network/server-salt.ts | 3 +- .../core/src/network/session-connection.ts | 27 +-- .../src/network/transports/intermediate.ts | 5 +- .../core/src/network/transports/obfuscated.ts | 10 +- packages/core/src/utils/async-lock.test.ts | 31 --- packages/core/src/utils/async-lock.ts | 44 ---- packages/core/src/utils/bigint-utils.test.ts | 145 +------------ packages/core/src/utils/bigint-utils.ts | 195 +----------------- packages/core/src/utils/buffer-utils.test.ts | 87 -------- packages/core/src/utils/buffer-utils.ts | 79 ------- .../core/src/utils/condition-variable.test.ts | 38 ---- packages/core/src/utils/condition-variable.ts | 30 --- .../src/utils/controllable-promise.test.ts | 17 -- .../core/src/utils/controllable-promise.ts | 25 --- .../src/utils/crypto/factorization.test.ts | 9 +- .../core/src/utils/crypto/factorization.ts | 23 +-- packages/core/src/utils/crypto/index.ts | 1 - .../core/src/utils/crypto/miller-rabin.ts | 14 +- .../core/src/utils/crypto/mtproto.test.ts | 6 +- packages/core/src/utils/crypto/mtproto.ts | 32 +-- packages/core/src/utils/crypto/password.ts | 50 ++--- packages/core/src/utils/crypto/utils.test.ts | 60 ------ packages/core/src/utils/crypto/utils.ts | 27 --- packages/core/src/utils/early-timer.ts | 2 +- .../core/src/utils/function-utils.test.ts | 2 +- packages/core/src/utils/function-utils.ts | 2 +- packages/core/src/utils/index.ts | 4 - packages/core/src/utils/long-utils.ts | 4 +- packages/core/src/utils/misc-utils.ts | 9 +- packages/core/src/utils/reloadable.ts | 3 +- packages/dispatcher/package.json | 1 + packages/dispatcher/src/state/update-state.ts | 2 +- packages/test/src/crypto.ts | 5 +- pnpm-lock.yaml | 6 + 63 files changed, 241 insertions(+), 1028 deletions(-) delete mode 100644 packages/core/src/utils/async-lock.test.ts delete mode 100644 packages/core/src/utils/async-lock.ts delete mode 100644 packages/core/src/utils/buffer-utils.test.ts delete mode 100644 packages/core/src/utils/buffer-utils.ts delete mode 100644 packages/core/src/utils/condition-variable.test.ts delete mode 100644 packages/core/src/utils/condition-variable.ts delete mode 100644 packages/core/src/utils/controllable-promise.test.ts delete mode 100644 packages/core/src/utils/controllable-promise.ts delete mode 100644 packages/core/src/utils/crypto/utils.test.ts delete mode 100644 packages/core/src/utils/crypto/utils.ts diff --git a/.config/vite-utils/test-setup.ts b/.config/vite-utils/test-setup.ts index 5c66fba7..69e9c285 100644 --- a/.config/vite-utils/test-setup.ts +++ b/.config/vite-utils/test-setup.ts @@ -1,7 +1,7 @@ import { expect } from 'vitest' +import { typed } from '@fuman/utils' import { setPlatform } from '../../packages/core/src/platform.js' -import { buffersEqual } from '../../packages/core/src/utils/buffer-utils.js' // @ts-expect-error no .env here if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') { @@ -14,7 +14,7 @@ if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno expect.addEqualityTesters([ function (a, b) { if (a instanceof Uint8Array && b instanceof Uint8Array) { - return buffersEqual(a, b) + return typed.equal(a, b) } }, ]) diff --git a/package.json b/package.json index c8005bfa..7b853969 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "devDependencies": { "@antfu/eslint-config": "2.26.0", + "@fuman/utils": "workspace:^", "@teidesu/slow-types-compiler": "1.1.0", "@types/deno": "npm:@teidesu/deno-types@1.46.3", "@types/node": "20.10.0", diff --git a/packages/convert/src/gramjs/parse.ts b/packages/convert/src/gramjs/parse.ts index 16a91925..edfd2c45 100644 --- a/packages/convert/src/gramjs/parse.ts +++ b/packages/convert/src/gramjs/parse.ts @@ -1,6 +1,5 @@ import { MtArgumentError } from '@mtcute/core' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { base64, utf8 } from '@fuman/utils' +import { base64, typed, utf8 } from '@fuman/utils' import type { TelethonSession } from '../telethon/types.js' @@ -13,7 +12,7 @@ export function parseGramjsSession(session: string): TelethonSession { session = session.slice(1) const data = base64.decode(session) - const dv = dataViewFromBuffer(data) + const dv = typed.toDataView(data) const dcId = dv.getUint8(0) diff --git a/packages/convert/src/gramjs/serialize.ts b/packages/convert/src/gramjs/serialize.ts index daf0d79a..558f9c79 100644 --- a/packages/convert/src/gramjs/serialize.ts +++ b/packages/convert/src/gramjs/serialize.ts @@ -1,6 +1,5 @@ import { MtArgumentError } from '@mtcute/core' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { base64, utf8 } from '@fuman/utils' +import { base64, typed, utf8 } from '@fuman/utils' import type { TelethonSession } from '../telethon/types.js' @@ -12,7 +11,7 @@ export function serializeGramjsSession(session: TelethonSession): string { const ipEncoded = utf8.encoder.encode(session.ipAddress) const u8 = new Uint8Array(261 + ipEncoded.length) - const dv = dataViewFromBuffer(u8) + const dv = typed.toDataView(u8) dv.setUint8(0, session.dcId) dv.setUint16(1, ipEncoded.length) diff --git a/packages/convert/src/pyrogram/parse.ts b/packages/convert/src/pyrogram/parse.ts index d914a55b..ff806f13 100644 --- a/packages/convert/src/pyrogram/parse.ts +++ b/packages/convert/src/pyrogram/parse.ts @@ -1,8 +1,8 @@ // source: https://github.com/pyrogram/pyrogram/blob/master/pyrogram/storage/storage.py import { Long } from '@mtcute/core' -import { dataViewFromBuffer, longFromBuffer } from '@mtcute/core/utils.js' -import { base64 } from '@fuman/utils' +import { longFromBuffer } from '@mtcute/core/utils.js' +import { base64, typed } from '@fuman/utils' import type { PyrogramSession } from './types.js' @@ -11,7 +11,7 @@ const SESSION_STRING_SIZE_64 = 356 export function parsePyrogramSession(session: string): PyrogramSession { const data = base64.decode(session, true) - const dv = dataViewFromBuffer(data) + const dv = typed.toDataView(data) if (session.length === SESSION_STRING_SIZE || session.length === SESSION_STRING_SIZE_64) { // old format diff --git a/packages/convert/src/pyrogram/serialize.ts b/packages/convert/src/pyrogram/serialize.ts index 3ddb1bad..19e1184c 100644 --- a/packages/convert/src/pyrogram/serialize.ts +++ b/packages/convert/src/pyrogram/serialize.ts @@ -1,6 +1,5 @@ import { Long, MtArgumentError } from '@mtcute/core' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { base64 } from '@fuman/utils' +import { base64, typed } from '@fuman/utils' import type { PyrogramSession } from './types.js' @@ -19,7 +18,7 @@ export function serializePyrogramSession(session: PyrogramSession): string { if (session.apiId === undefined) { // old format u8 = new Uint8Array(SESSION_STRING_SIZE_OLD) - const dv = dataViewFromBuffer(u8) + const dv = typed.toDataView(u8) dv.setUint8(0, session.dcId) dv.setUint8(1, session.isTest ? 1 : 0) @@ -29,7 +28,7 @@ export function serializePyrogramSession(session: PyrogramSession): string { dv.setUint8(266, session.isBot ? 1 : 0) } else { u8 = new Uint8Array(SESSION_STRING_SIZE) - const dv = dataViewFromBuffer(u8) + const dv = typed.toDataView(u8) dv.setUint8(0, session.dcId) dv.setUint32(1, session.apiId) diff --git a/packages/convert/src/telethon/parse.ts b/packages/convert/src/telethon/parse.ts index 2761f348..db0df758 100644 --- a/packages/convert/src/telethon/parse.ts +++ b/packages/convert/src/telethon/parse.ts @@ -1,6 +1,5 @@ import { MtArgumentError } from '@mtcute/core' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { base64 } from '@fuman/utils' +import { base64, typed } from '@fuman/utils' import { parseIpFromBytes } from '../utils/ip.js' @@ -15,7 +14,7 @@ export function parseTelethonSession(session: string): TelethonSession { session = session.slice(1) const data = base64.decode(session, true) - const dv = dataViewFromBuffer(data) + const dv = typed.toDataView(data) const dcId = dv.getUint8(0) diff --git a/packages/convert/src/telethon/serialize.ts b/packages/convert/src/telethon/serialize.ts index c44d1238..4110cc31 100644 --- a/packages/convert/src/telethon/serialize.ts +++ b/packages/convert/src/telethon/serialize.ts @@ -1,6 +1,5 @@ import { MtArgumentError } from '@mtcute/core' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { base64 } from '@fuman/utils' +import { base64, typed } from '@fuman/utils' import { serializeIpv4ToBytes, serializeIpv6ToBytes } from '../utils/ip.js' @@ -13,7 +12,7 @@ export function serializeTelethonSession(session: TelethonSession): string { const ipSize = session.ipv6 ? 16 : 4 const u8 = new Uint8Array(259 + ipSize) - const dv = dataViewFromBuffer(u8) + const dv = typed.toDataView(u8) dv.setUint8(0, session.dcId) diff --git a/packages/core/src/highlevel/methods/auth/sign-in-qr.ts b/packages/core/src/highlevel/methods/auth/sign-in-qr.ts index 660353a8..15307768 100644 --- a/packages/core/src/highlevel/methods/auth/sign-in-qr.ts +++ b/packages/core/src/highlevel/methods/auth/sign-in-qr.ts @@ -1,9 +1,7 @@ import { tl } from '@mtcute/tl' +import { Deferred, base64 } from '@fuman/utils' -import { getPlatform } from '../../../platform.js' import type { MaybePromise } from '../../../types/utils.js' -import type { ControllablePromise } from '../../../utils/controllable-promise.js' -import { createControllablePromise } from '../../../utils/controllable-promise.js' import { sleepWithAbort } from '../../../utils/misc-utils.js' import { assertTypeIs } from '../../../utils/type-assertions.js' import type { ITelegramClient, ServerUpdateHandler } from '../../client.types.js' @@ -53,7 +51,7 @@ export async function signInQr( ): Promise { const { onUrlUpdated, abortSignal, onQrScanned } = params - let waiter: ControllablePromise | undefined + let waiter: Deferred | undefined // crutch – we need to wait for the updateLoginToken update. // we replace the server update handler temporarily because: @@ -108,7 +106,6 @@ export async function signInQr( try { const { id, hash } = await client.getApiCrenetials() - const platform = getPlatform() loop: while (true) { let res: tl.auth.TypeLoginToken @@ -134,11 +131,11 @@ export async function signInQr( switch (res._) { case 'auth.loginToken': onUrlUpdated( - `tg://login?token=${platform.base64Encode(res.token, true)}`, + `tg://login?token=${base64.encode(res.token, true)}`, new Date(res.expires * 1000), ) - waiter = createControllablePromise() + waiter = new Deferred() await Promise.race([waiter, sleepWithAbort(res.expires * 1000 - Date.now(), client.stopSignal)]) break case 'auth.loginTokenMigrateTo': { diff --git a/packages/core/src/highlevel/methods/files/download-buffer.ts b/packages/core/src/highlevel/methods/files/download-buffer.ts index 1dc3e9a5..fdf77936 100644 --- a/packages/core/src/highlevel/methods/files/download-buffer.ts +++ b/packages/core/src/highlevel/methods/files/download-buffer.ts @@ -1,4 +1,5 @@ -import { concatBuffers } from '../../../utils/buffer-utils.js' +import { u8 } from '@fuman/utils' + import type { ITelegramClient } from '../../client.types.js' import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js' import { FileLocation } from '../../types/index.js' @@ -28,5 +29,5 @@ export async function downloadAsBuffer( chunks.push(chunk) } - return concatBuffers(chunks) + return u8.concat(chunks) } diff --git a/packages/core/src/highlevel/methods/files/download-iterable.ts b/packages/core/src/highlevel/methods/files/download-iterable.ts index ecb81c8d..d1e41376 100644 --- a/packages/core/src/highlevel/methods/files/download-iterable.ts +++ b/packages/core/src/highlevel/methods/files/download-iterable.ts @@ -1,10 +1,9 @@ import { parseFileId } from '@mtcute/file-id' import { tl } from '@mtcute/tl' +import { ConditionVariable } from '@fuman/utils' import type { ConnectionKind } from '../../../network/network-manager.js' -import { getPlatform } from '../../../platform.js' import { MtArgumentError, MtUnsupportedError } from '../../../types/errors.js' -import { ConditionVariable } from '../../../utils/condition-variable.js' import type { ITelegramClient } from '../../client.types.js' import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js' import { FileLocation } from '../../types/index.js' @@ -58,7 +57,7 @@ export async function* downloadAsIterable( if (!fileSize) fileSize = input.fileSize location = locationInner } else if (typeof input === 'string') { - const parsed = parseFileId(getPlatform(), input) + const parsed = parseFileId(input) if (parsed.location._ === 'web') { location = fileIdToInputWebFileLocation(parsed) diff --git a/packages/core/src/highlevel/methods/users/resolve-peer-many.ts b/packages/core/src/highlevel/methods/users/resolve-peer-many.ts index 97a46cb9..0e27e504 100644 --- a/packages/core/src/highlevel/methods/users/resolve-peer-many.ts +++ b/packages/core/src/highlevel/methods/users/resolve-peer-many.ts @@ -1,6 +1,6 @@ import type { tl } from '@mtcute/tl' +import { ConditionVariable } from '@fuman/utils' -import { ConditionVariable } from '../../../utils/condition-variable.js' import type { ITelegramClient } from '../../client.types.js' import { MtPeerNotFoundError } from '../../types/errors.js' import type { InputPeerLike } from '../../types/peers/index.js' diff --git a/packages/core/src/highlevel/storage/service/updates.ts b/packages/core/src/highlevel/storage/service/updates.ts index d35d2776..38181a31 100644 --- a/packages/core/src/highlevel/storage/service/updates.ts +++ b/packages/core/src/highlevel/storage/service/updates.ts @@ -1,7 +1,8 @@ +import { typed } from '@fuman/utils' + import type { IKeyValueRepository } from '../../../storage/repository/key-value.js' import type { ServiceOptions } from '../../../storage/service/base.js' import { BaseService } from '../../../storage/service/base.js' -import { dataViewFromBuffer } from '../../../utils/buffer-utils.js' const KV_PTS = 'updates_pts' const KV_QTS = 'updates_qts' @@ -23,12 +24,12 @@ export class UpdatesStateService extends BaseService { const val = await this._kv.get(key) if (!val) return null - return dataViewFromBuffer(val).getInt32(0, true) + return typed.toDataView(val).getInt32(0, true) } private async _setInt(key: string, val: number): Promise { const buf = new Uint8Array(4) - dataViewFromBuffer(buf).setInt32(0, val, true) + typed.toDataView(buf).setInt32(0, val, true) await this._kv.set(key, buf) } @@ -68,12 +69,12 @@ export class UpdatesStateService extends BaseService { const val = await this._kv.get(KV_CHANNEL_PREFIX + channelId) if (!val) return null - return dataViewFromBuffer(val).getUint32(0, true) + return typed.toDataView(val).getUint32(0, true) } async setChannelPts(channelId: number, pts: number): Promise { const buf = new Uint8Array(4) - dataViewFromBuffer(buf).setUint32(0, pts, true) + typed.toDataView(buf).setUint32(0, pts, true) await this._kv.set(KV_CHANNEL_PREFIX + channelId, buf) } diff --git a/packages/core/src/highlevel/types/conversation.ts b/packages/core/src/highlevel/types/conversation.ts index fe87e8d1..d8cbb1d2 100644 --- a/packages/core/src/highlevel/types/conversation.ts +++ b/packages/core/src/highlevel/types/conversation.ts @@ -1,10 +1,8 @@ import type { tl } from '@mtcute/tl' +import { AsyncLock, Deferred, timers } from '@fuman/utils' import { MtArgumentError, MtTimeoutError } from '../../types/errors.js' import type { MaybePromise } from '../../types/utils.js' -import { AsyncLock } from '../../utils/async-lock.js' -import type { ControllablePromise } from '../../utils/controllable-promise.js' -import { createControllablePromise } from '../../utils/controllable-promise.js' import { Deque } from '../../utils/deque.js' import { getMarkedPeerId } from '../../utils/peer-utils.js' import type { ITelegramClient } from '../client.types.js' @@ -14,7 +12,6 @@ import { sendMedia } from '../methods/messages/send-media.js' import { sendMediaGroup } from '../methods/messages/send-media-group.js' import { sendText } from '../methods/messages/send-text.js' import { resolvePeer } from '../methods/users/resolve-peer.js' -import { timers } from '../../utils/index.js' import type { Message } from './messages/message.js' import type { InputPeerLike } from './peers/index.js' @@ -22,7 +19,7 @@ import type { HistoryReadUpdate, ParsedUpdate } from './updates/index.js' import type { ParametersSkip2 } from './utils.js' interface QueuedHandler { - promise: ControllablePromise + promise: Deferred check?: (update: T) => MaybePromise timeout?: timers.Timer } @@ -340,7 +337,7 @@ export class Conversation { throw new MtArgumentError("Conversation hasn't started yet") } - const promise = createControllablePromise() + const promise = new Deferred() let timer: timers.Timer | undefined @@ -359,7 +356,7 @@ export class Conversation { this._processPendingNewMessages() - return promise + return promise.promise } /** @@ -481,7 +478,7 @@ export class Conversation { throw new MtArgumentError('Provide message for which to wait for edit for') } - const promise = createControllablePromise() + const promise = new Deferred() let timer: timers.Timer | undefined const timeout = params?.timeout @@ -501,7 +498,7 @@ export class Conversation { this._processRecentEdits() - return promise + return promise.promise } /** @@ -529,7 +526,7 @@ export class Conversation { const [dialog] = await getPeerDialogs(this.client, this._inputPeer) if (dialog.lastRead >= msgId) return - const promise = createControllablePromise() + const promise = new Deferred() let timer: timers.Timer | undefined @@ -545,7 +542,7 @@ export class Conversation { timeout: timer, }) - return promise + return promise.promise } private _onNewMessage(msg: Message) { diff --git a/packages/core/src/highlevel/updates/manager.ts b/packages/core/src/highlevel/updates/manager.ts index 5c91a8c3..90a41f79 100644 --- a/packages/core/src/highlevel/updates/manager.ts +++ b/packages/core/src/highlevel/updates/manager.ts @@ -1,4 +1,5 @@ import { tl } from '@mtcute/tl' +import { AsyncLock, ConditionVariable, timers } from '@fuman/utils' import Long from 'long' import { MtArgumentError } from '../../types/errors.js' @@ -8,8 +9,6 @@ import type { Logger, } from '../../utils/index.js' import { - AsyncLock, - ConditionVariable, Deque, EarlyTimer, SortedLinkedList, @@ -22,7 +21,6 @@ import { import type { BaseTelegramClient } from '../base.js' import type { CurrentUserInfo } from '../storage/service/current-user.js' import { PeersIndex } from '../types/peers/peers-index.js' -import * as timers from '../../utils/timers.js' import { _getChannelsBatched } from '../methods/chats/batched-queries.js' import type { PendingUpdate, PendingUpdateContainer, RawUpdateHandler, UpdatesManagerParams } from './types.js' diff --git a/packages/core/src/highlevel/updates/parsed.ts b/packages/core/src/highlevel/updates/parsed.ts index 1336392c..33c7bda7 100644 --- a/packages/core/src/highlevel/updates/parsed.ts +++ b/packages/core/src/highlevel/updates/parsed.ts @@ -1,4 +1,5 @@ -import { timers } from '../../utils/index.js' +import { timers } from '@fuman/utils' + import type { Message } from '../types/messages/index.js' import type { BusinessMessage, ParsedUpdate } from '../types/updates/index.js' import { _parseUpdate } from '../types/updates/parse-update.js' diff --git a/packages/core/src/highlevel/updates/types.ts b/packages/core/src/highlevel/updates/types.ts index d8ef335d..d53350fb 100644 --- a/packages/core/src/highlevel/updates/types.ts +++ b/packages/core/src/highlevel/updates/types.ts @@ -1,6 +1,7 @@ import type { tl } from '@mtcute/tl' +import type { AsyncLock, ConditionVariable, timers } from '@fuman/utils' -import type { AsyncLock, ConditionVariable, Deque, EarlyTimer, Logger, SortedLinkedList, timers } from '../../utils/index.js' +import type { Deque, EarlyTimer, Logger, SortedLinkedList } from '../../utils/index.js' import type { CurrentUserInfo } from '../storage/service/current-user.js' import type { PeersIndex } from '../types/peers/peers-index.js' diff --git a/packages/core/src/highlevel/utils/file-utils.ts b/packages/core/src/highlevel/utils/file-utils.ts index dff0b6e8..970e939b 100644 --- a/packages/core/src/highlevel/utils/file-utils.ts +++ b/packages/core/src/highlevel/utils/file-utils.ts @@ -1,8 +1,7 @@ import type { tl } from '@mtcute/tl' -import { hex, utf8 } from '@fuman/utils' +import { hex, u8, utf8 } from '@fuman/utils' import { MtArgumentError } from '../../types/errors.js' -import { concatBuffers } from '../../utils/buffer-utils.js' /** * Given file size, determine the appropriate chunk size (in KB) @@ -72,7 +71,7 @@ export function strippedPhotoToJpg(stripped: Uint8Array): Uint8Array { JPEG_HEADER_BYTES = JPEG_HEADER() } - const result = concatBuffers([JPEG_HEADER_BYTES, stripped.slice(3), JPEG_FOOTER]) + const result = u8.concat3(JPEG_HEADER_BYTES, stripped.slice(3), JPEG_FOOTER) result[164] = stripped[1] result[166] = stripped[2] diff --git a/packages/core/src/highlevel/utils/stream-utils.ts b/packages/core/src/highlevel/utils/stream-utils.ts index 123347f8..5110449f 100644 --- a/packages/core/src/highlevel/utils/stream-utils.ts +++ b/packages/core/src/highlevel/utils/stream-utils.ts @@ -1,5 +1,4 @@ -import { AsyncLock } from '../../utils/async-lock.js' -import { concatBuffers } from '../../utils/buffer-utils.js' +import { AsyncLock, u8 } from '@fuman/utils' export function bufferToStream(buf: Uint8Array): ReadableStream { return new ReadableStream({ @@ -21,7 +20,7 @@ export async function streamToBuffer(stream: ReadableStream): Promis chunks.push(value) } - return concatBuffers(chunks) + return u8.concat(chunks) } export function createChunkedReader(stream: ReadableStream, chunkSize: number): { @@ -77,12 +76,12 @@ export function createChunkedReader(stream: ReadableStream, chunkSiz if (length === chunkSize) { bufferLength -= chunkSize - return concatBuffers(chunks) + return u8.concat(chunks) } } else if (next === undefined && bufferLength > 0) { bufferLength = 0 - return concatBuffers(buffer) + return u8.concat(buffer) } const value = await readInner() diff --git a/packages/core/src/highlevel/utils/voice-utils.ts b/packages/core/src/highlevel/utils/voice-utils.ts index ea0396cb..8243efee 100644 --- a/packages/core/src/highlevel/utils/voice-utils.ts +++ b/packages/core/src/highlevel/utils/voice-utils.ts @@ -1,4 +1,4 @@ -import { dataViewFromBuffer } from '../../utils/buffer-utils.js' +import { typed } from '@fuman/utils' /** * Decode 5-bit encoded voice message waveform into @@ -24,7 +24,7 @@ export function decodeWaveform(wf: Uint8Array): number[] { // So we read in a general way all the entries except the last one. const result: number[] = [] - const dv = dataViewFromBuffer(wf) + const dv = typed.toDataView(wf) for (let i = 0, j = 0; i < lastIdx; i++, j += 5) { const byteIdx = ~~(j / 8) @@ -50,7 +50,7 @@ export function encodeWaveform(wf: number[]): Uint8Array { const bitsCount = wf.length * 5 const bytesCount = ~~((bitsCount + 7) / 8) const result = new Uint8Array(bytesCount + 1) - const dv = dataViewFromBuffer(result) + const dv = typed.toDataView(result) // Write each 0-31 unsigned char as 5 bit to result. // We reserve one extra byte to be able to dereference any of required bytes diff --git a/packages/core/src/highlevel/worker/invoker.ts b/packages/core/src/highlevel/worker/invoker.ts index c30036f6..96a75f7a 100644 --- a/packages/core/src/highlevel/worker/invoker.ts +++ b/packages/core/src/highlevel/worker/invoker.ts @@ -1,5 +1,4 @@ -import type { ControllablePromise } from '../../utils/controllable-promise.js' -import { createControllablePromise } from '../../utils/controllable-promise.js' +import { Deferred } from '@fuman/utils' import { deserializeError } from './errors.js' import type { SendFn, WorkerInboundMessage, WorkerOutboundMessage } from './protocol.js' @@ -11,7 +10,7 @@ export class WorkerInvoker { constructor(private send: SendFn) {} private _nextId = 0 - private _pending = new Map() + private _pending = new Map>() private _invoke(target: InvokeTarget, method: string, args: unknown[], isVoid: boolean, abortSignal?: AbortSignal) { const id = this._nextId++ @@ -34,11 +33,11 @@ export class WorkerInvoker { }) if (!isVoid) { - const promise = createControllablePromise() + const promise = new Deferred() this._pending.set(id, promise) - return promise + return promise.promise } } diff --git a/packages/core/src/network/auth-key.ts b/packages/core/src/network/auth-key.ts index e55ea4a9..323d9de8 100644 --- a/packages/core/src/network/auth-key.ts +++ b/packages/core/src/network/auth-key.ts @@ -2,11 +2,11 @@ import type Long from 'long' import type { tl } from '@mtcute/tl' import type { TlReaderMap } from '@mtcute/tl-runtime' import { TlBinaryReader } from '@mtcute/tl-runtime' +import { typed, u8 } from '@fuman/utils' import { MtcuteError } from '../types/errors.js' import { createAesIgeForMessage } from '../utils/crypto/mtproto.js' import type { ICryptoProvider, Logger } from '../utils/index.js' -import { buffersEqual, concatBuffers, dataViewFromBuffer } from '../utils/index.js' export class AuthKey { ready = false @@ -23,7 +23,7 @@ export class AuthKey { ) {} match(keyId: Uint8Array): boolean { - return this.ready && buffersEqual(keyId, this.id) + return this.ready && typed.equal(keyId, this.id) } setup(authKey?: Uint8Array | null): void { @@ -45,7 +45,7 @@ export class AuthKey { padding = 12 + (padding ? 16 - padding : 0) const buf = new Uint8Array(16 + message.length + padding) - const dv = dataViewFromBuffer(buf) + const dv = typed.toDataView(buf) dv.setInt32(0, serverSalt.low, true) dv.setInt32(4, serverSalt.high, true) @@ -54,11 +54,11 @@ export class AuthKey { buf.set(message, 16) this._crypto.randomFill(buf.subarray(16 + message.length, 16 + message.length + padding)) - const messageKey = this._crypto.sha256(concatBuffers([this.clientSalt, buf])).subarray(8, 24) + const messageKey = this._crypto.sha256(u8.concat2(this.clientSalt, buf)).subarray(8, 24) const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true) const encryptedData = ige.encrypt(buf) - return concatBuffers([this.id, messageKey, encryptedData]) + return u8.concat3(this.id, messageKey, encryptedData) } decryptMessage( @@ -81,10 +81,10 @@ export class AuthKey { const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false) const innerData = ige.decrypt(encryptedData) - const msgKeySource = this._crypto.sha256(concatBuffers([this.serverSalt, innerData])) + const msgKeySource = this._crypto.sha256(u8.concat2(this.serverSalt, innerData)) const expectedMessageKey = msgKeySource.subarray(8, 24) - if (!buffersEqual(messageKey, expectedMessageKey)) { + if (!typed.equal(messageKey, expectedMessageKey)) { this.log.warn('received message with invalid messageKey = %h (expected %h)', messageKey, expectedMessageKey) return diff --git a/packages/core/src/network/authorization.ts b/packages/core/src/network/authorization.ts index cf11d21e..651a0cf1 100644 --- a/packages/core/src/network/authorization.ts +++ b/packages/core/src/network/authorization.ts @@ -2,15 +2,13 @@ import Long from 'long' import { mtp } from '@mtcute/tl' import type { TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js' import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime' +import { bigint, typed, u8 } from '@fuman/utils' import { MtArgumentError, MtSecurityError, MtTypeAssertionError } from '../types/index.js' -import { buffersEqual, concatBuffers, dataViewFromBuffer } from '../utils/buffer-utils.js' import { findKeyByFingerprints } from '../utils/crypto/keys.js' import { millerRabin } from '../utils/crypto/miller-rabin.js' import { generateKeyAndIvFromNonce } from '../utils/crypto/mtproto.js' -import { xorBuffer, xorBufferInPlace } from '../utils/crypto/utils.js' import type { ICryptoProvider, Logger } from '../utils/index.js' -import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../utils/index.js' import { mtpAssertTypeIs } from '../utils/type-assertions.js' import type { SessionConnection } from './session-connection.js' @@ -140,7 +138,7 @@ function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Ui const aesKey = crypto.randomBytes(32) - const dataWithHash = concatBuffers([data, crypto.sha256(concatBuffers([aesKey, data]))]) + const dataWithHash = u8.concat2(data, crypto.sha256(u8.concat2(aesKey, data))) // we only need to reverse the data dataWithHash.subarray(0, 192).reverse() @@ -148,36 +146,36 @@ function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Ui const encrypted = aes.encrypt(dataWithHash) const encryptedHash = crypto.sha256(encrypted) - xorBufferInPlace(aesKey, encryptedHash) - const decryptedData = concatBuffers([aesKey, encrypted]) + u8.xorInPlace(aesKey, encryptedHash) + const decryptedData = u8.concat2(aesKey, encrypted) - const decryptedDataBigint = bufferToBigInt(decryptedData) + const decryptedDataBigint = bigint.fromBytes(decryptedData) if (decryptedDataBigint >= keyModulus) { continue } - const encryptedBigint = bigIntModPow(decryptedDataBigint, keyExponent, keyModulus) + const encryptedBigint = bigint.modPowBinary(decryptedDataBigint, keyExponent, keyModulus) - return bigIntToBuffer(encryptedBigint, 256) + return bigint.toBytes(encryptedBigint, 256) } } function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array { - const toEncrypt = concatBuffers([ + const toEncrypt = u8.concat3( crypto.sha1(data), data, // sha1 is always 20 bytes, so we're left with 255 - 20 - x padding crypto.randomBytes(235 - data.length), - ]) + ) - const encryptedBigInt = bigIntModPow( - bufferToBigInt(toEncrypt), + const encryptedBigInt = bigint.modPowBinary( + bigint.fromBytes(toEncrypt), BigInt(`0x${key.exponent}`), BigInt(`0x${key.modulus}`), ) - return bigIntToBuffer(encryptedBigInt) + return bigint.toBytes(encryptedBigInt) } /** @@ -233,7 +231,7 @@ export async function doAuthorization( mtpAssertTypeIs('auth step 1', resPq, 'mt_resPQ') - if (!buffersEqual(resPq.nonce, nonce)) { + if (!typed.equal(resPq.nonce, nonce)) { throw new MtSecurityError('Step 1: invalid nonce from server') } @@ -250,7 +248,7 @@ export async function doAuthorization( } log.debug('found server key, fp = %s, old = %s', publicKey.fingerprint, publicKey.old) - if (millerRabin(crypto, bufferToBigInt(resPq.pq))) { + if (millerRabin(crypto, bigint.fromBytes(resPq.pq))) { throw new MtSecurityError('Step 2: pq is prime') } @@ -295,10 +293,10 @@ export async function doAuthorization( mtpAssertTypeIs('auth step 2', serverDhParams, 'mt_server_DH_params_ok') - if (!buffersEqual(serverDhParams.nonce, nonce)) { + if (!typed.equal(serverDhParams.nonce, nonce)) { throw new MtSecurityError('Step 2: invalid nonce from server') } - if (!buffersEqual(serverDhParams.serverNonce, resPq.serverNonce)) { + if (!typed.equal(serverDhParams.serverNonce, resPq.serverNonce)) { throw new MtSecurityError('Step 2: invalid server nonce from server') } @@ -317,36 +315,36 @@ export async function doAuthorization( const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20) const serverDhInner = serverDhInnerReader.object() as mtp.TlObject - if (!buffersEqual(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) { + if (!typed.equal(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) { throw new MtSecurityError('Step 3: invalid inner data hash') } mtpAssertTypeIs('auth step 3', serverDhInner, 'mt_server_DH_inner_data') - if (!buffersEqual(serverDhInner.nonce, nonce)) { + if (!typed.equal(serverDhInner.nonce, nonce)) { throw new Error('Step 3: invalid nonce from server') } - if (!buffersEqual(serverDhInner.serverNonce, resPq.serverNonce)) { + if (!typed.equal(serverDhInner.serverNonce, resPq.serverNonce)) { throw new Error('Step 3: invalid server nonce from server') } - const dhPrime = bufferToBigInt(serverDhInner.dhPrime) + const dhPrime = bigint.fromBytes(serverDhInner.dhPrime) const timeOffset = Math.floor(Date.now() / 1000) - serverDhInner.serverTime session.updateTimeOffset(timeOffset) const g = BigInt(serverDhInner.g) - const gA = bufferToBigInt(serverDhInner.gA) + const gA = bigint.fromBytes(serverDhInner.gA) checkDhPrime(crypto, log, dhPrime, serverDhInner.g) let retryId = Long.ZERO - const serverSalt = xorBuffer(newNonce.subarray(0, 8), resPq.serverNonce.subarray(0, 8)) + const serverSalt = u8.xor(newNonce.subarray(0, 8), resPq.serverNonce.subarray(0, 8)) for (;;) { - const b = bufferToBigInt(crypto.randomBytes(256)) - const gB = bigIntModPow(g, b, dhPrime) + const b = bigint.fromBytes(crypto.randomBytes(256)) + const gB = bigint.modPowBinary(g, b, dhPrime) - const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime)) + const authKey = bigint.toBytes(bigint.modPowBinary(gA, b, dhPrime)) const authKeyAuxHash = crypto.sha1(authKey).subarray(0, 8) // validate DH params @@ -367,7 +365,7 @@ export async function doAuthorization( throw new MtSecurityError('g_b is not within (2^{2048-64}, dh_prime - 2^{2048-64})') } - const gB_ = bigIntToBuffer(gB, 0, false) + const gB_ = bigint.toBytes(gB, 0) // Step 4: send client DH const clientDhInner: mtp.RawMt_client_DH_inner_data = { @@ -404,10 +402,10 @@ export async function doAuthorization( throw new MtTypeAssertionError('auth step 4', 'set_client_DH_params_answer', dhGen._) } - if (!buffersEqual(dhGen.nonce, nonce)) { + if (!typed.equal(dhGen.nonce, nonce)) { throw new MtSecurityError('Step 4: invalid nonce from server') } - if (!buffersEqual(dhGen.serverNonce, resPq.serverNonce)) { + if (!typed.equal(dhGen.serverNonce, resPq.serverNonce)) { throw new MtSecurityError('Step 4: invalid server nonce from server') } @@ -419,9 +417,9 @@ export async function doAuthorization( } if (dhGen._ === 'mt_dh_gen_retry') { - const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash])) + const expectedHash = crypto.sha1(u8.concat3(newNonce, new Uint8Array([2]), authKeyAuxHash)) - if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) { + if (!typed.equal(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) { throw new MtSecurityError('Step 4: invalid retry nonce hash from server') } retryId = Long.fromBytesLE(authKeyAuxHash as unknown as number[]) @@ -430,15 +428,15 @@ export async function doAuthorization( if (dhGen._ !== 'mt_dh_gen_ok') throw new Error('unreachable') - const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash])) + const expectedHash = crypto.sha1(u8.concat3(newNonce, [1], authKeyAuxHash)) - if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) { + if (!typed.equal(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) { throw new MtSecurityError('Step 4: invalid nonce hash from server') } log.info('authorization successful') - const dv = dataViewFromBuffer(serverSalt) + const dv = typed.toDataView(serverSalt) return [authKey, new Long(dv.getInt32(0, true), dv.getInt32(4, true)), timeOffset] } diff --git a/packages/core/src/network/mtproto-session.ts b/packages/core/src/network/mtproto-session.ts index 3b6cb121..99e02fb8 100644 --- a/packages/core/src/network/mtproto-session.ts +++ b/packages/core/src/network/mtproto-session.ts @@ -2,10 +2,10 @@ import Long from 'long' import type { mtp, tl } from '@mtcute/tl' import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import { TlSerializationCounter } from '@mtcute/tl-runtime' +import type { Deferred } from '@fuman/utils' import { MtcuteError } from '../types/index.js' import type { - ControllablePromise, ICryptoProvider, Logger, } from '../utils/index.js' @@ -27,7 +27,7 @@ import type { ServerSaltManager } from './server-salt.js' export interface PendingRpc { method: string data: Uint8Array - promise: ControllablePromise + promise: Deferred stack?: string gzipOverhead?: number @@ -86,7 +86,7 @@ export type PendingMessage = } | { _: 'bind' - promise: ControllablePromise + promise: Deferred } /** diff --git a/packages/core/src/network/mtproxy/_fake-tls.ts b/packages/core/src/network/mtproxy/_fake-tls.ts index c720ab15..52afd11a 100644 --- a/packages/core/src/network/mtproxy/_fake-tls.ts +++ b/packages/core/src/network/mtproxy/_fake-tls.ts @@ -1,8 +1,7 @@ import { Bytes, type ISyncWritable, read } from '@fuman/io' +import { bigint, typed, u8 } from '@fuman/utils' import type { Logger } from '../../utils' -import { concatBuffers, dataViewFromBuffer } from '../../utils' -import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../../utils/bigint-utils' import type { ICryptoProvider } from '../../utils/crypto/abstract' import type { IPacketCodec } from '../transports' @@ -35,14 +34,14 @@ function _getDoubleX(x: bigint, mod: bigint): bigint { numerator = (numerator - 1n) % mod numerator = (numerator * numerator) % mod - denominator = bigIntModInv(denominator, mod) + denominator = bigint.modInv(denominator, mod) numerator = (numerator * denominator) % mod return numerator } function _isQuadraticResidue(a: bigint): boolean { - const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD) + const r = bigint.modPowBinary(a, QUAD_RES_POW, QUAD_RES_MOD) return r === 1n } @@ -126,7 +125,7 @@ class TlsHelloWriter { ) { this._domain = domain this.buf = new Uint8Array(size) - this.dv = dataViewFromBuffer(this.buf) + this.dv = typed.toDataView(this.buf) this._grease = initGrease(this.crypto, 7) } @@ -158,7 +157,7 @@ class TlsHelloWriter { const key = this.crypto.randomBytes(32) key[31] &= 127 - let x = bufferToBigInt(key) + let x = bigint.fromBytes(key) const y = _getY2(x, KEY_MOD) if (_isQuadraticResidue(y)) { @@ -166,7 +165,7 @@ class TlsHelloWriter { x = _getDoubleX(x, KEY_MOD) } - const key = bigIntToBuffer(x, 32, true) + const key = bigint.toBytes(x, 32, true) this.string(key) return @@ -201,7 +200,7 @@ class TlsHelloWriter { this.endScope() const hash = await this.crypto.hmacSha256(this.buf, secret) - const dv = dataViewFromBuffer(hash) + const dv = typed.toDataView(hash) const old = dv.getInt32(28, true) dv.setInt32(28, old ^ unixTime, true) @@ -255,12 +254,12 @@ export class FakeTlsPacketCodec implements IPacketCodec { let packet if (this._isFirstTls) { this._isFirstTls = false - packet = concatBuffers([this._tag, tmp.readSync(MAX_TLS_PACKET_LENGTH - this._tag.length)]) + packet = u8.concat2(this._tag, tmp.readSync(MAX_TLS_PACKET_LENGTH - this._tag.length)) } else { packet = tmp.readSync(MAX_TLS_PACKET_LENGTH) } - dataViewFromBuffer(header).setUint16(3, packet.length) + typed.toDataView(header).setUint16(3, packet.length) into.writeSync(header.length).set(header) into.writeSync(packet.length).set(packet) diff --git a/packages/core/src/network/mtproxy/index.ts b/packages/core/src/network/mtproxy/index.ts index 00d0cc21..17c774bb 100644 --- a/packages/core/src/network/mtproxy/index.ts +++ b/packages/core/src/network/mtproxy/index.ts @@ -1,11 +1,11 @@ import type { tl } from '@mtcute/tl' import type { ITcpConnection, TcpEndpoint } from '@fuman/net' import { Bytes, read, write } from '@fuman/io' -import { base64, hex } from '@fuman/utils' +import { base64, hex, typed, u8 } from '@fuman/utils' import type { IPacketCodec, ITelegramConnection, MtProxyInfo, TelegramTransport } from '../transports/index.js' import { IntermediatePacketCodec, ObfuscatedPacketCodec, PaddedIntermediatePacketCodec } from '../transports/index.js' -import { type BasicDcOption, type ICryptoProvider, type Logger, buffersEqual, concatBuffers, dataViewFromBuffer } from '../../utils/index.js' +import type { BasicDcOption, ICryptoProvider, Logger } from '../../utils/index.js' import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js' import { FakeTlsPacketCodec, generateFakeTlsHeader } from './_fake-tls.js' @@ -152,11 +152,11 @@ export abstract class BaseMtProxyTransport implements TelegramTransport { const buf = await read.async.exactly(conn, first.length + 2) write.bytes(resp, buf) - if (!buffersEqual(buf.slice(0, first.length), first)) { + if (!typed.equal(buf.slice(0, first.length), first)) { throw new MtSecurityError('Server hello is invalid') } - const skipSize = dataViewFromBuffer(buf).getUint16(first.length) + const skipSize = typed.toDataView(buf).getUint16(first.length) write.bytes(resp, await read.async.exactly(conn, skipSize)) } @@ -164,7 +164,7 @@ export abstract class BaseMtProxyTransport implements TelegramTransport { const respBuf = resp.result() const respRand = respBuf.slice(11, 11 + 32) const hash = await this._crypto.hmacSha256( - concatBuffers([ + u8.concat([ helloRand, respBuf.slice(0, 11), new Uint8Array(32), @@ -173,7 +173,7 @@ export abstract class BaseMtProxyTransport implements TelegramTransport { this._rawSecret, ) - if (!buffersEqual(hash, respRand)) { + if (!typed.equal(hash, respRand)) { throw new MtSecurityError('Response hash is invalid') } diff --git a/packages/core/src/network/multi-session-connection.ts b/packages/core/src/network/multi-session-connection.ts index 1232f764..2034213e 100644 --- a/packages/core/src/network/multi-session-connection.ts +++ b/packages/core/src/network/multi-session-connection.ts @@ -2,9 +2,9 @@ import EventEmitter from 'events' import type { mtp, tl } from '@mtcute/tl' +import { Deferred } from '@fuman/utils' import type { Logger } from '../utils/index.js' -import { createControllablePromise } from '../utils/index.js' import { MtprotoSession } from './mtproto-session.js' import type { SessionConnectionParams } from './session-connection.js' @@ -132,10 +132,10 @@ export class MultiSessionConnection extends EventEmitter { if (enforcePfsChanged) { // we need to fetch new auth keys first - const promise = createControllablePromise() + const promise = new Deferred() this.emit('request-keys', promise) - promise + promise.promise .then(() => { this._connections.forEach((conn) => { conn.setUsePfs(this.params.usePfs || this._enforcePfs) diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index 61211a89..c239075c 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -2,14 +2,14 @@ import type { mtp, tl } from '@mtcute/tl' import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import type Long from 'long' import { type ReconnectionStrategy, defaultReconnectionStrategy } from '@fuman/net' +import { Deferred } from '@fuman/utils' import { getPlatform } from '../platform.js' import type { StorageManager } from '../storage/storage.js' import { MtArgumentError, MtUnsupportedError, MtcuteError } from '../types/index.js' import type { ComposedMiddleware, Middleware } from '../utils/composer.js' import { composeMiddlewares } from '../utils/composer.js' -import type { ControllablePromise, DcOptions, ICryptoProvider, Logger } from '../utils/index.js' -import { createControllablePromise } from '../utils/index.js' +import type { DcOptions, ICryptoProvider, Logger } from '../utils/index.js' import { assertTypeIs, isTlRpcError } from '../utils/type-assertions.js' import type { ConfigManager } from './config-manager.js' @@ -382,7 +382,7 @@ export class DcConnectionManager { }) // fucking awesome architecture, but whatever - connection.on('request-keys', (promise: ControllablePromise) => { + connection.on('request-keys', (promise: Deferred) => { this.loadKeys(true) .then(() => promise.resolve()) .catch((e: Error) => promise.reject(e)) @@ -577,8 +577,8 @@ export class NetworkManager { return this._dcConnections.get(dcId)! } - const promise = createControllablePromise() - this._dcCreationPromise.set(dcId, promise) + const promise = new Deferred() + this._dcCreationPromise.set(dcId, promise.promise) this._log.debug('creating new DC %d', dcId) @@ -632,8 +632,8 @@ export class NetworkManager { } this._log.debug('exporting auth to dc %d', manager.dcId) - const promise = createControllablePromise() - this._pendingExports[manager.dcId] = promise + const promise = new Deferred() + this._pendingExports[manager.dcId] = promise.promise try { const auth = await this.call({ diff --git a/packages/core/src/network/persistent-connection.ts b/packages/core/src/network/persistent-connection.ts index 6f008bce..3c9663fd 100644 --- a/packages/core/src/network/persistent-connection.ts +++ b/packages/core/src/network/persistent-connection.ts @@ -4,9 +4,9 @@ import EventEmitter from 'events' import type { ReconnectionStrategy } from '@fuman/net' import { PersistentConnection as FumanPersistentConnection } from '@fuman/net' import { FramedReader, FramedWriter } from '@fuman/io' +import { timers } from '@fuman/utils' import type { BasicDcOption, ICryptoProvider, Logger } from '../utils/index.js' -import { timers } from '../utils/index.js' import type { IPacketCodec, ITelegramConnection, TelegramTransport } from './transports/abstract.js' diff --git a/packages/core/src/network/server-salt.ts b/packages/core/src/network/server-salt.ts index 2dd90326..7d057e2c 100644 --- a/packages/core/src/network/server-salt.ts +++ b/packages/core/src/network/server-salt.ts @@ -1,7 +1,6 @@ import Long from 'long' import type { mtp } from '@mtcute/tl' - -import { timers } from '../utils/index.js' +import { timers } from '@fuman/utils' export class ServerSaltManager { private _futureSalts: mtp.RawMt_future_salt[] = [] diff --git a/packages/core/src/network/session-connection.ts b/packages/core/src/network/session-connection.ts index f48b195c..20476498 100644 --- a/packages/core/src/network/session-connection.ts +++ b/packages/core/src/network/session-connection.ts @@ -3,19 +3,14 @@ import type { mtp } from '@mtcute/tl' import { tl } from '@mtcute/tl' import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime' +import { Deferred, u8 } from '@fuman/utils' import { getPlatform } from '../platform.js' import { MtArgumentError, MtTimeoutError, MtcuteError } from '../types/index.js' import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js' -import type { - ControllablePromise, - ICryptoProvider, -} from '../utils/index.js' +import type { ICryptoProvider } from '../utils/index.js' import { EarlyTimer, - - concatBuffers, - createControllablePromise, longFromBuffer, randomLong, removeFromLongArray, @@ -74,7 +69,7 @@ export class SessionConnection extends PersistentConnection { private _queuedDestroySession: Long[] = [] // waitForMessage - private _pendingWaitForUnencrypted: [ControllablePromise, timers.Timer][] = [] + private _pendingWaitForUnencrypted: [Deferred, timers.Timer][] = [] private _usePfs private _isPfsBindingPending = false @@ -389,9 +384,9 @@ export class SessionConnection extends PersistentConnection { const ige = createAesIgeForMessageOld(this._crypto, this._session._authKey.key, msgKey, true) const encryptedData = ige.encrypt(msgWithPadding) - const encryptedMessage = concatBuffers([this._session._authKey.id, msgKey, encryptedData]) + const encryptedMessage = u8.concat3(this._session._authKey.id, msgKey, encryptedData) - const promise = createControllablePromise() + const promise = new Deferred() // encrypt the message using temp key and same msg id // this is a bit of a hack, but it works @@ -432,7 +427,7 @@ export class SessionConnection extends PersistentConnection { ) await this.send(requestEncrypted) - const res = await promise + const res = await promise.promise this._session.pendingMessages.delete(msgId) @@ -496,14 +491,14 @@ export class SessionConnection extends PersistentConnection { if (this._destroyed) { return Promise.reject(new MtcuteError('Connection destroyed')) } - const promise = createControllablePromise() + const promise = new Deferred() const timeoutId = timers.setTimeout(() => { promise.reject(new MtTimeoutError(timeout)) this._pendingWaitForUnencrypted = this._pendingWaitForUnencrypted.filter(it => it[0] !== promise) }, timeout) this._pendingWaitForUnencrypted.push([promise, timeoutId]) - return promise + return promise.promise } protected onMessage(data: Uint8Array): void { @@ -1412,7 +1407,7 @@ export class SessionConnection extends PersistentConnection { const pending: PendingRpc = { method, - promise: createControllablePromise(), + promise: new Deferred(), data: content, // we will need to know size of gzip_packed overhead in _flush() gzipOverhead: shouldGzip ? 4 + TlSerializationCounter.countBytesOverhead(content.length) : 0, @@ -1435,7 +1430,7 @@ export class SessionConnection extends PersistentConnection { if (abortSignal?.aborted) { pending.promise.reject(abortSignal.reason) - return pending.promise + return pending.promise.promise } if (timeout) { @@ -1448,7 +1443,7 @@ export class SessionConnection extends PersistentConnection { this._enqueueRpc(pending, true) - return pending.promise + return pending.promise.promise } notifyNetworkChanged(online: boolean): void { diff --git a/packages/core/src/network/transports/intermediate.ts b/packages/core/src/network/transports/intermediate.ts index f34febaa..e59cb496 100644 --- a/packages/core/src/network/transports/intermediate.ts +++ b/packages/core/src/network/transports/intermediate.ts @@ -1,8 +1,9 @@ import type { Bytes, ISyncWritable } from '@fuman/io' import { read, write } from '@fuman/io' +import { typed } from '@fuman/utils' import type { ICryptoProvider } from '../../utils/index.js' -import { dataViewFromBuffer, getRandomInt } from '../../utils/index.js' +import { getRandomInt } from '../../utils/index.js' import type { IPacketCodec } from './abstract.js' import { TransportError } from './abstract.js' @@ -68,7 +69,7 @@ export class PaddedIntermediatePacketCodec extends IntermediatePacketCodec imple const padSize = getRandomInt(16) const ret = into.writeSync(frame.length + 4 + padSize) - const dv = dataViewFromBuffer(ret) + const dv = typed.toDataView(ret) dv.setUint32(0, frame.length + padSize, true) ret.set(frame, 4) this._crypto.randomFill(ret.subarray(4 + frame.length)) diff --git a/packages/core/src/network/transports/obfuscated.ts b/packages/core/src/network/transports/obfuscated.ts index 4dd34229..3b2e0b87 100644 --- a/packages/core/src/network/transports/obfuscated.ts +++ b/packages/core/src/network/transports/obfuscated.ts @@ -1,7 +1,7 @@ import type { ISyncWritable } from '@fuman/io' import { Bytes, write } from '@fuman/io' +import { typed, u8 } from '@fuman/utils' -import { bufferToReversed, concatBuffers, dataViewFromBuffer } from '../../utils/buffer-utils.js' import type { IAesCtr, ICryptoProvider, Logger } from '../../utils/index.js' import type { IPacketCodec } from './abstract.js' @@ -39,7 +39,7 @@ export class ObfuscatedPacketCodec implements IPacketCodec { random = this._crypto.randomBytes(64) if (random[0] === 0xEF) continue - dv = dataViewFromBuffer(random) + dv = typed.toDataView(random) const firstInt = dv.getUint32(0, true) if ( @@ -74,7 +74,7 @@ export class ObfuscatedPacketCodec implements IPacketCodec { dv.setInt16(60, dcId, true) } - const randomRev = bufferToReversed(random, 8, 56) + const randomRev = u8.toReversed(random.subarray(8, 56)) let encryptKey = random.subarray(8, 40) const encryptIv = random.subarray(40, 56) @@ -83,8 +83,8 @@ export class ObfuscatedPacketCodec implements IPacketCodec { const decryptIv = randomRev.subarray(32, 48) if (this._proxy) { - encryptKey = this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret])) - decryptKey = this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret])) + encryptKey = this._crypto.sha256(u8.concat2(encryptKey, this._proxy.secret)) + decryptKey = this._crypto.sha256(u8.concat2(decryptKey, this._proxy.secret)) } this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true) diff --git a/packages/core/src/utils/async-lock.test.ts b/packages/core/src/utils/async-lock.test.ts deleted file mode 100644 index 9b566b7f..00000000 --- a/packages/core/src/utils/async-lock.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { AsyncLock } from './async-lock.js' -import { sleep } from './misc-utils.js' - -describe('AsyncLock', () => { - it('should correctly lock execution', async () => { - const lock = new AsyncLock() - - const log: number[] = [] - await Promise.all( - Array.from({ length: 10 }, (_, idx) => - lock.with(async () => { - await sleep(10 - idx) - log.push(idx) - })), - ) - - expect(log).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - }) - - it('should correctly propagate errors', async () => { - const lock = new AsyncLock() - - await expect(async () => { - await lock.with(() => { - throw new Error('test') - }) - }).rejects.toThrow('test') - }) -}) diff --git a/packages/core/src/utils/async-lock.ts b/packages/core/src/utils/async-lock.ts deleted file mode 100644 index 87fb24c5..00000000 --- a/packages/core/src/utils/async-lock.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Deque } from './deque.js' - -type LockInfo = [Promise, () => void] - -/** - * Simple class implementing a semaphore like - * behaviour. - */ -export class AsyncLock { - private _queue = new Deque() - - async acquire(): Promise { - let info - - while ((info = this._queue.peekFront())) { - await info[0] - } - - let unlock: () => void - const prom = new Promise((resolve) => { - unlock = resolve - }) - - this._queue.pushBack([prom, unlock!]) - } - - release(): void { - if (!this._queue.length) throw new Error('Nothing to release') - - this._queue.popFront()![1]() - } - - with(func: () => Promise): Promise { - let err: unknown = null - - return this.acquire() - .then(() => func()) - .catch(e => void (err = e)) - .then(() => { - this.release() - if (err) throw err - }) - } -} diff --git a/packages/core/src/utils/bigint-utils.test.ts b/packages/core/src/utils/bigint-utils.test.ts index 9d498e29..7f2c26fe 100644 --- a/packages/core/src/utils/bigint-utils.test.ts +++ b/packages/core/src/utils/bigint-utils.test.ts @@ -1,90 +1,13 @@ import { describe, expect, it } from 'vitest' import { defaultTestCryptoProvider } from '@mtcute/test' -import { hex } from '@fuman/utils' +import { bigint } from '@fuman/utils' import { - bigIntBitLength, - bigIntGcd, - bigIntModInv, - bigIntModPow, - bigIntToBuffer, - bufferToBigInt, randomBigInt, randomBigIntBits, randomBigIntInRange, - twoMultiplicity, } from './index.js' -describe('bigIntBitLength', () => { - it('should correctly calculate bit length', () => { - expect(bigIntBitLength(0n)).eq(0) - expect(bigIntBitLength(1n)).eq(1) - expect(bigIntBitLength(2n)).eq(2) - expect(bigIntBitLength(255n)).eq(8) - expect(bigIntBitLength(256n)).eq(9) - }) -}) - -describe('bigIntToBuffer', () => { - it('should handle writing to BE', () => { - expect([...bigIntToBuffer(BigInt('10495708'), 0, false)]).eql([0xA0, 0x26, 0xDC]) - expect([...bigIntToBuffer(BigInt('10495708'), 4, false)]).eql([0x00, 0xA0, 0x26, 0xDC]) - expect([...bigIntToBuffer(BigInt('10495708'), 8, false)]).eql([0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x26, 0xDC]) - expect([...bigIntToBuffer(BigInt('3038102549'), 4, false)]).eql([0xB5, 0x15, 0xC4, 0x15]) - expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, false)]).eql([ - ...hex.decode('81A33C81D2020550'), - ]) - }) - - it('should handle writing to LE', () => { - expect([...bigIntToBuffer(BigInt('10495708'), 0, true)]).eql([0xDC, 0x26, 0xA0]) - expect([...bigIntToBuffer(BigInt('10495708'), 4, true)]).eql([0xDC, 0x26, 0xA0, 0x00]) - expect([...bigIntToBuffer(BigInt('10495708'), 8, true)]).eql([0xDC, 0x26, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00]) - expect([...bigIntToBuffer(BigInt('3038102549'), 4, true)]).eql([0x15, 0xC4, 0x15, 0xB5]) - expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, true)]).eql([ - ...hex.decode('81A33C81D2020550').reverse(), - ]) - }) - - it('should handle large integers', () => { - const buf = hex.decode( - '1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', - ) - const num = BigInt( - '0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', - ) - - expect([...bigIntToBuffer(num, 0, false)]).eql([...buf]) - expect([...bigIntToBuffer(num, 0, true)]).eql([...buf.reverse()]) - }) -}) - -describe('bufferToBigInt', () => { - it('should handle reading BE', () => { - expect(bufferToBigInt(new Uint8Array([0xA0, 0x26, 0xDC]), false).toString()).eq('10495708') - expect(bufferToBigInt(new Uint8Array([0x00, 0xA0, 0x26, 0xDC]), false).toString()).eq('10495708') - expect(bufferToBigInt(new Uint8Array([0xB5, 0x15, 0xC4, 0x15]), false).toString()).eq('3038102549') - }) - - it('should handle reading LE', () => { - expect(bufferToBigInt(new Uint8Array([0xDC, 0x26, 0xA0]), true).toString()).eq('10495708') - expect(bufferToBigInt(new Uint8Array([0xDC, 0x26, 0xA0, 0x00]), true).toString()).eq('10495708') - expect(bufferToBigInt(new Uint8Array([0x15, 0xC4, 0x15, 0xB5]), true).toString()).eq('3038102549') - }) - - it('should handle large integers', () => { - const buf = hex.decode( - '1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', - ) - const num = BigInt( - '0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', - ) - - expect(bufferToBigInt(buf, false).toString()).eq(num.toString()) - expect(bufferToBigInt(buf.reverse(), true).toString()).eq(num.toString()) - }) -}) - describe('randomBigInt', async () => { const c = await defaultTestCryptoProvider() @@ -99,8 +22,8 @@ describe('randomBigInt', async () => { const a = randomBigInt(c, 32) const b = randomBigInt(c, 64) - expect(bigIntBitLength(a)).toBeLessThanOrEqual(32 * 8) - expect(bigIntBitLength(b)).toBeLessThanOrEqual(64 * 8) + expect(bigint.bitLength(a)).toBeLessThanOrEqual(32 * 8) + expect(bigint.bitLength(b)).toBeLessThanOrEqual(64 * 8) }) }) @@ -118,8 +41,8 @@ describe('randomBigIntBits', async () => { const a = randomBigIntBits(c, 32) const b = randomBigIntBits(c, 64) - expect(bigIntBitLength(a)).toBeLessThanOrEqual(32) - expect(bigIntBitLength(b)).toBeLessThanOrEqual(64) + expect(bigint.bitLength(a)).toBeLessThanOrEqual(32) + expect(bigint.bitLength(b)).toBeLessThanOrEqual(64) }) }) @@ -140,61 +63,3 @@ describe('randomBigIntInRange', async () => { expect(a).toBeLessThan(200n) }) }) - -describe('twoMultiplicity', () => { - it('should return the multiplicity of 2 in the prime factorization of n', () => { - expect(twoMultiplicity(0n)).toEqual(0n) - expect(twoMultiplicity(1n)).toEqual(0n) - expect(twoMultiplicity(2n)).toEqual(1n) - expect(twoMultiplicity(4n)).toEqual(2n) - expect(twoMultiplicity(65536n)).toEqual(16n) - expect(twoMultiplicity(65537n)).toEqual(0n) - }) -}) - -describe('bigIntGcd', () => { - it('should return the greatest common divisor of a and b', () => { - expect(bigIntGcd(123n, 456n)).toEqual(3n) - }) - - it('should correctly handle zeros', () => { - expect(bigIntGcd(0n, 0n)).toEqual(0n) - expect(bigIntGcd(0n, 1n)).toEqual(1n) - expect(bigIntGcd(1n, 0n)).toEqual(1n) - }) - - it('should correctly handle equal values', () => { - expect(bigIntGcd(1n, 1n)).toEqual(1n) - }) -}) - -describe('bigIntModPow', () => { - it('should correctly calculate modular exponentiation', () => { - expect(bigIntModPow(2n, 3n, 5n)).toEqual(3n) - expect(bigIntModPow(2n, 3n, 6n)).toEqual(2n) - expect(bigIntModPow(2n, 3n, 7n)).toEqual(1n) - expect(bigIntModPow(2n, 3n, 8n)).toEqual(0n) - }) - - it('should correctly handle very large numbers', () => { - // calculating this with BigInt would either take forever or error with "Maximum BigInt size exceeded - expect(bigIntModPow(2n, 100000000000n, 100n)).toEqual(76n) - }) -}) - -describe('bigIntModInv', () => { - it('should correctly calculate modular inverse', () => { - expect(bigIntModInv(2n, 5n)).toEqual(3n) - expect(bigIntModInv(2n, 7n)).toEqual(4n) - }) - - it("should error if there's no modular inverse", () => { - expect(() => bigIntModInv(2n, 6n)).toThrow(RangeError) - expect(() => bigIntModInv(2n, 8n)).toThrow(RangeError) - }) - - it('should correctly handle very large numbers', () => { - // calculating this with BigInt would either take forever or error with "Maximum BigInt size exceeded - expect(bigIntModInv(123123123123n, 1829n)).toEqual(318n) - }) -}) diff --git a/packages/core/src/utils/bigint-utils.ts b/packages/core/src/utils/bigint-utils.ts index 950739a2..94424dee 100644 --- a/packages/core/src/utils/bigint-utils.ts +++ b/packages/core/src/utils/bigint-utils.ts @@ -1,93 +1,13 @@ -import { bufferToReversed } from './buffer-utils.js' +import { bigint } from '@fuman/utils' + import type { ICryptoProvider } from './crypto/abstract.js' -/** - * Get the minimum number of bits required to represent a number - */ -export function bigIntBitLength(n: bigint): number { - // not the fastest way, but at least not .toString(2) and not too complex - // taken from: https://stackoverflow.com/a/76616288/22656950 - - const i = (n.toString(16).length - 1) * 4 - - return i + 32 - Math.clz32(Number(n >> BigInt(i))) -} - -/** - * Convert a big integer to a buffer - * - * @param value Value to convert - * @param length Length of the resulting buffer (by default it's the minimum required) - * @param le Whether to use little-endian encoding - */ -export function bigIntToBuffer(value: bigint, length = 0, le = false): Uint8Array { - const bits = bigIntBitLength(value) - const bytes = Math.ceil(bits / 8) - - if (length !== 0 && bytes > length) { - throw new Error('Value out of bounds') - } - - if (length === 0) length = bytes - - const buf = new ArrayBuffer(length) - const u8 = new Uint8Array(buf) - - const unaligned = length % 8 - const dv = new DataView(buf, 0, length - unaligned) - - // it is faster to work with 64-bit words than with bytes directly - for (let i = 0; i < dv.byteLength; i += 8) { - dv.setBigUint64(i, value & 0xFFFFFFFFFFFFFFFFn, true) - value >>= 64n - } - - if (unaligned > 0) { - for (let i = length - unaligned; i < length; i++) { - u8[i] = Number(value & 0xFFn) - value >>= 8n - } - } - - if (!le) u8.reverse() - - return u8 -} - -/** - * Convert a buffer to a big integer - * - * @param buffer Buffer to convert - * @param le Whether to use little-endian encoding - */ -export function bufferToBigInt(buffer: Uint8Array, le = false): bigint { - if (le) buffer = bufferToReversed(buffer) - - const unaligned = buffer.length % 8 - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength - unaligned) - - let res = 0n - - // it is faster to work with 64-bit words than with bytes directly - for (let i = 0; i < dv.byteLength; i += 8) { - res = (res << 64n) | BigInt(dv.getBigUint64(i, false)) - } - - if (unaligned > 0) { - for (let i = buffer.length - unaligned; i < buffer.length; i++) { - res = (res << 8n) | BigInt(buffer[i]) - } - } - - return res -} - /** * Generate a cryptographically safe random big integer of the given size (in bytes) * @param size Size in bytes */ export function randomBigInt(crypto: ICryptoProvider, size: number): bigint { - return bufferToBigInt(crypto.randomBytes(size)) + return bigint.fromBytes(crypto.randomBytes(size)) } /** @@ -97,7 +17,7 @@ export function randomBigInt(crypto: ICryptoProvider, size: number): bigint { export function randomBigIntBits(crypto: ICryptoProvider, bits: number): bigint { let num = randomBigInt(crypto, Math.ceil(bits / 8)) - const bitLength = bigIntBitLength(num) + const bitLength = bigint.bitLength(num) if (bitLength > bits) { const toTrim = bitLength - bits @@ -117,115 +37,10 @@ export function randomBigIntInRange(crypto: ICryptoProvider, max: bigint, min = const interval = max - min if (interval < 0n) throw new Error('expected min < max') - const byteSize = Math.ceil(bigIntBitLength(interval) / 8) + const byteSize = Math.ceil(bigint.bitLength(interval) / 8) let result = randomBigInt(crypto, byteSize) while (result > interval) result -= interval return min + result } - -/** - * Compute the multiplicity of 2 in the prime factorization of n - * @param n - */ -export function twoMultiplicity(n: bigint): bigint { - if (n === 0n) return 0n - - let m = 0n - let pow = 1n - - while (true) { - if ((n & pow) !== 0n) return m - m += 1n - pow <<= 1n - } -} - -export function bigIntMin(a: bigint, b: bigint): bigint { - return a < b ? a : b -} - -export function bigIntAbs(a: bigint): bigint { - return a < 0n ? -a : a -} - -export function bigIntGcd(a: bigint, b: bigint): bigint { - // using euclidean algorithm is fast enough on smaller numbers - // https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations - - while (b !== 0n) { - const t = b - b = a % b - a = t - } - - return a -} - -export function bigIntModPow(base: bigint, exp: bigint, mod: bigint): bigint { - // using the binary method is good enough for our use case - // https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method - - base %= mod - - let result = 1n - - while (exp > 0n) { - if (exp % 2n === 1n) { - result = (result * base) % mod - } - - exp >>= 1n - base = base ** 2n % mod - } - - return result -} - -// below code is based on https://github.com/juanelas/bigint-mod-arith, MIT license - -function eGcd(a: bigint, b: bigint): [bigint, bigint, bigint] { - let x = 0n - let y = 1n - let u = 1n - let v = 0n - - while (a !== 0n) { - const q = b / a - const r: bigint = b % a - const m = x - u * q - const n = y - v * q - b = a - a = r - x = u - y = v - u = m - v = n - } - - return [b, x, y] -} - -function toZn(a: number | bigint, n: number | bigint): bigint { - if (typeof a === 'number') a = BigInt(a) - if (typeof n === 'number') n = BigInt(n) - - if (n <= 0n) { - throw new RangeError('n must be > 0') - } - - const aZn = a % n - - return aZn < 0n ? aZn + n : aZn -} - -export function bigIntModInv(a: bigint, n: bigint): bigint { - const [g, x] = eGcd(toZn(a, n), n) - - if (g !== 1n) { - throw new RangeError(`${a.toString()} does not have inverse modulo ${n.toString()}`) // modular inverse does not exist - } else { - return toZn(x, n) - } -} diff --git a/packages/core/src/utils/buffer-utils.test.ts b/packages/core/src/utils/buffer-utils.test.ts deleted file mode 100644 index b18d5e72..00000000 --- a/packages/core/src/utils/buffer-utils.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' - -import { bufferToReversed, buffersEqual, cloneBuffer, concatBuffers } from './buffer-utils.js' - -describe('buffersEqual', () => { - it('should return true for equal buffers', () => { - expect(buffersEqual(new Uint8Array([]), new Uint8Array([]))).toEqual(true) - expect(buffersEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]))).toEqual(true) - }) - - it('should return false for non-equal buffers', () => { - expect(buffersEqual(new Uint8Array([1]), new Uint8Array([]))).toEqual(false) - expect(buffersEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 4]))).toEqual(false) - }) -}) - -describe('cloneBuffer', () => { - it('should clone buffer', () => { - const orig = new Uint8Array([1, 2, 3]) - const copy = cloneBuffer(orig) - - expect([...copy]).eql([1, 2, 3]) - orig[0] = 0xFF - expect(copy[0]).not.eql(0xFF) - }) - - it('should clone buffer partially', () => { - const orig = new Uint8Array([1, 2, 3, 4, 5]) - const copy = cloneBuffer(orig, 1, 4) - - expect([...copy]).eql([2, 3, 4]) - orig[0] = 0xFF - expect(copy[0]).not.eql(0xFF) - }) -}) - -describe('concatBuffers', () => { - it('should concat buffers', () => { - const buf = concatBuffers([new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])]) - - expect([...buf]).eql([1, 2, 3, 4, 5, 6]) - }) - - it('should create a new buffer', () => { - const buf1 = new Uint8Array([1, 2, 3]) - const buf2 = new Uint8Array([4, 5, 6]) - const buf = concatBuffers([buf1, buf2]) - - buf[0] = 0xFF - expect(buf1[0]).not.eql(0xFF) - }) - - it('should work without native Buffer', () => { - vi.stubGlobal('Buffer', undefined) - const buf1 = new Uint8Array([1, 2, 3]) - const buf2 = new Uint8Array([4, 5, 6]) - const buf = concatBuffers([buf1, buf2]) - - buf1[0] = 0xFF - - expect([...buf]).eql([1, 2, 3, 4, 5, 6]) - }) - - afterEach(() => void vi.unstubAllGlobals()) -}) - -describe('bufferToReversed', () => { - it('should reverse the buffer', () => { - const buf = bufferToReversed(new Uint8Array([1, 2, 3, 4, 5, 6])) - - expect([...buf]).eql([6, 5, 4, 3, 2, 1]) - }) - - it('should reverse a part of the buffer', () => { - const buf = bufferToReversed(new Uint8Array([1, 2, 3, 4, 5, 6]), 1, 5) - - expect([...buf]).eql([5, 4, 3, 2]) - }) - - it('should create a new buffer', () => { - const buf1 = new Uint8Array([1, 2, 3]) - const buf2 = bufferToReversed(buf1) - - buf2[0] = 0xFF - expect([...buf1]).eql([1, 2, 3]) - }) -}) diff --git a/packages/core/src/utils/buffer-utils.ts b/packages/core/src/utils/buffer-utils.ts deleted file mode 100644 index f143b786..00000000 --- a/packages/core/src/utils/buffer-utils.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Check if two buffers are equal - * - * @param a First buffer - * @param b Second buffer - */ -export function buffersEqual(a: Uint8Array, b: Uint8Array): boolean { - if (a.length !== b.length) return false - - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false - } - - return true -} - -/** - * Copy a buffer - * - * @param buf Buffer to copy - * @param start Start offset - * @param end End offset - */ -export function cloneBuffer(buf: Uint8Array, start = 0, end: number = buf.length): Uint8Array { - const ret = new Uint8Array(end - start) - ret.set(buf.subarray(start, end)) - - return ret -} - -/** - * Concatenate multiple buffers into one - */ -export function concatBuffers(buffers: Uint8Array[]): Uint8Array { - if (buffers.length === 1) return buffers[0] - - /* eslint-disable no-restricted-globals */ - if (typeof Buffer !== 'undefined') { - return Buffer.concat(buffers) - } - /* eslint-enable no-restricted-globals */ - - let length = 0 - - for (const buf of buffers) { - length += buf.length - } - - const ret = new Uint8Array(length) - let offset = 0 - - for (const buf of buffers) { - ret.set(buf, offset) - offset += buf.length - } - - return ret -} - -/** - * Shortcut for creating a DataView from a Uint8Array - */ -export function dataViewFromBuffer(buf: Uint8Array): DataView { - return new DataView(buf.buffer, buf.byteOffset, buf.byteLength) -} - -/** - * Reverse a buffer (or a part of it) into a new buffer - */ -export function bufferToReversed(buf: Uint8Array, start = 0, end: number = buf.length): Uint8Array { - const len = end - start - const ret = new Uint8Array(len) - - for (let i = 0; i < len; i++) { - ret[i] = buf[end - i - 1] - } - - return ret -} diff --git a/packages/core/src/utils/condition-variable.test.ts b/packages/core/src/utils/condition-variable.test.ts deleted file mode 100644 index fccc1abc..00000000 --- a/packages/core/src/utils/condition-variable.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { ConditionVariable } from './condition-variable.js' - -describe('ConditionVariable', () => { - it('should correctly unlock execution', async () => { - const cv = new ConditionVariable() - - // eslint-disable-next-line no-restricted-globals - setTimeout(() => cv.notify(), 10) - - await cv.wait() - - expect(true).toBeTruthy() - }) - - it('should correctly time out', async () => { - const cv = new ConditionVariable() - - await cv.wait(10) - - expect(true).toBeTruthy() - }) - - it('should only unlock once', async () => { - const cv = new ConditionVariable() - - // eslint-disable-next-line no-restricted-globals - setTimeout(() => { - cv.notify() - cv.notify() - }, 10) - - await cv.wait() - - expect(true).toBeTruthy() - }) -}) diff --git a/packages/core/src/utils/condition-variable.ts b/packages/core/src/utils/condition-variable.ts deleted file mode 100644 index dc3ab44f..00000000 --- a/packages/core/src/utils/condition-variable.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as timers from './timers.js' - -/** - * Class implementing a condition variable like behaviour. - */ -export class ConditionVariable { - private _notify?: () => void - private _timeout?: timers.Timer - - wait(timeout?: number): Promise { - const prom = new Promise((resolve) => { - this._notify = resolve - }) - - if (timeout) { - this._timeout = timers.setTimeout(() => { - this._notify?.() - this._timeout = undefined - }, timeout) - } - - return prom - } - - notify(): void { - this._notify?.() - if (this._timeout) timers.clearTimeout(this._timeout) - this._notify = undefined - } -} diff --git a/packages/core/src/utils/controllable-promise.test.ts b/packages/core/src/utils/controllable-promise.test.ts deleted file mode 100644 index a638de51..00000000 --- a/packages/core/src/utils/controllable-promise.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { createControllablePromise } from './controllable-promise.js' - -describe('createControllablePromise', () => { - it('should resolve', async () => { - const p = createControllablePromise() - p.resolve(1) - await expect(p).resolves.toBe(1) - }) - - it('should reject', async () => { - const p = createControllablePromise() - p.reject(1) - await expect(p).rejects.toBe(1) - }) -}) diff --git a/packages/core/src/utils/controllable-promise.ts b/packages/core/src/utils/controllable-promise.ts deleted file mode 100644 index 73b24c2a..00000000 --- a/packages/core/src/utils/controllable-promise.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * A promise that can be resolved or rejected from outside. - */ -export type ControllablePromise = Promise & { - resolve: (val: T) => void - reject: (err?: unknown) => void -} - -/** - * Creates a promise that can be resolved or rejected from outside. - */ -export function createControllablePromise(): ControllablePromise { - let _resolve: ControllablePromise['resolve'] - let _reject: ControllablePromise['reject'] - const promise = new Promise((resolve, reject) => { - _resolve = resolve - _reject = reject - }) - // ts doesn't like this, but it's fine - - ;(promise as ControllablePromise).resolve = _resolve! - ;(promise as ControllablePromise).reject = _reject! - - return promise as ControllablePromise -} diff --git a/packages/core/src/utils/crypto/factorization.test.ts b/packages/core/src/utils/crypto/factorization.test.ts index 36879ea5..a3a2582d 100644 --- a/packages/core/src/utils/crypto/factorization.test.ts +++ b/packages/core/src/utils/crypto/factorization.test.ts @@ -1,15 +1,14 @@ import { describe, expect, it } from 'vitest' import { defaultCryptoProvider } from '@mtcute/test' - -import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js' +import { bigint } from '@fuman/utils' import { factorizePQSync } from './factorization.js' describe('prime factorization', () => { const testFactorization = (pq: bigint, p: bigint, q: bigint) => { - const [p_, q_] = factorizePQSync(defaultCryptoProvider, bigIntToBuffer(pq)) - expect(bufferToBigInt(p_)).toBe(p) - expect(bufferToBigInt(q_)).toBe(q) + const [p_, q_] = factorizePQSync(defaultCryptoProvider, bigint.toBytes(pq)) + expect(bigint.fromBytes(p_)).toBe(p) + expect(bigint.fromBytes(q_)).toBe(q) } it('should factorize', () => { diff --git a/packages/core/src/utils/crypto/factorization.ts b/packages/core/src/utils/crypto/factorization.ts index c07f9712..4064f3cd 100644 --- a/packages/core/src/utils/crypto/factorization.ts +++ b/packages/core/src/utils/crypto/factorization.ts @@ -1,11 +1,6 @@ -import { - bigIntAbs, - bigIntGcd, - bigIntMin, - bigIntToBuffer, - bufferToBigInt, - randomBigIntInRange, -} from '../bigint-utils.js' +import { bigint } from '@fuman/utils' + +import { randomBigIntInRange } from '../bigint-utils.js' import type { ICryptoProvider } from './abstract.js' @@ -14,7 +9,7 @@ import type { ICryptoProvider } from './abstract.js' * @param pq */ export function factorizePQSync(crypto: ICryptoProvider, pq: Uint8Array): [Uint8Array, Uint8Array] { - const pq_ = bufferToBigInt(pq) + const pq_ = bigint.fromBytes(pq) const n = PollardRhoBrent(crypto, pq_) const m = pq_ / n @@ -30,7 +25,7 @@ export function factorizePQSync(crypto: ICryptoProvider, pq: Uint8Array): [Uint8 q = n } - return [bigIntToBuffer(p), bigIntToBuffer(q)] + return [bigint.toBytes(p), bigint.toBytes(q)] } function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint { @@ -55,12 +50,12 @@ function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint { while (k < r && g === 1n) { ys = y - for (let i = 0n; i < bigIntMin(m, r - k); i++) { + for (let i = 0n; i < bigint.min2(m, r - k); i++) { y = (((y * y) % n) + c) % n - q = (q * bigIntAbs(x - y)) % n + q = (q * bigint.abs(x - y)) % n } - g = bigIntGcd(q, n) + g = bigint.euclideanGcd(q, n) k = k + m } @@ -71,7 +66,7 @@ function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint { do { ys = (((ys! * ys!) % n) + c) % n - g = bigIntGcd(x! - ys!, n) + g = bigint.euclideanGcd(x! - ys!, n) } while (g <= 1n) } diff --git a/packages/core/src/utils/crypto/index.ts b/packages/core/src/utils/crypto/index.ts index 278772be..c92bd3a7 100644 --- a/packages/core/src/utils/crypto/index.ts +++ b/packages/core/src/utils/crypto/index.ts @@ -4,4 +4,3 @@ export * from './keys.js' export * from './miller-rabin.js' export * from './mtproto.js' export * from './password.js' -export * from './utils.js' diff --git a/packages/core/src/utils/crypto/miller-rabin.ts b/packages/core/src/utils/crypto/miller-rabin.ts index c151ff2f..c18e5e41 100644 --- a/packages/core/src/utils/crypto/miller-rabin.ts +++ b/packages/core/src/utils/crypto/miller-rabin.ts @@ -1,4 +1,6 @@ -import { bigIntBitLength, bigIntModPow, randomBigIntBits, twoMultiplicity } from '../bigint-utils.js' +import { bigint } from '@fuman/utils' + +import { randomBigIntBits } from '../bigint-utils.js' import type { ICryptoProvider } from './abstract.js' @@ -7,10 +9,10 @@ export function millerRabin(crypto: ICryptoProvider, n: bigint, rounds = 20): bo if (n < 4n) return n > 1n if (n % 2n === 0n || n < 0n) return false - const nBits = bigIntBitLength(n) + const nBits = bigint.bitLength(n) const nSub = n - 1n - const r = twoMultiplicity(nSub) + const r = bigint.twoMultiplicity(nSub) const d = nSub >> r for (let i = 0; i < rounds; i++) { @@ -20,16 +22,14 @@ export function millerRabin(crypto: ICryptoProvider, n: bigint, rounds = 20): bo base = randomBigIntBits(crypto, nBits) } while (base <= 1n || base >= nSub) - let x = bigIntModPow(base, d, n) - // if (x.eq(bigInt.one) || x.eq(nSub)) continue + let x = bigint.modPowBinary(base, d, n) if (x === 1n || x === nSub) continue let i = 0n let y: bigint while (i < r) { - // y = x.modPow(bigInt[2], n) - y = bigIntModPow(x, 2n, n) + y = bigint.modPowBinary(x, 2n, n) if (x === 1n) return false if (x === nSub) break diff --git a/packages/core/src/utils/crypto/mtproto.test.ts b/packages/core/src/utils/crypto/mtproto.test.ts index c38a5dbb..0c4c1303 100644 --- a/packages/core/src/utils/crypto/mtproto.test.ts +++ b/packages/core/src/utils/crypto/mtproto.test.ts @@ -1,13 +1,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { defaultTestCryptoProvider } from '@mtcute/test' -import { hex } from '@fuman/utils' - -import { concatBuffers } from '../index.js' +import { hex, u8 } from '@fuman/utils' import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js' const authKeyChunk = hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0') -const authKey = concatBuffers(Array.from({ length: 8 }, () => authKeyChunk)) +const authKey = u8.concat(Array.from({ length: 8 }, () => authKeyChunk)) const messageKey = hex.decode('25d701f2a29205526757825a99eb2d32') describe('mtproto 2.0', async () => { diff --git a/packages/core/src/utils/crypto/mtproto.ts b/packages/core/src/utils/crypto/mtproto.ts index ae1e9417..89d46331 100644 --- a/packages/core/src/utils/crypto/mtproto.ts +++ b/packages/core/src/utils/crypto/mtproto.ts @@ -1,4 +1,4 @@ -import { concatBuffers } from '../buffer-utils.js' +import { u8 } from '@fuman/utils' import type { ICryptoProvider, IEncryptionScheme } from './abstract.js' @@ -16,12 +16,12 @@ export function generateKeyAndIvFromNonce( serverNonce: Uint8Array, newNonce: Uint8Array, ): [Uint8Array, Uint8Array] { - const hash1 = crypto.sha1(concatBuffers([newNonce, serverNonce])) - const hash2 = crypto.sha1(concatBuffers([serverNonce, newNonce])) - const hash3 = crypto.sha1(concatBuffers([newNonce, newNonce])) + const hash1 = crypto.sha1(u8.concat2(newNonce, serverNonce)) + const hash2 = crypto.sha1(u8.concat2(serverNonce, newNonce)) + const hash3 = crypto.sha1(u8.concat2(newNonce, newNonce)) - const key = concatBuffers([hash1, hash2.subarray(0, 12)]) - const iv = concatBuffers([hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)]) + const key = u8.concat2(hash1, hash2.subarray(0, 12)) + const iv = u8.concat3(hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)) return [key, iv] } @@ -42,11 +42,11 @@ export function createAesIgeForMessage( client: boolean, ): IEncryptionScheme { const x = client ? 0 : 8 - const sha256a = crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)])) - const sha256b = crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey])) + const sha256a = crypto.sha256(u8.concat2(messageKey, authKey.subarray(x, 36 + x))) + const sha256b = crypto.sha256(u8.concat2(authKey.subarray(40 + x, 76 + x), messageKey)) - const key = concatBuffers([sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32)]) - const iv = concatBuffers([sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32)]) + const key = u8.concat3(sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32)) + const iv = u8.concat3(sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32)) return crypto.createAesIge(key, iv) } @@ -67,15 +67,15 @@ export function createAesIgeForMessageOld( client: boolean, ): IEncryptionScheme { const x = client ? 0 : 8 - const sha1a = crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)])) + const sha1a = crypto.sha1(u8.concat2(messageKey, authKey.subarray(x, 32 + x))) const sha1b = crypto.sha1( - concatBuffers([authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)]), + u8.concat3(authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)), ) - const sha1c = crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey])) - const sha1d = crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)])) + const sha1c = crypto.sha1(u8.concat2(authKey.subarray(64 + x, 96 + x), messageKey)) + const sha1d = crypto.sha1(u8.concat2(messageKey, authKey.subarray(96 + x, 128 + x))) - const key = concatBuffers([sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)]) - const iv = concatBuffers([ + const key = u8.concat3(sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)) + const iv = u8.concat([ sha1a.subarray(8, 20), sha1b.subarray(0, 8), sha1c.subarray(16, 20), diff --git a/packages/core/src/utils/crypto/password.ts b/packages/core/src/utils/crypto/password.ts index 300fc69a..197c64cb 100644 --- a/packages/core/src/utils/crypto/password.ts +++ b/packages/core/src/utils/crypto/password.ts @@ -1,13 +1,10 @@ import type { tl } from '@mtcute/tl' -import { utf8 } from '@fuman/utils' +import { bigint, u8, utf8 } from '@fuman/utils' import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js' -import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js' -import { concatBuffers } from '../buffer-utils.js' import { assertTypeIs } from '../type-assertions.js' import type { ICryptoProvider } from './abstract.js' -import { xorBuffer } from './utils.js' /** * Compute password hash as defined by MTProto. @@ -25,7 +22,7 @@ export async function computePasswordHash( salt1: Uint8Array, salt2: Uint8Array, ): Promise { - const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(concatBuffers([salt, data, salt])) + const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(u8.concat3(salt, data, salt)) const PH1 = (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(SH(pwd, salt1), salt2) return SH(await crypto.pbkdf2(PH1(password, salt1, salt2), salt1, 100000), salt2) @@ -53,10 +50,10 @@ export async function computeNewPasswordHash( const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2) const g = BigInt(algo.g) - const p = bufferToBigInt(algo.p) - const x = bufferToBigInt(_x) + const p = bigint.fromBytes(algo.p) + const x = bigint.fromBytes(_x) - return bigIntToBuffer(bigIntModPow(g, x, p), 256) + return bigint.toBytes(bigint.modPowBinary(g, x, p), 256) } /** @@ -92,32 +89,39 @@ export async function computeSrpParams( } const g = BigInt(algo.g) - const _g = bigIntToBuffer(g, 256) - const p = bufferToBigInt(algo.p) - const gB = bufferToBigInt(request.srpB) + const _g = bigint.toBytes(g, 256) + const p = bigint.fromBytes(algo.p) + const gB = bigint.fromBytes(request.srpB) - const a = bufferToBigInt(crypto.randomBytes(256)) - const gA = bigIntModPow(g, a, p) - const _gA = bigIntToBuffer(gA, 256) + const a = bigint.fromBytes(crypto.randomBytes(256)) + const gA = bigint.modPowBinary(g, a, p) + const _gA = bigint.toBytes(gA, 256) const H = (data: Uint8Array) => crypto.sha256(data) - const _k = crypto.sha256(concatBuffers([algo.p, _g])) - const _u = crypto.sha256(concatBuffers([_gA, request.srpB])) + const _k = crypto.sha256(u8.concat2(algo.p, _g)) + const _u = crypto.sha256(u8.concat2(_gA, request.srpB)) const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2) - const k = bufferToBigInt(_k) - const u = bufferToBigInt(_u) - const x = bufferToBigInt(_x) + const k = bigint.fromBytes(_k) + const u = bigint.fromBytes(_u) + const x = bigint.fromBytes(_x) - const v = bigIntModPow(g, x, p) + const v = bigint.modPowBinary(g, x, p) const kV = (k * v) % p let t = gB - kV if (t < 0n) t += p - const sA = bigIntModPow(t, a + u * x, p) - const _kA = H(bigIntToBuffer(sA, 256)) + const sA = bigint.modPowBinary(t, a + u * x, p) + const _kA = H(bigint.toBytes(sA, 256)) - const _M1 = H(concatBuffers([xorBuffer(H(algo.p), H(_g)), H(algo.salt1), H(algo.salt2), _gA, request.srpB, _kA])) + const _M1 = H(u8.concat([ + u8.xor(H(algo.p), H(_g)), + H(algo.salt1), + H(algo.salt2), + _gA, + request.srpB, + _kA, + ])) return { _: 'inputCheckPasswordSRP', diff --git a/packages/core/src/utils/crypto/utils.test.ts b/packages/core/src/utils/crypto/utils.test.ts deleted file mode 100644 index eb935fb6..00000000 --- a/packages/core/src/utils/crypto/utils.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { hex, utf8 } from '@fuman/utils' - -import { xorBuffer, xorBufferInPlace } from './utils.js' - -describe('xorBuffer', () => { - it('should xor buffers without modifying original', () => { - const data = utf8.encoder.encode('hello') - const key = utf8.encoder.encode('xor') - - const xored = xorBuffer(data, key) - expect(utf8.decoder.decode(data)).eq('hello') - expect(utf8.decoder.decode(key)).eq('xor') - expect(hex.encode(xored)).eq('100a1e6c6f') - }) - - it('should be deterministic', () => { - const data = utf8.encoder.encode('hello') - const key = utf8.encoder.encode('xor') - - const xored1 = xorBuffer(data, key) - expect(hex.encode(xored1)).eq('100a1e6c6f') - - const xored2 = xorBuffer(data, key) - expect(hex.encode(xored2)).eq('100a1e6c6f') - }) - - it('second call should decode content', () => { - const data = utf8.encoder.encode('hello') - const key = utf8.encoder.encode('xor') - - const xored1 = xorBuffer(data, key) - expect(hex.encode(xored1)).eq('100a1e6c6f') - - const xored2 = xorBuffer(xored1, key) - expect(utf8.decoder.decode(xored2)).eq('hello') - }) -}) - -describe('xorBufferInPlace', () => { - it('should xor buffers by modifying original', () => { - const data = utf8.encoder.encode('hello') - const key = utf8.encoder.encode('xor') - - xorBufferInPlace(data, key) - expect(hex.encode(data)).eq('100a1e6c6f') - expect(utf8.decoder.decode(key)).eq('xor') - }) - - it('second call should decode content', () => { - const data = utf8.encoder.encode('hello') - const key = utf8.encoder.encode('xor') - - xorBufferInPlace(data, key) - expect(hex.encode(data)).eq('100a1e6c6f') - - xorBufferInPlace(data, key) - expect(utf8.decoder.decode(data)).eq('hello') - }) -}) diff --git a/packages/core/src/utils/crypto/utils.ts b/packages/core/src/utils/crypto/utils.ts deleted file mode 100644 index 2bacf734..00000000 --- a/packages/core/src/utils/crypto/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Perform XOR operation on two buffers and return the new buffer - * - * @param data Buffer to XOR - * @param key Key to XOR with - */ -export function xorBuffer(data: Uint8Array, key: Uint8Array): Uint8Array { - const ret = new Uint8Array(data.length) - - for (let i = 0; i < data.length; i++) { - ret[i] = data[i] ^ key[i] - } - - return ret -} - -/** - * Perform XOR operation on two buffers in-place - * - * @param data Buffer to XOR - * @param key Key to XOR with - */ -export function xorBufferInPlace(data: Uint8Array, key: Uint8Array): void { - for (let i = 0; i < data.length; i++) { - data[i] ^= key[i] - } -} diff --git a/packages/core/src/utils/early-timer.ts b/packages/core/src/utils/early-timer.ts index 62105d93..20bda802 100644 --- a/packages/core/src/utils/early-timer.ts +++ b/packages/core/src/utils/early-timer.ts @@ -1,4 +1,4 @@ -import * as timers from './timers.js' +import { timers } from '@fuman/utils' /** * Wrapper over JS timers that allows re-scheduling them diff --git a/packages/core/src/utils/function-utils.test.ts b/packages/core/src/utils/function-utils.test.ts index 0492d8fd..90168e10 100644 --- a/packages/core/src/utils/function-utils.test.ts +++ b/packages/core/src/utils/function-utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' +import { ConditionVariable } from '@fuman/utils' -import { ConditionVariable } from './condition-variable.js' import { throttle } from './function-utils.js' describe('throttle', () => { diff --git a/packages/core/src/utils/function-utils.ts b/packages/core/src/utils/function-utils.ts index 4aea60db..666c4d10 100644 --- a/packages/core/src/utils/function-utils.ts +++ b/packages/core/src/utils/function-utils.ts @@ -1,4 +1,4 @@ -import * as timers from './timers.js' +import { timers } from '@fuman/utils' export type ThrottledFunction = (() => void) & { reset: () => void diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index c127d05b..bf7d783b 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -7,12 +7,8 @@ export * from '../highlevel/storage/service/updates.js' export * from '../storage/service/base.js' export * from '../storage/service/default-dcs.js' // end todo -export * from './async-lock.js' export * from './bigint-utils.js' -export * from './buffer-utils.js' export * from './composer.js' -export * from './condition-variable.js' -export * from './controllable-promise.js' export * from './crypto/index.js' export * from './dcs.js' export * from './deque.js' diff --git a/packages/core/src/utils/long-utils.ts b/packages/core/src/utils/long-utils.ts index 8036a8d7..a1c969e8 100644 --- a/packages/core/src/utils/long-utils.ts +++ b/packages/core/src/utils/long-utils.ts @@ -1,6 +1,6 @@ import Long from 'long' +import { typed } from '@fuman/utils' -import { dataViewFromBuffer } from './buffer-utils.js' import { getRandomInt } from './misc-utils.js' /** @@ -23,7 +23,7 @@ export function randomLong(unsigned = false): Long { * @param le Whether the number is little-endian */ export function longFromBuffer(buf: Uint8Array, unsigned = false, le = true): Long { - const dv = dataViewFromBuffer(buf) + const dv = typed.toDataView(buf) if (le) { return new Long(dv.getInt32(0, true), dv.getInt32(4, true), unsigned) diff --git a/packages/core/src/utils/misc-utils.ts b/packages/core/src/utils/misc-utils.ts index 781fbb06..a127946e 100644 --- a/packages/core/src/utils/misc-utils.ts +++ b/packages/core/src/utils/misc-utils.ts @@ -1,11 +1,4 @@ -import * as timers from './timers.js' - -/** - * Sleep for the given number of ms - * - * @param ms Number of ms to sleep - */ -export const sleep = (ms: number): Promise => new Promise(resolve => timers.setTimeout(resolve, ms)) +import { timers } from '@fuman/utils' export function sleepWithAbort(ms: number, signal: AbortSignal): Promise { return new Promise((resolve, reject) => { diff --git a/packages/core/src/utils/reloadable.ts b/packages/core/src/utils/reloadable.ts index 0fa2a1fe..d0d806e9 100644 --- a/packages/core/src/utils/reloadable.ts +++ b/packages/core/src/utils/reloadable.ts @@ -1,5 +1,6 @@ +import { timers } from '@fuman/utils' + import { asyncResettable } from './function-utils.js' -import * as timers from './timers.js' export interface ReloadableParams { reload: (old?: Data) => Promise diff --git a/packages/dispatcher/package.json b/packages/dispatcher/package.json index b9176187..aa036730 100644 --- a/packages/dispatcher/package.json +++ b/packages/dispatcher/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@mtcute/core": "workspace:^", + "@fuman/utils": "workspace:^", "events": "3.2.0" }, "devDependencies": { diff --git a/packages/dispatcher/src/state/update-state.ts b/packages/dispatcher/src/state/update-state.ts index 49a7f5c6..81e41c89 100644 --- a/packages/dispatcher/src/state/update-state.ts +++ b/packages/dispatcher/src/state/update-state.ts @@ -1,5 +1,5 @@ import { MtArgumentError, MtcuteError } from '@mtcute/core' -import { sleep } from '@mtcute/core/utils.js' +import { sleep } from '@fuman/utils' import type { Dispatcher } from '../dispatcher.js' diff --git a/packages/test/src/crypto.ts b/packages/test/src/crypto.ts index 588e2c08..b827f21b 100644 --- a/packages/test/src/crypto.ts +++ b/packages/test/src/crypto.ts @@ -3,8 +3,7 @@ import { gzipSync, inflateSync } from 'node:zlib' import type { MockInstance } from 'vitest' import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { ICryptoProvider } from '@mtcute/core/utils.js' -import { dataViewFromBuffer } from '@mtcute/core/utils.js' -import { hex, utf8 } from '@fuman/utils' +import { hex, typed, utf8 } from '@fuman/utils' import { defaultCryptoProvider } from './platform.js' @@ -53,7 +52,7 @@ export function withFakeRandom(provider: ICryptoProvider, source: string = DEFAU export function useFakeMathRandom(source: string = DEFAULT_ENTROPY): void { const sourceBytes = hex.decode(source) - const dv = dataViewFromBuffer(sourceBytes) + const dv = typed.toDataView(sourceBytes) let spy: MockInstance<() => number> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd38c27b..ed41de06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@antfu/eslint-config': specifier: 2.26.0 version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0)(typescript@5.5.4))(@vue/compiler-sfc@3.4.37)(eslint@9.9.0)(typescript@5.5.4)(vitest@2.0.5(@types/node@20.10.0)(@vitest/browser@2.0.5)(@vitest/ui@2.0.5)) + '@fuman/utils': + specifier: workspace:^ + version: link:private/fuman/packages/utils '@teidesu/slow-types-compiler': specifier: 1.1.0 version: 1.1.0(typescript@5.5.4) @@ -260,6 +263,9 @@ importers: packages/dispatcher: dependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils '@mtcute/core': specifier: workspace:^ version: link:../core