feat: add utilities for read/write Uint8Array

This commit is contained in:
Simon Chan 2024-04-16 22:17:36 +08:00
parent d06b5f2ed6
commit dcda6459ac
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
66 changed files with 2039 additions and 287 deletions

View file

@ -191,7 +191,7 @@
"ignoreMissingScript": true, "ignoreMissingScript": true,
"allowWarningsInSuccessfulBuild": true, "allowWarningsInSuccessfulBuild": true,
"enableParallelism": true, "enableParallelism": true,
"incremental": true, // "incremental": true,
"safeForSimultaneousRushProcesses": true "safeForSimultaneousRushProcesses": true
}, },
{ {

View file

@ -62,15 +62,15 @@ importers:
'@yume-chan/event': '@yume-chan/event':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../event version: link:../event
'@yume-chan/no-data-view':
specifier: workspace:^0.0.23
version: link:../no-data-view
'@yume-chan/stream-extra': '@yume-chan/stream-extra':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../stream-extra version: link:../stream-extra
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -105,9 +105,6 @@ importers:
'@yume-chan/adb': '@yume-chan/adb':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../adb version: link:../adb
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@yume-chan/eslint-config': '@yume-chan/eslint-config':
specifier: workspace:^1.0.0 specifier: workspace:^1.0.0
@ -136,9 +133,6 @@ importers:
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@yume-chan/eslint-config': '@yume-chan/eslint-config':
specifier: workspace:^1.0.0 specifier: workspace:^1.0.0
@ -173,9 +167,6 @@ importers:
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -213,9 +204,6 @@ importers:
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@types/node': '@types/node':
specifier: ^20.12.7 specifier: ^20.12.7
@ -247,9 +235,6 @@ importers:
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -281,9 +266,6 @@ importers:
'@types/w3c-web-usb': '@types/w3c-web-usb':
specifier: ^1.0.10 specifier: ^1.0.10
version: 1.0.10 version: 1.0.10
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@yume-chan/eslint-config': '@yume-chan/eslint-config':
specifier: workspace:^1.0.0 specifier: workspace:^1.0.0
@ -296,10 +278,6 @@ importers:
version: 5.4.5 version: 5.4.5
../../libraries/dataview-bigint-polyfill: ../../libraries/dataview-bigint-polyfill:
dependencies:
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@yume-chan/eslint-config': '@yume-chan/eslint-config':
specifier: workspace:^1.0.0 specifier: workspace:^1.0.0
@ -322,9 +300,6 @@ importers:
'@yume-chan/async': '@yume-chan/async':
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -361,11 +336,40 @@ importers:
specifier: ^20.12.7 specifier: ^20.12.7
version: 20.12.7 version: 20.12.7
../../libraries/no-data-view:
devDependencies:
'@jest/globals':
specifier: ^30.0.0-alpha.3
version: 30.0.0-alpha.3
'@types/node':
specifier: ^20.12.7
version: 20.12.7
'@yume-chan/eslint-config':
specifier: workspace:^1.0.0
version: link:../../toolchain/eslint-config
'@yume-chan/tsconfig':
specifier: workspace:^1.0.0
version: link:../../toolchain/tsconfig
cross-env:
specifier: ^7.0.3
version: 7.0.3
jest:
specifier: ^30.0.0-alpha.3
version: 30.0.0-alpha.3(@types/node@20.12.7)
prettier:
specifier: ^3.2.5
version: 3.2.5
tinybench:
specifier: ^2.7.0
version: 2.7.0
ts-jest:
specifier: ^29.1.2
version: 29.1.2(jest@30.0.0-alpha.3)(typescript@5.4.5)
typescript:
specifier: ^5.4.5
version: 5.4.5
../../libraries/pcm-player: ../../libraries/pcm-player:
dependencies:
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -397,6 +401,9 @@ importers:
../../libraries/scrcpy: ../../libraries/scrcpy:
dependencies: dependencies:
'@yume-chan/no-data-view':
specifier: workspace:^0.0.23
version: link:../no-data-view
'@yume-chan/stream-extra': '@yume-chan/stream-extra':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../stream-extra version: link:../stream-extra
@ -449,9 +456,6 @@ importers:
tinyh264: tinyh264:
specifier: ^0.0.7 specifier: ^0.0.7
version: 0.0.7 version: 0.0.7
tslib:
specifier: ^2.6.2
version: 2.6.2
yuv-buffer: yuv-buffer:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
@ -489,6 +493,9 @@ importers:
'@yume-chan/event': '@yume-chan/event':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../event version: link:../event
'@yume-chan/no-data-view':
specifier: workspace:^0.0.23
version: link:../no-data-view
'@yume-chan/scrcpy': '@yume-chan/scrcpy':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../scrcpy version: link:../scrcpy
@ -498,9 +505,6 @@ importers:
'@yume-chan/stream-extra': '@yume-chan/stream-extra':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../stream-extra version: link:../stream-extra
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -535,9 +539,6 @@ importers:
'@yume-chan/struct': '@yume-chan/struct':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../struct version: link:../struct
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -566,12 +567,9 @@ importers:
../../libraries/struct: ../../libraries/struct:
dependencies: dependencies:
'@yume-chan/dataview-bigint-polyfill': '@yume-chan/no-data-view':
specifier: workspace:^0.0.23 specifier: workspace:^0.0.23
version: link:../dataview-bigint-polyfill version: link:../no-data-view
tslib:
specifier: ^2.6.2
version: 2.6.2
devDependencies: devDependencies:
'@jest/globals': '@jest/globals':
specifier: ^30.0.0-alpha.3 specifier: ^30.0.0-alpha.3
@ -4571,6 +4569,10 @@ packages:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: false dev: false
/tinybench@2.7.0:
resolution: {integrity: sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==}
dev: true
/tinyh264@0.0.7: /tinyh264@0.0.7:
resolution: {integrity: sha512-etkBRgYkSFBdAi2Cqk4sZgi+xWs/vhzNgvjO3z2i4WILeEmORiNqxuQ4URJatrWQ9LPNV3WPWAtzsh/LA/XL/g==} resolution: {integrity: sha512-etkBRgYkSFBdAi2Cqk4sZgi+xWs/vhzNgvjO3z2i4WILeEmORiNqxuQ4URJatrWQ9LPNV3WPWAtzsh/LA/XL/g==}
dev: false dev: false

View file

@ -1,5 +1,5 @@
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
{ {
"pnpmShrinkwrapHash": "5dfb0a8a0ad6b0505870eeb38140a9ba82571aee", "pnpmShrinkwrapHash": "0fb46a0dd9d3d20531a5eb59ce8ba9cacfe15a30",
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
} }

View file

@ -30,8 +30,7 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"@yume-chan/adb": "workspace:^0.0.23", "@yume-chan/adb": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/eslint-config": "workspace:^1.0.0",

View file

@ -34,8 +34,7 @@
"@types/w3c-web-usb": "^1.0.10", "@types/w3c-web-usb": "^1.0.10",
"@yume-chan/adb": "workspace:^0.0.23", "@yume-chan/adb": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/eslint-config": "workspace:^1.0.0",

View file

@ -37,8 +37,7 @@
"@yume-chan/event": "workspace:^0.0.23", "@yume-chan/event": "workspace:^0.0.23",
"@yume-chan/scrcpy": "workspace:^0.0.23", "@yume-chan/scrcpy": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -34,8 +34,7 @@
"dependencies": { "dependencies": {
"@yume-chan/adb": "workspace:^0.0.23", "@yume-chan/adb": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.7", "@types/node": "^20.12.7",

View file

@ -35,9 +35,9 @@
"@yume-chan/async": "^2.2.0", "@yume-chan/async": "^2.2.0",
"@yume-chan/dataview-bigint-polyfill": "workspace:^0.0.23", "@yume-chan/dataview-bigint-polyfill": "workspace:^0.0.23",
"@yume-chan/event": "workspace:^0.0.23", "@yume-chan/event": "workspace:^0.0.23",
"@yume-chan/no-data-view": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -97,10 +97,7 @@ export enum AdbSyncSendV2Flags {
* 4 * 4
*/ */
Zstd = 1 << 2, Zstd = 1 << 2,
/** DryRun = 0x80000000,
* 0x80000000
*/
DryRun = (1 << 31) >>> 0,
} }
export interface AdbSyncPushV2Options extends AdbSyncPushV1Options { export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {

View file

@ -1,7 +1,8 @@
import { import {
getBigUint64, getUint64BigEndian,
setBigUint64, setInt64BigEndian,
} from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js"; setInt64LittleEndian,
} from "@yume-chan/no-data-view";
/** /**
* Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is * Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
@ -11,7 +12,7 @@ import {
* @param byteOffset The place in the buffer at which the value should be retrieved. * @param byteOffset The place in the buffer at which the value should be retrieved.
*/ */
export function getBigUint( export function getBigUint(
dataView: DataView, array: Uint8Array,
byteOffset: number, byteOffset: number,
length: number, length: number,
): bigint { ): bigint {
@ -22,8 +23,8 @@ export function getBigUint(
for (let i = byteOffset; i < byteOffset + length; i += 8) { for (let i = byteOffset; i < byteOffset + length; i += 8) {
result <<= 64n; result <<= 64n;
const value = getBigUint64(dataView, i, false); const value = getUint64BigEndian(array, i);
result += value; result |= value;
} }
return result; return result;
@ -37,7 +38,7 @@ export function getBigUint(
* otherwise a little-endian value should be written. * otherwise a little-endian value should be written.
*/ */
export function setBigUint( export function setBigUint(
dataView: DataView, array: Uint8Array,
byteOffset: number, byteOffset: number,
value: bigint, value: bigint,
littleEndian?: boolean, littleEndian?: boolean,
@ -46,7 +47,7 @@ export function setBigUint(
if (littleEndian) { if (littleEndian) {
while (value > 0n) { while (value > 0n) {
setBigUint64(dataView, byteOffset, value, true); setInt64LittleEndian(array, byteOffset, value);
byteOffset += 8; byteOffset += 8;
value >>= 64n; value >>= 64n;
} }
@ -60,7 +61,7 @@ export function setBigUint(
} }
for (let i = uint64Array.length - 1; i >= 0; i -= 1) { for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
setBigUint64(dataView, byteOffset, uint64Array[i]!, false); setInt64BigEndian(array, byteOffset, uint64Array[i]!);
byteOffset += 8; byteOffset += 8;
} }
} }
@ -95,9 +96,8 @@ const RsaPrivateKeyDOffset = 303;
const RsaPrivateKeyDLength = 2048 / 8; const RsaPrivateKeyDLength = 2048 / 8;
export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] { export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
const view = new DataView(key.buffer, key.byteOffset, key.byteLength); const n = getBigUint(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
const n = getBigUint(view, RsaPrivateKeyNOffset, RsaPrivateKeyNLength); const d = getBigUint(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
return [n, d]; return [n, d];
} }
@ -193,12 +193,12 @@ export function adbGeneratePublicKey(
outputOffset += 4; outputOffset += 4;
// Write n // Write n
setBigUint(outputView, outputOffset, n, true); setBigUint(output, outputOffset, n, true);
outputOffset += 256; outputOffset += 256;
// Calculate rr = (2^(rsa_size)) ^ 2 mod n // Calculate rr = (2^(rsa_size)) ^ 2 mod n
const rr = 2n ** 4096n % n; const rr = 2n ** 4096n % n;
outputOffset += setBigUint(outputView, outputOffset, rr, true); outputOffset += setBigUint(output, outputOffset, rr, true);
// exponent // exponent
outputView.setUint32(outputOffset, 65537, true); outputView.setUint32(outputOffset, 65537, true);
@ -305,12 +305,11 @@ export function rsaSign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
// Encryption // Encryption
// signature = padded ** d % n // signature = padded ** d % n
const view = new DataView(padded.buffer); const signature = powMod(getBigUint(padded, 0, padded.length), d, n);
const signature = powMod(getBigUint(view, 0, view.byteLength), d, n);
// `padded` is not used anymore, // `padded` is not used anymore,
// re-use the buffer to store the result // re-use the buffer to store the result
setBigUint(view, 0, signature, false); setBigUint(padded, 0, signature, false);
return padded; return padded;
} }

View file

@ -3,6 +3,10 @@ import {
PromiseResolver, PromiseResolver,
delay, delay,
} from "@yume-chan/async"; } from "@yume-chan/async";
import {
getUint32BigEndian,
setUint32LittleEndian,
} from "@yume-chan/no-data-view";
import { import {
AbortController, AbortController,
Consumable, Consumable,
@ -10,7 +14,7 @@ import {
type ReadableWritablePair, type ReadableWritablePair,
type WritableStreamDefaultWriter, type WritableStreamDefaultWriter,
} from "@yume-chan/stream-extra"; } from "@yume-chan/stream-extra";
import { EMPTY_UINT8_ARRAY, NumberFieldType } from "@yume-chan/struct"; import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js"; import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
import { decodeUtf8, encodeUtf8 } from "../utils/index.js"; import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
@ -189,7 +193,7 @@ export class AdbPacketDispatcher implements Closeable {
"Invalid OKAY packet. Payload size should be 4", "Invalid OKAY packet. Payload size should be 4",
); );
} }
ackBytes = NumberFieldType.Uint32.deserialize(packet.payload, true); ackBytes = getUint32BigEndian(packet.payload, 0);
} else { } else {
if (packet.payload.byteLength !== 0) { if (packet.payload.byteLength !== 0) {
throw new Error( throw new Error(
@ -228,10 +232,7 @@ export class AdbPacketDispatcher implements Closeable {
let payload: Uint8Array; let payload: Uint8Array;
if (this.options.initialDelayedAckBytes !== 0) { if (this.options.initialDelayedAckBytes !== 0) {
payload = new Uint8Array(4); payload = new Uint8Array(4);
payload[0] = ackBytes & 0xff; setUint32LittleEndian(payload, 0, ackBytes);
payload[1] = (ackBytes >> 8) & 0xff;
payload[2] = (ackBytes >> 16) & 0xff;
payload[3] = (ackBytes >> 24) & 0xff;
} else { } else {
payload = EMPTY_UINT8_ARRAY; payload = EMPTY_UINT8_ARRAY;
} }

View file

@ -17,7 +17,6 @@ import type {
ValueOrPromise, ValueOrPromise,
} from "@yume-chan/struct"; } from "@yume-chan/struct";
import { import {
BigIntFieldType,
EMPTY_UINT8_ARRAY, EMPTY_UINT8_ARRAY,
SyncPromise, SyncPromise,
decodeUtf8, decodeUtf8,
@ -29,6 +28,7 @@ import { AdbBanner } from "../banner.js";
import type { AdbFeature } from "../features.js"; import type { AdbFeature } from "../features.js";
import { NOOP, hexToNumber, numberToHex, unreachable } from "../utils/index.js"; import { NOOP, hexToNumber, numberToHex, unreachable } from "../utils/index.js";
import { getUint64LittleEndian } from "@yume-chan/no-data-view";
import { AdbServerTransport } from "./transport.js"; import { AdbServerTransport } from "./transport.js";
export interface AdbServerConnectionOptions { export interface AdbServerConnectionOptions {
@ -391,13 +391,7 @@ export class AdbServerClient {
try { try {
if (transportId === undefined) { if (transportId === undefined) {
const array = await readable.readExactly(8); const array = await readable.readExactly(8);
// TODO: switch to a more performant algorithm. transportId = getUint64LittleEndian(array, 0);
const dataView = new DataView(
array.buffer,
array.byteOffset,
array.byteLength,
);
transportId = BigIntFieldType.Uint64.getter(dataView, 0, true);
} }
await AdbServerClient.readOkay(readable); await AdbServerClient.readOkay(readable);

View file

@ -34,8 +34,7 @@
"dependencies": { "dependencies": {
"@yume-chan/adb": "workspace:^0.0.23", "@yume-chan/adb": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -31,8 +31,7 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"@types/w3c-web-usb": "^1.0.10", "@types/w3c-web-usb": "^1.0.10"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/eslint-config": "workspace:^1.0.0",

View file

@ -35,9 +35,7 @@
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4", "lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {},
"tslib": "^2.6.2"
},
"devDependencies": { "devDependencies": {
"@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/eslint-config": "workspace:^1.0.0",
"@yume-chan/tsconfig": "workspace:^1.0.0", "@yume-chan/tsconfig": "workspace:^1.0.0",

View file

@ -33,8 +33,7 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"@yume-chan/async": "^2.2.0", "@yume-chan/async": "^2.2.0"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -0,0 +1,19 @@
.rush
# Test
coverage
**/*.spec.ts
**/*.spec.js
**/*.spec.js.map
**/__helpers__
jest.config.js
.eslintrc.cjs
tsconfig.json
tsconfig.test.json
# Logs
*.log
benchmark.js
benchmark.md

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2023 Simon Chan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,21 @@
# @yume-chan/no-data-view
Plain methods to avoid creating `DataView`s.
## Why?
If you have many short `Uint8Array`s and you want to read numbers from them, creating `DataView`s for each of them is not a good idea:
Each `DataView` needs to allocate some memory, it impacts the performance, increases the memory usage, and adds GC pressure.
(If you are using `DataView`s for large `ArrayBuffer`s, it's fine)
## How does it work?
This package provides a set of methods to read/write numbers from `Uint8Array`s without creating `DataView`s.
Because they are very basic number operations, the performance between a JavaScript implementation and the native `DataView` is nearly identical.
(Except for `getBigUint64` and `setBigUint64`, Chrome uses an inefficient implementation, so this JavaScript implementation is even faster than the native one).
Check the [benchmark](./benchmark.md) for more details.

View file

@ -0,0 +1,243 @@
/// <reference types="node" />
import { once } from "events";
import { createWriteStream } from "fs";
import { Bench } from "tinybench";
import {
getInt16LittleEndian,
getInt32LittleEndian,
getInt64LittleEndian,
getUint16,
getUint16BigEndian,
getUint16LittleEndian,
getUint32LittleEndian,
getUint64BigEndian,
getUint64LittleEndian,
} from "./esm/index.js";
console.log(
"Adjust priority for process",
process.pid,
", then press Enter to start...",
);
await once(process.stdin, "data");
console.log("Starting benchmark");
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
const buffer = data.buffer;
const dataView = new DataView(data.buffer);
const output = createWriteStream("benchmark.md");
/**
*
* @param {string} name
* @param {Bench} bench
*/
function print(name, bench) {
console.log();
console.log(name);
console.table(bench.table());
output.write("## " + name + "\n\n");
output.write(
"| " +
["Name", "Ops/s", "Avg Time", "Compare"].join(" | ") +
" |\n" +
"|---|---|---|---|\n",
);
let firstResultHz;
for (const task of bench.tasks) {
const result = task.result;
if (!result) {
return;
}
let compare = 1;
if (firstResultHz) {
compare = result.hz / firstResultHz;
} else {
firstResultHz = result.hz;
}
output.write(
"| " +
[
task.name,
(result.hz | 0).toLocaleString("en-US"),
(result.mean * 1_000_000).toLocaleString("en-US") + " ns",
(compare * 100).toFixed(2) + "%",
].join(" | ") +
" |\n",
);
}
output.write("\n");
}
const only = process.argv[2];
/**
*
* @param {string} name
* @param {(bench:Bench)=>void} callback
* @returns
*/
async function runBenchmark(name, callback) {
if (only && only !== name) {
return;
}
const bench = new Bench();
callback(bench);
await bench.warmup();
await bench.run();
print(name, bench);
}
await runBenchmark("getUint16LittleEndian", (bench) => {
bench
.add("getUint16LittleEndian", () => {
getUint16LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getUint16(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getUint16(0, true);
});
});
await runBenchmark("getUint16BigEndian", (bench) => {
bench
.add("getUint16BigEndian", () => {
getUint16BigEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getUint16(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getUint16(0, true);
});
});
await runBenchmark("getUint16", (bench) => {
let littleEndian = true;
bench
.add(
"getUint16",
() => {
getUint16(data, 0, littleEndian);
},
{
beforeEach: () => {
littleEndian = Math.random() > 0.5;
},
},
)
.add(
"cached DataView",
() => {
dataView.getUint16(0, true);
},
{
beforeEach: () => {
littleEndian = Math.random() > 0.5;
},
},
)
.add(
"new DataView",
() => {
new DataView(buffer).getUint16(0, true);
},
{
beforeEach: () => {
littleEndian = Math.random() > 0.5;
},
},
);
});
await runBenchmark("getInt16LittleEndian", (bench) => {
bench
.add("getInt16LittleEndian", () => {
getInt16LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getInt16(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getInt16(0, true);
});
});
await runBenchmark("getUint32LittleEndian", (bench) => {
bench
.add("getUint32LittleEndian", () => {
getUint32LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getUint32(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getUint32(0, true);
});
});
await runBenchmark("getInt32LittleEndian", (bench) => {
bench
.add("getInt32LittleEndian", () => {
getInt32LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getInt32(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getInt32(0, true);
});
});
await runBenchmark("getUint64LittleEndian", (bench) => {
bench
.add("getUint64LittleEndian", () => {
getUint64LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getBigUint64(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getBigUint64(0, true);
});
});
await runBenchmark("getUint64BigEndian", (bench) => {
bench
.add("getUint64BigEndian", () => {
getUint64BigEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getBigUint64(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getBigUint64(0, true);
});
});
await runBenchmark("getInt64LittleEndian", (bench) => {
bench
.add("getInt64LittleEndian", () => {
getInt64LittleEndian(data, 0);
})
.add("cached DataView", () => {
dataView.getBigInt64(0, true);
})
.add("new DataView", () => {
new DataView(buffer).getBigInt64(0, true);
});
});
output.close(() => {
process.exit(0);
});

View file

@ -0,0 +1,71 @@
## getUint16LittleEndian
| Name | Ops/s | Avg Time | Compare |
| --------------------- | ---------- | ---------- | ------- |
| getUint16LittleEndian | 19,892,072 | 50.271 ns | 100.00% |
| cached DataView | 18,810,767 | 53.161 ns | 94.56% |
| new DataView | 7,585,290 | 131.834 ns | 38.13% |
## getUint16BigEndian
| Name | Ops/s | Avg Time | Compare |
| ------------------ | ---------- | ---------- | ------- |
| getUint16BigEndian | 19,553,782 | 51.141 ns | 100.00% |
| cached DataView | 19,919,910 | 50.201 ns | 101.87% |
| new DataView | 7,843,575 | 127.493 ns | 40.11% |
## getUint16
| Name | Ops/s | Avg Time | Compare |
| --------------- | ---------- | ---------- | ------- |
| getUint16 | 18,571,186 | 53.847 ns | 100.00% |
| cached DataView | 19,434,597 | 51.455 ns | 104.65% |
| new DataView | 7,480,502 | 133.681 ns | 40.28% |
## getInt16LittleEndian
| Name | Ops/s | Avg Time | Compare |
| -------------------- | ---------- | --------- | ------- |
| getInt16LittleEndian | 19,719,781 | 50.71 ns | 100.00% |
| cached DataView | 20,285,727 | 49.296 ns | 102.87% |
| new DataView | 7,963,668 | 125.57 ns | 40.38% |
## getUint32LittleEndian
| Name | Ops/s | Avg Time | Compare |
| --------------------- | ---------- | ---------- | ------- |
| getUint32LittleEndian | 20,037,121 | 49.907 ns | 100.00% |
| cached DataView | 19,750,168 | 50.632 ns | 98.57% |
| new DataView | 8,127,370 | 123.041 ns | 40.56% |
## getInt32LittleEndian
| Name | Ops/s | Avg Time | Compare |
| -------------------- | ---------- | ---------- | ------- |
| getInt32LittleEndian | 19,955,247 | 50.112 ns | 100.00% |
| cached DataView | 19,721,023 | 50.707 ns | 98.83% |
| new DataView | 7,888,401 | 126.768 ns | 39.53% |
## getUint64LittleEndian
| Name | Ops/s | Avg Time | Compare |
| --------------------- | ---------- | ---------- | ------- |
| getUint64LittleEndian | 19,438,620 | 51.444 ns | 100.00% |
| cached DataView | 16,597,547 | 60.25 ns | 85.38% |
| new DataView | 6,957,942 | 143.721 ns | 35.79% |
## getUint64BigEndian
| Name | Ops/s | Avg Time | Compare |
| ------------------ | ---------- | ---------- | ------- |
| getUint64BigEndian | 20,075,843 | 49.811 ns | 100.00% |
| cached DataView | 16,787,123 | 59.569 ns | 83.62% |
| new DataView | 7,469,821 | 133.872 ns | 37.21% |
## getInt64LittleEndian
| Name | Ops/s | Avg Time | Compare |
| -------------------- | ---------- | --------- | ------- |
| getInt64LittleEndian | 20,167,285 | 49.585 ns | 100.00% |
| cached DataView | 17,129,268 | 58.38 ns | 84.94% |
| new DataView | 7,605,130 | 131.49 ns | 37.71% |

View file

@ -0,0 +1,14 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
export default {
preset: "ts-jest/presets/default-esm",
extensionsToTreatAsEsm: [".ts"],
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{ tsconfig: "tsconfig.test.json", useESM: true },
],
},
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
},
};

View file

@ -0,0 +1,45 @@
{
"name": "@yume-chan/no-data-view",
"version": "0.0.23",
"description": "Plain methods to avoid creating `DataView`s",
"keywords": [],
"license": "MIT",
"author": {
"name": "Simon Chan",
"email": "cnsimonchan@live.com",
"url": "https://chensi.moe/blog"
},
"homepage": "https://github.com/yume-chan/ya-webadb/tree/main/packages/no-data-view#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/yume-chan/ya-webadb.git",
"directory": "packages/no-data-view"
},
"bugs": {
"url": "https://github.com/yume-chan/ya-webadb/issues"
},
"type": "module",
"main": "esm/index.js",
"types": "esm/index.d.ts",
"scripts": {
"benchmark": "node benchmark.js",
"build": "tsc -b tsconfig.build.json",
"build:watch": "tsc -b tsconfig.build.json",
"test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --no-warnings\" TS_JEST_DISABLE_VER_CHECKER=true jest --coverage",
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
"prepublishOnly": "npm run build"
},
"dependencies": {},
"devDependencies": {
"@types/node": "^20.12.7",
"@jest/globals": "^30.0.0-alpha.3",
"@yume-chan/eslint-config": "workspace:^1.0.0",
"@yume-chan/tsconfig": "workspace:^1.0.0",
"cross-env": "^7.0.3",
"jest": "^30.0.0-alpha.3",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"tinybench": "^2.7.0",
"typescript": "^5.4.5"
}
}

View file

@ -0,0 +1,7 @@
export * from "./int16.js";
export * from "./int32.js";
export * from "./int64.js";
export * from "./int8.js";
export * from "./uint16.js";
export * from "./uint32.js";
export * from "./uint64.js";

View file

@ -0,0 +1,149 @@
import { describe, expect, it } from "@jest/globals";
import {
getInt16,
getInt16BigEndian,
getInt16LittleEndian,
setInt16,
setInt16BigEndian,
setInt16LittleEndian,
} from "./int16.js";
describe("getInt16", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 128]);
expect(getInt16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 127]);
expect(getInt16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0]);
expect(getInt16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2]);
expect(getInt16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([128, 0]);
expect(getInt16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([127, 255]);
expect(getInt16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0]);
expect(getInt16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt16(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2]);
expect(getInt16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2]);
expect(getInt16(array, 0, false)).toBe(
new DataView(array.buffer).getInt16(0, false),
);
expect(getInt16(array, 0, true)).toBe(
new DataView(array.buffer).getInt16(0, true),
);
});
});
describe("setInt16", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, -0x8000, true);
const actual = new Uint8Array(2);
setInt16LittleEndian(actual, 0, -0x8000);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, 0x7fff, true);
const actual = new Uint8Array(2);
setInt16LittleEndian(actual, 0, 0x7fff);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, 0, true);
const actual = new Uint8Array(2);
setInt16LittleEndian(actual, 0, 0);
expect(actual).toEqual(expected);
});
});
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, -0x8000, false);
const actual = new Uint8Array(2);
setInt16BigEndian(actual, 0, -0x8000);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, 0x7fff, false);
const actual = new Uint8Array(2);
setInt16BigEndian(actual, 0, 0x7fff);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, 0, false);
const actual = new Uint8Array(2);
setInt16BigEndian(actual, 0, 0);
expect(actual).toEqual(expected);
});
});
it("should work for selected endianness", () => {
const expected = new Uint8Array(2);
const actual = new Uint8Array(2);
new DataView(expected.buffer).setInt16(0, 0x7fff, false);
setInt16(actual, 0, 0x7fff, false);
expect(actual).toEqual(expected);
new DataView(expected.buffer).setInt16(0, 0x7fff, true);
setInt16(actual, 0, 0x7fff, true);
expect(actual).toEqual(expected);
});
});

View file

@ -0,0 +1,53 @@
export function getInt16LittleEndian(
buffer: Uint8Array,
offset: number,
): number {
return ((buffer[offset]! | (buffer[offset + 1]! << 8)) << 16) >> 16;
}
export function getInt16BigEndian(buffer: Uint8Array, offset: number): number {
return (((buffer[offset]! << 8) | buffer[offset + 1]!) << 16) >> 16;
}
export function getInt16(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
) {
return littleEndian
? ((buffer[offset]! | (buffer[offset + 1]! << 8)) << 16) >> 16
: (((buffer[offset]! << 8) | buffer[offset + 1]!) << 16) >> 16;
}
export function setInt16LittleEndian(
buffer: Uint8Array,
offset: number,
value: number,
): void {
buffer[offset] = value & 0xff;
buffer[offset + 1] = (value >> 8) & 0xff;
}
export function setInt16BigEndian(
buffer: Uint8Array,
offset: number,
value: number,
): void {
buffer[offset] = (value >> 8) & 0xff;
buffer[offset + 1] = value & 0xff;
}
export function setInt16(
buffer: Uint8Array,
offset: number,
value: number,
littleEndian: boolean,
): void {
if (littleEndian) {
buffer[offset] = value & 0xff;
buffer[offset + 1] = (value >> 8) & 0xff;
} else {
buffer[offset] = (value >> 8) & 0xff;
buffer[offset + 1] = value & 0xff;
}
}

View file

@ -0,0 +1,75 @@
import { describe, expect, it } from "@jest/globals";
import { getInt32, getInt32BigEndian, getInt32LittleEndian } from "./int32.js";
describe("getInt32", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 128]);
expect(getInt32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 255, 255, 127]);
expect(getInt32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 0]);
expect(getInt32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getInt32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([128, 0, 0, 0]);
expect(getInt32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([127, 255, 255, 255]);
expect(getInt32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 0]);
expect(getInt32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getInt32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getInt32(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getInt32(array, 0, false)).toBe(
new DataView(array.buffer).getInt32(0, false),
);
expect(getInt32(array, 0, true)).toBe(
new DataView(array.buffer).getInt32(0, true),
);
});
});

