chore: remove platform-specific code from tl-runtime
This commit is contained in:
parent
930d0558f3
commit
806c62bda8
18 changed files with 125 additions and 485 deletions
|
@ -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": {
|
||||
".": {
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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')
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
|
@ -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<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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export * from './base64.js'
|
||||
export * from './hex.js'
|
||||
export * from './utf8.js'
|
|
@ -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)
|
||||
})
|
||||
})
|
|
@ -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')
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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'
|
||||
|
|
9
packages/tl-runtime/src/platform.test-utils.ts
Normal file
9
packages/tl-runtime/src/platform.test-utils.ts
Normal file
|
@ -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,
|
||||
}
|
8
packages/tl-runtime/src/platform.ts
Normal file
8
packages/tl-runtime/src/platform.ts
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<T>(objectsMap: TlReaderMap, data: Uint8Array, start = 0): T {
|
||||
return new TlBinaryReader(objectsMap, data, start).object() as T
|
||||
static deserializeObject<T>(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
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue