From 806c62bda867061b633c64885ec9a33acbd0e71e Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Fri, 23 Feb 2024 00:24:21 +0300 Subject: [PATCH] chore: remove platform-specific code from tl-runtime --- packages/tl-runtime/package.json | 5 - .../tl-runtime/src/encodings/base64.test.ts | 41 ----- packages/tl-runtime/src/encodings/base64.ts | 39 ----- .../tl-runtime/src/encodings/base64.web.ts | 142 ------------------ packages/tl-runtime/src/encodings/hex.test.ts | 25 --- packages/tl-runtime/src/encodings/hex.ts | 13 -- packages/tl-runtime/src/encodings/hex.web.ts | 78 ---------- packages/tl-runtime/src/encodings/index.ts | 3 - .../tl-runtime/src/encodings/utf8.test.ts | 36 ----- packages/tl-runtime/src/encodings/utf8.ts | 21 --- packages/tl-runtime/src/encodings/utf8.web.ts | 15 -- packages/tl-runtime/src/index.ts | 4 +- .../tl-runtime/src/platform.test-utils.ts | 9 ++ packages/tl-runtime/src/platform.ts | 8 + packages/tl-runtime/src/reader.test.ts | 99 +++++++----- packages/tl-runtime/src/reader.ts | 16 +- packages/tl-runtime/src/writer.test.ts | 20 ++- packages/tl-runtime/src/writer.ts | 36 +++-- 18 files changed, 125 insertions(+), 485 deletions(-) delete mode 100644 packages/tl-runtime/src/encodings/base64.test.ts delete mode 100644 packages/tl-runtime/src/encodings/base64.ts delete mode 100644 packages/tl-runtime/src/encodings/base64.web.ts delete mode 100644 packages/tl-runtime/src/encodings/hex.test.ts delete mode 100644 packages/tl-runtime/src/encodings/hex.ts delete mode 100644 packages/tl-runtime/src/encodings/hex.web.ts delete mode 100644 packages/tl-runtime/src/encodings/index.ts delete mode 100644 packages/tl-runtime/src/encodings/utf8.test.ts delete mode 100644 packages/tl-runtime/src/encodings/utf8.ts delete mode 100644 packages/tl-runtime/src/encodings/utf8.web.ts create mode 100644 packages/tl-runtime/src/platform.test-utils.ts create mode 100644 packages/tl-runtime/src/platform.ts diff --git a/packages/tl-runtime/package.json b/packages/tl-runtime/package.json index add769ea..21ad32e3 100644 --- a/packages/tl-runtime/package.json +++ b/packages/tl-runtime/package.json @@ -10,11 +10,6 @@ "docs": "typedoc", "build": "pnpm run -w build-package tl-runtime" }, - "browser": { - "./src/encodings/hex.js": "./src/encodings/hex.web.js", - "./src/encodings/utf8.js": "./src/encodings/utf8.web.js", - "./src/encodings/base64.js": "./src/encodings/base64.web.js" - }, "distOnlyFields": { "exports": { ".": { diff --git a/packages/tl-runtime/src/encodings/base64.test.ts b/packages/tl-runtime/src/encodings/base64.test.ts deleted file mode 100644 index 42cacfc3..00000000 --- a/packages/tl-runtime/src/encodings/base64.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, expect, it } from 'vitest' - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const _imported = await import( - import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun' ? './base64.js' : './base64.web.js' -) -const { base64Decode, base64DecodeToBuffer, base64Encode } = _imported as typeof import('./base64.js') - -describe('base64', () => { - it('should decode base64 string to existing buffer', () => { - const buf = new Uint8Array(4) - base64Decode(buf, 'AQIDBA==') - expect(buf).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should decode base64 string to new buffer', () => { - const buf = base64DecodeToBuffer('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(base64Encode(buf)).toEqual('AQIDBA==') - }) - - it('should decode url-safe base64 string to existing buffer', () => { - const buf = new Uint8Array(4) - base64Decode(buf, 'AQIDBA', true) - expect(buf).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should decode url-safe base64 string to new buffer', () => { - const buf = base64DecodeToBuffer('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(base64Encode(buf, true)).toEqual('AQIDBA') - }) -}) diff --git a/packages/tl-runtime/src/encodings/base64.ts b/packages/tl-runtime/src/encodings/base64.ts deleted file mode 100644 index ea1a5c74..00000000 --- a/packages/tl-runtime/src/encodings/base64.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable no-restricted-globals */ - -export const BUFFER_BASE64_URL_AVAILABLE = Buffer.isEncoding('base64url') - -export function base64Encode(buf: Uint8Array, url = false): string { - const nodeBuffer = Buffer.from( - buf.buffer, - buf.byteOffset, - buf.byteLength, - ) - - 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 -} - -export function base64DecodeToBuffer(string: string, url = false): Uint8Array { - let buffer - - if (url && BUFFER_BASE64_URL_AVAILABLE) { - buffer = Buffer.from(string, 'base64url') - } else { - buffer = Buffer.from(string, 'base64') - - if (url) { - string = string.replace(/-/g, '+').replace(/_/g, '/') - while (string.length % 4) string += '=' - } - } - - return buffer -} - -export function base64Decode(buf: Uint8Array, string: string, url = false): void { - (base64DecodeToBuffer(string, url) as Buffer).copy(buf) -} diff --git a/packages/tl-runtime/src/encodings/base64.web.ts b/packages/tl-runtime/src/encodings/base64.web.ts deleted file mode 100644 index e76fe5e6..00000000 --- a/packages/tl-runtime/src/encodings/base64.web.ts +++ /dev/null @@ -1,142 +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 = false): string { - const str = fromByteArray(buf) - if (url) return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') - - return str -} - -export function base64Decode(buf: Uint8Array, string: string, url = false): void { - if (url) { - string = string.replace(/-/g, '+').replace(/_/g, '/') - while (string.length % 4) string += '=' - } - - const res = toByteArray(string, buf) - buf.set(res) -} - -export function base64DecodeToBuffer(string: string, url = false): Uint8Array { - if (url) { - string = string.replace(/-/g, '+').replace(/_/g, '/') - while (string.length % 4) string += '=' - } - - const buf = new Uint8Array(_byteLength(string, ...getLens(string))) - - toByteArray(string, buf) - - return buf -} diff --git a/packages/tl-runtime/src/encodings/hex.test.ts b/packages/tl-runtime/src/encodings/hex.test.ts deleted file mode 100644 index 17b678a7..00000000 --- a/packages/tl-runtime/src/encodings/hex.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, it } from 'vitest' - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const _imported = await import( - import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun' ? './hex.js' : './hex.web.js' -) -const { hexDecode, hexDecodeToBuffer, hexEncode } = _imported as typeof import('./hex.js') - -describe('hex', () => { - it('should decode hex string to existing buffer', () => { - const buf = new Uint8Array(4) - hexDecode(buf, '01020304') - expect(buf).toEqual(new Uint8Array([1, 2, 3, 4])) - }) - - it('should decode hex string to new buffer', () => { - const buf = hexDecodeToBuffer('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(hexEncode(buf)).toEqual('01020304') - }) -}) diff --git a/packages/tl-runtime/src/encodings/hex.ts b/packages/tl-runtime/src/encodings/hex.ts deleted file mode 100644 index db76f585..00000000 --- a/packages/tl-runtime/src/encodings/hex.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable no-restricted-globals */ - -export function hexEncode(buf: Uint8Array): string { - return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength).toString('hex') -} - -export function hexDecodeToBuffer(string: string): Uint8Array { - return Buffer.from(string, 'hex') -} - -export function hexDecode(buf: Uint8Array, string: string): number { - return (hexDecodeToBuffer(string) as Buffer).copy(buf) -} diff --git a/packages/tl-runtime/src/encodings/hex.web.ts b/packages/tl-runtime/src/encodings/hex.web.ts deleted file mode 100644 index a084b8fb..00000000 --- a/packages/tl-runtime/src/encodings/hex.web.ts +++ /dev/null @@ -1,78 +0,0 @@ -/// Based on https://github.com/feross/buffer, MIT license - -const hexSliceLookupTable = (function () { - const alphabet = '0123456789abcdef' - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const table: string[] = new Array(256) - - for (let i = 0; i < 16; ++i) { - const i16 = i * 16 - - for (let j = 0; j < 16; ++j) { - table[i16 + j] = alphabet[i] + alphabet[j] - } - } - - return table -})() - -const hexCharValueTable: Record = { - '0': 0, - '1': 1, - '2': 2, - '3': 3, - '4': 4, - '5': 5, - '6': 6, - '7': 7, - '8': 8, - '9': 9, - a: 10, - b: 11, - c: 12, - d: 13, - e: 14, - f: 15, - A: 10, - B: 11, - C: 12, - D: 13, - E: 14, - F: 15, -} - -export function hexEncode(buf: Uint8Array): string { - let out = '' - - for (let i = 0; i < buf.byteLength; ++i) { - out += hexSliceLookupTable[buf[i]] - } - - return out -} - -export function hexDecode(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 - } - - return -} - -export function hexDecodeToBuffer(string: string): Uint8Array { - const buf = new Uint8Array(Math.ceil(string.length / 2)) - hexDecode(buf, string) - - return buf -} diff --git a/packages/tl-runtime/src/encodings/index.ts b/packages/tl-runtime/src/encodings/index.ts deleted file mode 100644 index ce22da50..00000000 --- a/packages/tl-runtime/src/encodings/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './base64.js' -export * from './hex.js' -export * from './utf8.js' diff --git a/packages/tl-runtime/src/encodings/utf8.test.ts b/packages/tl-runtime/src/encodings/utf8.test.ts deleted file mode 100644 index df48f6c8..00000000 --- a/packages/tl-runtime/src/encodings/utf8.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, expect, it } from 'vitest' - -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -const _imported = await import( - import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun' ? './utf8.js' : './utf8.web.js' -) -const { byteLengthUtf8, utf8Decode, utf8Encode, utf8EncodeToBuffer } = _imported as typeof import('./utf8.js') - -describe('utf8', () => { - it('should encode utf8 string into existing buffer', () => { - const buf = new Uint8Array(4) - utf8Encode(buf, 'abcd') - expect(buf).toEqual(new Uint8Array([97, 98, 99, 100])) - }) - - it('should encode utf8 string into new buffer', () => { - const buf = utf8EncodeToBuffer('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(utf8Decode(buf)).toEqual('abcd') - }) -}) - -describe('byteLengthUtf8', () => { - it('should return byte length of utf8 string', () => { - expect(byteLengthUtf8('abcd')).toEqual(4) - }) - - it('should properly handle utf8 string with non-ascii characters', () => { - expect(byteLengthUtf8('абвг')).toEqual(8) - expect(byteLengthUtf8('🌸')).toEqual(4) - }) -}) diff --git a/packages/tl-runtime/src/encodings/utf8.ts b/packages/tl-runtime/src/encodings/utf8.ts deleted file mode 100644 index 5543e8c0..00000000 --- a/packages/tl-runtime/src/encodings/utf8.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable no-restricted-globals */ - -export function byteLengthUtf8(str: string) { - return Buffer.byteLength(str, 'utf8') -} - -export function utf8Decode(buf: Uint8Array): string { - return Buffer.from( - buf.buffer, - buf.byteOffset, - buf.byteLength, - ).toString('utf8') -} - -export function utf8Encode(buf: Uint8Array, str: string) { - return Buffer.from(str, 'utf8').copy(buf) -} - -export function utf8EncodeToBuffer(str: string): Uint8Array { - return Buffer.from(str, 'utf8') -} diff --git a/packages/tl-runtime/src/encodings/utf8.web.ts b/packages/tl-runtime/src/encodings/utf8.web.ts deleted file mode 100644 index 5eeb30e4..00000000 --- a/packages/tl-runtime/src/encodings/utf8.web.ts +++ /dev/null @@ -1,15 +0,0 @@ -export function byteLengthUtf8(str: string) { - return new TextEncoder().encode(str).length -} - -export function utf8Decode(buf: Uint8Array): string { - return new TextDecoder('utf8').decode(buf) -} - -export function utf8Encode(buf: Uint8Array, str: string) { - return new TextEncoder().encodeInto(str, buf) -} - -export function utf8EncodeToBuffer(str: string): Uint8Array { - return new TextEncoder().encode(str) -} diff --git a/packages/tl-runtime/src/index.ts b/packages/tl-runtime/src/index.ts index def86778..4aa541a0 100644 --- a/packages/tl-runtime/src/index.ts +++ b/packages/tl-runtime/src/index.ts @@ -1,5 +1,3 @@ -export * from './encodings/base64.js' -export * from './encodings/hex.js' -export * from './encodings/utf8.js' +export * from './platform.js' export * from './reader.js' export * from './writer.js' diff --git a/packages/tl-runtime/src/platform.test-utils.ts b/packages/tl-runtime/src/platform.test-utils.ts new file mode 100644 index 00000000..e98cac08 --- /dev/null +++ b/packages/tl-runtime/src/platform.test-utils.ts @@ -0,0 +1,9 @@ +// todo: move to platform-specific packages, add them to dev deps and remove this file + +import { ITlPlatform } from './platform.js' + +export const defaultTlPlatform: ITlPlatform = { + utf8Encode: (str: string) => new TextEncoder().encode(str), + utf8Decode: (buf: Uint8Array) => new TextDecoder().decode(buf), + utf8ByteLength: (str: string) => new TextEncoder().encode(str).length, +} diff --git a/packages/tl-runtime/src/platform.ts b/packages/tl-runtime/src/platform.ts new file mode 100644 index 00000000..34b18533 --- /dev/null +++ b/packages/tl-runtime/src/platform.ts @@ -0,0 +1,8 @@ +/** + * 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 +} diff --git a/packages/tl-runtime/src/reader.test.ts b/packages/tl-runtime/src/reader.test.ts index a6aeb0e1..6ec2cad0 100644 --- a/packages/tl-runtime/src/reader.test.ts +++ b/packages/tl-runtime/src/reader.test.ts @@ -1,13 +1,17 @@ // eslint-disable-next-line max-len /* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-argument */ -// import { randomBytes } from 'crypto' +// import Long from 'long' import Long from 'long' import { describe, expect, it } from 'vitest' -import { hexDecodeToBuffer, hexEncode } from './encodings/hex.js' +import { defaultTlPlatform } from './platform.test-utils.js' import { TlBinaryReader, TlReaderMap } 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) => parseInt(byte, 16))) + let randomBytes: (n: number) => Uint8Array if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { @@ -23,69 +27,94 @@ if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { describe('TlBinaryReader', () => { it('should read int32', () => { - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0, 0])).int()).toEqual(0) - expect(TlBinaryReader.manual(new Uint8Array([1, 0, 0, 0])).int()).toEqual(1) - expect(TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).int()).toEqual(67305985) - expect(TlBinaryReader.manual(new Uint8Array([0xff, 0xff, 0xff, 0xff])).int()).toEqual(-1) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0, 0])).int()).toEqual(0) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 0, 0, 0])).int()).toEqual(1) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).int()).toEqual(67305985) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0xff, 0xff, 0xff, 0xff])).int()).toEqual(-1) }) it('should read uint32', () => { - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0, 0])).uint()).toEqual(0) - expect(TlBinaryReader.manual(new Uint8Array([1, 0, 0, 0])).uint()).toEqual(1) - expect(TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).uint()).toEqual(67305985) - expect(TlBinaryReader.manual(new Uint8Array([0xff, 0xff, 0xff, 0xff])).uint()).toEqual(4294967295) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0, 0])).uint()).toEqual(0) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 0, 0, 0])).uint()).toEqual(1) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).uint()).toEqual(67305985) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0xff, 0xff, 0xff, 0xff])).uint()).toEqual( + 4294967295, + ) }) it('should read int53', () => { - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])).int53()).toEqual(0) - expect(TlBinaryReader.manual(new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0])).int53()).toEqual(1) - expect(TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4, 0, 0, 0, 0])).int53()).toEqual(67305985) - expect(TlBinaryReader.manual(new Uint8Array([1, 0, 1, 0, 1, 0, 1, 0])).int53()).toEqual(281479271743489) - expect(TlBinaryReader.manual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])).int53()).toEqual( - -1, + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])).int53()).toEqual(0) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0])).int53()).toEqual(1) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4, 0, 0, 0, 0])).int53()).toEqual( + 67305985, ) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 0, 1, 0, 1, 0, 1, 0])).int53()).toEqual( + 281479271743489, + ) + expect( + TlBinaryReader.manual( + defaultTlPlatform, + new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), + ).int53(), + ).toEqual(-1) }) it('should read long', () => { expect( - TlBinaryReader.manual(new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])) + TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff])) .long() .toString(), ).toEqual('-1') expect( - TlBinaryReader.manual(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78])) + TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78])) .long() .toString(), ).toEqual('8671175386481439762') expect( - TlBinaryReader.manual(new Uint8Array([0x15, 0xc4, 0x15, 0xb5, 0xc4, 0x1c, 0x03, 0xa3])) + TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0x15, 0xc4, 0x15, 0xb5, 0xc4, 0x1c, 0x03, 0xa3])) .long() .toString(), ).toEqual('-6700480189419895787') }) it('should read float', () => { - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0x80, 0x3f])).float()).toBeCloseTo(1, 0.001) - expect(TlBinaryReader.manual(new Uint8Array([0xb6, 0xf3, 0x9d, 0x3f])).float()).toBeCloseTo(1.234, 0.001) - expect(TlBinaryReader.manual(new Uint8Array([0xfa, 0x7e, 0x2a, 0x3f])).float()).toBeCloseTo(0.666, 0.001) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0x80, 0x3f])).float()).toBeCloseTo( + 1, + 0.001, + ) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0xb6, 0xf3, 0x9d, 0x3f])).float()).toBeCloseTo( + 1.234, + 0.001, + ) + expect(TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0xfa, 0x7e, 0x2a, 0x3f])).float()).toBeCloseTo( + 0.666, + 0.001, + ) }) it('should read double', () => { - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0, 0, 0, 0, 0xf0, 0x3f])).double()).toBeCloseTo(1, 0.001) - expect(TlBinaryReader.manual(new Uint8Array([0, 0, 0, 0, 0, 0, 0x25, 0x40])).double()).toBeCloseTo(10.5, 0.001) expect( - TlBinaryReader.manual(new Uint8Array([0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0x21, 0x40])).double(), + TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0, 0, 0, 0, 0xf0, 0x3f])).double(), + ).toBeCloseTo(1, 0.001) + expect( + TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([0, 0, 0, 0, 0, 0, 0x25, 0x40])).double(), + ).toBeCloseTo(10.5, 0.001) + expect( + TlBinaryReader.manual( + defaultTlPlatform, + new Uint8Array([0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0x21, 0x40]), + ).double(), ).toBeCloseTo(8.8, 0.001) }) it('should read raw bytes', () => { - expect([...TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).raw(2)]).toEqual([1, 2]) - expect([...TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).raw()]).toEqual([1, 2, 3, 4]) - expect([...TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).raw(0)]).toEqual([]) + expect([...TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).raw(2)]).toEqual([1, 2]) + expect([...TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).raw()]).toEqual([1, 2, 3, 4]) + expect([...TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).raw(0)]).toEqual([]) }) it('should move cursor', () => { - const reader = TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])) + const reader = TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])) reader.int() expect(reader.pos).toEqual(4) @@ -116,10 +145,10 @@ describe('TlBinaryReader', () => { }) it('should read tg-encoded bytes', () => { - expect([...TlBinaryReader.manual(new Uint8Array([1, 2, 3, 4])).bytes()]).toEqual([2]) + expect([...TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([1, 2, 3, 4])).bytes()]).toEqual([2]) const random250bytes = randomBytes(250) - let reader = TlBinaryReader.manual(new Uint8Array([250, ...random250bytes, 0, 0, 0, 0, 0])) + let reader = TlBinaryReader.manual(defaultTlPlatform, new Uint8Array([250, ...random250bytes, 0, 0, 0, 0, 0])) expect([...reader.bytes()]).toEqual([...random250bytes]) expect(reader.pos).toEqual(252) @@ -128,7 +157,7 @@ describe('TlBinaryReader', () => { buffer[0] = 254 new DataView(buffer.buffer).setUint32(1, 1000, true) buffer.set(random1000bytes, 4) - reader = TlBinaryReader.manual(buffer) + reader = TlBinaryReader.manual(defaultTlPlatform, buffer) expect([...reader.bytes()]).toEqual([...random1000bytes]) expect(reader.pos).toEqual(1004) }) @@ -170,7 +199,7 @@ describe('TlBinaryReader', () => { 0x00, 0x00, // int32 2 ]) - const reader = new TlBinaryReader(stubObjectsMap, buffer) + const reader = new TlBinaryReader(defaultTlPlatform, stubObjectsMap, buffer) const deadBeef = reader.object() expect(deadBeef).toEqual({ a: 1, b: 42 }) @@ -232,7 +261,7 @@ describe('TlBinaryReader', () => { 0x00, 0x00, // int32 2 ]) - const reader = new TlBinaryReader(stubObjectsMap, buffer) + const reader = new TlBinaryReader(defaultTlPlatform, stubObjectsMap, buffer) const vector = reader.vector() expect(vector).toEqual([{ a: 1, b: 42 }, 2, { vec: [1, 2] }]) @@ -268,7 +297,7 @@ describe('TlBinaryReader', () => { serverPublicKeyFingerprints: [Long.fromString('c3b42b026ce86b21', false, 16)], } - const r = new TlBinaryReader(map, hexDecodeToBuffer(input)) + const r = new TlBinaryReader(defaultTlPlatform, map, hexDecodeToBuffer(input)) expect(r.long().toString()).toEqual('0') // authKeyId expect(r.long().toString(16)).toEqual('51E57AC91E83C801'.toLowerCase()) // messageId expect(r.uint()).toEqual(64) // messageLength diff --git a/packages/tl-runtime/src/reader.ts b/packages/tl-runtime/src/reader.ts index faa247c8..3bffd3bf 100644 --- a/packages/tl-runtime/src/reader.ts +++ b/packages/tl-runtime/src/reader.ts @@ -1,7 +1,6 @@ import Long from 'long' -import { hexEncode } from './encodings/hex.js' -import { utf8Decode } from './encodings/utf8.js' +import { ITlPlatform } from './platform.js' const TWO_PWR_32_DBL = (1 << 16) * (1 << 16) @@ -39,6 +38,7 @@ export class TlBinaryReader { * @param start Position to start reading from */ constructor( + readonly platform: ITlPlatform, readonly objectsMap: TlReaderMap | undefined, data: ArrayBuffer, start = 0, @@ -60,8 +60,8 @@ export class TlBinaryReader { * @param data Buffer to read from * @param start Position to start reading from */ - static manual(data: ArrayBuffer, start = 0): TlBinaryReader { - return new TlBinaryReader(undefined, data, start) + static manual(platform: ITlPlatform, data: ArrayBuffer, start = 0): TlBinaryReader { + return new TlBinaryReader(platform, undefined, data, start) } /** @@ -71,8 +71,8 @@ export class TlBinaryReader { * @param data Buffer to read from * @param start Position to start reading from */ - static deserializeObject(objectsMap: TlReaderMap, data: Uint8Array, start = 0): T { - return new TlBinaryReader(objectsMap, data, start).object() as T + static deserializeObject(platform: ITlPlatform, objectsMap: TlReaderMap, data: Uint8Array, start = 0): T { + return new TlBinaryReader(platform, objectsMap, data, start).object() as T } int(): number { @@ -174,7 +174,7 @@ export class TlBinaryReader { } string(): string { - return utf8Decode(this.bytes()) + return this.platform.utf8Decode(this.bytes()) } object(id = this.uint()): unknown { @@ -197,7 +197,7 @@ export class TlBinaryReader { // mtproto sucks and there's no way we can just skip it this.seek(-4) const pos = this.pos - const error = new TypeError(`Unknown object id: 0x${id.toString(16)}. Content: ${hexEncode(this.raw())}`) + const error = new TypeError(`Unknown object id: 0x${id.toString(16)}`) this.pos = pos throw error } diff --git a/packages/tl-runtime/src/writer.test.ts b/packages/tl-runtime/src/writer.test.ts index 92c8531e..1132b3ab 100644 --- a/packages/tl-runtime/src/writer.test.ts +++ b/packages/tl-runtime/src/writer.test.ts @@ -2,9 +2,13 @@ import Long from 'long' import { describe, expect, it } from 'vitest' -import { hexDecodeToBuffer, hexEncode } from '../src/encodings/hex.js' +import { defaultTlPlatform } from './platform.test-utils.js' import { TlBinaryWriter, TlSerializationCounter, TlWriterMap } 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) => parseInt(byte, 16))) + let randomBytes: (n: number) => Uint8Array if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { @@ -20,7 +24,7 @@ if (import.meta.env.TEST_ENV === 'node' || import.meta.env.TEST_ENV === 'bun') { describe('TlBinaryWriter', () => { const testSingleMethod = (size: number, fn: (w: TlBinaryWriter) => void, map?: TlWriterMap): string => { - const w = TlBinaryWriter.alloc(map, size) + const w = TlBinaryWriter.alloc(defaultTlPlatform, map, size) fn(w) expect(w.pos).toEqual(size) @@ -124,8 +128,8 @@ describe('TlBinaryWriter', () => { } const length = - TlSerializationCounter.countNeededBytes(stubObjectsMap, object1) + - TlSerializationCounter.countNeededBytes(stubObjectsMap, object2) + TlSerializationCounter.countNeededBytes(defaultTlPlatform, stubObjectsMap, object1) + + TlSerializationCounter.countNeededBytes(defaultTlPlatform, stubObjectsMap, object2) expect(length).toEqual(20) expect( @@ -156,9 +160,9 @@ describe('TlBinaryWriter', () => { } const length = - TlSerializationCounter.countNeededBytes(stubObjectsMap, object1) + - TlSerializationCounter.countNeededBytes(stubObjectsMap, object2) + - TlSerializationCounter.countNeededBytes(stubObjectsMap, object3) + + TlSerializationCounter.countNeededBytes(defaultTlPlatform, stubObjectsMap, object1) + + TlSerializationCounter.countNeededBytes(defaultTlPlatform, stubObjectsMap, object2) + + TlSerializationCounter.countNeededBytes(defaultTlPlatform, stubObjectsMap, object3) + 8 // because technically in tl vector can't be top-level, but whatever :shrug: expect(length).toEqual(48) @@ -201,7 +205,7 @@ describe('TlBinaryWriter', () => { const length = 20 + // mtproto header - TlSerializationCounter.countNeededBytes(map, resPq) + TlSerializationCounter.countNeededBytes(defaultTlPlatform, map, resPq) expect(length).toEqual(expected.length / 2) expect( diff --git a/packages/tl-runtime/src/writer.ts b/packages/tl-runtime/src/writer.ts index 72345551..b7a03e63 100644 --- a/packages/tl-runtime/src/writer.ts +++ b/packages/tl-runtime/src/writer.ts @@ -1,6 +1,6 @@ import Long from 'long' -import { byteLengthUtf8, utf8EncodeToBuffer } from './encodings/utf8.js' +import { ITlPlatform } from './platform.js' const TWO_PWR_32_DBL = (1 << 16) * (1 << 16) @@ -27,7 +27,10 @@ export class TlSerializationCounter { /** * @param objectMap Writers map */ - constructor(readonly objectMap: TlWriterMap) {} + constructor( + readonly platform: ITlPlatform, + readonly objectMap: TlWriterMap, + ) {} /** * Count bytes required to serialize the given object. @@ -35,8 +38,8 @@ export class TlSerializationCounter { * @param objectMap Writers map * @param obj Object to count bytes for */ - static countNeededBytes(objectMap: TlWriterMap, obj: { _: string }): number { - const cnt = new TlSerializationCounter(objectMap) + static countNeededBytes(platform: ITlPlatform, objectMap: TlWriterMap, obj: { _: string }): number { + const cnt = new TlSerializationCounter(platform, objectMap) cnt.object(obj) return cnt.count @@ -115,7 +118,7 @@ export class TlSerializationCounter { } string(val: string): void { - const length = byteLengthUtf8(val) + const length = this.platform.utf8ByteLength(val) this.count += TlSerializationCounter.countBytesOverhead(length) + length } @@ -148,6 +151,7 @@ export class TlBinaryWriter { * @param start Position to start writing at */ constructor( + readonly platform: ITlPlatform, readonly objectMap: TlWriterMap | undefined, data: ArrayBuffer, start = 0, @@ -169,8 +173,8 @@ export class TlBinaryWriter { * @param objectMap Writers map * @param size Size of the writer's buffer */ - static alloc(objectMap: TlWriterMap | undefined, size: number): TlBinaryWriter { - return new TlBinaryWriter(objectMap, new ArrayBuffer(size)) + static alloc(platform: ITlPlatform, objectMap: TlWriterMap | undefined, size: number): TlBinaryWriter { + return new TlBinaryWriter(platform, objectMap, new ArrayBuffer(size)) } /** @@ -179,10 +183,10 @@ export class TlBinaryWriter { * @param buffer Buffer to write to, or its size * @param start Position to start writing at */ - static manual(buffer: ArrayBuffer | number, start = 0): TlBinaryWriter { + static manual(platform: ITlPlatform, buffer: ArrayBuffer | number, start = 0): TlBinaryWriter { if (typeof buffer === 'number') buffer = new ArrayBuffer(buffer) - return new TlBinaryWriter(undefined, buffer, start) + return new TlBinaryWriter(platform, undefined, buffer, start) } /** @@ -192,12 +196,18 @@ export class TlBinaryWriter { * @param obj Object to serialize * @param knownSize In case the size is known, pass it here */ - static serializeObject(objectMap: TlWriterMap, obj: { _: string }, knownSize = -1): Uint8Array { + static serializeObject( + platform: ITlPlatform, + objectMap: TlWriterMap, + obj: { _: string }, + knownSize = -1, + ): Uint8Array { if (knownSize === -1) { - knownSize = objectMap._staticSize[obj._] || TlSerializationCounter.countNeededBytes(objectMap, obj) + knownSize = + objectMap._staticSize[obj._] || TlSerializationCounter.countNeededBytes(platform, objectMap, obj) } - const writer = TlBinaryWriter.alloc(objectMap, knownSize) + const writer = TlBinaryWriter.alloc(platform, objectMap, knownSize) writer.object(obj) @@ -298,7 +308,7 @@ export class TlBinaryWriter { } string(val: string): void { - this.bytes(utf8EncodeToBuffer(val)) + this.bytes(this.platform.utf8Encode(val)) } // hot path, avoid additional runtime checks