View file

@ -0,0 +1,36 @@
export function getInt32LittleEndian(
buffer: Uint8Array,
offset: number,
): number {
return (
buffer[offset]! |
(buffer[offset + 1]! << 8) |
(buffer[offset + 2]! << 16) |
(buffer[offset + 3]! << 24)
);
}
export function getInt32BigEndian(buffer: Uint8Array, offset: number): number {
return (
(buffer[offset]! << 24) |
(buffer[offset + 1]! << 16) |
(buffer[offset + 2]! << 8) |
buffer[offset + 3]!
);
}
export function getInt32(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
) {
return littleEndian
? buffer[offset]! |
(buffer[offset + 1]! << 8) |
(buffer[offset + 2]! << 16) |
(buffer[offset + 3]! << 24)
: (buffer[offset]! << 24) |
(buffer[offset + 1]! << 16) |
(buffer[offset + 2]! << 8) |
buffer[offset + 3]!;
}

View file

@ -0,0 +1,181 @@
import { describe, expect, it } from "@jest/globals";
import {
getInt64,
getInt64BigEndian,
getInt64LittleEndian,
setInt64,
setInt64BigEndian,
setInt64LittleEndian,
} from "./int64.js";
describe("getInt64", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x80]);
expect(getInt64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
]);
expect(getInt64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
expect(getInt64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
]);
expect(getInt64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0x80, 0, 0, 0, 0, 0, 0, 0]);
expect(getInt64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
expect(getInt64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
expect(getInt64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
]);
expect(getInt64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigInt64(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
expect(getInt64(array, 0, false)).toBe(
new DataView(array.buffer).getBigInt64(0, false),
);
expect(getInt64(array, 0, true)).toBe(
new DataView(array.buffer).getBigInt64(0, true),
);
});
});
describe("setInt64", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(
0,
-0x8000_0000_0000_0000n,
true,
);
const actual = new Uint8Array(8);
setInt64LittleEndian(actual, 0, -0x8000_0000_0000_0000n);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(
0,
0x7fff_ffff_ffff_ffffn,
true,
);
const actual = new Uint8Array(8);
setInt64LittleEndian(actual, 0, 0x7fff_ffff_ffff_ffffn);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(0, 0n, true);
const actual = new Uint8Array(8);
setInt64LittleEndian(actual, 0, 0n);
expect(actual).toEqual(expected);
});
});
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(
0,
-0x8000_0000_0000_0000n,
false,
);
const actual = new Uint8Array(8);
setInt64BigEndian(actual, 0, -0x8000_0000_0000_0000n);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(
0,
0x7fff_ffff_ffff_ffffn,
false,
);
const actual = new Uint8Array(8);
setInt64BigEndian(actual, 0, 0x7fff_ffff_ffff_ffffn);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(0, 0n, false);
const actual = new Uint8Array(8);
setInt64BigEndian(actual, 0, 0n);
expect(actual).toEqual(expected);
});
});
it("should work for selected endianness", () => {
const expected = new Uint8Array(8);
const actual = new Uint8Array(8);
new DataView(expected.buffer).setBigInt64(
0,
0x7fff_ffff_ffff_ffffn,
false,
);
setInt64(actual, 0, 0x7fff_ffff_ffff_ffffn, false);
expect(actual).toEqual(expected);
new DataView(expected.buffer).setBigInt64(
0,
0x7fff_ffff_ffff_ffffn,
true,
);
setInt64(actual, 0, 0x7fff_ffff_ffff_ffffn, true);
expect(actual).toEqual(expected);
});
});

View file

@ -0,0 +1,109 @@
export function getInt64LittleEndian(
buffer: Uint8Array,
offset: number,
): bigint {
return (
BigInt(buffer[offset]!) |
(BigInt(buffer[offset + 1]!) << 8n) |
(BigInt(buffer[offset + 2]!) << 16n) |
(BigInt(buffer[offset + 3]!) << 24n) |
(BigInt(buffer[offset + 4]!) << 32n) |
(BigInt(buffer[offset + 5]!) << 40n) |
(BigInt(buffer[offset + 6]!) << 48n) |
(BigInt(buffer[offset + 7]! << 24) << 32n)
);
}
export function getInt64BigEndian(buffer: Uint8Array, offset: number): bigint {
return (
(BigInt(buffer[offset]! << 24) << 32n) |
(BigInt(buffer[offset + 1]!) << 48n) |
(BigInt(buffer[offset + 2]!) << 40n) |
(BigInt(buffer[offset + 3]!) << 32n) |
(BigInt(buffer[offset + 4]!) << 24n) |
(BigInt(buffer[offset + 5]!) << 16n) |
(BigInt(buffer[offset + 6]!) << 8n) |
BigInt(buffer[offset + 7]!)
);
}
export function getInt64(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
): bigint {
return littleEndian
? BigInt(buffer[offset]!) |
(BigInt(buffer[offset + 1]!) << 8n) |
(BigInt(buffer[offset + 2]!) << 16n) |
(BigInt(buffer[offset + 3]!) << 24n) |
(BigInt(buffer[offset + 4]!) << 32n) |
(BigInt(buffer[offset + 5]!) << 40n) |
(BigInt(buffer[offset + 6]!) << 48n) |
(BigInt(buffer[offset + 7]! << 24) << 32n)
: (BigInt(buffer[offset]! << 24) << 32n) |
(BigInt(buffer[offset + 1]!) << 48n) |
(BigInt(buffer[offset + 2]!) << 40n) |
(BigInt(buffer[offset + 3]!) << 32n) |
(BigInt(buffer[offset + 4]!) << 24n) |
(BigInt(buffer[offset + 5]!) << 16n) |
(BigInt(buffer[offset + 6]!) << 8n) |
BigInt(buffer[offset + 7]!);
}
export function setInt64LittleEndian(
buffer: Uint8Array,
offset: number,
value: bigint,
): void {
buffer[offset] = Number(value & 0xffn);
buffer[offset + 1] = Number((value >> 8n) & 0xffn);
buffer[offset + 2] = Number((value >> 16n) & 0xffn);
buffer[offset + 3] = Number((value >> 24n) & 0xffn);
buffer[offset + 4] = Number((value >> 32n) & 0xffn);
buffer[offset + 5] = Number((value >> 40n) & 0xffn);
buffer[offset + 6] = Number((value >> 48n) & 0xffn);
buffer[offset + 7] = Number((value >> 56n) & 0xffn);
}
export function setInt64BigEndian(
buffer: Uint8Array,
offset: number,
value: bigint,
): void {
buffer[offset] = Number((value >> 56n) & 0xffn);
buffer[offset + 1] = Number((value >> 48n) & 0xffn);
buffer[offset + 2] = Number((value >> 40n) & 0xffn);
buffer[offset + 3] = Number((value >> 32n) & 0xffn);
buffer[offset + 4] = Number((value >> 24n) & 0xffn);
buffer[offset + 5] = Number((value >> 16n) & 0xffn);
buffer[offset + 6] = Number((value >> 8n) & 0xffn);
buffer[offset + 7] = Number(value & 0xffn);
}
export function setInt64(
buffer: Uint8Array,
offset: number,
value: bigint,
littleEndian: boolean,
): void {
if (littleEndian) {
buffer[offset] = Number(value & 0xffn);
buffer[offset + 1] = Number((value >> 8n) & 0xffn);
buffer[offset + 2] = Number((value >> 16n) & 0xffn);
buffer[offset + 3] = Number((value >> 24n) & 0xffn);
buffer[offset + 4] = Number((value >> 32n) & 0xffn);
buffer[offset + 5] = Number((value >> 40n) & 0xffn);
buffer[offset + 6] = Number((value >> 48n) & 0xffn);
buffer[offset + 7] = Number((value >> 56n) & 0xffn);
} else {
buffer[offset] = Number((value >> 56n) & 0xffn);
buffer[offset + 1] = Number((value >> 48n) & 0xffn);
buffer[offset + 2] = Number((value >> 40n) & 0xffn);
buffer[offset + 3] = Number((value >> 32n) & 0xffn);
buffer[offset + 4] = Number((value >> 24n) & 0xffn);
buffer[offset + 5] = Number((value >> 16n) & 0xffn);
buffer[offset + 6] = Number((value >> 8n) & 0xffn);
buffer[offset + 7] = Number(value & 0xffn);
}
}

View file

@ -0,0 +1,45 @@
import { describe, expect, it } from "@jest/globals";
import { getInt8, setInt8 } from "./int8.js";
describe("getInt8", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0x80]);
expect(getInt8(array, 0)).toBe(new DataView(array.buffer).getInt8(0));
});
it("should work for maximal value", () => {
const array = new Uint8Array([0x7f]);
expect(getInt8(array, 0)).toBe(new DataView(array.buffer).getInt8(0));
});
it("should work for middle value", () => {
const array = new Uint8Array([0]);
expect(getInt8(array, 0)).toBe(new DataView(array.buffer).getInt8(0));
});
});
describe("setInt8", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(1);
new DataView(expected.buffer).setInt8(0, -0x80);
const actual = new Uint8Array(1);
setInt8(actual, 0, -0x80);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(1);
new DataView(expected.buffer).setInt8(0, 0x7f);
const actual = new Uint8Array(1);
setInt8(actual, 0, 0x7f);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(1);
new DataView(expected.buffer).setInt8(0, 0);
const actual = new Uint8Array(1);
setInt8(actual, 0, 0);
expect(actual).toEqual(expected);
});
});

View file

@ -0,0 +1,11 @@
export function getInt8(buffer: Uint8Array, offset: number): number {
return (buffer[offset]! << 24) >> 24;
}
export function setInt8(
buffer: Uint8Array,
offset: number,
value: number,
): void {
buffer[offset] = value >>> 0;
}

View file

@ -0,0 +1,79 @@
import { describe, expect, it } from "@jest/globals";
import {
getUint16,
getUint16BigEndian,
getUint16LittleEndian,
} from "./uint16.js";
describe("getUint16", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0]);
expect(getUint16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 255]);
expect(getUint16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 128]);
expect(getUint16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2]);
expect(getUint16LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0]);
expect(getUint16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 255]);
expect(getUint16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([128, 255]);
expect(getUint16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2]);
expect(getUint16BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2]);
expect(getUint16(array, 0, false)).toBe(
new DataView(array.buffer).getUint16(0, false),
);
expect(getUint16(array, 0, true)).toBe(
new DataView(array.buffer).getUint16(0, true),
);
});
});

View file

