diff --git a/packages/convert/package.json b/packages/convert/package.json index c7b20b84..add2c059 100644 --- a/packages/convert/package.json +++ b/packages/convert/package.json @@ -12,7 +12,8 @@ "build": "pnpm run -w build-package convert" }, "dependencies": { - "@mtcute/core": "workspace:^" + "@mtcute/core": "workspace:^", + "@fuman/utils": "workspace:^" }, "devDependencies": { "@mtcute/test": "workspace:^" diff --git a/packages/convert/src/dcs.ts b/packages/convert/src/dcs.ts index d42afcad..fff1b54a 100644 --- a/packages/convert/src/dcs.ts +++ b/packages/convert/src/dcs.ts @@ -75,11 +75,13 @@ export const DC_MAPPING_TEST: Record = { id: 1, ipAddress: '149.154.175.10', port: 80, + testMode: true, }, media: { id: 1, ipAddress: '149.154.175.10', port: 80, + testMode: true, }, }, 2: { @@ -87,11 +89,13 @@ export const DC_MAPPING_TEST: Record = { id: 2, ipAddress: '149.154.167.40', port: 443, + testMode: true, }, media: { id: 2, ipAddress: '149.154.167.40', port: 443, + testMode: true, }, }, 3: { @@ -99,11 +103,13 @@ export const DC_MAPPING_TEST: Record = { id: 3, ipAddress: '149.154.175.117', port: 443, + testMode: true, }, media: { id: 3, ipAddress: '149.154.175.117', port: 443, + testMode: true, }, }, } diff --git a/packages/convert/src/gramjs/convert.test.ts b/packages/convert/src/gramjs/convert.test.ts index f18d5318..0555957d 100644 --- a/packages/convert/src/gramjs/convert.test.ts +++ b/packages/convert/src/gramjs/convert.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { GRAMJS_SESSION } from './__fixtures__/session.js' import { convertFromGramjsSession, convertToGramjsSession } from './convert.js' @@ -7,7 +7,7 @@ import { convertFromGramjsSession, convertToGramjsSession } from './convert.js' describe('gramjs/convert', () => { it('should correctly convert from gramjs sessions', () => { expect(convertFromGramjsSession(GRAMJS_SESSION)).toEqual({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( 'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' + '1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' + '552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' @@ -39,7 +39,7 @@ describe('gramjs/convert', () => { it('should correctly convert to gramjs sessions', () => { expect( convertToGramjsSession({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( 'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' + '1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' + '552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' @@ -55,15 +55,16 @@ describe('gramjs/convert', () => { ipAddress: '149.154.167.40', ipv6: false, port: 443, + testMode: true, }, media: { id: 2, ipAddress: '149.154.167.40', ipv6: false, port: 443, + testMode: true, }, }, - testMode: true, version: 3, }), ).toEqual(GRAMJS_SESSION) diff --git a/packages/convert/src/gramjs/parse.test.ts b/packages/convert/src/gramjs/parse.test.ts index 8c944c08..49bacf4c 100644 --- a/packages/convert/src/gramjs/parse.test.ts +++ b/packages/convert/src/gramjs/parse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { GRAMJS_SESSION } from './__fixtures__/session.js' import { parseGramjsSession } from './parse.js' @@ -11,7 +11,7 @@ describe('gramjs/parse', () => { ipAddress: '149.154.167.40', port: 443, ipv6: false, - authKey: getPlatform().hexDecode( + authKey: hex.decode( 'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' + '1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' + '552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' diff --git a/packages/convert/src/gramjs/parse.ts b/packages/convert/src/gramjs/parse.ts index 99623166..16a91925 100644 --- a/packages/convert/src/gramjs/parse.ts +++ b/packages/convert/src/gramjs/parse.ts @@ -1,6 +1,6 @@ import { MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { base64, utf8 } from '@fuman/utils' import type { TelethonSession } from '../telethon/types.js' @@ -12,7 +12,7 @@ export function parseGramjsSession(session: string): TelethonSession { session = session.slice(1) - const data = getPlatform().base64Decode(session) + const data = base64.decode(session) const dv = dataViewFromBuffer(data) const dcId = dv.getUint8(0) @@ -20,7 +20,7 @@ export function parseGramjsSession(session: string): TelethonSession { const ipSize = dv.getUint16(1) let pos = 3 + ipSize - const ip = getPlatform().utf8Decode(data.subarray(3, pos)) + const ip = utf8.decoder.decode(data.subarray(3, pos)) const port = dv.getUint16(pos) pos += 2 const authKey = data.subarray(pos, pos + 256) diff --git a/packages/convert/src/gramjs/serialize.test.ts b/packages/convert/src/gramjs/serialize.test.ts index dd6a43d8..633b2880 100644 --- a/packages/convert/src/gramjs/serialize.test.ts +++ b/packages/convert/src/gramjs/serialize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { GRAMJS_SESSION } from './__fixtures__/session.js' import { serializeGramjsSession } from './serialize.js' @@ -12,7 +12,7 @@ describe('gramjs/serialize', () => { ipAddress: '149.154.167.40', port: 443, ipv6: false, - authKey: getPlatform().hexDecode( + authKey: hex.decode( 'ad286dc1184bc61bfc8ed8942c1a2ef5bce1d5c25f6a069c1606fb3b8c722261' + '1cff7d73c649bf0c49807f3253542ba88f8687490ad0902e42e708a437eafe32' + '552d9d594629aae72cb55db784b3ae60b59035f925306515da861f8dcc66cf98' diff --git a/packages/convert/src/gramjs/serialize.ts b/packages/convert/src/gramjs/serialize.ts index 4c0dc98e..daf0d79a 100644 --- a/packages/convert/src/gramjs/serialize.ts +++ b/packages/convert/src/gramjs/serialize.ts @@ -1,6 +1,6 @@ import { MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { base64, utf8 } from '@fuman/utils' import type { TelethonSession } from '../telethon/types.js' @@ -9,7 +9,7 @@ export function serializeGramjsSession(session: TelethonSession): string { throw new MtArgumentError('authKey must be 256 bytes long') } - const ipEncoded = getPlatform().utf8Encode(session.ipAddress) + const ipEncoded = utf8.encoder.encode(session.ipAddress) const u8 = new Uint8Array(261 + ipEncoded.length) const dv = dataViewFromBuffer(u8) @@ -24,5 +24,5 @@ export function serializeGramjsSession(session: TelethonSession): string { pos += 2 u8.set(session.authKey, pos) - return `1${getPlatform().base64Encode(u8)}` + return `1${base64.encode(u8)}` } diff --git a/packages/convert/src/mtkruto/convert.test.ts b/packages/convert/src/mtkruto/convert.test.ts index 3f123445..5c63474c 100644 --- a/packages/convert/src/mtkruto/convert.test.ts +++ b/packages/convert/src/mtkruto/convert.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { u8HexDecode } from '@mtcute/test' +import { hex } from '@fuman/utils' import { MTKRUTO_SESSION } from './__fixtures__/session.js' import { convertFromMtkrutoSession, convertToMtkrutoSession } from './convert.js' @@ -7,7 +7,7 @@ import { convertFromMtkrutoSession, convertToMtkrutoSession } from './convert.js describe('mtkruto/convert', () => { it('should correctly convert from mtkruto sessions', () => { expect(convertFromMtkrutoSession(MTKRUTO_SESSION)).toEqual({ - authKey: u8HexDecode( + authKey: hex.decode( '58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' + '026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' + '3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' @@ -37,7 +37,7 @@ describe('mtkruto/convert', () => { it('should correctly convert to mtkruto sessions', () => { expect( convertToMtkrutoSession({ - authKey: u8HexDecode( + authKey: hex.decode( '58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' + '026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' + '3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' @@ -53,15 +53,16 @@ describe('mtkruto/convert', () => { ipAddress: '149.154.167.40', ipv6: false, port: 443, + testMode: true, }, media: { id: 2, ipAddress: '149.154.167.40', ipv6: false, port: 443, + testMode: true, }, }, - testMode: true, version: 3, }), ).toEqual(MTKRUTO_SESSION) diff --git a/packages/convert/src/mtkruto/convert.ts b/packages/convert/src/mtkruto/convert.ts index 1fdcbdc8..4729129b 100644 --- a/packages/convert/src/mtkruto/convert.ts +++ b/packages/convert/src/mtkruto/convert.ts @@ -14,7 +14,6 @@ export function convertFromMtkrutoSession(session: MtkrutoSession | string): Str return { version: 3, - testMode: session.isTest, primaryDcs: (session.isTest ? DC_MAPPING_TEST : DC_MAPPING_PROD)[session.dcId], authKey: session.authKey, } @@ -27,7 +26,7 @@ export function convertToMtkrutoSession(session: StringSessionData | string): st return serializeMtkrutoSession({ dcId: session.primaryDcs.main.id, - isTest: session.testMode, + isTest: session.primaryDcs.main.testMode ?? false, authKey: session.authKey, }) } diff --git a/packages/convert/src/mtkruto/parse.test.ts b/packages/convert/src/mtkruto/parse.test.ts index 46be4c2e..fe54dc5e 100644 --- a/packages/convert/src/mtkruto/parse.test.ts +++ b/packages/convert/src/mtkruto/parse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { u8HexDecode } from '@mtcute/test' +import { hex } from '@fuman/utils' import { MTKRUTO_SESSION } from './__fixtures__/session.js' import { parseMtkrutoSession } from './parse.js' @@ -9,7 +9,7 @@ describe('mtkruto/parse', () => { expect(parseMtkrutoSession(MTKRUTO_SESSION)).toEqual({ dcId: 2, isTest: true, - authKey: u8HexDecode( + authKey: hex.decode( '58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' + '026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' + '3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' diff --git a/packages/convert/src/mtkruto/parse.ts b/packages/convert/src/mtkruto/parse.ts index 5afa35a9..e7da642c 100644 --- a/packages/convert/src/mtkruto/parse.ts +++ b/packages/convert/src/mtkruto/parse.ts @@ -1,13 +1,13 @@ import { MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { TlBinaryReader } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import { telegramRleDecode } from '../utils/rle.js' import type { MtkrutoSession } from './types.js' export function parseMtkrutoSession(session: string): MtkrutoSession { - const data = telegramRleDecode(getPlatform().base64Decode(session, true)) + const data = telegramRleDecode(base64.decode(session, true)) const reader = TlBinaryReader.manual(data) let dcIdStr = reader.string() diff --git a/packages/convert/src/mtkruto/serialize.test.ts b/packages/convert/src/mtkruto/serialize.test.ts index de8011bb..d01a2cb8 100644 --- a/packages/convert/src/mtkruto/serialize.test.ts +++ b/packages/convert/src/mtkruto/serialize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { u8HexDecode } from '@mtcute/test' +import { hex } from '@fuman/utils' import { MTKRUTO_SESSION } from './__fixtures__/session.js' import { serializeMtkrutoSession } from './serialize.js' @@ -10,7 +10,7 @@ describe('mtkruto/serialize', () => { serializeMtkrutoSession({ dcId: 2, isTest: true, - authKey: u8HexDecode( + authKey: hex.decode( '58420a6b4ec287ef73a00d36e260cea6cbf6d135b8630ba845144ea928b8d584' + '026c3ddce272a7cfb05c148bb599f9fa7fa5e6dce4d5aa84f4ce26f8a7f02e64' + '3f47fadf23e406a079c460fa84a94259a3a2251acca412c67c56a2d1967f598f' diff --git a/packages/convert/src/mtkruto/serialize.ts b/packages/convert/src/mtkruto/serialize.ts index 1e2b6722..6dbdf78c 100644 --- a/packages/convert/src/mtkruto/serialize.ts +++ b/packages/convert/src/mtkruto/serialize.ts @@ -1,5 +1,5 @@ -import { getPlatform } from '@mtcute/core/platform.js' import { TlBinaryWriter } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import { telegramRleEncode } from '../utils/rle.js' @@ -13,5 +13,5 @@ export function serializeMtkrutoSession(session: MtkrutoSession): string { writer.string(dcIdStr) writer.bytes(session.authKey) - return getPlatform().base64Encode(telegramRleEncode(writer.result()), true) + return base64.encode(telegramRleEncode(writer.result()), true) } diff --git a/packages/convert/src/pyrogram/convert.test.ts b/packages/convert/src/pyrogram/convert.test.ts index 12d575a6..9c36d62d 100644 --- a/packages/convert/src/pyrogram/convert.test.ts +++ b/packages/convert/src/pyrogram/convert.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js' import { convertFromPyrogramSession, convertToPyrogramSession } from './convert.js' @@ -7,7 +7,7 @@ import { convertFromPyrogramSession, convertToPyrogramSession } from './convert. describe('pyrogram/convert', () => { it('should correctly convert from pyrogram sessions', () => { expect(convertFromPyrogramSession(PYROGRAM_TEST_SESSION_OLD)).toEqual({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( '1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' + 'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' + '73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' @@ -43,7 +43,7 @@ describe('pyrogram/convert', () => { it('should correctly convert to pyrogram sessions', () => { expect( convertToPyrogramSession({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( '1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' + 'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' + '73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' @@ -58,11 +58,13 @@ describe('pyrogram/convert', () => { id: 2, ipAddress: '149.154.167.40', port: 443, + testMode: true, }, media: { id: 2, ipAddress: '149.154.167.40', port: 443, + testMode: true, }, }, self: { @@ -71,7 +73,6 @@ describe('pyrogram/convert', () => { userId: 5000801609, usernames: [], }, - testMode: true, version: 3, }), ).toEqual(PYROGRAM_TEST_SESSION_OLD) diff --git a/packages/convert/src/pyrogram/convert.ts b/packages/convert/src/pyrogram/convert.ts index 605e8a8b..3d75cffc 100644 --- a/packages/convert/src/pyrogram/convert.ts +++ b/packages/convert/src/pyrogram/convert.ts @@ -14,7 +14,6 @@ export function convertFromPyrogramSession(session: PyrogramSession | string): S return { version: 3, - testMode: session.isTest, primaryDcs: (session.isTest ? DC_MAPPING_TEST : DC_MAPPING_PROD)[session.dcId], authKey: session.authKey, self: { @@ -39,7 +38,7 @@ export function convertToPyrogramSession( return serializePyrogramSession({ apiId: params?.apiId, isBot: session.self?.isBot ?? false, - isTest: session.testMode, + isTest: session.primaryDcs.main.testMode ?? false, userId: session.self?.userId ?? 0, dcId: session.primaryDcs.main.id, authKey: session.authKey, diff --git a/packages/convert/src/pyrogram/parse.test.ts b/packages/convert/src/pyrogram/parse.test.ts index 141b38da..139b1858 100644 --- a/packages/convert/src/pyrogram/parse.test.ts +++ b/packages/convert/src/pyrogram/parse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { PYROGRAM_TEST_SESSION } from './__fixtures__/session.js' import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js' @@ -12,7 +12,7 @@ describe('pyrogram/parse', () => { isTest: true, userId: 5000801609, dcId: 2, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' + 'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' + '73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' @@ -32,7 +32,7 @@ describe('pyrogram/parse', () => { isTest: true, userId: 5000801609, dcId: 2, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '4e4e8ab2caa290a3f1121c5f5c9a64a0522043fa3c5690a4ff8834b5c1ded2b5' + '425f1df801a0cabda34e95b909399e23037008f220d8908da8a6e89f6ccffb4b' + '6bbd30c767ae37e0e63a5f9177c8f7ec05f032bf5011887b5ce4fc86e7d081cb' diff --git a/packages/convert/src/pyrogram/parse.ts b/packages/convert/src/pyrogram/parse.ts index 6c6d7b67..d914a55b 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 { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer, longFromBuffer } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import type { PyrogramSession } from './types.js' @@ -10,7 +10,7 @@ const SESSION_STRING_SIZE = 351 const SESSION_STRING_SIZE_64 = 356 export function parsePyrogramSession(session: string): PyrogramSession { - const data = getPlatform().base64Decode(session, true) + const data = base64.decode(session, true) const dv = dataViewFromBuffer(data) if (session.length === SESSION_STRING_SIZE || session.length === SESSION_STRING_SIZE_64) { diff --git a/packages/convert/src/pyrogram/serialize.test.ts b/packages/convert/src/pyrogram/serialize.test.ts index b9c4c0d1..dfabdbc5 100644 --- a/packages/convert/src/pyrogram/serialize.test.ts +++ b/packages/convert/src/pyrogram/serialize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { PYROGRAM_TEST_SESSION } from './__fixtures__/session.js' import { PYROGRAM_TEST_SESSION_OLD } from './__fixtures__/session_old.js' @@ -13,7 +13,7 @@ describe('pyrogram/serialize', () => { isTest: true, userId: 5000801609, dcId: 2, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '1674732db80d690b4d5890d887a4bd5b0b4c810b7c331990b049158a940fdaeb' + 'd46178f50ddcce753699f0497ad6de9655f454bc5a3030524036dee4ffe3db7c' + '73b526c378a184a0fafa14e9679c170b632e0b412c174f99e96b216214f78263' @@ -35,7 +35,7 @@ describe('pyrogram/serialize', () => { isTest: true, userId: 5000801609, dcId: 2, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '4e4e8ab2caa290a3f1121c5f5c9a64a0522043fa3c5690a4ff8834b5c1ded2b5' + '425f1df801a0cabda34e95b909399e23037008f220d8908da8a6e89f6ccffb4b' + '6bbd30c767ae37e0e63a5f9177c8f7ec05f032bf5011887b5ce4fc86e7d081cb' diff --git a/packages/convert/src/pyrogram/serialize.ts b/packages/convert/src/pyrogram/serialize.ts index f6c74795..3ddb1bad 100644 --- a/packages/convert/src/pyrogram/serialize.ts +++ b/packages/convert/src/pyrogram/serialize.ts @@ -1,6 +1,6 @@ import { Long, MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import type { PyrogramSession } from './types.js' @@ -41,5 +41,5 @@ export function serializePyrogramSession(session: PyrogramSession): string { dv.setUint8(270, session.isBot ? 1 : 0) } - return getPlatform().base64Encode(u8, true) + return base64.encode(u8, true) } diff --git a/packages/convert/src/telethon/convert.test.ts b/packages/convert/src/telethon/convert.test.ts index d9d8867c..caa57bd1 100644 --- a/packages/convert/src/telethon/convert.test.ts +++ b/packages/convert/src/telethon/convert.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { TELETHON_TEST_SESSION } from './__fixtures__/session.js' import { convertFromTelethonSession, convertToTelethonSession } from './convert.js' @@ -7,7 +7,7 @@ import { convertFromTelethonSession, convertToTelethonSession } from './convert. describe('telethon/convert', () => { it('should correctly convert from telethon sessions', () => { expect(convertFromTelethonSession(TELETHON_TEST_SESSION)).toEqual({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( '28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' + 'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' + 'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' @@ -39,7 +39,7 @@ describe('telethon/convert', () => { it('should correctly convert to telethon sessions', () => { expect( convertToTelethonSession({ - authKey: getPlatform().hexDecode( + authKey: hex.decode( '28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' + 'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' + 'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' @@ -55,15 +55,16 @@ describe('telethon/convert', () => { ipAddress: '149.154.167.40', ipv6: false, port: 80, + testMode: true, }, media: { id: 2, ipAddress: '149.154.167.40', ipv6: false, port: 80, + testMode: true, }, }, - testMode: true, version: 3, }), ).toEqual(TELETHON_TEST_SESSION) diff --git a/packages/convert/src/telethon/convert.ts b/packages/convert/src/telethon/convert.ts index c14c49a2..2c210aaa 100644 --- a/packages/convert/src/telethon/convert.ts +++ b/packages/convert/src/telethon/convert.ts @@ -17,13 +17,13 @@ export function convertFromTelethonSession(session: TelethonSession | string): S ipAddress: session.ipAddress, port: session.port, ipv6: session.ipv6, + // we don't exactly have that information. try to deduce it from DC_MAPPING_TEST + // todo: we should maybe check this at connect? + testMode: isTestDc(session.ipAddress), } return { version: 3, - // we don't exactly have that information. try to deduce it from DC_MAPPING_TEST - // todo: we should maybe check this at connect? - testMode: isTestDc(session.ipAddress), primaryDcs: { main: dc, media: dc, diff --git a/packages/convert/src/telethon/parse.test.ts b/packages/convert/src/telethon/parse.test.ts index ca85915e..dd525c8e 100644 --- a/packages/convert/src/telethon/parse.test.ts +++ b/packages/convert/src/telethon/parse.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { TELETHON_TEST_SESSION } from './__fixtures__/session.js' import { TELETHON_TEST_SESSION_V6 } from './__fixtures__/session_v6.js' @@ -12,7 +12,7 @@ describe('telethon/parse', () => { ipAddress: '149.154.167.40', port: 80, ipv6: false, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' + 'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' + 'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' @@ -31,7 +31,7 @@ describe('telethon/parse', () => { ipAddress: '2001:0b28:f23d:f001:0000:0000:0000:000e', port: 443, ipv6: true, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '8a6f780156484e75fedacab2b45078cbc65cc97c7c8e8db06696a9dad75deab2' + '6979def6a36d86a9eb0661f9ea41df3a115408f4a857334dac682742bebb0184' + '1b921a4ffd89a5d840ddf1ea5d73a1b2c21e2ad8d0606325ba5414fc50a83cf7' diff --git a/packages/convert/src/telethon/parse.ts b/packages/convert/src/telethon/parse.ts index e366f5b7..2761f348 100644 --- a/packages/convert/src/telethon/parse.ts +++ b/packages/convert/src/telethon/parse.ts @@ -1,6 +1,6 @@ import { MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import { parseIpFromBytes } from '../utils/ip.js' @@ -14,7 +14,7 @@ export function parseTelethonSession(session: string): TelethonSession { session = session.slice(1) - const data = getPlatform().base64Decode(session, true) + const data = base64.decode(session, true) const dv = dataViewFromBuffer(data) const dcId = dv.getUint8(0) diff --git a/packages/convert/src/telethon/serialize.test.ts b/packages/convert/src/telethon/serialize.test.ts index ed9fcfc4..9c745ebc 100644 --- a/packages/convert/src/telethon/serialize.test.ts +++ b/packages/convert/src/telethon/serialize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { TELETHON_TEST_SESSION } from './__fixtures__/session.js' import { TELETHON_TEST_SESSION_V6 } from './__fixtures__/session_v6.js' @@ -13,7 +13,7 @@ describe('telethon/serialize', () => { ipAddress: '149.154.167.40', port: 80, ipv6: false, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '28494b5ff1c142b4d48b3870ebd06b524a4e7d4f39a6dd31409f2e65cd605532' + 'bc6deff59fea6c5345a77cd83fefb7695a53608d83a41d886f8ea9fdbc120b48' + 'f54048ef750c498f6e9c563f0d7ec96b0a462b755de094e85d7334aad3c929df' @@ -34,7 +34,7 @@ describe('telethon/serialize', () => { ipAddress: '2001:0b28:f23d:f001:0000:0000:0000:000e', port: 443, ipv6: true, - authKey: getPlatform().hexDecode( + authKey: hex.decode( '8a6f780156484e75fedacab2b45078cbc65cc97c7c8e8db06696a9dad75deab2' + '6979def6a36d86a9eb0661f9ea41df3a115408f4a857334dac682742bebb0184' + '1b921a4ffd89a5d840ddf1ea5d73a1b2c21e2ad8d0606325ba5414fc50a83cf7' diff --git a/packages/convert/src/telethon/serialize.ts b/packages/convert/src/telethon/serialize.ts index 4b2957c7..c44d1238 100644 --- a/packages/convert/src/telethon/serialize.ts +++ b/packages/convert/src/telethon/serialize.ts @@ -1,6 +1,6 @@ import { MtArgumentError } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { base64 } from '@fuman/utils' import { serializeIpv4ToBytes, serializeIpv6ToBytes } from '../utils/ip.js' @@ -31,7 +31,7 @@ export function serializeTelethonSession(session: TelethonSession): string { pos += 2 u8.set(session.authKey, pos) - let b64 = getPlatform().base64Encode(u8, true) + let b64 = base64.encode(u8, true) while (b64.length % 4 !== 0) b64 += '=' // for some reason telethon uses padding return `1${b64}` diff --git a/packages/convert/src/utils/ip.ts b/packages/convert/src/utils/ip.ts index e73b199b..90dcbbc5 100644 --- a/packages/convert/src/utils/ip.ts +++ b/packages/convert/src/utils/ip.ts @@ -1,5 +1,6 @@ import { MtArgumentError } from '@mtcute/core' +// todo: use @fuman/ip export function parseIpFromBytes(data: Uint8Array): string { if (data.length === 4) { return `${data[0]}.${data[1]}.${data[2]}.${data[3]}` diff --git a/packages/core/src/highlevel/methods/bots/get-callback-answer.ts b/packages/core/src/highlevel/methods/bots/get-callback-answer.ts index 761695f9..31225f2c 100644 --- a/packages/core/src/highlevel/methods/bots/get-callback-answer.ts +++ b/packages/core/src/highlevel/methods/bots/get-callback-answer.ts @@ -1,6 +1,6 @@ import type { tl } from '@mtcute/tl' +import { utf8 } from '@fuman/utils' -import { getPlatform } from '../../../platform.js' import type { ITelegramClient } from '../../client.types.js' import type { InputMessageId } from '../../types/index.js' import { normalizeInputMessageId } from '../../types/index.js' @@ -66,7 +66,7 @@ export async function getCallbackAnswer( _: 'messages.getBotCallbackAnswer', peer: await resolvePeer(client, chatId), msgId: message, - data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data, + data: typeof data === 'string' ? utf8.encoder.encode(data) : data, password, game, }, diff --git a/packages/core/src/highlevel/types/bots/keyboards/factories.ts b/packages/core/src/highlevel/types/bots/keyboards/factories.ts index 0f48134a..679cd5a5 100644 --- a/packages/core/src/highlevel/types/bots/keyboards/factories.ts +++ b/packages/core/src/highlevel/types/bots/keyboards/factories.ts @@ -1,6 +1,6 @@ import { tl } from '@mtcute/tl' +import { utf8 } from '@fuman/utils' -import { getPlatform } from '../../../../platform.js' import { assertNever } from '../../../../types/utils.js' import { toInputUser } from '../../../utils/peer-utils.js' @@ -169,7 +169,7 @@ export function callback( _: 'keyboardButtonCallback', text, requiresPassword, - data: typeof data === 'string' ? getPlatform().utf8Encode(data) : data, + data: typeof data === 'string' ? utf8.encoder.encode(data) : data, } } diff --git a/packages/core/src/highlevel/types/updates/callback-query.ts b/packages/core/src/highlevel/types/updates/callback-query.ts index b16fcbf1..1fe48259 100644 --- a/packages/core/src/highlevel/types/updates/callback-query.ts +++ b/packages/core/src/highlevel/types/updates/callback-query.ts @@ -1,6 +1,6 @@ import type { tl } from '@mtcute/tl' +import { utf8 } from '@fuman/utils' -import { getPlatform } from '../../../platform.js' import { MtArgumentError } from '../../../types/errors.js' import { makeInspectable } from '../../utils/index.js' import { encodeInlineMessageId } from '../../utils/inline-utils.js' @@ -62,7 +62,7 @@ class BaseCallbackQuery { get dataStr(): string | null { if (!this.raw.data) return null - return getPlatform().utf8Decode(this.raw.data) + return utf8.decoder.decode(this.raw.data) } /** diff --git a/packages/core/src/highlevel/utils/file-type.test.ts b/packages/core/src/highlevel/utils/file-type.test.ts index f0500760..9c33ac35 100644 --- a/packages/core/src/highlevel/utils/file-type.test.ts +++ b/packages/core/src/highlevel/utils/file-type.test.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest' - -import { getPlatform } from '../../platform.js' +import { hex } from '@fuman/utils' import { MIME_TO_EXTENSION, guessFileMime } from './file-type.js' -const p = getPlatform() - describe('guessFileMime', () => { it.each([ ['424d', 'image/bmp', 'bmp'], @@ -62,7 +59,7 @@ describe('guessFileMime', () => { ])('should detect %s as %s with %s extension', (header, mime, ext) => { header += '00'.repeat(16) - expect(guessFileMime(p.hexDecode(header))).toEqual(mime) + expect(guessFileMime(hex.decode(header))).toEqual(mime) expect(MIME_TO_EXTENSION[mime]).toEqual(ext) }) }) diff --git a/packages/core/src/highlevel/utils/file-utils.test.ts b/packages/core/src/highlevel/utils/file-utils.test.ts index fd5e6aa5..116e266a 100644 --- a/packages/core/src/highlevel/utils/file-utils.test.ts +++ b/packages/core/src/highlevel/utils/file-utils.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' - -import { getPlatform } from '../../platform.js' +import { hex, utf8 } from '@fuman/utils' import { extractFileName, @@ -10,24 +9,22 @@ import { svgPathToFile, } from './file-utils.js' -const p = getPlatform() - describe('isProbablyPlainText', () => { it('should return true for buffers only containing printable ascii', () => { - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text'))).toEqual(true) - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\nwith unix new lines'))).toEqual(true) - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\r\nwith windows new lines'))).toEqual(true) - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\n\twith unix new lines and tabs'))).toEqual(true) - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text\r\n\twith windows new lines and tabs'))).toEqual(true) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text'))).toEqual(true) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text\nwith unix new lines'))).toEqual(true) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text\r\nwith windows new lines'))).toEqual(true) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text\n\twith unix new lines and tabs'))).toEqual(true) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text\r\n\twith windows new lines and tabs'))).toEqual(true) }) it('should return false for buffers containing some binary data', () => { - expect(isProbablyPlainText(p.utf8Encode('hello this is cedilla: ç'))).toEqual(false) - expect(isProbablyPlainText(p.utf8Encode('hello this is some ascii text with emojis 🌸'))).toEqual(false) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is cedilla: ç'))).toEqual(false) + expect(isProbablyPlainText(utf8.encoder.encode('hello this is some ascii text with emojis 🌸'))).toEqual(false) // random strings of 16 bytes - expect(isProbablyPlainText(p.hexDecode('717f80f08eb9d88c3931712c0e2be32f'))).toEqual(false) - expect(isProbablyPlainText(p.hexDecode('20e8e218e54254c813b261432b0330d7'))).toEqual(false) + expect(isProbablyPlainText(hex.decode('717f80f08eb9d88c3931712c0e2be32f'))).toEqual(false) + expect(isProbablyPlainText(hex.decode('20e8e218e54254c813b261432b0330d7'))).toEqual(false) }) }) @@ -49,14 +46,14 @@ describe('svgPathToFile', () => { it('should convert SVG path to a file', () => { const path = 'M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z' - expect(p.utf8Decode(svgPathToFile(path))).toMatchInlineSnapshot( + expect(utf8.decoder.decode(svgPathToFile(path))).toMatchInlineSnapshot( '""', ) }) }) describe('inflateSvgPath', () => { - const data = p.hexDecode( + const data = hex.decode( '1a05b302dc5f4446068649064247424a6a4c704550535b5e665e5e4c044a024c' + '074e06414d80588863935fad74be4704854684518b528581904695498b488b56' + '965c85438d8191818543894a8f4d834188818a4284498454895d9a6f86074708' @@ -81,9 +78,9 @@ describe('inflateSvgPath', () => { describe('strippedPhotoToJpg', () => { // strippedThumb of @Channel_Bot - const dataPfp = p.hexDecode('010808b1f2f95fed673451457033ad1f') + const dataPfp = hex.decode('010808b1f2f95fed673451457033ad1f') // photoStrippedSize of a random image - const dataPicture = p.hexDecode( + const dataPicture = hex.decode( '012728b532aacce4b302d8c1099c74a634718675cb6381f73d3ffd557667d9b5' + '816f4c28ce69aa58a863238cf62a334590f999042234cbe1986d03eefe14c68e' + '32847cc00ce709ea7ffad577773f78fe54d6c927f78c3db14ac1ccca91a2ef4f' @@ -95,7 +92,7 @@ describe('strippedPhotoToJpg', () => { ) it('should inflate stripped jpeg (from profile picture)', () => { - expect(p.hexEncode(strippedPhotoToJpg(dataPfp))).toMatchInlineSnapshot( + expect(hex.encode(strippedPhotoToJpg(dataPfp))).toMatchInlineSnapshot( '"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' + '82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' + '0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' @@ -120,7 +117,7 @@ describe('strippedPhotoToJpg', () => { }) it('should inflate stripped jpeg (from a picture)', () => { - expect(p.hexEncode(strippedPhotoToJpg(dataPicture))).toMatchInlineSnapshot( + expect(hex.encode(strippedPhotoToJpg(dataPicture))).toMatchInlineSnapshot( '"ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e192' + '82321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a' + '0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2' diff --git a/packages/core/src/highlevel/utils/file-utils.ts b/packages/core/src/highlevel/utils/file-utils.ts index fadd6d70..dff0b6e8 100644 --- a/packages/core/src/highlevel/utils/file-utils.ts +++ b/packages/core/src/highlevel/utils/file-utils.ts @@ -1,6 +1,6 @@ import type { tl } from '@mtcute/tl' +import { hex, utf8 } from '@fuman/utils' -import { getPlatform } from '../../platform.js' import { MtArgumentError } from '../../types/errors.js' import { concatBuffers } from '../../utils/buffer-utils.js' @@ -35,7 +35,7 @@ export function isProbablyPlainText(buf: Uint8Array): boolean { // from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225 function JPEG_HEADER() { - return getPlatform().hexDecode( + return hex.decode( 'ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e1928' + '2321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aad' + 'aad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c35' @@ -117,7 +117,7 @@ export function inflateSvgPath(encoded: Uint8Array): string { * @param size Size attribute of the document, if available */ export function svgPathToFile(path: string, size?: tl.RawDocumentAttributeImageSize): Uint8Array { - return getPlatform().utf8Encode( + return utf8.encoder.encode( '' + '` diff --git a/packages/core/src/highlevel/utils/voice-utils.test.ts b/packages/core/src/highlevel/utils/voice-utils.test.ts index deb92651..da173bd1 100644 --- a/packages/core/src/highlevel/utils/voice-utils.test.ts +++ b/packages/core/src/highlevel/utils/voice-utils.test.ts @@ -1,16 +1,13 @@ import { describe, expect, it } from 'vitest' - -import { getPlatform } from '../../platform.js' +import { hex } from '@fuman/utils' import { decodeWaveform, encodeWaveform } from './voice-utils.js' -const p = getPlatform() - describe('decodeWaveform', () => { it('should correctly decode telegram-encoded waveform', () => { expect( decodeWaveform( - p.hexDecode( + hex.decode( '0000104210428c310821a51463cc39072184524a4aa9b51663acb5e69c7bef41' + '08618c514a39e7a494d65aadb5f75e8c31ce396badf7de9cf3debbf7feff0f', ), @@ -123,7 +120,7 @@ describe('decodeWaveform', () => { describe('encodeWaveform', () => { it('should correctly decode telegram-encoded waveform', () => { expect( - p.hexEncode( + hex.encode( encodeWaveform([ 0, 0, diff --git a/packages/core/src/network/auth-key.test.ts b/packages/core/src/network/auth-key.test.ts index 9ee31e7b..53182feb 100644 --- a/packages/core/src/network/auth-key.test.ts +++ b/packages/core/src/network/auth-key.test.ts @@ -1,21 +1,17 @@ import Long from 'long' import { describe, expect, it, vi } from 'vitest' import { defaultTestCryptoProvider } from '@mtcute/test' -import type { - TlBinaryReader, - TlReaderMap, -} from '@mtcute/tl-runtime' +import type { TlBinaryReader, TlReaderMap } from '@mtcute/tl-runtime' +import { hex, utf8 } from '@fuman/utils' -import { getPlatform } from '../platform.js' import { LogManager } from '../utils/index.js' import { AuthKey } from './auth-key.js' const authKey = new Uint8Array(256) -const p = getPlatform() for (let i = 0; i < 256; i += 32) { - authKey.subarray(i, i + 32).set(p.hexDecode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')) + authKey.subarray(i, i + 32).set(hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')) } describe('AuthKey', () => { @@ -51,19 +47,19 @@ describe('AuthKey', () => { it('should calculate derivatives', async () => { const key = await create() - expect(p.hexEncode(key.key)).toEqual(p.hexEncode(authKey)) - expect(p.hexEncode(key.clientSalt)).toEqual('f73c3622dec230e098cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4b') - expect(p.hexEncode(key.serverSalt)).toEqual('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0') - expect(p.hexEncode(key.id)).toEqual('40fa5bb7cb56a895') + expect(hex.encode(key.key)).toEqual(hex.encode(authKey)) + expect(hex.encode(key.clientSalt)).toEqual('f73c3622dec230e098cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4b') + expect(hex.encode(key.serverSalt)).toEqual('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0') + expect(hex.encode(key.id)).toEqual('40fa5bb7cb56a895') }) it('should encrypt a message', async () => { - const message = writeMessage(p.utf8Encode('hello, world!!!!')) + const message = writeMessage(utf8.encoder.encode('hello, world!!!!')) const key = await create() const msg = key.encryptMessage(message, serverSalt, sessionId) - expect(p.hexEncode(msg)).toEqual( + expect(hex.encode(msg)).toEqual( '40fa5bb7cb56a895f6f5a88914892aadf87c68031cc953ba29d68e118021f329' + 'be386a620d49f3ad3a50c60dcef3733f214e8cefa3e403c11d193637d4971dc1' + '5db7f74b26fd16cb0e8fee30bf7e3f68858fe82927e2cd06', @@ -86,7 +82,7 @@ describe('AuthKey', () => { } it('should decrypt a message', async () => { - const message = p.hexDecode( + const message = hex.decode( '40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' + '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' + '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35', @@ -96,11 +92,11 @@ describe('AuthKey', () => { expect(decMsgId).toEqual(msgId) expect(decSeqNo).toEqual(seqNo) - expect(p.utf8Decode(data.raw(16))).toEqual('hello, world!!!!') + expect(utf8.decoder.decode(data.raw(16))).toEqual('hello, world!!!!') }) it('should decrypt a message with padding', async () => { - const message = p.hexDecode( + const message = hex.decode( '40fa5bb7cb56a8950c394b884f1529efc42fea22d972fea650a714ce6d2d1bdb' + '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' + '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35' @@ -111,11 +107,11 @@ describe('AuthKey', () => { expect(decMsgId).toEqual(msgId) expect(decSeqNo).toEqual(seqNo) - expect(p.utf8Decode(data.raw(16))).toEqual('hello, world!!!!') + expect(utf8.decoder.decode(data.raw(16))).toEqual('hello, world!!!!') }) it('should ignore messages with invalid message key', async () => { - const message = p.hexDecode( + const message = hex.decode( '40fa5bb7cb56a8950000000000000000000000000000000050a714ce6d2d1bdb' + '3d98ff5929b8768c401771a69795f36a7e720dcafac2efbccd0ba368e8a7f48b' + '07362cac1a32ffcabe188b51a36cc4d54e1d0633cf9eaf35', @@ -125,7 +121,7 @@ describe('AuthKey', () => { }) it('should ignore messages with invalid session_id', async () => { - const message = p.hexDecode( + const message = hex.decode( '40fa5bb7cb56a895a986a7e97f4e90aa2769b5e702c6e86f5e1e82c6ff0c6829' + '2521a2ba9704fa37fb341d895cf32662c6cf47ba31cbf27c30d5c03f6c2930f4' + '30fd8858b836b73fe32d4a95b8ebcdbc9ca8908f7964c40a', @@ -135,12 +131,12 @@ describe('AuthKey', () => { }) it('should ignore messages with invalid length', async () => { - const messageTooLong = p.hexDecode( + const messageTooLong = hex.decode( '40fa5bb7cb56a8950d19412233dd5d24be697c73274e08fbe515cf65e0c5f70c' + 'ad75fd2badc18c9f999f287351144eeb1cfcaa9bea33ef5058999ad96a498306' + '08d2859425685a55b21fab413bfabc42ec5da283853b28c0', ) - const messageUnaligned = p.hexDecode( + const messageUnaligned = hex.decode( '40fa5bb7cb56a8957b4e4bec561eee4a5a1025bc8a35d3d0c79a3685d2b90ff0' + '5f638e9c42c9fd9448b0ce8e7d49e7ea1ce458e47b825b5c7fd8ddf5b4fded46' + '2a4bcc02f3ff2e89de6764d6d219f575e457fdcf8c163cdf', @@ -153,7 +149,7 @@ describe('AuthKey', () => { }) it('should ignore messages with invalid padding', async () => { - const message = p.hexDecode( + const message = hex.decode( '40fa5bb7cb56a895133671d1c637a9836e2c64b4d1a0521d8a25a6416fd4dc9e' + '79f9478fb837703cc9efa0a19d12143c2a26e57cb4bc64d7bc972dd8f19c53c590cc258162f44afc', ) diff --git a/packages/core/src/network/transports/obfuscated.test.ts b/packages/core/src/network/transports/obfuscated.test.ts index 16611610..f11afed4 100644 --- a/packages/core/src/network/transports/obfuscated.test.ts +++ b/packages/core/src/network/transports/obfuscated.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest' -import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test' +import { defaultTestCryptoProvider } from '@mtcute/test' import { Bytes } from '@fuman/io' +import { hex } from '@fuman/utils' import { getPlatform } from '../../platform.js' import { LogManager } from '../../utils/index.js' @@ -28,7 +29,7 @@ describe('ObfuscatedPacketCodec', () => { const tag = await codec.tag() - expect(p.hexEncode(tag)).toEqual( + expect(hex.encode(tag)).toEqual( `${'ff'.repeat(56)}fce8ab2203db2bff`, // encrypted part ) }) @@ -46,7 +47,7 @@ describe('ObfuscatedPacketCodec', () => { const tag = await codec.tag() - expect(p.hexEncode(tag)).toEqual( + expect(hex.encode(tag)).toEqual( `${'ff'.repeat(56)}ecec4cbda8bb188b`, // encrypted part with dcId = 1 ) }) @@ -63,7 +64,7 @@ describe('ObfuscatedPacketCodec', () => { const tag = await codec.tag() - expect(p.hexEncode(tag)).toEqual( + expect(hex.encode(tag)).toEqual( `${'ff'.repeat(56)}ecec4cbdb89c188b`, // encrypted part with dcId = 10001 ) }) @@ -80,7 +81,7 @@ describe('ObfuscatedPacketCodec', () => { const tag = await codec.tag() - expect(p.hexEncode(tag)).toEqual( + expect(hex.encode(tag)).toEqual( `${'ff'.repeat(56)}ecec4cbd5644188b`, // encrypted part with dcId = -1 ) }) @@ -115,14 +116,14 @@ describe('ObfuscatedPacketCodec', () => { expect(spyCreateAesCtr).toHaveBeenCalledTimes(2) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 1, - u8HexDecode('10b6b4ad6d56ef5df9453f88e6ee6adb6e0544ba635dc6a8a990c9b8b980c343'), - u8HexDecode('936b33fa7f97bae025102532233abb26'), + hex.decode('10b6b4ad6d56ef5df9453f88e6ee6adb6e0544ba635dc6a8a990c9b8b980c343'), + hex.decode('936b33fa7f97bae025102532233abb26'), true, ) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 2, - u8HexDecode('26bb3a2332251025e0ba977ffa336b9343c380b9b8c990a9a8c65d63ba44056e'), - u8HexDecode('db6aeee6883f45f95def566dadb4b610'), + hex.decode('26bb3a2332251025e0ba977ffa336b9343c380b9b8c990a9a8c65d63ba44056e'), + hex.decode('db6aeee6883f45f95def566dadb4b610'), false, ) }) @@ -130,7 +131,7 @@ describe('ObfuscatedPacketCodec', () => { it('should correctly create aes ctr for mtproxy', async () => { const proxy: MtProxyInfo = { dcId: 1, - secret: p.hexDecode('00112233445566778899aabbccddeeff'), + secret: hex.decode('00112233445566778899aabbccddeeff'), test: true, media: false, } @@ -143,20 +144,20 @@ describe('ObfuscatedPacketCodec', () => { expect(spyCreateAesCtr).toHaveBeenCalledTimes(2) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 1, - u8HexDecode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'), - u8HexDecode('936b33fa7f97bae025102532233abb26'), + hex.decode('dd03188944590983e28dad14d97d0952389d118af4ffcbdb28d56a6a612ef7a6'), + hex.decode('936b33fa7f97bae025102532233abb26'), true, ) expect(spyCreateAesCtr).toHaveBeenNthCalledWith( 2, - u8HexDecode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'), - u8HexDecode('db6aeee6883f45f95def566dadb4b610'), + hex.decode('413b8e08021fbb08a2962b6d7187194fe46565c6b329d3bbdfcffd4870c16119'), + hex.decode('db6aeee6883f45f95def566dadb4b610'), false, ) }) it('should correctly encrypt the underlying codec', async () => { - const data = p.hexDecode('6cfeffff') + const data = hex.decode('6cfeffff') const msg1 = 'a1020630a410e940' const msg2 = 'f53ff53f371db495' @@ -166,11 +167,11 @@ describe('ObfuscatedPacketCodec', () => { const buf = Bytes.alloc() await codec.encode(data, buf) - expect(p.hexEncode(buf.result())).toEqual(msg1) + expect(hex.encode(buf.result())).toEqual(msg1) buf.reset() await codec.encode(data, buf) - expect(p.hexEncode(buf.result())).toEqual(msg2) + expect(hex.encode(buf.result())).toEqual(msg2) }) it('should correctly decrypt the underlying codec', async () => { @@ -181,8 +182,8 @@ describe('ObfuscatedPacketCodec', () => { await codec.tag() - expect(codec.decode(Bytes.from(p.hexDecode(msg1)), false)).rejects.toThrow(TransportError) - expect(codec.decode(Bytes.from(p.hexDecode(msg2)), false)).rejects.toThrow(TransportError) + expect(codec.decode(Bytes.from(hex.decode(msg1)), false)).rejects.toThrow(TransportError) + expect(codec.decode(Bytes.from(hex.decode(msg2)), false)).rejects.toThrow(TransportError) }) it('should correctly reset', async () => { diff --git a/packages/core/src/platform.ts b/packages/core/src/platform.ts index 30d480d0..15f005fe 100644 --- a/packages/core/src/platform.ts +++ b/packages/core/src/platform.ts @@ -1,11 +1,10 @@ -import type { ITlPlatform } from '@mtcute/tl-runtime' -import { TlBinaryReader, TlBinaryWriter } from '@mtcute/tl-runtime' - import type { UploadFileLike } from './highlevel/types/files/utils.js' import { MtUnsupportedError } from './types/errors.js' import type { MaybePromise } from './types/index.js' -export interface ICorePlatform extends ITlPlatform { +// todo: can we make this non-global? + +export interface ICorePlatform { beforeExit: (fn: () => void) => () => void log: (color: number, level: number, tag: string, fmt: string, args: unknown[]) => void getDefaultLogLevel: () => number | null @@ -37,8 +36,6 @@ export function setPlatform(platform: ICorePlatform): void { } _platform = platform - TlBinaryReader.platform = platform - TlBinaryWriter.platform = platform ;(globalThis as any)[platformKey] = platform } diff --git a/packages/core/src/utils/bigint-utils.test.ts b/packages/core/src/utils/bigint-utils.test.ts index 0b1e28f3..9d498e29 100644 --- a/packages/core/src/utils/bigint-utils.test.ts +++ b/packages/core/src/utils/bigint-utils.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import { defaultTestCryptoProvider } from '@mtcute/test' - -import { getPlatform } from '../platform.js' +import { hex } from '@fuman/utils' import { bigIntBitLength, @@ -16,8 +15,6 @@ import { twoMultiplicity, } from './index.js' -const p = getPlatform() - describe('bigIntBitLength', () => { it('should correctly calculate bit length', () => { expect(bigIntBitLength(0n)).eq(0) @@ -35,7 +32,7 @@ describe('bigIntToBuffer', () => { 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([ - ...p.hexDecode('81A33C81D2020550'), + ...hex.decode('81A33C81D2020550'), ]) }) @@ -45,12 +42,12 @@ describe('bigIntToBuffer', () => { 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([ - ...p.hexDecode('81A33C81D2020550').reverse(), + ...hex.decode('81A33C81D2020550').reverse(), ]) }) it('should handle large integers', () => { - const buf = p.hexDecode( + const buf = hex.decode( '1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', ) const num = BigInt( @@ -76,7 +73,7 @@ describe('bufferToBigInt', () => { }) it('should handle large integers', () => { - const buf = p.hexDecode( + const buf = hex.decode( '1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538', ) const num = BigInt( diff --git a/packages/core/src/utils/binary/asn1-parser.ts b/packages/core/src/utils/binary/asn1-parser.ts index 8bb460d6..601ce728 100644 --- a/packages/core/src/utils/binary/asn1-parser.ts +++ b/packages/core/src/utils/binary/asn1-parser.ts @@ -1,5 +1,7 @@ // all available libraries either suck or are extremely large for the use case, so i made my own~ +import { hex } from '@fuman/utils' + import { getPlatform } from '../../platform.js' /** @@ -66,7 +68,7 @@ export function parseAsn1(data: Uint8Array): Asn1Object { if (0x80 & asn1.length) { asn1.lengthSize = 0x7F & asn1.length // I think that buf->hex->int solves the problem of Endianness... not sure - asn1.length = Number.parseInt(getPlatform().hexEncode(buf.subarray(index, index + asn1.lengthSize)), 16) + asn1.length = Number.parseInt(hex.encode(buf.subarray(index, index + asn1.lengthSize)), 16) index += asn1.lengthSize } diff --git a/packages/core/src/utils/crypto/keys.ts b/packages/core/src/utils/crypto/keys.ts index d18010b8..42115b3e 100644 --- a/packages/core/src/utils/crypto/keys.ts +++ b/packages/core/src/utils/crypto/keys.ts @@ -2,8 +2,8 @@ import type Long from 'long' import type { TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js' import { __publicKeyIndex as keysIndex } from '@mtcute/tl/binary/rsa-keys.js' import { TlBinaryWriter } from '@mtcute/tl-runtime' +import { hex } from '@fuman/utils' -import { getPlatform } from '../../platform.js' import { parseAsn1, parsePemContents } from '../binary/asn1-parser.js' import type { ICryptoProvider } from './abstract.js' @@ -27,15 +27,13 @@ export function parsePublicKey(crypto: ICryptoProvider, key: string, old = false writer.bytes(modulus) writer.bytes(exponent) - const platform = getPlatform() - const data = writer.result() const sha = crypto.sha1(data) - const fp = platform.hexEncode(sha.slice(-8).reverse()) + const fp = hex.encode(sha.slice(-8).reverse()) return { - modulus: platform.hexEncode(modulus), - exponent: platform.hexEncode(exponent), + modulus: hex.encode(modulus), + exponent: hex.encode(exponent), fingerprint: fp, old, } diff --git a/packages/core/src/utils/crypto/mtproto.test.ts b/packages/core/src/utils/crypto/mtproto.test.ts index f276a5ea..c38a5dbb 100644 --- a/packages/core/src/utils/crypto/mtproto.test.ts +++ b/packages/core/src/utils/crypto/mtproto.test.ts @@ -1,16 +1,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { defaultTestCryptoProvider, u8HexDecode } from '@mtcute/test' +import { defaultTestCryptoProvider } from '@mtcute/test' +import { hex } from '@fuman/utils' -import { getPlatform } from '../../platform.js' import { concatBuffers } from '../index.js' import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js' -const p = getPlatform() - -const authKeyChunk = p.hexDecode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0') +const authKeyChunk = hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0') const authKey = concatBuffers(Array.from({ length: 8 }, () => authKeyChunk)) -const messageKey = p.hexDecode('25d701f2a29205526757825a99eb2d32') +const messageKey = hex.decode('25d701f2a29205526757825a99eb2d32') describe('mtproto 2.0', async () => { const crypto = await defaultTestCryptoProvider() @@ -21,10 +19,10 @@ describe('mtproto 2.0', async () => { it('should correctly derive message key and iv for client', () => { createAesIgeForMessage(crypto, authKey, messageKey, true) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][0])).toEqual( 'af3f8e1ffa75f4c981eec33a3e5bbaa2ea48f9bb93e91597627eb1f67960a0c9', ) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][1])).toEqual( '9874d77f95155b35221bff94b7df4594c6996e2a62e44fcb7d93c8c4e41b79ee', ) }) @@ -32,10 +30,10 @@ describe('mtproto 2.0', async () => { it('should correctly derive message key and iv for server', () => { createAesIgeForMessage(crypto, authKey, messageKey, false) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][0])).toEqual( 'd4b378e1e0525f10ff9d4c42807ccce5b30a033a8088c0b922b5259421751648', ) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][1])).toEqual( '4d7194f42f0135d2fd83050b403265b4c40ee3e9e9fba56f0f4d8ea6bcb121f5', ) }) @@ -50,10 +48,10 @@ describe('mtproto 1.0', async () => { it('should correctly derive message key and iv for client', () => { createAesIgeForMessageOld(crypto, authKey, messageKey, true) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][0])).toEqual( '1fc7b40b1d9ffbdaf4d652525a748864259698f89214abf27c0d36cb9d4cd5db', ) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][1])).toEqual( '7251fbda39ec5e6e089f15ded5963b03d6d8d0f7078898431fc7b40b1d9ffbda', ) }) @@ -61,10 +59,10 @@ describe('mtproto 1.0', async () => { it('should correctly derive message key and iv for server', () => { createAesIgeForMessageOld(crypto, authKey, messageKey, false) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][0])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][0])).toEqual( 'af0e4e01318654be40ab42b125909d43b44bdeef571ff1a5dfb81474ae26d467', ) - expect(p.hexEncode(createAesIgeSpy.mock.calls[0][1])).toEqual( + expect(hex.encode(createAesIgeSpy.mock.calls[0][1])).toEqual( '15c9ba6021d2c5cf04f0842540ae216a970b4eac8f46ef01af0e4e01318654be', ) }) @@ -76,13 +74,13 @@ describe('mtproto key/iv from nonce', async () => { it('should correctly derive message key and iv for given nonces', () => { const res = generateKeyAndIvFromNonce( crypto, - u8HexDecode('8af24c551836e5ed7002f5857e6e71b2'), - u8HexDecode('3bf48b2d3152f383d82d1f2b32ac7fb5'), + hex.decode('8af24c551836e5ed7002f5857e6e71b2'), + hex.decode('3bf48b2d3152f383d82d1f2b32ac7fb5'), ) expect(res).to.eql([ - u8HexDecode('b0b5ffeadff0249fa6292f5ae0351556fd6619ba5dd4809601669292456d3e5a'), - u8HexDecode('13fef5bfd8c46b12dfd1753013b86cc012e1ce8ed6f8ecdd7bf36f3a3bf48b2d'), + hex.decode('b0b5ffeadff0249fa6292f5ae0351556fd6619ba5dd4809601669292456d3e5a'), + hex.decode('13fef5bfd8c46b12dfd1753013b86cc012e1ce8ed6f8ecdd7bf36f3a3bf48b2d'), ]) }) }) diff --git a/packages/core/src/utils/crypto/password.test.ts b/packages/core/src/utils/crypto/password.test.ts index c5622989..b2d2a07f 100644 --- a/packages/core/src/utils/crypto/password.test.ts +++ b/packages/core/src/utils/crypto/password.test.ts @@ -2,20 +2,17 @@ import Long from 'long' import { describe, expect, it } from 'vitest' import { defaultTestCryptoProvider } from '@mtcute/test' import type { tl } from '@mtcute/tl' - -import { getPlatform } from '../../platform.js' +import { hex, utf8 } from '@fuman/utils' import { computeNewPasswordHash, computePasswordHash, computeSrpParams } from './index.js' -const p = getPlatform() - // a real-world request from an account with "qwe123" password const fakeAlgo: tl.RawPasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow = { _: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow', - salt1: p.hexDecode('9b3accc457c0d5288e8cff31eb21094048bc11902f6614dbb9afb839ee7641c37619537d8ebe749e'), - salt2: p.hexDecode('6c619bb0786dc4ed1bf211d23f6e4065'), + salt1: hex.decode('9b3accc457c0d5288e8cff31eb21094048bc11902f6614dbb9afb839ee7641c37619537d8ebe749e'), + salt2: hex.decode('6c619bb0786dc4ed1bf211d23f6e4065'), g: 3, - p: p.hexDecode( + p: hex.decode( 'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' + '48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' + '20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' @@ -32,7 +29,7 @@ const fakeRequest: tl.account.RawPassword = { hasSecureValues: false, hasPassword: true, currentAlgo: fakeAlgo, - srpB: p.hexDecode( + srpB: hex.decode( '1476a7b5991d7f028bbee33b3455cad3f2cd0eb3737409fcce92fa7d4cd5c733' + 'ec6d2cb3454e587d4c17eda2fd7ef9a57327215f38292cc8bd5dc77d3e1d31cd' + 'dae2652f8347c4b0093f7c78242f70e6cc13137ee7acc257a49855a63113db8f' @@ -45,10 +42,10 @@ const fakeRequest: tl.account.RawPassword = { srpId: Long.fromBits(-2046015018, 875006452), newAlgo: { _: 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow', - salt1: p.hexDecode('9b3accc457c0d528'), - salt2: p.hexDecode('6c619bb0786dc4ed1bf211d23f6e4065'), + salt1: hex.decode('9b3accc457c0d528'), + salt2: hex.decode('6c619bb0786dc4ed1bf211d23f6e4065'), g: 3, - p: p.hexDecode( + p: hex.decode( 'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f' + '48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c37' + '20fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f64' @@ -61,7 +58,7 @@ const fakeRequest: tl.account.RawPassword = { }, newSecureAlgo: { _: 'securePasswordKdfAlgoPBKDF2HMACSHA512iter100000', - salt: p.hexDecode('fdd59abc0bffb24d'), + salt: hex.decode('fdd59abc0bffb24d'), }, secureRandom: new Uint8Array(), // unused } @@ -70,16 +67,16 @@ const password = 'qwe123' describe('SRP', () => { it('should correctly compute password hash as defined by MTProto', async () => { const crypto = await defaultTestCryptoProvider() - const hash = await computePasswordHash(crypto, p.utf8Encode(password), fakeAlgo.salt1, fakeAlgo.salt2) + const hash = await computePasswordHash(crypto, utf8.encoder.encode(password), fakeAlgo.salt1, fakeAlgo.salt2) - expect(p.hexEncode(hash)).toEqual('750f1fe282965e63ce17b98427b35549fb864465211840f6a7c1f2fb657cc33b') + expect(hex.encode(hash)).toEqual('750f1fe282965e63ce17b98427b35549fb864465211840f6a7c1f2fb657cc33b') }) it('should correctly compute new password hash as defined by MTProto', async () => { const crypto = await defaultTestCryptoProvider() const hash = await computeNewPasswordHash(crypto, fakeAlgo, '123qwe') - expect(p.hexEncode(hash)).toEqual( + expect(hex.encode(hash)).toEqual( '2540539ceeffd4543cd845bf319b8392e6b17bf7cf26bafcf6282ce9ae795368' + '4ff49469c2863b17e6d65ddb16ae6f60bc07cc254c00e5ba389292f6cea0b3aa' + 'c459d1d08984d65319df8c5d124042169bbe2ab8c0c93bc7178827f2ea84e7c3' @@ -96,7 +93,7 @@ describe('SRP', () => { const params = await computeSrpParams(crypto, fakeRequest, password) expect(params.srpId).toEqual(fakeRequest.srpId) - expect(p.hexEncode(params.A)).toEqual( + expect(hex.encode(params.A)).toEqual( '363976f55edb57cc5cc0c4aaca9b7539eff98a43a93fa84be34860d18ac3a80f' + 'ffd57c4617896ff667677d0552a079eb189d25d147ec96edd4495c946a18652d' + '31d78eede40a8b29da340c19b32ccac78f8482406e392102c03d850d1db87223' @@ -106,6 +103,6 @@ describe('SRP', () => { + '4fa454aa69d9219d9c5fa3625f5c6f1ac03892a70aa17269c76cd9bf2949a961' + 'fad2a71e5fa961824b32db037130c7e9aad4c1e9f02ebc5b832622f98b59597e', ) - expect(p.hexEncode(params.M1)).toEqual('25a91b21c634ad670a144165a9829192d152e131a716f676abc48cd817f508c6') + expect(hex.encode(params.M1)).toEqual('25a91b21c634ad670a144165a9829192d152e131a716f676abc48cd817f508c6') }) }) diff --git a/packages/core/src/utils/crypto/password.ts b/packages/core/src/utils/crypto/password.ts index 3a41d341..300fc69a 100644 --- a/packages/core/src/utils/crypto/password.ts +++ b/packages/core/src/utils/crypto/password.ts @@ -1,6 +1,6 @@ import type { tl } from '@mtcute/tl' +import { utf8 } from '@fuman/utils' -import { getPlatform } from '../../platform.js' import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js' import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js' import { concatBuffers } from '../buffer-utils.js' @@ -50,7 +50,7 @@ export async function computeNewPasswordHash( crypto.randomFill(salt1.subarray(algo.salt1.length)) ;(algo as tl.Mutable).salt1 = salt1 - const _x = await computePasswordHash(crypto, getPlatform().utf8Encode(password), algo.salt1, algo.salt2) + const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2) const g = BigInt(algo.g) const p = bufferToBigInt(algo.p) @@ -104,7 +104,7 @@ export async function computeSrpParams( const _k = crypto.sha256(concatBuffers([algo.p, _g])) const _u = crypto.sha256(concatBuffers([_gA, request.srpB])) - const _x = await computePasswordHash(crypto, getPlatform().utf8Encode(password), algo.salt1, algo.salt2) + 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) diff --git a/packages/core/src/utils/crypto/utils.test.ts b/packages/core/src/utils/crypto/utils.test.ts index 2f874a83..eb935fb6 100644 --- a/packages/core/src/utils/crypto/utils.test.ts +++ b/packages/core/src/utils/crypto/utils.test.ts @@ -1,63 +1,60 @@ import { describe, expect, it } from 'vitest' - -import { getPlatform } from '../../platform.js' +import { hex, utf8 } from '@fuman/utils' import { xorBuffer, xorBufferInPlace } from './utils.js' -const p = getPlatform() - describe('xorBuffer', () => { it('should xor buffers without modifying original', () => { - const data = p.utf8Encode('hello') - const key = p.utf8Encode('xor') + const data = utf8.encoder.encode('hello') + const key = utf8.encoder.encode('xor') const xored = xorBuffer(data, key) - expect(p.utf8Decode(data)).eq('hello') - expect(p.utf8Decode(key)).eq('xor') - expect(p.hexEncode(xored)).eq('100a1e6c6f') + 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 = p.utf8Encode('hello') - const key = p.utf8Encode('xor') + const data = utf8.encoder.encode('hello') + const key = utf8.encoder.encode('xor') const xored1 = xorBuffer(data, key) - expect(p.hexEncode(xored1)).eq('100a1e6c6f') + expect(hex.encode(xored1)).eq('100a1e6c6f') const xored2 = xorBuffer(data, key) - expect(p.hexEncode(xored2)).eq('100a1e6c6f') + expect(hex.encode(xored2)).eq('100a1e6c6f') }) it('second call should decode content', () => { - const data = p.utf8Encode('hello') - const key = p.utf8Encode('xor') + const data = utf8.encoder.encode('hello') + const key = utf8.encoder.encode('xor') const xored1 = xorBuffer(data, key) - expect(p.hexEncode(xored1)).eq('100a1e6c6f') + expect(hex.encode(xored1)).eq('100a1e6c6f') const xored2 = xorBuffer(xored1, key) - expect(p.utf8Decode(xored2)).eq('hello') + expect(utf8.decoder.decode(xored2)).eq('hello') }) }) describe('xorBufferInPlace', () => { it('should xor buffers by modifying original', () => { - const data = p.utf8Encode('hello') - const key = p.utf8Encode('xor') + const data = utf8.encoder.encode('hello') + const key = utf8.encoder.encode('xor') xorBufferInPlace(data, key) - expect(p.hexEncode(data)).eq('100a1e6c6f') - expect(p.utf8Decode(key)).eq('xor') + expect(hex.encode(data)).eq('100a1e6c6f') + expect(utf8.decoder.decode(key)).eq('xor') }) it('second call should decode content', () => { - const data = p.utf8Encode('hello') - const key = p.utf8Encode('xor') + const data = utf8.encoder.encode('hello') + const key = utf8.encoder.encode('xor') xorBufferInPlace(data, key) - expect(p.hexEncode(data)).eq('100a1e6c6f') + expect(hex.encode(data)).eq('100a1e6c6f') xorBufferInPlace(data, key) - expect(p.utf8Decode(data)).eq('hello') + expect(utf8.decoder.decode(data)).eq('hello') }) }) diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 93519db5..763d3aef 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -1,4 +1,5 @@ import { tl } from '@mtcute/tl' +import { hex } from '@fuman/utils' import type { ICorePlatform } from '../platform.js' import { getPlatform } from '../platform.js' @@ -67,7 +68,7 @@ export class Logger { args.splice(idx, 1) if (m === '%h') { - if (ArrayBuffer.isView(val)) return this.mgr.platform.hexEncode(val as Uint8Array) + if (ArrayBuffer.isView(val)) return hex.encode(val as Uint8Array) if (typeof val === 'number' || typeof val === 'bigint') return val.toString(16) return String(val) @@ -85,7 +86,7 @@ export class Logger { || (typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data)) // todo: how can we do this better? ) { // eslint-disable-next-line - let str = v.data ? Buffer.from(v.data as number[]).toString('hex') : this.mgr.platform.hexEncode(v) + let str = v.data ? Buffer.from(v.data as number[]).toString('hex') : hex.encode(v) if (str.length > 300) { str = `${str.slice(0, 300)}...` diff --git a/packages/deno/src/platform.ts b/packages/deno/src/platform.ts index 24bccea4..4f8f4200 100644 --- a/packages/deno/src/platform.ts +++ b/packages/deno/src/platform.ts @@ -1,9 +1,6 @@ import type { ICorePlatform } from '@mtcute/core/platform.js' -import { base64Decode, base64Encode } from './common-internals-web/base64.js' -import { hexDecode, hexEncode } from './common-internals-web/hex.js' import { defaultLoggingHandler } from './common-internals-web/logging.js' -import { utf8ByteLength, utf8Decode, utf8Encode } from './common-internals-web/utf8.js' import { beforeExit } from './utils/exit-hook.js' import { normalizeFile } from './utils/normalize-file.js' @@ -25,24 +22,8 @@ export class DenoPlatform implements ICorePlatform { return null } - - // ITlPlatform - declare utf8ByteLength: typeof utf8ByteLength - declare utf8Encode: typeof utf8Encode - declare utf8Decode: typeof utf8Decode - declare hexEncode: typeof hexEncode - declare hexDecode: typeof hexDecode - declare base64Encode: typeof base64Encode - declare base64Decode: typeof base64Decode } -DenoPlatform.prototype.utf8ByteLength = utf8ByteLength -DenoPlatform.prototype.utf8Encode = utf8Encode -DenoPlatform.prototype.utf8Decode = utf8Decode -DenoPlatform.prototype.hexEncode = hexEncode -DenoPlatform.prototype.hexDecode = hexDecode -DenoPlatform.prototype.base64Encode = base64Encode -DenoPlatform.prototype.base64Decode = base64Decode DenoPlatform.prototype.log = defaultLoggingHandler DenoPlatform.prototype.beforeExit = beforeExit DenoPlatform.prototype.normalizeFile = normalizeFile diff --git a/packages/dispatcher/src/callback-data-builder.test.ts b/packages/dispatcher/src/callback-data-builder.test.ts index 9de9d7d2..579e5b91 100644 --- a/packages/dispatcher/src/callback-data-builder.test.ts +++ b/packages/dispatcher/src/callback-data-builder.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { CallbackQuery, MtArgumentError, PeersIndex } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' import { createStub } from '@mtcute/test' import { CallbackDataBuilder } from './callback-data-builder.js' @@ -52,7 +51,7 @@ describe('CallbackDataBuilder', () => { const createCb = (data: string) => new CallbackQuery( createStub('updateBotCallbackQuery', { - data: getPlatform().utf8Encode(data), + data: new TextEncoder().encode(data), }), new PeersIndex(), ) diff --git a/packages/file-id/package.json b/packages/file-id/package.json index b08a50f8..3b7e5008 100644 --- a/packages/file-id/package.json +++ b/packages/file-id/package.json @@ -15,9 +15,7 @@ }, "dependencies": { "@mtcute/tl-runtime": "workspace:^", + "@fuman/utils": "workspace:^", "long": "5.2.3" - }, - "devDependencies": { - "@mtcute/test": "workspace:^" } } diff --git a/packages/file-id/src/parse.test.ts b/packages/file-id/src/parse.test.ts index 87db9db3..603914a9 100644 --- a/packages/file-id/src/parse.test.ts +++ b/packages/file-id/src/parse.test.ts @@ -1,6 +1,6 @@ import Long from 'long' import { describe, expect, it } from 'vitest' -import { defaultPlatform } from '@mtcute/test' +import { hex } from '@fuman/utils' import { parseFileId } from './parse.js' import { tdFileId as td } from './types.js' @@ -9,14 +9,14 @@ import { tdFileId as td } from './types.js' describe('parsing file ids', () => { const test = (id: string, expected: td.RawFullRemoteFileLocation) => { - expect(parseFileId(defaultPlatform, id)).eql(expected) + expect(parseFileId(id)).eql(expected) } it('parses common file ids', () => { test('CAACAgIAAxkBAAEJny9gituz1_V_uSKBUuG_nhtzEtFOeQACXFoAAuCjggfYjw_KAAGSnkgfBA', { _: 'remoteFileLocation', dcId: 2, - fileReference: defaultPlatform.hexDecode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'), + fileReference: hex.decode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'), location: { _: 'common', accessHash: Long.fromString('5232780349138767832'), @@ -27,7 +27,7 @@ describe('parsing file ids', () => { test('BQACAgIAAxkBAAEJnzNgit00IDsKd07OdSeanwz8osecYAACdAwAAueoWEicaPvNdOYEwB8E', { _: 'remoteFileLocation', dcId: 2, - fileReference: defaultPlatform.hexDecode('0100099f33608add34203b0a774ece75279a9f0cfca2c79c60'), + fileReference: hex.decode('0100099f33608add34203b0a774ece75279a9f0cfca2c79c60'), location: { _: 'common', accessHash: Long.fromString('-4610306729174144868'), @@ -41,7 +41,7 @@ describe('parsing file ids', () => { test('AAMCAgADGQEAAQmfL2CK27PX9X-5IoFS4b-eG3MS0U55AAJcWgAC4KOCB9iPD8oAAZKeSK1c8w4ABAEAB20AA1kCAAIfBA', { _: 'remoteFileLocation', dcId: 2, - fileReference: defaultPlatform.hexDecode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'), + fileReference: hex.decode('0100099f2f608adbb3d7f57fb9228152e1bf9e1b7312d14e79'), location: { _: 'photo', accessHash: Long.fromString('5232780349138767832'), diff --git a/packages/file-id/src/parse.ts b/packages/file-id/src/parse.ts index 8763af51..fe860f01 100644 --- a/packages/file-id/src/parse.ts +++ b/packages/file-id/src/parse.ts @@ -1,5 +1,5 @@ -import type { ITlPlatform } from '@mtcute/tl-runtime' import { TlBinaryReader } from '@mtcute/tl-runtime' +import { base64 } from '@fuman/utils' import { tdFileId as td } from './types.js' import { telegramRleDecode } from './utils.js' @@ -12,7 +12,7 @@ function parseWebFileLocation(reader: TlBinaryReader): td.RawWebRemoteFileLocati } } -function parsePhotoSizeSource(platform: ITlPlatform, reader: TlBinaryReader): td.TypePhotoSizeSource { +function parsePhotoSizeSource(reader: TlBinaryReader): td.TypePhotoSizeSource { const variant = reader.int() switch (variant) { @@ -26,7 +26,7 @@ function parsePhotoSizeSource(platform: ITlPlatform, reader: TlBinaryReader): td if (fileType < 0 || fileType >= td.FileType.Size) { throw new td.UnsupportedError( - `Unsupported file type: ${fileType} (${platform.base64Encode(reader.uint8View)})`, + `Unsupported file type: ${fileType} (${base64.encode(reader.uint8View)})`, ) } @@ -34,7 +34,7 @@ function parsePhotoSizeSource(platform: ITlPlatform, reader: TlBinaryReader): td if (thumbnailType < 0 || thumbnailType > 255) { throw new td.InvalidFileIdError( - `Wrong thumbnail type: ${thumbnailType} (${platform.base64Encode(reader.uint8View)})`, + `Wrong thumbnail type: ${thumbnailType} (${base64.encode(reader.uint8View)})`, ) } @@ -113,13 +113,12 @@ function parsePhotoSizeSource(platform: ITlPlatform, reader: TlBinaryReader): td } default: throw new td.UnsupportedError( - `Unsupported photo size source ${variant} (${platform.base64Encode(reader.uint8View)})`, + `Unsupported photo size source ${variant} (${base64.encode(reader.uint8View)})`, ) } } function parsePhotoFileLocation( - platform: ITlPlatform, reader: TlBinaryReader, version: number, ): td.RawPhotoRemoteFileLocation { @@ -128,13 +127,13 @@ function parsePhotoFileLocation( let source: td.TypePhotoSizeSource if (version >= 32) { - source = parsePhotoSizeSource(platform, reader) + source = parsePhotoSizeSource(reader) } else { const volumeId = reader.long() let localId = 0 if (version >= 22) { - source = parsePhotoSizeSource(platform, reader) + source = parsePhotoSizeSource(reader) localId = reader.int() } else { source = { @@ -197,9 +196,9 @@ function parseCommonFileLocation(reader: TlBinaryReader): td.RawCommonRemoteFile } } -function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: number): td.RawFullRemoteFileLocation { +function fromPersistentIdV23(binary: Uint8Array, version: number): td.RawFullRemoteFileLocation { if (version < 0 || version > td.CURRENT_VERSION) { - throw new td.UnsupportedError(`Unsupported file ID v3 subversion: ${version} (${platform.base64Encode(binary)})`) + throw new td.UnsupportedError(`Unsupported file ID v3 subversion: ${version} (${base64.encode(binary)})`) } binary = telegramRleDecode(binary) @@ -215,7 +214,7 @@ function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: fileType &= ~td.FILE_REFERENCE_FLAG if (fileType < 0 || fileType >= td.FileType.Size) { - throw new td.UnsupportedError(`Unsupported file type: ${fileType} (${platform.base64Encode(binary)})`) + throw new td.UnsupportedError(`Unsupported file type: ${fileType} (${base64.encode(binary)})`) } const dcId = reader.int() @@ -244,7 +243,7 @@ function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: case td.FileType.EncryptedThumbnail: case td.FileType.Wallpaper: { // location_type = photo - location = parsePhotoFileLocation(platform, reader, version) + location = parsePhotoFileLocation(reader, version) // validate switch (location.source._) { @@ -294,7 +293,7 @@ function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: break } default: - throw new td.UnsupportedError(`Invalid file type: ${fileType} (${platform.base64Encode(binary)})`) + throw new td.UnsupportedError(`Invalid file type: ${fileType} (${base64.encode(binary)})`) } } @@ -307,14 +306,14 @@ function fromPersistentIdV23(platform: ITlPlatform, binary: Uint8Array, version: } } -function fromPersistentIdV2(platform: ITlPlatform, binary: Uint8Array) { - return fromPersistentIdV23(platform, binary.subarray(0, -1), 0) +function fromPersistentIdV2(binary: Uint8Array) { + return fromPersistentIdV23(binary.subarray(0, -1), 0) } -function fromPersistentIdV3(platform: ITlPlatform, binary: Uint8Array) { +function fromPersistentIdV3(binary: Uint8Array) { const subversion = binary[binary.length - 2] - return fromPersistentIdV23(platform, binary.subarray(0, -2), subversion) + return fromPersistentIdV23(binary.subarray(0, -2), subversion) } /** @@ -322,18 +321,18 @@ function fromPersistentIdV3(platform: ITlPlatform, binary: Uint8Array) { * * @param fileId File ID as a base-64 encoded string or Buffer */ -export function parseFileId(platform: ITlPlatform, fileId: string | Uint8Array): td.RawFullRemoteFileLocation { - if (typeof fileId === 'string') fileId = platform.base64Decode(fileId, true) +export function parseFileId(fileId: string | Uint8Array): td.RawFullRemoteFileLocation { + if (typeof fileId === 'string') fileId = base64.decode(fileId, true) const version = fileId[fileId.length - 1] if (version === td.PERSISTENT_ID_VERSION_OLD) { - return fromPersistentIdV2(platform, fileId) + return fromPersistentIdV2(fileId) } if (version === td.PERSISTENT_ID_VERSION) { - return fromPersistentIdV3(platform, fileId) + return fromPersistentIdV3(fileId) } - throw new td.UnsupportedError(`Unsupported file ID version: ${version} (${platform.base64Encode(fileId)})`) + throw new td.UnsupportedError(`Unsupported file ID version: ${version} (${base64.encode(fileId)})`) } diff --git a/packages/file-id/src/serialize-unique.test.ts b/packages/file-id/src/serialize-unique.test.ts index 9ef927f7..dfec815f 100644 --- a/packages/file-id/src/serialize-unique.test.ts +++ b/packages/file-id/src/serialize-unique.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from 'vitest' -import { defaultPlatform } from '@mtcute/test' import { parseFileId } from './parse.js' import { toUniqueFileId } from './serialize-unique.js' @@ -8,7 +7,7 @@ import { toUniqueFileId } from './serialize-unique.js' describe('serializing unique file ids', () => { const test = (id: string, expected: string) => { - expect(toUniqueFileId(defaultPlatform, parseFileId(defaultPlatform, id))).eql(expected) + expect(toUniqueFileId(parseFileId(id))).eql(expected) } it('serializes unique ids for old file ids', () => { diff --git a/packages/file-id/src/serialize-unique.ts b/packages/file-id/src/serialize-unique.ts index 220e3036..dd9e9b7a 100644 --- a/packages/file-id/src/serialize-unique.ts +++ b/packages/file-id/src/serialize-unique.ts @@ -1,5 +1,5 @@ -import type { ITlPlatform } from '@mtcute/tl-runtime' import { TlBinaryWriter } from '@mtcute/tl-runtime' +import { base64, utf8 } from '@fuman/utils' import { tdFileId as td } from './types.js' import { assertNever, telegramRleEncode } from './utils.js' @@ -21,11 +21,10 @@ export type InputUniqueLocation = * * @param location Information about file location */ -export function toUniqueFileId(platform: ITlPlatform, location: Omit): string -export function toUniqueFileId(platform: ITlPlatform, type: td.FileType, location: InputUniqueLocation): string +export function toUniqueFileId(location: Omit): string +export function toUniqueFileId(type: td.FileType, location: InputUniqueLocation): string export function toUniqueFileId( - platform: ITlPlatform, first: td.FileType | Omit, second?: InputUniqueLocation, ): string { @@ -142,7 +141,7 @@ export function toUniqueFileId( break } case 'web': - writer = TlBinaryWriter.manual(platform.utf8ByteLength(inputLocation.url) + 8) + writer = TlBinaryWriter.manual(utf8.encodedLength(inputLocation.url) + 8) writer.int(type) writer.string(inputLocation.url) break @@ -155,5 +154,5 @@ export function toUniqueFileId( assertNever(inputLocation) } - return platform.base64Encode(telegramRleEncode(writer.result()), true) + return base64.encode(telegramRleEncode(writer.result()), true) } diff --git a/packages/file-id/src/serialize.ts b/packages/file-id/src/serialize.ts index 07ee7da0..2342bd6f 100644 --- a/packages/file-id/src/serialize.ts +++ b/packages/file-id/src/serialize.ts @@ -1,5 +1,5 @@ -import type { ITlPlatform } from '@mtcute/tl-runtime' import { TlBinaryWriter } from '@mtcute/tl-runtime' +import { base64, utf8 } from '@fuman/utils' import { tdFileId as td } from './types.js' import { assertNever, telegramRleEncode } from './utils.js' @@ -12,7 +12,7 @@ const SUFFIX = new Uint8Array([td.CURRENT_VERSION, td.PERSISTENT_ID_VERSION]) * * @param location Information about file location */ -export function toFileId(platform: ITlPlatform, location: Omit): string { +export function toFileId(location: Omit): string { const loc = location.location let type: number = location.type @@ -26,7 +26,7 @@ export function toFileId(platform: ITlPlatform, location: Omit { it('should not modify input if there are no \\x00', () => { - expect(p.hexEncode(telegramRleEncode(p.hexDecode('aaeeff')))).eq('aaeeff') + expect(hex.encode(telegramRleEncode(hex.decode('aaeeff')))).eq('aaeeff') }) it('should collapse consecutive \\x00', () => { - expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa')))).eq('0004aa') - expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa000000aa')))).eq('0004aa0003aa') - expect(p.hexEncode(telegramRleEncode(p.hexDecode('00000000aa0000')))).eq('0004aa0002') - expect(p.hexEncode(telegramRleEncode(p.hexDecode('00aa00')))).eq('0001aa0001') + expect(hex.encode(telegramRleEncode(hex.decode('00000000aa')))).eq('0004aa') + expect(hex.encode(telegramRleEncode(hex.decode('00000000aa000000aa')))).eq('0004aa0003aa') + expect(hex.encode(telegramRleEncode(hex.decode('00000000aa0000')))).eq('0004aa0002') + expect(hex.encode(telegramRleEncode(hex.decode('00aa00')))).eq('0001aa0001') }) }) describe('telegramRleDecode', () => { it('should not mofify input if there are no \\x00', () => { - expect(p.hexEncode(telegramRleDecode(p.hexDecode('aaeeff')))).eq('aaeeff') + expect(hex.encode(telegramRleDecode(hex.decode('aaeeff')))).eq('aaeeff') }) it('should expand two-byte sequences starting with \\x00', () => { - expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa')))).eq('00000000aa') - expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0000')))).eq('00000000aa') - expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0003aa')))).eq('00000000aa000000aa') - expect(p.hexEncode(telegramRleDecode(p.hexDecode('0004aa0002')))).eq('00000000aa0000') - expect(p.hexEncode(telegramRleDecode(p.hexDecode('0001aa0001')))).eq('00aa00') + expect(hex.encode(telegramRleDecode(hex.decode('0004aa')))).eq('00000000aa') + expect(hex.encode(telegramRleDecode(hex.decode('0004aa0000')))).eq('00000000aa') + expect(hex.encode(telegramRleDecode(hex.decode('0004aa0003aa')))).eq('00000000aa000000aa') + expect(hex.encode(telegramRleDecode(hex.decode('0004aa0002')))).eq('00000000aa0000') + expect(hex.encode(telegramRleDecode(hex.decode('0001aa0001')))).eq('00aa00') }) }) diff --git a/packages/node/src/common-internals-node/platform.ts b/packages/node/src/common-internals-node/platform.ts index df7079ff..06ac456a 100644 --- a/packages/node/src/common-internals-node/platform.ts +++ b/packages/node/src/common-internals-node/platform.ts @@ -7,8 +7,6 @@ import { normalizeFile } from '../utils/normalize-file.js' import { beforeExit } from './exit-hook.js' import { defaultLoggingHandler } from './logging.js' -const BUFFER_BASE64_URL_AVAILABLE = typeof Buffer.isEncoding === 'function' && Buffer.isEncoding('base64url') - const toBuffer = (buf: Uint8Array): Buffer => Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength) export class NodePlatform implements ICorePlatform { @@ -30,51 +28,6 @@ export class NodePlatform implements ICorePlatform { return null } - - // ITlPlatform - utf8ByteLength(str: string): number { - return Buffer.byteLength(str, 'utf8') - } - - utf8Encode(str: string): Uint8Array { - return Buffer.from(str, 'utf8') - } - - utf8Decode(buf: Uint8Array): string { - return toBuffer(buf).toString('utf8') - } - - hexEncode(buf: Uint8Array): string { - return toBuffer(buf).toString('hex') - } - - hexDecode(str: string): Uint8Array { - return Buffer.from(str, 'hex') - } - - base64Encode(buf: Uint8Array, url = false): string { - const nodeBuffer = toBuffer(buf) - - if (url && BUFFER_BASE64_URL_AVAILABLE) return nodeBuffer.toString('base64url') - - const str = nodeBuffer.toString('base64') - if (url) return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') - - return str - } - - base64Decode(string: string, url = false): Uint8Array { - if (url && BUFFER_BASE64_URL_AVAILABLE) { - return Buffer.from(string, 'base64url') - } - - if (url) { - string = string.replace(/-/g, '+').replace(/_/g, '/') - while (string.length % 4) string += '=' - } - - return Buffer.from(string, 'base64') - } } NodePlatform.prototype.log = defaultLoggingHandler diff --git a/packages/test/package.json b/packages/test/package.json index 9e30c076..b05447e6 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -27,7 +27,8 @@ } }, "dependencies": { - "long": "5.2.3" + "long": "5.2.3", + "@fuman/utils": "workspace:^" }, "devDependencies": { "@mtcute/tl-utils": "workspace:^" diff --git a/packages/test/src/crypto.ts b/packages/test/src/crypto.ts index eff3dd72..588e2c08 100644 --- a/packages/test/src/crypto.ts +++ b/packages/test/src/crypto.ts @@ -2,9 +2,9 @@ import { gzipSync, inflateSync } from 'node:zlib' import type { MockInstance } from 'vitest' import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' import type { ICryptoProvider } from '@mtcute/core/utils.js' import { dataViewFromBuffer } from '@mtcute/core/utils.js' +import { hex, utf8 } from '@fuman/utils' import { defaultCryptoProvider } from './platform.js' @@ -29,7 +29,7 @@ fa3de8e50aac96c1275591a1221c32a60a1513370a33a228e00894341b10cf44a6ae6ac250d17a36 `.replace(/\s/g, '') export function withFakeRandom(provider: ICryptoProvider, source: string = DEFAULT_ENTROPY): ICryptoProvider { - const sourceBytes = getPlatform().hexDecode(source) + const sourceBytes = hex.decode(source) let offset = 0 function getRandomValues(buf: Uint8Array) { @@ -52,7 +52,7 @@ export function withFakeRandom(provider: ICryptoProvider, source: string = DEFAU } export function useFakeMathRandom(source: string = DEFAULT_ENTROPY): void { - const sourceBytes = getPlatform().hexDecode(source) + const sourceBytes = hex.decode(source) const dv = dataViewFromBuffer(sourceBytes) let spy: MockInstance<() => number> @@ -82,8 +82,6 @@ export async function defaultTestCryptoProvider(source: string = DEFAULT_ENTROPY export function testCryptoProvider(c: ICryptoProvider): void { beforeAll(() => c.initialize?.()) - const p = getPlatform() - function gzipSyncWrap(data: Uint8Array) { if (import.meta.env.TEST_ENV === 'browser') { // @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason @@ -107,85 +105,85 @@ export function testCryptoProvider(c: ICryptoProvider): void { } it('should calculate sha1', () => { - expect(p.hexEncode(c.sha1(p.utf8Encode('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709') - expect(p.hexEncode(c.sha1(p.utf8Encode('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d') - expect(p.hexEncode(c.sha1(p.hexDecode('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850') + expect(hex.encode(c.sha1(utf8.encoder.encode('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709') + expect(hex.encode(c.sha1(utf8.encoder.encode('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d') + expect(hex.encode(c.sha1(hex.decode('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850') }) it('should calculate sha256', () => { - expect(p.hexEncode(c.sha256(p.utf8Encode('')))).to.eq( + expect(hex.encode(c.sha256(utf8.encoder.encode('')))).to.eq( 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', ) - expect(p.hexEncode(c.sha256(p.utf8Encode('hello')))).to.eq( + expect(hex.encode(c.sha256(utf8.encoder.encode('hello')))).to.eq( '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', ) - expect(p.hexEncode(c.sha256(p.hexDecode('aebb1f')))).to.eq( + expect(hex.encode(c.sha256(hex.decode('aebb1f')))).to.eq( '2d29658aba48f2b286fe8bbddb931b7ad297e5adb5b9a6fc3aab67ef7fbf4e80', ) }) it('should calculate hmac-sha256', async () => { - const key = p.hexDecode('aaeeff') + const key = hex.decode('aaeeff') - expect(p.hexEncode(await c.hmacSha256(p.utf8Encode(''), key))).to.eq( + expect(hex.encode(await c.hmacSha256(utf8.encoder.encode(''), key))).to.eq( '642711307c9e4437df09d6ebaa6bdc1b3a810c7f15c50fd1d0f8d7d5490f44dd', ) - expect(p.hexEncode(await c.hmacSha256(p.utf8Encode('hello'), key))).to.eq( + expect(hex.encode(await c.hmacSha256(utf8.encoder.encode('hello'), key))).to.eq( '39b00bab151f9868e6501655c580b5542954711181243474d46b894703b1c1c2', ) - expect(p.hexEncode(await c.hmacSha256(p.hexDecode('aebb1f'), key))).to.eq( + expect(hex.encode(await c.hmacSha256(hex.decode('aebb1f'), key))).to.eq( 'a3a7273871808711cab17aba14f58e96f63f3ccfc5097d206f0f00ead2c3dd35', ) }) it('should derive pbkdf2 key', async () => { - expect(p.hexEncode(await c.pbkdf2(p.utf8Encode('pbkdf2 test'), p.utf8Encode('some salt'), 10))).to.eq( + expect(hex.encode(await c.pbkdf2(utf8.encoder.encode('pbkdf2 test'), utf8.encoder.encode('some salt'), 10))).to.eq( 'e43276cfa27f135f261cec8ddcf593fd74ec251038e459c165461f2308f3a7235e0744ee1aed9710b00db28d1a2112e20fea3601c60e770ac57ffe6b33ca8be1', ) }) it('should encrypt and decrypt aes-ctr', () => { let aes = c.createAesCtr( - p.hexDecode('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b'), - p.hexDecode('0182de2bd789c295c3c6c875c5e9e190'), + hex.decode('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b'), + hex.decode('0182de2bd789c295c3c6c875c5e9e190'), true, ) - const data = p.hexDecode('7baae571e4c2f4cfadb1931d5923aca7') - expect(p.hexEncode(aes.process(data))).eq('df5647dbb70bc393f2fb05b72f42286f') - expect(p.hexEncode(aes.process(data))).eq('3917147082672516b3177150129bc579') - expect(p.hexEncode(aes.process(data))).eq('2a7a9089270a5de45d5e3dd399cac725') - expect(p.hexEncode(aes.process(data))).eq('56d085217771398ac13583de4d677dd8') - expect(p.hexEncode(aes.process(data))).eq('cc639b488126cf36e79c4515e8012b92') - expect(p.hexEncode(aes.process(data))).eq('01384d100646cd562cc5586ec3f8f8c4') + const data = hex.decode('7baae571e4c2f4cfadb1931d5923aca7') + expect(hex.encode(aes.process(data))).eq('df5647dbb70bc393f2fb05b72f42286f') + expect(hex.encode(aes.process(data))).eq('3917147082672516b3177150129bc579') + expect(hex.encode(aes.process(data))).eq('2a7a9089270a5de45d5e3dd399cac725') + expect(hex.encode(aes.process(data))).eq('56d085217771398ac13583de4d677dd8') + expect(hex.encode(aes.process(data))).eq('cc639b488126cf36e79c4515e8012b92') + expect(hex.encode(aes.process(data))).eq('01384d100646cd562cc5586ec3f8f8c4') aes.close?.() aes = c.createAesCtr( - p.hexDecode('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b'), - p.hexDecode('0182de2bd789c295c3c6c875c5e9e190'), + hex.decode('d450aae0bf0060a4af1044886b42a13f7c506b35255d134a7e87ab3f23a9493b'), + hex.decode('0182de2bd789c295c3c6c875c5e9e190'), false, ) - expect(p.hexEncode(aes.process(p.hexDecode('df5647dbb70bc393f2fb05b72f42286f')))).eq(p.hexEncode(data)) - expect(p.hexEncode(aes.process(p.hexDecode('3917147082672516b3177150129bc579')))).eq(p.hexEncode(data)) - expect(p.hexEncode(aes.process(p.hexDecode('2a7a9089270a5de45d5e3dd399cac725')))).eq(p.hexEncode(data)) - expect(p.hexEncode(aes.process(p.hexDecode('56d085217771398ac13583de4d677dd8')))).eq(p.hexEncode(data)) - expect(p.hexEncode(aes.process(p.hexDecode('cc639b488126cf36e79c4515e8012b92')))).eq(p.hexEncode(data)) - expect(p.hexEncode(aes.process(p.hexDecode('01384d100646cd562cc5586ec3f8f8c4')))).eq(p.hexEncode(data)) + expect(hex.encode(aes.process(hex.decode('df5647dbb70bc393f2fb05b72f42286f')))).eq(hex.encode(data)) + expect(hex.encode(aes.process(hex.decode('3917147082672516b3177150129bc579')))).eq(hex.encode(data)) + expect(hex.encode(aes.process(hex.decode('2a7a9089270a5de45d5e3dd399cac725')))).eq(hex.encode(data)) + expect(hex.encode(aes.process(hex.decode('56d085217771398ac13583de4d677dd8')))).eq(hex.encode(data)) + expect(hex.encode(aes.process(hex.decode('cc639b488126cf36e79c4515e8012b92')))).eq(hex.encode(data)) + expect(hex.encode(aes.process(hex.decode('01384d100646cd562cc5586ec3f8f8c4')))).eq(hex.encode(data)) aes.close?.() }) it('should encrypt and decrypt aes-ige', () => { const aes = c.createAesIge( - p.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65'), - p.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353'), + hex.decode('5468697320697320616E20696D706C655468697320697320616E20696D706C65'), + hex.decode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353'), ) expect( - p.hexEncode(aes.encrypt(p.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b'))), + hex.encode(aes.encrypt(hex.decode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b'))), ).to.eq('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') expect( - p.hexEncode(aes.decrypt(p.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69'))), + hex.encode(aes.decrypt(hex.decode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69'))), ).to.eq('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') }) @@ -193,9 +191,9 @@ export function testCryptoProvider(c: ICryptoProvider): void { 'should decompose PQ to prime factors P and Q', async () => { const testFactorization = async (pq: string, p_: string, q: string) => { - const [p1, q1] = await c.factorizePQ(p.hexDecode(pq)) - expect(p.hexEncode(p1)).eq(p_.toLowerCase()) - expect(p.hexEncode(q1)).eq(q.toLowerCase()) + const [p1, q1] = await c.factorizePQ(hex.decode(pq)) + expect(hex.encode(p1)).eq(p_.toLowerCase()) + expect(hex.encode(q1)).eq(q.toLowerCase()) } // from samples at https://core.telegram.org/mtproto/samples-auth_key @@ -217,7 +215,7 @@ export function testCryptoProvider(c: ICryptoProvider): void { const decompressed = inflateSyncWrap(compressed!) expect(compressed!.length).toBeLessThan(data.length) - expect(p.hexEncode(decompressed)).toEqual(p.hexEncode(data)) + expect(hex.encode(decompressed)).toEqual(hex.encode(data)) }) it('should correctly gunzip', () => { @@ -226,7 +224,7 @@ export function testCryptoProvider(c: ICryptoProvider): void { const compressed = gzipSyncWrap(data) const decompressed = c.gunzip(compressed) - expect(p.hexEncode(decompressed)).toEqual(p.hexEncode(data)) + expect(hex.encode(decompressed)).toEqual(hex.encode(data)) }) describe('randomBytes', () => { @@ -249,14 +247,3 @@ export function testCryptoProvider(c: ICryptoProvider): void { }) }) } - -export function u8HexDecode(hex: string): Uint8Array { - const buf = getPlatform().hexDecode(hex) - - // eslint-disable-next-line no-restricted-globals - if ((import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') && Buffer.isBuffer(buf)) { - return new Uint8Array(buf) - } - - return buf -} diff --git a/packages/test/src/platform.test.ts b/packages/test/src/platform.test.ts deleted file mode 100644 index 9de8cb0f..00000000 --- a/packages/test/src/platform.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' - -const p = getPlatform() - -describe('base64', () => { - it('should decode base64 string to new buffer', () => { - const buf = p.base64Decode('AQIDBA==') - expect(new Uint8Array(buf)).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should encode buffer to base64 string', () => { - const buf = new Uint8Array([1, 2, 3, 4]) - expect(p.base64Encode(buf)).toEqual('AQIDBA==') - }) - - it('should decode url-safe base64 string to new buffer', () => { - const buf = p.base64Decode('AQIDBA', true) - expect(new Uint8Array(buf)).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should encode buffer to url-safe base64 string', () => { - const buf = new Uint8Array([1, 2, 3, 4]) - expect(p.base64Encode(buf, true)).toEqual('AQIDBA') - }) -}) - -describe('hex', () => { - it('should decode hex string to new buffer', () => { - const buf = p.hexDecode('01020304') - expect(new Uint8Array(buf)).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should encode buffer to hex string', () => { - const buf = new Uint8Array([1, 2, 3, 4]) - expect(p.hexEncode(buf)).toEqual('01020304') - }) -}) - -describe('utf8', () => { - it('should encode utf8 string into new buffer', () => { - const buf = p.utf8Encode('abcd') - expect(new Uint8Array(buf)).toEqual(new Uint8Array([97, 98, 99, 100])) - }) - - it('should decode utf8 string from existing buffer', () => { - const buf = new Uint8Array([97, 98, 99, 100]) - expect(p.utf8Decode(buf)).toEqual('abcd') - }) - - it('should return byte length of utf8 string', () => { - expect(p.utf8ByteLength('abcd')).toEqual(4) - }) - - it('should properly handle utf8 string with non-ascii characters', () => { - expect(p.utf8ByteLength('абвг')).toEqual(8) - expect(p.utf8ByteLength('🌸')).toEqual(4) - }) -}) diff --git a/packages/tl-runtime/README.md b/packages/tl-runtime/README.md index f51a4592..d1ba268f 100644 --- a/packages/tl-runtime/README.md +++ b/packages/tl-runtime/README.md @@ -12,5 +12,4 @@ are patching the schema (which is a rare case anyways). ## Features - Supports all TL features used by the public schema -- Uint8Array utilities like `hexDecode` - Supports browsers out of the box diff --git a/packages/tl-runtime/package.json b/packages/tl-runtime/package.json index ff7b9e99..787e7787 100644 --- a/packages/tl-runtime/package.json +++ b/packages/tl-runtime/package.json @@ -15,6 +15,7 @@ "build": "pnpm run -w build-package tl-runtime" }, "dependencies": { - "long": "5.2.3" + "long": "5.2.3", + "@fuman/utils": "workspace:^" } } diff --git a/packages/tl-runtime/src/index.ts b/packages/tl-runtime/src/index.ts index 4aa541a0..20b488fc 100644 --- a/packages/tl-runtime/src/index.ts +++ b/packages/tl-runtime/src/index.ts @@ -1,3 +1,2 @@ -export * from './platform.js' export * from './reader.js' export * from './writer.js' diff --git a/packages/tl-runtime/src/platform.ts b/packages/tl-runtime/src/platform.ts deleted file mode 100644 index c3cd104e..00000000 --- a/packages/tl-runtime/src/platform.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Platform-specific functions used by {@link TlBinaryReader} and {@link TlBinaryWriter} - */ -export interface ITlPlatform { - utf8Encode: (str: string) => Uint8Array - utf8Decode: (buf: Uint8Array) => string - utf8ByteLength: (str: string) => number - - hexEncode: (buf: Uint8Array) => string - hexDecode: (str: string) => Uint8Array - - base64Encode: (buf: Uint8Array, url?: boolean) => string - base64Decode: (str: string, url?: boolean) => Uint8Array -} diff --git a/packages/tl-runtime/src/reader.test.ts b/packages/tl-runtime/src/reader.test.ts index fbf924f8..58119400 100644 --- a/packages/tl-runtime/src/reader.test.ts +++ b/packages/tl-runtime/src/reader.test.ts @@ -3,14 +3,11 @@ // import Long from 'long' import Long from 'long' import { describe, expect, it } from 'vitest' +import { hex } from '@fuman/utils' import type { TlReaderMap } from './reader.js' import { TlBinaryReader } from './reader.js' -// todo: replace with platform-specific packages -const hexEncode = (buf: Uint8Array) => buf.reduce((acc, val) => acc + val.toString(16).padStart(2, '0'), '') -const hexDecodeToBuffer = (hex: string) => new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => Number.parseInt(byte, 16))) - let randomBytes: (n: number) => Uint8Array if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { @@ -287,22 +284,22 @@ describe('TlBinaryReader', () => { } const expected = { - nonce: hexDecodeToBuffer('3E0549828CCA27E966B301A48FECE2FC'), - serverNonce: hexDecodeToBuffer('A5CF4D33F4A11EA877BA4AA573907330'), - pq: hexDecodeToBuffer('17ED48941A08F981'), + nonce: hex.decode('3E0549828CCA27E966B301A48FECE2FC'), + serverNonce: hex.decode('A5CF4D33F4A11EA877BA4AA573907330'), + pq: hex.decode('17ED48941A08F981'), serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', false, 16)], } - const r = new TlBinaryReader(map, hexDecodeToBuffer(input)) + const r = new TlBinaryReader(map, hex.decode(input)) expect(r.long().toString()).toEqual('0') // authKeyId expect(r.long().toString(16)).toEqual('51E57AC91E83C801'.toLowerCase()) // messageId expect(r.uint()).toEqual(64) // messageLength const obj = r.object() as any expect(obj._).toEqual('mt_resPQ') - expect(hexEncode(obj.nonce)).toEqual(hexEncode(expected.nonce)) - expect(hexEncode(obj.serverNonce)).toEqual(hexEncode(expected.serverNonce)) - expect(hexEncode(obj.pq)).toEqual(hexEncode(expected.pq)) + expect(hex.encode(obj.nonce)).toEqual(hex.encode(expected.nonce)) + expect(hex.encode(obj.serverNonce)).toEqual(hex.encode(expected.serverNonce)) + expect(hex.encode(obj.pq)).toEqual(hex.encode(expected.pq)) expect(obj.serverPublicKeyFingerprints.length).toEqual(1) expect(obj.serverPublicKeyFingerprints[0].toString(16)).toEqual( expected.serverPublicKeyFingerprints[0].toString(16), diff --git a/packages/tl-runtime/src/reader.ts b/packages/tl-runtime/src/reader.ts index 0a721bc8..73aaca00 100644 --- a/packages/tl-runtime/src/reader.ts +++ b/packages/tl-runtime/src/reader.ts @@ -1,7 +1,6 @@ +import { utf8 } from '@fuman/utils' import Long from 'long' -import type { ITlPlatform } from './platform.js' - const TWO_PWR_32_DBL = (1 << 16) * (1 << 16) /** @@ -25,8 +24,6 @@ export type TlReaderMap = Record unknown> & { * Reader for TL objects. */ export class TlBinaryReader { - static platform: ITlPlatform - readonly dataView: DataView readonly uint8View: Uint8Array @@ -173,7 +170,7 @@ export class TlBinaryReader { } string(): string { - return TlBinaryReader.platform.utf8Decode(this.bytes()) + return utf8.decoder.decode(this.bytes()) } object(id: number = this.uint()): unknown { diff --git a/packages/tl-runtime/src/writer.test.ts b/packages/tl-runtime/src/writer.test.ts index 7d9878bf..fed46e33 100644 --- a/packages/tl-runtime/src/writer.test.ts +++ b/packages/tl-runtime/src/writer.test.ts @@ -1,14 +1,11 @@ /* eslint-disable ts/no-unsafe-call */ import Long from 'long' import { describe, expect, it } from 'vitest' +import { hex } from '@fuman/utils' import type { TlWriterMap } from './writer.js' import { TlBinaryWriter, TlSerializationCounter } from './writer.js' -// todo: replace with platform-specific packages -const hexEncode = (buf: Uint8Array) => buf.reduce((acc, val) => acc + val.toString(16).padStart(2, '0'), '') -const hexDecodeToBuffer = (hex: string) => new Uint8Array(hex.match(/.{1,2}/g)!.map(byte => Number.parseInt(byte, 16))) - let randomBytes: (n: number) => Uint8Array if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { @@ -28,7 +25,7 @@ describe('TlBinaryWriter', () => { fn(w) expect(w.pos).toEqual(size) - return hexEncode(w.uint8View) + return hex.encode(w.uint8View) } it('should write int32', () => { @@ -85,14 +82,14 @@ describe('TlBinaryWriter', () => { expect(testSingleMethod(8, w => w.bytes(new Uint8Array([1, 2, 3, 4])))).toEqual('0401020304000000') const random250bytes = randomBytes(250) - expect(testSingleMethod(252, w => w.bytes(random250bytes))).toEqual(`fa${hexEncode(random250bytes)}00`) + expect(testSingleMethod(252, w => w.bytes(random250bytes))).toEqual(`fa${hex.encode(random250bytes)}00`) const random1000bytes = randomBytes(1000) const buffer = new Uint8Array(1004) buffer[0] = 254 new DataView(buffer.buffer).setUint32(1, 1000, true) buffer.set(random1000bytes, 4) - expect(testSingleMethod(1004, w => w.bytes(random1000bytes))).toEqual(hexEncode(buffer)) + expect(testSingleMethod(1004, w => w.bytes(random1000bytes))).toEqual(hex.encode(buffer)) }) it('should write tg-encoded string', () => { @@ -189,9 +186,9 @@ describe('TlBinaryWriter', () => { const resPq = { _: 'mt_resPQ', - nonce: hexDecodeToBuffer('3E0549828CCA27E966B301A48FECE2FC'), - serverNonce: hexDecodeToBuffer('A5CF4D33F4A11EA877BA4AA573907330'), - pq: hexDecodeToBuffer('17ED48941A08F981'), + nonce: hex.decode('3E0549828CCA27E966B301A48FECE2FC'), + serverNonce: hex.decode('A5CF4D33F4A11EA877BA4AA573907330'), + pq: hex.decode('17ED48941A08F981'), serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', 16)], } diff --git a/packages/tl-runtime/src/writer.ts b/packages/tl-runtime/src/writer.ts index 056d4d51..6fe3327c 100644 --- a/packages/tl-runtime/src/writer.ts +++ b/packages/tl-runtime/src/writer.ts @@ -1,7 +1,6 @@ +import { utf8 } from '@fuman/utils' import type Long from 'long' -import type { ITlPlatform } from './platform.js' - const TWO_PWR_32_DBL = (1 << 16) * (1 << 16) /** @@ -115,7 +114,7 @@ export class TlSerializationCounter { } string(val: string): void { - const length = TlBinaryWriter.platform.utf8ByteLength(val) + const length = utf8.encodedLength(val) this.count += TlSerializationCounter.countBytesOverhead(length) + length } @@ -134,8 +133,6 @@ export class TlSerializationCounter { * Writer for TL objects. */ export class TlBinaryWriter { - static platform: ITlPlatform - readonly dataView: DataView readonly uint8View: Uint8Array @@ -305,7 +302,7 @@ export class TlBinaryWriter { } string(val: string): void { - this.bytes(TlBinaryWriter.platform.utf8Encode(val)) + this.bytes(utf8.encoder.encode(val)) } // hot path, avoid additional runtime checks diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 0d011121..cc46ea43 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -19,7 +19,8 @@ "devDependencies": { "@mtcute/core": "workspace:^", "@mtcute/node": "workspace:^", - "@mtcute/web": "workspace:^" + "@mtcute/web": "workspace:^", + "@fuman/utils": "workspace:^" }, "jsrOnlyFields": { "exports": "./src/index.ts" diff --git a/packages/wasm/tests/ctr.test.ts b/packages/wasm/tests/ctr.test.ts index 03bb98f4..a8554d49 100644 --- a/packages/wasm/tests/ctr.test.ts +++ b/packages/wasm/tests/ctr.test.ts @@ -1,29 +1,27 @@ import { beforeAll, describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { __getWasm, createCtr256, ctr256, freeCtr256 } from '../src/index.js' import { initWasm } from './init.js' -const p = getPlatform() - beforeAll(async () => { await initWasm() }) describe('aes-ctr', () => { - const key = p.hexDecode('603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4') - const iv = p.hexDecode('F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF') + const key = hex.decode('603DEB1015CA71BE2B73AEF0857D77811F352C073B6108D72D9810A30914DFF4') + const iv = hex.decode('F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF') describe('NIST', () => { // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CTR.pdf - const data = p.hexDecode( + const data = hex.decode( `6BC1BEE2 2E409F96 E93D7E11 7393172A AE2D8A57 1E03AC9C 9EB76FAC 45AF8E51 30C81C46 A35CE411 E5FBC119 1A0A52EF F69F2445 DF4F9B17 AD2B417B E66C3710`.replace(/\s/g, ''), ) - const dataEnc = p.hexDecode( + const dataEnc = hex.decode( `601EC313 775789A5 B7A7F504 BBF3D228 F443E3CA 4D62B59A CA84E990 CACAF5C5 2B0930DA A23DE94C E87017BA 2D84988D @@ -35,7 +33,7 @@ describe('aes-ctr', () => { const res = ctr256(ctr, data) freeCtr256(ctr) - expect(p.hexEncode(res)).toEqual(p.hexEncode(dataEnc)) + expect(hex.encode(res)).toEqual(hex.encode(dataEnc)) }) it('should correctly decrypt', () => { @@ -43,15 +41,15 @@ describe('aes-ctr', () => { const res = ctr256(ctr, dataEnc) freeCtr256(ctr) - expect(p.hexEncode(res)).toEqual(p.hexEncode(data)) + expect(hex.encode(res)).toEqual(hex.encode(data)) }) }) describe('stream', () => { - const data = p.hexDecode('6BC1BEE22E409F96E93D7E117393172A') - const dataEnc1 = p.hexDecode('601ec313775789a5b7a7f504bbf3d228') - const dataEnc2 = p.hexDecode('31afd77f7d218690bd0ef82dfcf66cbe') - const dataEnc3 = p.hexDecode('7000927e2f2192cbe4b6a8b2441ddd48') + const data = hex.decode('6BC1BEE22E409F96E93D7E117393172A') + const dataEnc1 = hex.decode('601ec313775789a5b7a7f504bbf3d228') + const dataEnc2 = hex.decode('31afd77f7d218690bd0ef82dfcf66cbe') + const dataEnc3 = hex.decode('7000927e2f2192cbe4b6a8b2441ddd48') it('should correctly encrypt', () => { const ctr = createCtr256(key, iv) @@ -61,9 +59,9 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(p.hexEncode(res1)).toEqual(p.hexEncode(dataEnc1)) - expect(p.hexEncode(res2)).toEqual(p.hexEncode(dataEnc2)) - expect(p.hexEncode(res3)).toEqual(p.hexEncode(dataEnc3)) + expect(hex.encode(res1)).toEqual(hex.encode(dataEnc1)) + expect(hex.encode(res2)).toEqual(hex.encode(dataEnc2)) + expect(hex.encode(res3)).toEqual(hex.encode(dataEnc3)) }) it('should correctly decrypt', () => { @@ -74,20 +72,20 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(p.hexEncode(res1)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res2)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res3)).toEqual(p.hexEncode(data)) + expect(hex.encode(res1)).toEqual(hex.encode(data)) + expect(hex.encode(res2)).toEqual(hex.encode(data)) + expect(hex.encode(res3)).toEqual(hex.encode(data)) }) }) describe('stream (unaligned)', () => { - const data = p.hexDecode('6BC1BEE22E40') - const dataEnc1 = p.hexDecode('601ec3137757') - const dataEnc2 = p.hexDecode('7df2e078a555') - const dataEnc3 = p.hexDecode('a3a17be0742e') - const dataEnc4 = p.hexDecode('025ced833746') - const dataEnc5 = p.hexDecode('3ff238dea125') - const dataEnc6 = p.hexDecode('1055a52302dc') + const data = hex.decode('6BC1BEE22E40') + const dataEnc1 = hex.decode('601ec3137757') + const dataEnc2 = hex.decode('7df2e078a555') + const dataEnc3 = hex.decode('a3a17be0742e') + const dataEnc4 = hex.decode('025ced833746') + const dataEnc5 = hex.decode('3ff238dea125') + const dataEnc6 = hex.decode('1055a52302dc') it('should correctly encrypt', () => { const ctr = createCtr256(key, iv) @@ -100,12 +98,12 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(p.hexEncode(res1)).toEqual(p.hexEncode(dataEnc1)) - expect(p.hexEncode(res2)).toEqual(p.hexEncode(dataEnc2)) - expect(p.hexEncode(res3)).toEqual(p.hexEncode(dataEnc3)) - expect(p.hexEncode(res4)).toEqual(p.hexEncode(dataEnc4)) - expect(p.hexEncode(res5)).toEqual(p.hexEncode(dataEnc5)) - expect(p.hexEncode(res6)).toEqual(p.hexEncode(dataEnc6)) + expect(hex.encode(res1)).toEqual(hex.encode(dataEnc1)) + expect(hex.encode(res2)).toEqual(hex.encode(dataEnc2)) + expect(hex.encode(res3)).toEqual(hex.encode(dataEnc3)) + expect(hex.encode(res4)).toEqual(hex.encode(dataEnc4)) + expect(hex.encode(res5)).toEqual(hex.encode(dataEnc5)) + expect(hex.encode(res6)).toEqual(hex.encode(dataEnc6)) }) it('should correctly decrypt', () => { @@ -119,17 +117,17 @@ describe('aes-ctr', () => { freeCtr256(ctr) - expect(p.hexEncode(res1)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res2)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res3)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res4)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res5)).toEqual(p.hexEncode(data)) - expect(p.hexEncode(res6)).toEqual(p.hexEncode(data)) + expect(hex.encode(res1)).toEqual(hex.encode(data)) + expect(hex.encode(res2)).toEqual(hex.encode(data)) + expect(hex.encode(res3)).toEqual(hex.encode(data)) + expect(hex.encode(res4)).toEqual(hex.encode(data)) + expect(hex.encode(res5)).toEqual(hex.encode(data)) + expect(hex.encode(res6)).toEqual(hex.encode(data)) }) }) it('should not leak memory', () => { - const data = p.hexDecode('6BC1BEE22E409F96E93D7E117393172A') + const data = hex.decode('6BC1BEE22E409F96E93D7E117393172A') const mem = __getWasm().memory.buffer const memSize = mem.byteLength diff --git a/packages/wasm/tests/gunzip.test.ts b/packages/wasm/tests/gunzip.test.ts index ded96562..83f11673 100644 --- a/packages/wasm/tests/gunzip.test.ts +++ b/packages/wasm/tests/gunzip.test.ts @@ -1,7 +1,7 @@ import { gzipSync } from 'node:zlib' import { beforeAll, describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { utf8 } from '@fuman/utils' import { __getWasm, gunzip } from '../src/index.js' @@ -11,8 +11,6 @@ beforeAll(async () => { await initWasm() }) -const p = getPlatform() - function gzipSyncWrap(data: Uint8Array) { if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') { // @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason @@ -27,7 +25,7 @@ function gzipSyncWrap(data: Uint8Array) { describe('gunzip', () => { it('should correctly read zlib headers', () => { const wasm = __getWasm() - const data = gzipSyncWrap(p.utf8Encode('hello world')) + const data = gzipSyncWrap(utf8.encoder.encode('hello world')) const inputPtr = wasm.__malloc(data.length) new Uint8Array(wasm.memory.buffer).set(data, inputPtr) @@ -37,11 +35,11 @@ describe('gunzip', () => { it('should correctly inflate', () => { const data = Array.from({ length: 1000 }, () => 'a').join('') - const res = gzipSyncWrap(p.utf8Encode(data)) + const res = gzipSyncWrap(utf8.encoder.encode(data)) expect(res).not.toBeNull() expect(res.length).toBeLessThan(100) - expect(gunzip(res)).toEqual(new Uint8Array(p.utf8Encode(data))) + expect(gunzip(res)).toEqual(new Uint8Array(utf8.encoder.encode(data))) }) it('should not leak memory', () => { @@ -49,11 +47,11 @@ describe('gunzip', () => { for (let i = 0; i < 100; i++) { const data = Array.from({ length: 1000 }, () => 'a').join('') - const deflated = gzipSyncWrap(p.utf8Encode(data)) + const deflated = gzipSyncWrap(utf8.encoder.encode(data)) const res = gunzip(deflated) - expect(p.utf8Decode(res)).toEqual(data) + expect(utf8.decoder.decode(res)).toEqual(data) } expect(__getWasm().memory.buffer.byteLength).toEqual(memSize) diff --git a/packages/wasm/tests/hash.test.ts b/packages/wasm/tests/hash.test.ts index e7a62853..edd02ce8 100644 --- a/packages/wasm/tests/hash.test.ts +++ b/packages/wasm/tests/hash.test.ts @@ -1,5 +1,5 @@ import { beforeAll, describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex, utf8 } from '@fuman/utils' import { __getWasm, sha1, sha256 } from '../src/index.js' @@ -9,13 +9,11 @@ beforeAll(async () => { await initWasm() }) -const p = getPlatform() - describe('sha256', () => { it('should correctly calculate sha-256 hash', () => { - const hash = sha256(p.utf8Encode('abc')) + const hash = sha256(utf8.encoder.encode('abc')) - expect(p.hexEncode(hash)).toEqual('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + expect(hex.encode(hash)).toEqual('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') }) it('should not leak memory', () => { @@ -23,7 +21,7 @@ describe('sha256', () => { const memSize = mem.byteLength for (let i = 0; i < 100; i++) { - sha256(p.utf8Encode('abc')) + sha256(utf8.encoder.encode('abc')) } expect(mem.byteLength).toEqual(memSize) @@ -32,9 +30,9 @@ describe('sha256', () => { describe('sha1', () => { it('should correctly calculate sha-1 hash', () => { - const hash = sha1(p.utf8Encode('abc')) + const hash = sha1(utf8.encoder.encode('abc')) - expect(p.hexEncode(hash)).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d') + expect(hex.encode(hash)).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d') }) it('should not leak memory', () => { @@ -42,7 +40,7 @@ describe('sha1', () => { const memSize = mem.byteLength for (let i = 0; i < 100; i++) { - sha1(p.utf8Encode('abc')) + sha1(utf8.encoder.encode('abc')) } expect(mem.byteLength).toEqual(memSize) diff --git a/packages/wasm/tests/ige.test.ts b/packages/wasm/tests/ige.test.ts index 6bed3b82..3ee648d1 100644 --- a/packages/wasm/tests/ige.test.ts +++ b/packages/wasm/tests/ige.test.ts @@ -1,33 +1,31 @@ import { beforeAll, describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { hex } from '@fuman/utils' import { __getWasm, ige256Decrypt, ige256Encrypt } from '../src/index.js' import { initWasm } from './init.js' -const p = getPlatform() - beforeAll(async () => { await initWasm() }) describe('aes-ige', () => { - const key = p.hexDecode('5468697320697320616E20696D706C655468697320697320616E20696D706C65') - const iv = p.hexDecode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353') + const key = hex.decode('5468697320697320616E20696D706C655468697320697320616E20696D706C65') + const iv = hex.decode('6D656E746174696F6E206F6620494745206D6F646520666F72204F70656E5353') - const data = p.hexDecode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') - const dataEnc = p.hexDecode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') + const data = hex.decode('99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b') + const dataEnc = hex.decode('792ea8ae577b1a66cb3bd92679b8030ca54ee631976bd3a04547fdcb4639fa69') it('should correctly encrypt', () => { const aes = ige256Encrypt(data, key, iv) - expect(p.hexEncode(aes)).toEqual(p.hexEncode(dataEnc)) + expect(hex.encode(aes)).toEqual(hex.encode(dataEnc)) }) it('should correctly decrypt', () => { const aes = ige256Decrypt(dataEnc, key, iv) - expect(p.hexEncode(aes)).toEqual(p.hexEncode(data)) + expect(hex.encode(aes)).toEqual(hex.encode(data)) }) it('should not leak memory', () => { diff --git a/packages/wasm/tests/tsconfig.json b/packages/wasm/tests/tsconfig.json index ba289afc..029bccff 100644 --- a/packages/wasm/tests/tsconfig.json +++ b/packages/wasm/tests/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "../../../tsconfig.json", - "references": [ - { "path": "../" } - ], "include": [ "." ] diff --git a/packages/wasm/tests/zlib.test.ts b/packages/wasm/tests/zlib.test.ts index 7aeca117..0b501394 100644 --- a/packages/wasm/tests/zlib.test.ts +++ b/packages/wasm/tests/zlib.test.ts @@ -1,7 +1,7 @@ import { inflateSync } from 'node:zlib' import { beforeAll, describe, expect, it } from 'vitest' -import { getPlatform } from '@mtcute/core/platform.js' +import { utf8 } from '@fuman/utils' import { __getWasm, deflateMaxSize } from '../src/index.js' @@ -11,8 +11,6 @@ beforeAll(async () => { await initWasm() }) -const p = getPlatform() - function inflateSyncWrap(data: Uint8Array) { if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') { // @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason @@ -26,25 +24,25 @@ function inflateSyncWrap(data: Uint8Array) { describe('zlib deflate', () => { it('should add zlib headers', () => { - const res = deflateMaxSize(p.utf8Encode('hello world'), 100) + const res = deflateMaxSize(utf8.encoder.encode('hello world'), 100) expect(res).not.toBeNull() expect(res!.slice(0, 2)).toEqual(new Uint8Array([0x78, 0x9C])) }) it('should return null if compressed data is larger than size', () => { - const res = deflateMaxSize(p.utf8Encode('hello world'), 1) + const res = deflateMaxSize(utf8.encoder.encode('hello world'), 1) expect(res).toBeNull() }) it('should correctly deflate', () => { const data = Array.from({ length: 1000 }, () => 'a').join('') - const res = deflateMaxSize(p.utf8Encode(data), 100) + const res = deflateMaxSize(utf8.encoder.encode(data), 100) expect(res).not.toBeNull() expect(res!.length).toBeLessThan(100) - expect(inflateSyncWrap(res!)).toEqual(p.utf8Encode(data)) + expect(inflateSyncWrap(res!)).toEqual(utf8.encoder.encode(data)) }) it('should not leak memory', () => { @@ -52,11 +50,11 @@ describe('zlib deflate', () => { for (let i = 0; i < 100; i++) { const data = Array.from({ length: 1000 }, () => 'a').join('') - const deflated = deflateMaxSize(p.utf8Encode(data), 100) + const deflated = deflateMaxSize(utf8.encoder.encode(data), 100) const res = inflateSyncWrap(deflated!) - expect(p.utf8Decode(res)).toEqual(data) + expect(utf8.decoder.decode(res)).toEqual(data) } expect(__getWasm().memory.buffer.byteLength).toEqual(memSize) diff --git a/packages/web/src/common-internals-web/base64.ts b/packages/web/src/common-internals-web/base64.ts deleted file mode 100644 index 4d8a8026..00000000 --- a/packages/web/src/common-internals-web/base64.ts +++ /dev/null @@ -1,132 +0,0 @@ -/// Based on https://github.com/beatgammit/base64-js, MIT license -const lookup: string[] = [] -const revLookup: number[] = [] - -const code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - -for (let i = 0, len = code.length; i < len; ++i) { - lookup[i] = code[i] - revLookup[code.charCodeAt(i)] = i -} - -function getLens(b64: string): [number, number] { - const len = b64.length - - if (len % 4 > 0) { - throw new Error('Invalid string. Length must be a multiple of 4') - } - - // Trim off extra bytes after placeholder bytes are found - // See: https://github.com/beatgammit/base64-js/issues/42 - let validLen = b64.indexOf('=') - if (validLen === -1) validLen = len - - const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4) - - return [validLen, placeHoldersLen] -} - -function _byteLength(b64: string, validLen: number, placeHoldersLen: number) { - return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen -} - -function toByteArray(b64: string, arr: Uint8Array) { - let tmp - const lens = getLens(b64) - const validLen = lens[0] - const placeHoldersLen = lens[1] - - let curByte = 0 - - // if there are placeholders, only get up to the last complete 4 chars - const len = placeHoldersLen > 0 ? validLen - 4 : validLen - - let i - - for (i = 0; i < len; i += 4) { - tmp - = (revLookup[b64.charCodeAt(i)] << 18) - | (revLookup[b64.charCodeAt(i + 1)] << 12) - | (revLookup[b64.charCodeAt(i + 2)] << 6) - | revLookup[b64.charCodeAt(i + 3)] - arr[curByte++] = (tmp >> 16) & 0xFF - arr[curByte++] = (tmp >> 8) & 0xFF - arr[curByte++] = tmp & 0xFF - } - - if (placeHoldersLen === 2) { - tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) - arr[curByte++] = tmp & 0xFF - } - - if (placeHoldersLen === 1) { - tmp - = (revLookup[b64.charCodeAt(i)] << 10) - | (revLookup[b64.charCodeAt(i + 1)] << 4) - | (revLookup[b64.charCodeAt(i + 2)] >> 2) - arr[curByte++] = (tmp >> 8) & 0xFF - arr[curByte++] = tmp & 0xFF - } - - return arr -} - -function tripletToBase64(num: number) { - return lookup[(num >> 18) & 0x3F] + lookup[(num >> 12) & 0x3F] + lookup[(num >> 6) & 0x3F] + lookup[num & 0x3F] -} - -function encodeChunk(uint8: Uint8Array, start: number, end: number) { - let tmp - const output = [] - - for (let i = start; i < end; i += 3) { - tmp = ((uint8[i] << 16) & 0xFF0000) + ((uint8[i + 1] << 8) & 0xFF00) + (uint8[i + 2] & 0xFF) - output.push(tripletToBase64(tmp)) - } - - return output.join('') -} - -function fromByteArray(uint8: Uint8Array) { - let tmp - const len = uint8.length - const extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes - const parts = [] - const maxChunkLength = 16383 // must be multiple of 3 - - // go through the array every three bytes, we'll deal with trailing stuff later - for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { - parts.push(encodeChunk(uint8, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength)) - } - - // pad the end with zeros, but make sure to not forget the extra bytes - if (extraBytes === 1) { - tmp = uint8[len - 1] - parts.push(`${lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3F]}==`) - } else if (extraBytes === 2) { - tmp = (uint8[len - 2] << 8) + uint8[len - 1] - parts.push(`${lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3F] + lookup[(tmp << 2) & 0x3F]}=`) - } - - return parts.join('') -} - -export function base64Encode(buf: Uint8Array, url: boolean = false): string { - const str = fromByteArray(buf) - if (url) return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') - - return str -} - -export function base64Decode(string: string, url: boolean = false): Uint8Array { - if (url) { - string = string.replace(/-/g, '+').replace(/_/g, '/') - while (string.length % 4) string += '=' - } - - const buf = new Uint8Array(_byteLength(string, ...getLens(string))) - - toByteArray(string, buf) - - return buf -} diff --git a/packages/web/src/common-internals-web/hex.ts b/packages/web/src/common-internals-web/hex.ts deleted file mode 100644 index 0c3e4a16..00000000 --- a/packages/web/src/common-internals-web/hex.ts +++ /dev/null @@ -1,75 +0,0 @@ -/// Based on https://github.com/feross/buffer, MIT license - -const hexSliceLookupTable = (function () { - const alphabet = '0123456789abcdef' - const table: string[] = Array.from({ length: 256 }) - - for (let i = 0; i < 16; ++i) { - const i16 = i * 16 - - for (let j = 0; j < 16; ++j) { - table[i16 + j] = alphabet[i] + alphabet[j] - } - } - - return table -})() - -const hexCharValueTable: Record = { - 0: 0, - 1: 1, - 2: 2, - 3: 3, - 4: 4, - 5: 5, - 6: 6, - 7: 7, - 8: 8, - 9: 9, - a: 10, - b: 11, - c: 12, - d: 13, - e: 14, - f: 15, - A: 10, - B: 11, - C: 12, - D: 13, - E: 14, - F: 15, -} - -export function hexEncode(buf: Uint8Array): string { - let out = '' - - for (let i = 0; i < buf.byteLength; ++i) { - out += hexSliceLookupTable[buf[i]] - } - - return out -} - -function hexDecodeInner(buf: Uint8Array, string: string): void { - const strLen = string.length - const length = Math.min(buf.length, strLen / 2) - - let i - - for (i = 0; i < length; ++i) { - const a = hexCharValueTable[string[i * 2]] - const b = hexCharValueTable[string[i * 2 + 1]] - - if (a === undefined || b === undefined) { - return - } - buf[i] = (a << 4) | b - } -} - -export function hexDecode(string: string): Uint8Array { - const buf = new Uint8Array(Math.ceil(string.length / 2)) - hexDecodeInner(buf, string) - - return buf -} diff --git a/packages/web/src/common-internals-web/utf8.ts b/packages/web/src/common-internals-web/utf8.ts deleted file mode 100644 index 08294d5e..00000000 --- a/packages/web/src/common-internals-web/utf8.ts +++ /dev/null @@ -1,24 +0,0 @@ -const sharedEncoder = new TextEncoder() -const sharedDecoder = new TextDecoder('utf8') - -export function utf8ByteLength(str: string): number { - // https://stackoverflow.com/a/23329386 - let s = str.length - - for (let i = str.length - 1; i >= 0; i--) { - const code = str.charCodeAt(i) - if (code > 0x7F && code <= 0x7FF) s++ - else if (code > 0x7FF && code <= 0xFFFF) s += 2 - if (code >= 0xDC00 && code <= 0xDFFF) i-- // trail surrogate - } - - return s -} - -export function utf8Decode(buf: Uint8Array): string { - return sharedDecoder.decode(buf) -} - -export function utf8Encode(str: string): Uint8Array { - return sharedEncoder.encode(str) -} diff --git a/packages/web/src/platform.ts b/packages/web/src/platform.ts index baa3075f..25583d63 100644 --- a/packages/web/src/platform.ts +++ b/packages/web/src/platform.ts @@ -1,9 +1,6 @@ import type { ICorePlatform } from '@mtcute/core/platform.js' -import { base64Decode, base64Encode } from './common-internals-web/base64.js' -import { hexDecode, hexEncode } from './common-internals-web/hex.js' import { defaultLoggingHandler } from './common-internals-web/logging.js' -import { utf8ByteLength, utf8Decode, utf8Encode } from './common-internals-web/utf8.js' import { beforeExit } from './exit-hook.js' export class WebPlatform implements ICorePlatform { @@ -45,23 +42,7 @@ export class WebPlatform implements ICorePlatform { isOnline(): boolean { return navigator.onLine } - - // ITlPlatform - declare utf8ByteLength: typeof utf8ByteLength - declare utf8Encode: typeof utf8Encode - declare utf8Decode: typeof utf8Decode - declare hexEncode: typeof hexEncode - declare hexDecode: typeof hexDecode - declare base64Encode: typeof base64Encode - declare base64Decode: typeof base64Decode } WebPlatform.prototype.log = defaultLoggingHandler WebPlatform.prototype.beforeExit = beforeExit -WebPlatform.prototype.utf8ByteLength = utf8ByteLength -WebPlatform.prototype.utf8Encode = utf8Encode -WebPlatform.prototype.utf8Decode = utf8Decode -WebPlatform.prototype.hexEncode = hexEncode -WebPlatform.prototype.hexDecode = hexDecode -WebPlatform.prototype.base64Encode = base64Encode -WebPlatform.prototype.base64Decode = base64Decode diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51e4f8f9..cd38c27b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers: packages/convert: dependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils '@mtcute/core': specifier: workspace:^ version: link:../core @@ -270,16 +273,15 @@ importers: packages/file-id: dependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils '@mtcute/tl-runtime': specifier: workspace:^ version: link:../tl-runtime long: specifier: 5.2.3 version: 5.2.3 - devDependencies: - '@mtcute/test': - specifier: workspace:^ - version: link:../test packages/html-parser: dependencies: @@ -344,6 +346,9 @@ importers: packages/test: dependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils '@mtcute/core': specifier: workspace:^ version: link:../core @@ -400,6 +405,9 @@ importers: packages/tl-runtime: dependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils long: specifier: 5.2.3 version: 5.2.3 @@ -415,6 +423,9 @@ importers: packages/wasm: devDependencies: + '@fuman/utils': + specifier: workspace:^ + version: link:../../private/fuman/packages/utils '@mtcute/core': specifier: workspace:^ version: link:../core