chore: use hex/base64/utf from @fuman/utils

This commit is contained in:
alina 🌸 2024-09-05 03:34:13 +03:00
parent baef78403e
commit 66786064e3
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
77 changed files with 408 additions and 846 deletions

View file

@ -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:^"

View file

@ -75,11 +75,13 @@ export const DC_MAPPING_TEST: Record<number, DcOptions> = {
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<number, DcOptions> = {
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<number, DcOptions> = {
id: 3,
ipAddress: '149.154.175.117',
port: 443,
testMode: true,
},
media: {
id: 3,
ipAddress: '149.154.175.117',
port: 443,
testMode: true,
},
},
}

View file

@ -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,
},
},
version: 3,
}),
).toEqual(GRAMJS_SESSION)

View file

@ -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'

View file

@ -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)

View file

@ -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'

View file

@ -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)}`
}

View file

@ -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,
},
},
version: 3,
}),
).toEqual(MTKRUTO_SESSION)

View file

@ -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,
})
}

View file

@ -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'

View file

@ -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()

View file

@ -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'

View file

@ -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)
}

View file

@ -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)

View file

@ -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,

View file

@ -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'

View file

@ -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) {

View file

@ -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'

View file

@ -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)
}

View file

@ -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,
},
},
version: 3,
}),
).toEqual(TELETHON_TEST_SESSION)

View file

@ -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,

View file

@ -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'

View file

@ -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)

View file

@ -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'

View file

@ -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}`

View file

@ -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]}`

View file

@ -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,
},

View file

@ -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,
}
}

View file

@ -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)
}
/**

View file

@ -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)
})
})

View file

@ -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(
'"<?xml version="1.0" encoding="utf-8"?><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"viewBox="0 0 512 512" xml:space="preserve"><path d="M 0 0 L 100 0 L 100 100 L 0 100 L 0 0 Z"/></svg>"',
)
})
})
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'

View file

@ -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(
'<?xml version="1.0" encoding="utf-8"?>'
+ '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'
+ `viewBox="0 0 ${size?.w ?? 512} ${size?.h ?? 512}" xml:space="preserve">`

View file

@ -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,

View file

@ -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',
)

View file

@ -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 () => {

View file

@ -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
}

View file

@ -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(

View file

@ -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
}

View file

@ -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,
}

View file

@ -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'),
])
})
})

View file

@ -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')
})
})

View file

@ -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<typeof algo>).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)

View file

@ -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')
})
})

View file

@ -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)}...`

View file

@ -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

View file

@ -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(),
)

View file

@ -15,9 +15,7 @@
},
"dependencies": {
"@mtcute/tl-runtime": "workspace:^",
"@fuman/utils": "workspace:^",
"long": "5.2.3"
},
"devDependencies": {
"@mtcute/test": "workspace:^"
}
}

View file

@ -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'),

View file

@ -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)})`)
}

View file

@ -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', () => {

View file

@ -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<td.RawFullRemoteFileLocation, '_'>): string
export function toUniqueFileId(platform: ITlPlatform, type: td.FileType, location: InputUniqueLocation): string
export function toUniqueFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): string
export function toUniqueFileId(type: td.FileType, location: InputUniqueLocation): string
export function toUniqueFileId(
platform: ITlPlatform,
first: td.FileType | Omit<td.RawFullRemoteFileLocation, '_'>,
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)
}

View file

@ -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<td.RawFullRemoteFileLocation, '_'>): string {
export function toFileId(location: Omit<td.RawFullRemoteFileLocation, '_'>): string {
const loc = location.location
let type: number = location.type
@ -26,7 +26,7 @@ export function toFileId(platform: ITlPlatform, location: Omit<td.RawFullRemoteF
//
// longest file ids are around 80 bytes, so i guess
// we are safe with allocating 100 bytes
const writer = TlBinaryWriter.manual(loc._ === 'web' ? platform.utf8ByteLength(loc.url) + 32 : 100)
const writer = TlBinaryWriter.manual(loc._ === 'web' ? utf8.encodedLength(loc.url) + 32 : 100)
writer.int(type)
writer.int(location.dcId)
@ -109,5 +109,5 @@ export function toFileId(platform: ITlPlatform, location: Omit<td.RawFullRemoteF
withSuffix.set(result)
withSuffix.set(SUFFIX, result.length)
return platform.base64Encode(withSuffix, true)
return base64.encode(withSuffix, true)
}

View file

@ -1,33 +1,31 @@
import { describe, expect, it } from 'vitest'
import { defaultPlatform } from '@mtcute/test'
import { hex } from '@fuman/utils'
import { telegramRleDecode, telegramRleEncode } from './utils.js'
const p = defaultPlatform
describe('telegramRleEncode', () => {
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')
})
})

View file

@ -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

View file

@ -27,7 +27,8 @@
}
},
"dependencies": {
"long": "5.2.3"
"long": "5.2.3",
"@fuman/utils": "workspace:^"
},
"devDependencies": {
"@mtcute/tl-utils": "workspace:^"

View file

@ -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
}

View file

@ -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)
})
})

View file

@ -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

View file

@ -15,6 +15,7 @@
"build": "pnpm run -w build-package tl-runtime"
},
"dependencies": {
"long": "5.2.3"
"long": "5.2.3",
"@fuman/utils": "workspace:^"
}
}

View file

@ -1,3 +1,2 @@
export * from './platform.js'
export * from './reader.js'
export * from './writer.js'

View file

@ -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
}

View file

@ -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),

View file

@ -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<number, (r: any) => 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 {

View file

@ -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)],
}

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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', () => {

View file

@ -1,8 +1,5 @@
{
"extends": "../../../tsconfig.json",
"references": [
{ "path": "../" }
],
"include": [
"."
]

View file

@ -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)

View file

@ -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
}

View file

@ -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<string, number> = {
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
}

View file

@ -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)
}

View file

@ -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

View file

@ -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