@ -0,0 +1,20 @@
export function getUint16LittleEndian(
buffer: Uint8Array,
offset: number,
): number {
return buffer[offset]! | (buffer[offset + 1]! << 8);
}
export function getUint16BigEndian(buffer: Uint8Array, offset: number): number {
return (buffer[offset]! << 8) | buffer[offset + 1]!;
}
export function getUint16(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
) {
return littleEndian
? buffer[offset]! | (buffer[offset + 1]! << 8)
: buffer[offset + 1]! | (buffer[offset]! << 8);
}

View file

@ -0,0 +1,149 @@
import { describe, expect, it } from "@jest/globals";
import {
getUint32,
getUint32BigEndian,
getUint32LittleEndian,
setUint32,
setUint32BigEndian,
setUint32LittleEndian,
} from "./uint32.js";
describe("getUint32", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 0]);
expect(getUint32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 255, 255, 255]);
expect(getUint32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 128]);
expect(getUint32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getUint32LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 0]);
expect(getUint32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([255, 255, 255, 255]);
expect(getUint32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([128, 0, 0, 0]);
expect(getUint32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getUint32BigEndian(array, 0)).toBe(
new DataView(array.buffer).getUint32(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2, 3, 4]);
expect(getUint32(array, 0, false)).toBe(
new DataView(array.buffer).getUint32(0, false),
);
expect(getUint32(array, 0, true)).toBe(
new DataView(array.buffer).getUint32(0, true),
);
});
});
describe("setUint32", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0, true);
const actual = new Uint8Array(4);
setUint32LittleEndian(actual, 0, 0);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0xffff_ffff, true);
const actual = new Uint8Array(4);
setUint32LittleEndian(actual, 0, 0xffff_ffff);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0x8000_0000, true);
const actual = new Uint8Array(4);
setUint32LittleEndian(actual, 0, 0x8000_0000);
expect(actual).toEqual(expected);
});
});
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0, false);
const actual = new Uint8Array(4);
setUint32BigEndian(actual, 0, 0);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0xffff_ffff, false);
const actual = new Uint8Array(4);
setUint32BigEndian(actual, 0, 0xffff_ffff);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0x8000_0000, false);
const actual = new Uint8Array(4);
setUint32BigEndian(actual, 0, 0x8000_0000);
expect(actual).toEqual(expected);
});
});
it("should work for selected endianness", () => {
const expected = new Uint8Array(4);
const actual = new Uint8Array(4);
new DataView(expected.buffer).setUint32(0, 0xffff_ffff, false);
setUint32(actual, 0, 0xffff_ffff, false);
expect(actual).toEqual(expected);
new DataView(expected.buffer).setUint32(0, 0xffff_ffff, true);
setUint32(actual, 0, 0xffff_ffff, true);
expect(actual).toEqual(expected);
});
});

View file

@ -0,0 +1,81 @@
export function getUint32LittleEndian(
buffer: Uint8Array,
offset: number,
): number {
return (
(buffer[offset]! |
(buffer[offset + 1]! << 8) |
(buffer[offset + 2]! << 16) |
(buffer[offset + 3]! << 24)) >>>
0
);
}
export function getUint32BigEndian(buffer: Uint8Array, offset: number): number {
return (
((buffer[offset]! << 24) |
(buffer[offset + 1]! << 16) |
(buffer[offset + 2]! << 8) |
buffer[offset + 3]!) >>>
0
);
}
export function getUint32(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
) {
return littleEndian
? (buffer[offset]! |
(buffer[offset + 1]! << 8) |
(buffer[offset + 2]! << 16) |
(buffer[offset + 3]! << 24)) >>>
0
: ((buffer[offset]! << 24) |
(buffer[offset + 1]! << 16) |
(buffer[offset + 2]! << 8) |
buffer[offset + 3]!) >>>
0;
}
export function setUint32LittleEndian(
buffer: Uint8Array,
offset: number,
value: number,
): void {
buffer[offset] = value & 0xff;
buffer[offset + 1] = (value >> 8) & 0xff;
buffer[offset + 2] = (value >> 16) & 0xff;
buffer[offset + 3] = (value >> 24) & 0xff;
}
export function setUint32BigEndian(
buffer: Uint8Array,
offset: number,
value: number,
): void {
buffer[offset] = (value >> 24) & 0xff;
buffer[offset + 1] = (value >> 16) & 0xff;
buffer[offset + 2] = (value >> 8) & 0xff;
buffer[offset + 3] = value & 0xff;
}
export function setUint32(
buffer: Uint8Array,
offset: number,
value: number,
littleEndian: boolean,
): void {
if (littleEndian) {
buffer[offset] = value & 0xff;
buffer[offset + 1] = (value >> 8) & 0xff;
buffer[offset + 2] = (value >> 16) & 0xff;
buffer[offset + 3] = (value >> 24) & 0xff;
} else {
buffer[offset] = (value >> 24) & 0xff;
buffer[offset + 1] = (value >> 16) & 0xff;
buffer[offset + 2] = (value >> 8) & 0xff;
buffer[offset + 3] = value & 0xff;
}
}

View file

@ -0,0 +1,181 @@
import { describe, expect, it } from "@jest/globals";
import {
getUint64,
getUint64BigEndian,
getUint64LittleEndian,
setUint64,
setUint64BigEndian,
setUint64LittleEndian,
} from "./uint64.js";
describe("getUint64", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
expect(getUint64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, true),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
expect(getUint64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, true),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x80]);
expect(getUint64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, true),
);
});
it("should work for random value", () => {
const array = new Uint8Array([
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
]);
expect(getUint64LittleEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, true),
);
});
});
describe("big endian", () => {
it("should work for minimal value", () => {
const array = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
expect(getUint64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, false),
);
});
it("should work for maximal value", () => {
const array = new Uint8Array([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]);
expect(getUint64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, false),
);
});
it("should work for middle value", () => {
const array = new Uint8Array([0x80, 0, 0, 0, 0, 0, 0, 0]);
expect(getUint64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, false),
);
});
it("should work for random value", () => {
const array = new Uint8Array([
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
]);
expect(getUint64BigEndian(array, 0)).toBe(
new DataView(array.buffer).getBigUint64(0, false),
);
});
});
it("should work for selected endianness", () => {
const array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
expect(getUint64(array, 0, false)).toBe(
new DataView(array.buffer).getBigUint64(0, false),
);
expect(getUint64(array, 0, true)).toBe(
new DataView(array.buffer).getBigUint64(0, true),
);
});
});
describe("setUint64", () => {
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(0, 0n, true);
const actual = new Uint8Array(8);
setUint64LittleEndian(actual, 0, 0n);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(
0,
0xffff_ffff_ffff_ffffn,
true,
);
const actual = new Uint8Array(8);
setUint64LittleEndian(actual, 0, 0xffff_ffff_ffff_ffffn);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(
0,
0x8000_0000_0000_0000n,
true,
);
const actual = new Uint8Array(8);
setUint64LittleEndian(actual, 0, 0x8000_0000_0000_0000n);
expect(actual).toEqual(expected);
});
});
describe("little endian", () => {
it("should work for minimal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(0, 0n, false);
const actual = new Uint8Array(8);
setUint64BigEndian(actual, 0, 0n);
expect(actual).toEqual(expected);
});
it("should work for maximal value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(
0,
0xffff_ffff_ffff_ffffn,
false,
);
const actual = new Uint8Array(8);
setUint64BigEndian(actual, 0, 0xffff_ffff_ffff_ffffn);
expect(actual).toEqual(expected);
});
it("should work for middle value", () => {
const expected = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(
0,
0x8000_0000_0000_0000n,
false,
);
const actual = new Uint8Array(8);
setUint64BigEndian(actual, 0, 0x8000_0000_0000_0000n);
expect(actual).toEqual(expected);
});
});
it("should work for selected endianness", () => {
const expected = new Uint8Array(8);
const actual = new Uint8Array(8);
new DataView(expected.buffer).setBigUint64(
0,
0xffff_ffff_ffff_ffffn,
false,
);
setUint64(actual, 0, 0xffff_ffff_ffff_ffffn, false);
expect(actual).toEqual(expected);
new DataView(expected.buffer).setBigUint64(
0,
0xffff_ffff_ffff_ffffn,
true,
);
setUint64(actual, 0, 0xffff_ffff_ffff_ffffn, true);
expect(actual).toEqual(expected);
});
});

View file

@ -0,0 +1,109 @@
export function getUint64LittleEndian(
buffer: Uint8Array,
offset: number,
): bigint {
return (
BigInt(buffer[offset]!) |
(BigInt(buffer[offset + 1]!) << 8n) |
(BigInt(buffer[offset + 2]!) << 16n) |
(BigInt(buffer[offset + 3]!) << 24n) |
(BigInt(buffer[offset + 4]!) << 32n) |
(BigInt(buffer[offset + 5]!) << 40n) |
(BigInt(buffer[offset + 6]!) << 48n) |
(BigInt(buffer[offset + 7]!) << 56n)
);
}
export function getUint64BigEndian(buffer: Uint8Array, offset: number): bigint {
return (
(BigInt(buffer[offset]!) << 56n) |
(BigInt(buffer[offset + 1]!) << 48n) |
(BigInt(buffer[offset + 2]!) << 40n) |
(BigInt(buffer[offset + 3]!) << 32n) |
(BigInt(buffer[offset + 4]!) << 24n) |
(BigInt(buffer[offset + 5]!) << 16n) |
(BigInt(buffer[offset + 6]!) << 8n) |
BigInt(buffer[offset + 7]!)
);
}
export function getUint64(
buffer: Uint8Array,
offset: number,
littleEndian: boolean,
): bigint {
return littleEndian
? BigInt(buffer[offset]!) |
(BigInt(buffer[offset + 1]!) << 8n) |
(BigInt(buffer[offset + 2]!) << 16n) |
(BigInt(buffer[offset + 3]!) << 24n) |
(BigInt(buffer[offset + 4]!) << 32n) |
(BigInt(buffer[offset + 5]!) << 40n) |
(BigInt(buffer[offset + 6]!) << 48n) |
(BigInt(buffer[offset + 7]!) << 56n)
: (BigInt(buffer[offset]!) << 56n) |
(BigInt(buffer[offset + 1]!) << 48n) |
(BigInt(buffer[offset + 2]!) << 40n) |
(BigInt(buffer[offset + 3]!) << 32n) |
(BigInt(buffer[offset + 4]!) << 24n) |
(BigInt(buffer[offset + 5]!) << 16n) |
(BigInt(buffer[offset + 6]!) << 8n) |
BigInt(buffer[offset + 7]!);
}
export function setUint64LittleEndian(
buffer: Uint8Array,
offset: number,
value: bigint,
): void {
buffer[offset] = Number(value & 0xffn);
buffer[offset + 1] = Number((value >> 8n) & 0xffn);
buffer[offset + 2] = Number((value >> 16n) & 0xffn);
buffer[offset + 3] = Number((value >> 24n) & 0xffn);
buffer[offset + 4] = Number((value >> 32n) & 0xffn);
buffer[offset + 5] = Number((value >> 40n) & 0xffn);
buffer[offset + 6] = Number((value >> 48n) & 0xffn);
buffer[offset + 7] = Number((value >> 56n) & 0xffn);
}
export function setUint64BigEndian(
buffer: Uint8Array,
offset: number,
value: bigint,
): void {
buffer[offset] = Number((value >> 56n) & 0xffn);
buffer[offset + 1] = Number((value >> 48n) & 0xffn);
buffer[offset + 2] = Number((value >> 40n) & 0xffn);
buffer[offset + 3] = Number((value >> 32n) & 0xffn);
buffer[offset + 4] = Number((value >> 24n) & 0xffn);
buffer[offset + 5] = Number((value >> 16n) & 0xffn);
buffer[offset + 6] = Number((value >> 8n) & 0xffn);
buffer[offset + 7] = Number(value & 0xffn);
}
export function setUint64(
buffer: Uint8Array,
offset: number,
value: bigint,
littleEndian: boolean,
): void {
if (littleEndian) {
buffer[offset] = Number(value & 0xffn);
buffer[offset + 1] = Number((value >> 8n) & 0xffn);
buffer[offset + 2] = Number((value >> 16n) & 0xffn);
buffer[offset + 3] = Number((value >> 24n) & 0xffn);
buffer[offset + 4] = Number((value >> 32n) & 0xffn);
buffer[offset + 5] = Number((value >> 40n) & 0xffn);
buffer[offset + 6] = Number((value >> 48n) & 0xffn);
buffer[offset + 7] = Number((value >> 56n) & 0xffn);
} else {
buffer[offset] = Number((value >> 56n) & 0xffn);
buffer[offset + 1] = Number((value >> 48n) & 0xffn);
buffer[offset + 2] = Number((value >> 40n) & 0xffn);
buffer[offset + 3] = Number((value >> 32n) & 0xffn);
buffer[offset + 4] = Number((value >> 24n) & 0xffn);
buffer[offset + 5] = Number((value >> 16n) & 0xffn);
buffer[offset + 6] = Number((value >> 8n) & 0xffn);
buffer[offset + 7] = Number(value & 0xffn);
}
}

View file

@ -0,0 +1,3 @@
{
"extends": "./node_modules/@yume-chan/tsconfig/tsconfig.base.json"
}

View file

@ -0,0 +1,10 @@
{
"references": [
{
"path": "./tsconfig.test.json"
},
{
"path": "./tsconfig.build.json"
},
]
}

View file

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.build.json",
"compilerOptions": {
"types": [
],
},
"exclude": []
}

View file

@ -29,9 +29,7 @@
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4", "lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {},
"tslib": "^2.6.2"
},
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",
"@types/audioworklet": "^0.0.54", "@types/audioworklet": "^0.0.54",

View file

@ -39,7 +39,6 @@
"@yume-chan/scrcpy": "workspace:^0.0.23", "@yume-chan/scrcpy": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"tinyh264": "^0.0.7", "tinyh264": "^0.0.7",
"tslib": "^2.6.2",
"yuv-buffer": "^1.0.0", "yuv-buffer": "^1.0.0",
"yuv-canvas": "^1.2.11" "yuv-canvas": "^1.2.11"
}, },

View file

@ -34,10 +34,10 @@
}, },
"dependencies": { "dependencies": {
"@yume-chan/event": "workspace:^0.0.23", "@yume-chan/event": "workspace:^0.0.23",
"@yume-chan/no-data-view": "workspace:^0.0.23",
"@yume-chan/scrcpy": "workspace:^0.0.23", "@yume-chan/scrcpy": "workspace:^0.0.23",
"@yume-chan/scrcpy-decoder-tinyh264": "workspace:^0.0.23", "@yume-chan/scrcpy-decoder-tinyh264": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -8,6 +8,7 @@ import {
h264ParseConfiguration, h264ParseConfiguration,
h265ParseConfiguration, h265ParseConfiguration,
} from "@yume-chan/scrcpy"; } from "@yume-chan/scrcpy";
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
import type { import type {
ScrcpyVideoDecoder, ScrcpyVideoDecoder,
ScrcpyVideoDecoderCapability, ScrcpyVideoDecoderCapability,
@ -22,15 +23,6 @@ function toHex(value: number) {
return value.toString(16).padStart(2, "0").toUpperCase(); return value.toString(16).padStart(2, "0").toUpperCase();
} }
function toUint32Le(data: Uint8Array, offset: number) {
return (
data[offset]! |
(data[offset + 1]! << 8) |
(data[offset + 2]! << 16) |
(data[offset + 3]! << 24)
);
}
export class WebCodecsDecoder implements ScrcpyVideoDecoder { export class WebCodecsDecoder implements ScrcpyVideoDecoder {
static isSupported() { static isSupported() {
return typeof globalThis.VideoDecoder !== "undefined"; return typeof globalThis.VideoDecoder !== "undefined";
@ -137,9 +129,7 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
this.#animationFrameId = requestAnimationFrame(this.#onFramePresented); this.#animationFrameId = requestAnimationFrame(this.#onFramePresented);
}; };
#configure(data: Uint8Array) { #configureH264(data: Uint8Array) {
switch (this.#codec) {
case ScrcpyVideoCodecId.H264: {
const { const {
profileIndex, profileIndex,
constraintSet, constraintSet,
@ -157,16 +147,18 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3 // https://www.rfc-editor.org/rfc/rfc6381#section-3.3
// ISO Base Media File Format Name Space // ISO Base Media File Format Name Space
const codec = `avc1.${[profileIndex, constraintSet, levelIndex] const codec =
.map(toHex) "avc1." +
.join("")}`; toHex(profileIndex) +
toHex(constraintSet) +
toHex(levelIndex);
this.#decoder.configure({ this.#decoder.configure({
codec: codec, codec: codec,
optimizeForLatency: true, optimizeForLatency: true,
}); });
break;
} }
case ScrcpyVideoCodecId.H265: {
#configureH265(data: Uint8Array) {
const { const {
generalProfileSpace, generalProfileSpace,
generalProfileIndex, generalProfileIndex,
@ -189,13 +181,14 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
"hev1", "hev1",
["", "A", "B", "C"][generalProfileSpace]! + ["", "A", "B", "C"][generalProfileSpace]! +
generalProfileIndex.toString(), generalProfileIndex.toString(),
toUint32Le(generalProfileCompatibilitySet, 0).toString(16), getUint32LittleEndian(generalProfileCompatibilitySet, 0).toString(
(generalTierFlag ? "H" : "L") + 16,
generalLevelIndex.toString(), ),
toUint32Le(generalConstraintSet, 0) (generalTierFlag ? "H" : "L") + generalLevelIndex.toString(),
getUint32LittleEndian(generalConstraintSet, 0)
.toString(16) .toString(16)
.toUpperCase(), .toUpperCase(),
toUint32Le(generalConstraintSet, 4) getUint32LittleEndian(generalConstraintSet, 4)
.toString(16) .toString(16)
.toUpperCase(), .toUpperCase(),
].join("."); ].join(".");
@ -203,8 +196,16 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
codec, codec,
optimizeForLatency: true, optimizeForLatency: true,
}); });
break;
} }
#configure(data: Uint8Array) {
switch (this.#codec) {
case ScrcpyVideoCodecId.H264:
this.#configureH264(data);
break;
case ScrcpyVideoCodecId.H265:
this.#configureH265(data);
break;
} }
this.#config = data; this.#config = data;
} }

View file

@ -1,5 +1,8 @@
{ {
"references": [ "references": [
{
"path": "../no-data-view/tsconfig.build.json"
},
{ {
"path": "./tsconfig.test.json" "path": "./tsconfig.test.json"
}, },

View file

@ -33,6 +33,7 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"@yume-chan/no-data-view": "workspace:^0.0.23",
"@yume-chan/stream-extra": "workspace:^0.0.23", "@yume-chan/stream-extra": "workspace:^0.0.23",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23",
"tslib": "^2.6.2" "tslib": "^2.6.2"

View file

@ -1,4 +1,6 @@
import { NumberFieldDefinition, NumberFieldType } from "@yume-chan/struct"; import { getUint16 } from "@yume-chan/no-data-view";
import type { NumberFieldType } from "@yume-chan/struct";
import { NumberFieldDefinition } from "@yume-chan/struct";
export function clamp(value: number, min: number, max: number): number { export function clamp(value: number, min: number, max: number): number {
if (value < min) { if (value < min) {
@ -16,7 +18,7 @@ export const ScrcpyFloatToUint16NumberType: NumberFieldType = {
size: 2, size: 2,
signed: false, signed: false,
deserialize(array, littleEndian) { deserialize(array, littleEndian) {
const value = NumberFieldType.Uint16.deserialize(array, littleEndian); const value = getUint16(array, 0, littleEndian);
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/server/src/main/java/com/genymobile/scrcpy/Binary.java#L22 // https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/server/src/main/java/com/genymobile/scrcpy/Binary.java#L22
return value === 0xffff ? 1 : value / 0x10000; return value === 0xffff ? 1 : value / 0x10000;
}, },
@ -24,7 +26,7 @@ export const ScrcpyFloatToUint16NumberType: NumberFieldType = {
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51 // https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51
value = clamp(value, -1, 1); value = clamp(value, -1, 1);
value = value === 1 ? 0xffff : value * 0x10000; value = value === 1 ? 0xffff : value * 0x10000;
NumberFieldType.Uint16.serialize(dataView, offset, value, littleEndian); dataView.setUint16(offset, value, littleEndian);
}, },
}; };

View file

@ -5,7 +5,7 @@ import {
TransformStream, TransformStream,
} from "@yume-chan/stream-extra"; } from "@yume-chan/stream-extra";
import type { AsyncExactReadable, ValueOrPromise } from "@yume-chan/struct"; import type { AsyncExactReadable, ValueOrPromise } from "@yume-chan/struct";
import { NumberFieldType, decodeUtf8 } from "@yume-chan/struct"; import { decodeUtf8 } from "@yume-chan/struct";
import type { import type {
ScrcpyBackOrScreenOnControlMessage, ScrcpyBackOrScreenOnControlMessage,
@ -23,6 +23,10 @@ import { ScrcpyVideoCodecId } from "../codec.js";
import type { ScrcpyDisplay, ScrcpyEncoder, ScrcpyOptions } from "../types.js"; import type { ScrcpyDisplay, ScrcpyEncoder, ScrcpyOptions } from "../types.js";
import { toScrcpyOptionValue } from "../types.js"; import { toScrcpyOptionValue } from "../types.js";
import {
getUint16BigEndian,
getUint32BigEndian,
} from "@yume-chan/no-data-view";
import { CodecOptions } from "./codec-options.js"; import { CodecOptions } from "./codec-options.js";
import type { ScrcpyOptionsInit1_16 } from "./init.js"; import type { ScrcpyOptionsInit1_16 } from "./init.js";
import { ScrcpyLogLevel1_16, ScrcpyVideoOrientation1_16 } from "./init.js"; import { ScrcpyLogLevel1_16, ScrcpyVideoOrientation1_16 } from "./init.js";
@ -84,13 +88,13 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
} }
static async parseUint16BE(stream: AsyncExactReadable): Promise<number> { static async parseUint16BE(stream: AsyncExactReadable): Promise<number> {
const buffer = await stream.readExactly(NumberFieldType.Uint16.size); const buffer = await stream.readExactly(2);
return NumberFieldType.Uint16.deserialize(buffer, false); return getUint16BigEndian(buffer, 0);
} }
static async parseUint32BE(stream: AsyncExactReadable): Promise<number> { static async parseUint32BE(stream: AsyncExactReadable): Promise<number> {
const buffer = await stream.readExactly(NumberFieldType.Uint32.size); const buffer = await stream.readExactly(4);
return NumberFieldType.Uint32.deserialize(buffer, false); return getUint32BigEndian(buffer, 0);
} }
value: Required<ScrcpyOptionsInit1_16>; value: Required<ScrcpyOptionsInit1_16>;

View file

@ -1,7 +1,6 @@
import Struct, { import { getInt16 } from "@yume-chan/no-data-view";
NumberFieldDefinition, import type { NumberFieldType } from "@yume-chan/struct";
NumberFieldType, import Struct, { NumberFieldDefinition } from "@yume-chan/struct";
} from "@yume-chan/struct";
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js"; import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
import { ScrcpyControlMessageType } from "../../control/index.js"; import { ScrcpyControlMessageType } from "../../control/index.js";
@ -12,7 +11,7 @@ export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
size: 2, size: 2,
signed: true, signed: true,
deserialize(array, littleEndian) { deserialize(array, littleEndian) {
const value = NumberFieldType.Int16.deserialize(array, littleEndian); const value = getInt16(array, 0, littleEndian);
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/server/src/main/java/com/genymobile/scrcpy/Binary.java#L34 // https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/server/src/main/java/com/genymobile/scrcpy/Binary.java#L34
return value === 0x7fff ? 1 : value / 0x8000; return value === 0x7fff ? 1 : value / 0x8000;
}, },
@ -20,7 +19,7 @@ export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L65 // https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L65
value = clamp(value, -1, 1); value = clamp(value, -1, 1);
value = value === 1 ? 0x7fff : value * 0x8000; value = value === 1 ? 0x7fff : value * 0x8000;
NumberFieldType.Int16.serialize(dataView, offset, value, littleEndian); dataView.setInt16(offset, value, littleEndian);
}, },
}; };

View file

@ -4,13 +4,14 @@ import {
PushReadableStream, PushReadableStream,
} from "@yume-chan/stream-extra"; } from "@yume-chan/stream-extra";
import type { ValueOrPromise } from "@yume-chan/struct"; import type { ValueOrPromise } from "@yume-chan/struct";
import Struct, { NumberFieldType, placeholder } from "@yume-chan/struct"; import Struct, { placeholder } from "@yume-chan/struct";
import type { import type {
AndroidMotionEventAction, AndroidMotionEventAction,
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
} from "../control/index.js"; } from "../control/index.js";
import { getUint32BigEndian } from "@yume-chan/no-data-view";
import { import {
CodecOptions, CodecOptions,
ScrcpyFloatToUint16FieldDefinition, ScrcpyFloatToUint16FieldDefinition,
@ -256,14 +257,9 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
): ValueOrPromise<ScrcpyAudioStreamMetadata> { ): ValueOrPromise<ScrcpyAudioStreamMetadata> {
return (async (): Promise<ScrcpyAudioStreamMetadata> => { return (async (): Promise<ScrcpyAudioStreamMetadata> => {
const buffered = new BufferedReadableStream(stream); const buffered = new BufferedReadableStream(stream);
const buffer = await buffered.readExactly( const buffer = await buffered.readExactly(4);
NumberFieldType.Uint32.size,
);
const codecMetadataValue = NumberFieldType.Uint32.deserialize( const codecMetadataValue = getUint32BigEndian(buffer, 0);
buffer,
false,
);
// Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false // Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false
switch (codecMetadataValue) { switch (codecMetadataValue) {
case 0x00_00_00_00: case 0x00_00_00_00:

View file

@ -33,8 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@yume-chan/async": "^2.2.0", "@yume-chan/async": "^2.2.0",
"@yume-chan/struct": "workspace:^0.0.23", "@yume-chan/struct": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -34,8 +34,7 @@
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {
"@yume-chan/dataview-bigint-polyfill": "workspace:^0.0.23", "@yume-chan/no-data-view": "workspace:^0.0.23"
"tslib": "^2.6.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "^30.0.0-alpha.3", "@jest/globals": "^30.0.0-alpha.3",

View file

@ -13,8 +13,13 @@ describe("StructFieldValue", () => {
describe(".constructor", () => { describe(".constructor", () => {
it("should save parameters", () => { it("should save parameters", () => {
class MockStructFieldValue extends StructFieldValue<never> { class MockStructFieldValue extends StructFieldValue<never> {
serialize(dataView: DataView, offset: number): void { serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void {
void dataView; void dataView;
void array;
void offset; void offset;
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
@ -78,8 +83,13 @@ describe("StructFieldValue", () => {
} }
class MockStructFieldValue extends StructFieldValue<any> { class MockStructFieldValue extends StructFieldValue<any> {
serialize(dataView: DataView, offset: number): void { serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void {
void dataView; void dataView;
void array;
void offset; void offset;
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
@ -99,8 +109,13 @@ describe("StructFieldValue", () => {
describe("#set", () => { describe("#set", () => {
it("should update its internal value", () => { it("should update its internal value", () => {
class MockStructFieldValue extends StructFieldValue<any> { class MockStructFieldValue extends StructFieldValue<any> {
serialize(dataView: DataView, offset: number): void { serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void {
void dataView; void dataView;
void array;
void offset; void offset;
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }

View file

@ -67,5 +67,9 @@ export abstract class StructFieldValue<
/** /**
* When implemented in derived classes, serializes this field into `dataView` at `offset` * When implemented in derived classes, serializes this field into `dataView` at `offset`
*/ */
abstract serialize(dataView: DataView, offset: number): void; abstract serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void;
} }

View file

@ -697,7 +697,7 @@ export class Struct<
); );
let offset = 0; let offset = 0;
for (const { fieldValue, size } of fieldsInfo) { for (const { fieldValue, size } of fieldsInfo) {
fieldValue.serialize(dataView, offset); fieldValue.serialize(dataView, output, offset);
offset += size; offset += size;
} }

View file

@ -1,10 +1,9 @@
import { import {
getBigInt64, getInt64,
getBigUint64, getUint64,
setBigInt64, setInt64,
setBigUint64, setUint64,
} from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js"; } from "@yume-chan/no-data-view";
import type { import type {
AsyncExactReadable, AsyncExactReadable,
ExactReadable, ExactReadable,
@ -15,17 +14,17 @@ import { StructFieldDefinition, StructFieldValue } from "../basic/index.js";
import { SyncPromise } from "../sync-promise.js"; import { SyncPromise } from "../sync-promise.js";
import type { ValueOrPromise } from "../utils.js"; import type { ValueOrPromise } from "../utils.js";
type DataViewBigInt64Getter = ( type GetBigInt64 = (
dataView: DataView, array: Uint8Array,
byteOffset: number, byteOffset: number,
littleEndian: boolean | undefined, littleEndian: boolean,
) => bigint; ) => bigint;
type DataViewBigInt64Setter = ( type SetBigInt64 = (
dataView: DataView, array: Uint8Array,
byteOffset: number, byteOffset: number,
value: bigint, value: bigint,
littleEndian: boolean | undefined, littleEndian: boolean,
) => void; ) => void;
export class BigIntFieldType { export class BigIntFieldType {
@ -33,23 +32,19 @@ export class BigIntFieldType {
readonly size: number; readonly size: number;
readonly getter: DataViewBigInt64Getter; readonly getter: GetBigInt64;
readonly setter: DataViewBigInt64Setter; readonly setter: SetBigInt64;
constructor( constructor(size: number, getter: GetBigInt64, setter: SetBigInt64) {
size: number,
getter: DataViewBigInt64Getter,
setter: DataViewBigInt64Setter,
) {
this.size = size; this.size = size;
this.getter = getter; this.getter = getter;
this.setter = setter; this.setter = setter;
} }
static readonly Int64 = new BigIntFieldType(8, getBigInt64, setBigInt64); static readonly Int64 = new BigIntFieldType(8, getInt64, setInt64);
static readonly Uint64 = new BigIntFieldType(8, getBigUint64, setBigUint64); static readonly Uint64 = new BigIntFieldType(8, getUint64, setUint64);
} }
export class BigIntFieldDefinition< export class BigIntFieldDefinition<
@ -95,12 +90,7 @@ export class BigIntFieldDefinition<
return stream.readExactly(this.getSize()); return stream.readExactly(this.getSize());
}) })
.then((array) => { .then((array) => {
const view = new DataView( const value = this.type.getter(array, 0, options.littleEndian);
array.buffer,
array.byteOffset,
array.byteLength,
);
const value = this.type.getter(view, 0, options.littleEndian);
return this.create(options, struct, value as never); return this.create(options, struct, value as never);
}) })
.valueOrPromise(); .valueOrPromise();
@ -110,9 +100,13 @@ export class BigIntFieldDefinition<
export class BigIntFieldValue< export class BigIntFieldValue<
TDefinition extends BigIntFieldDefinition<BigIntFieldType, unknown>, TDefinition extends BigIntFieldDefinition<BigIntFieldType, unknown>,
> extends StructFieldValue<TDefinition> { > extends StructFieldValue<TDefinition> {
serialize(dataView: DataView, offset: number): void { override serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void {
this.definition.type.setter( this.definition.type.setter(
dataView, array,
offset, offset,
this.value as never, this.value as never,
this.options.littleEndian, this.options.littleEndian,

View file

@ -189,7 +189,7 @@ describe("Types", () => {
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer); const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0); fieldValue.serialize(targetView, targetArray, 0);
expect(targetArray).toEqual(sourceArray); expect(targetArray).toEqual(sourceArray);
}); });
@ -219,7 +219,7 @@ describe("Types", () => {
const targetArray = new Uint8Array(size); const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer); const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0); fieldValue.serialize(targetView, targetArray, 0);
expect(targetArray).toEqual(sourceArray); expect(targetArray).toEqual(sourceArray);
}); });

View file

@ -186,15 +186,12 @@ export class BufferLikeFieldValue<
this.array = undefined; this.array = undefined;
} }
serialize(dataView: DataView, offset: number): void { override serialize(
if (!this.array) { dataView: DataView,
this.array = this.definition.type.toBuffer(this.value); array: Uint8Array,
} offset: number,
): void {
new Uint8Array( this.array ??= this.definition.type.toBuffer(this.value);
dataView.buffer, array.set(this.array, offset);
dataView.byteOffset,
dataView.byteLength,
).set(this.array, offset);
} }
} }

View file

@ -39,10 +39,12 @@ class MockLengthFieldValue extends StructFieldValue<any> {
void value; void value;
}); });
serialize = jest.fn((dataView: DataView, offset: number): void => { serialize = jest.fn(
(dataView: DataView, array: Uint8Array, offset: number): void => {
void dataView; void dataView;
void offset; void offset;
}); },
);
} }
describe("Types", () => { describe("Types", () => {
@ -62,8 +64,13 @@ describe("Types", () => {
override getSize = jest.fn(() => this.size); override getSize = jest.fn(() => this.size);
serialize(dataView: DataView, offset: number): void { serialize(
dataView: DataView,
array: Uint8Array,
offset: number,
): void {
void dataView; void dataView;
void array;
void offset; void offset;
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
@ -181,11 +188,12 @@ describe("Types", () => {
); );
const dataView = 0 as any; const dataView = 0 as any;
const array = 2 as any;
const offset = 1 as any; const offset = 1 as any;
mockOriginalFieldValue.value = 10; mockOriginalFieldValue.value = 10;
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.get).toHaveReturnedWith(10); expect(mockOriginalFieldValue.get).toHaveReturnedWith(10);
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
@ -195,13 +203,14 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith(100); expect(mockOriginalFieldValue.set).toHaveBeenCalledWith(100);
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes(
@ -209,6 +218,7 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
}); });
@ -224,11 +234,12 @@ describe("Types", () => {
); );
const dataView = 0 as any; const dataView = 0 as any;
const array = 2 as any;
const offset = 1 as any; const offset = 1 as any;
mockOriginalFieldValue.value = "10"; mockOriginalFieldValue.value = "10";
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.get).toHaveReturnedWith("10"); expect(mockOriginalFieldValue.get).toHaveReturnedWith("10");
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
@ -238,13 +249,14 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith("100"); expect(mockOriginalFieldValue.set).toHaveBeenCalledWith("100");
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes(
@ -252,6 +264,7 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
}); });
@ -271,11 +284,12 @@ describe("Types", () => {
radix; radix;
const dataView = 0 as any; const dataView = 0 as any;
const array = 2 as any;
const offset = 1 as any; const offset = 1 as any;
mockOriginalFieldValue.value = "10"; mockOriginalFieldValue.value = "10";
mockArrayBufferFieldValue.size = 0; mockArrayBufferFieldValue.size = 0;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.get).toHaveReturnedWith("10"); expect(mockOriginalFieldValue.get).toHaveReturnedWith("10");
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
@ -285,13 +299,14 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
mockOriginalFieldValue.set.mockClear(); mockOriginalFieldValue.set.mockClear();
mockOriginalFieldValue.serialize.mockClear(); mockOriginalFieldValue.serialize.mockClear();
mockArrayBufferFieldValue.size = 100; mockArrayBufferFieldValue.size = 100;
lengthFieldValue.serialize(dataView, offset); lengthFieldValue.serialize(dataView, array, offset);
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1); expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith( expect(mockOriginalFieldValue.set).toHaveBeenCalledWith(
(100).toString(radix), (100).toString(radix),
@ -301,6 +316,7 @@ describe("Types", () => {
); );
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith( expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
dataView, dataView,
array,
offset, offset,
); );
}); });

View file

@ -180,8 +180,8 @@ export class VariableLengthBufferLikeFieldLengthValue extends StructFieldValue<
// It will always be in sync with the buffer size // It will always be in sync with the buffer size
} }
serialize(dataView: DataView, offset: number) { serialize(dataView: DataView, array: Uint8Array, offset: number) {
this.originalField.set(this.get()); this.originalField.set(this.get());
this.originalField.serialize(dataView, offset); this.originalField.serialize(dataView, array, offset);
} }
} }

View file

@ -316,7 +316,7 @@ describe("Types", () => {
const array = new Uint8Array(10); const array = new Uint8Array(10);
const dataView = new DataView(array.buffer); const dataView = new DataView(array.buffer);
value.serialize(dataView, 2); value.serialize(dataView, array, 2);
expect(Array.from(array)).toEqual([ expect(Array.from(array)).toEqual([
0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0,

View file

@ -1,3 +1,9 @@
import {
getInt16,
getInt32,
getUint16,
getUint32,
} from "@yume-chan/no-data-view";
import type { import type {
AsyncExactReadable, AsyncExactReadable,
ExactReadable, ExactReadable,
@ -51,9 +57,7 @@ export namespace NumberFieldType {
// PERF: Creating many `DataView`s over small buffers is 90% slower // PERF: Creating many `DataView`s over small buffers is 90% slower
// than this. Even if the `DataView` is cached, `DataView#getUint16` // than this. Even if the `DataView` is cached, `DataView#getUint16`
// is still 1% slower than this. // is still 1% slower than this.
const a = (array[1]! << 8) | array[0]!; return getUint16(array, 0, littleEndian);
const b = (array[0]! << 8) | array[1]!;
return littleEndian ? a : b;
}, },
serialize(dataView, offset, value, littleEndian) { serialize(dataView, offset, value, littleEndian) {
dataView.setUint16(offset, value, littleEndian); dataView.setUint16(offset, value, littleEndian);
@ -64,8 +68,7 @@ export namespace NumberFieldType {
signed: true, signed: true,
size: 2, size: 2,
deserialize(array, littleEndian) { deserialize(array, littleEndian) {
const value = Uint16.deserialize(array, littleEndian); return getInt16(array, 0, littleEndian);
return (value << 16) >> 16;
}, },
serialize(dataView, offset, value, littleEndian) { serialize(dataView, offset, value, littleEndian) {
dataView.setInt16(offset, value, littleEndian); dataView.setInt16(offset, value, littleEndian);
@ -76,8 +79,7 @@ export namespace NumberFieldType {
signed: false, signed: false,
size: 4, size: 4,
deserialize(array, littleEndian) { deserialize(array, littleEndian) {
const value = Int32.deserialize(array, littleEndian); return getUint32(array, 0, littleEndian);
return value >>> 0;
}, },
serialize(dataView, offset, value, littleEndian) { serialize(dataView, offset, value, littleEndian) {
dataView.setUint32(offset, value, littleEndian); dataView.setUint32(offset, value, littleEndian);
@ -88,17 +90,7 @@ export namespace NumberFieldType {
signed: true, signed: true,
size: 4, size: 4,
deserialize(array, littleEndian) { deserialize(array, littleEndian) {
const a = return getInt32(array, 0, littleEndian);
(array[3]! << 24) |
(array[2]! << 16) |
(array[1]! << 8) |
array[0]!;
const b =
(array[0]! << 24) |
(array[1]! << 16) |
(array[2]! << 8) |
array[3]!;
return littleEndian ? a : b;
}, },
serialize(dataView, offset, value, littleEndian) { serialize(dataView, offset, value, littleEndian) {
dataView.setInt32(offset, value, littleEndian); dataView.setInt32(offset, value, littleEndian);
@ -162,7 +154,7 @@ export class NumberFieldDefinition<
export class NumberFieldValue< export class NumberFieldValue<
TDefinition extends NumberFieldDefinition<NumberFieldType, unknown>, TDefinition extends NumberFieldDefinition<NumberFieldType, unknown>,
> extends StructFieldValue<TDefinition> { > extends StructFieldValue<TDefinition> {
serialize(dataView: DataView, offset: number): void { serialize(dataView: DataView, array: Uint8Array, offset: number): void {
this.definition.type.serialize( this.definition.type.serialize(
dataView, dataView,
offset, offset,

View file

@ -1,5 +1,8 @@
{ {
"references": [ "references": [
{
"path": "../no-data-view/tsconfig.build.json"
},
{ {
"path": "./tsconfig.test.json" "path": "./tsconfig.test.json"
}, },

View file

@ -437,6 +437,12 @@
"shouldPublish": true, "shouldPublish": true,
"versionPolicyName": "adb" "versionPolicyName": "adb"
}, },
{
"packageName": "@yume-chan/no-data-view",
"projectFolder": "libraries/no-data-view",
"shouldPublish": true,
"versionPolicyName": "adb"
},
{ {
"packageName": "@yume-chan/scrcpy-decoder-tinyh264", "packageName": "@yume-chan/scrcpy-decoder-tinyh264",
"projectFolder": "libraries/scrcpy-decoder-tinyh264", "projectFolder": "libraries/scrcpy-decoder-tinyh264",

View file

@ -16,7 +16,7 @@
"stripInternal": true, "stripInternal": true,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "noEmit": true, /* Do not emit outputs. */ // "noEmit": true, /* Do not emit outputs. */
"importHelpers": true, // /* Import emit helpers from 'tslib'. */ // "importHelpers": true, // /* Import emit helpers from 'tslib'. */
"skipLibCheck": true, // /* Skip type checking of all declaration files (*.d.ts). */ "skipLibCheck": true, // /* Skip type checking of all declaration files (*.d.ts). */
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
"strict": true, // /* Enable all strict type-checking options. */ "strict": true, // /* Enable all strict type-checking options. */