mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 09:49:24 +02:00
feat: add utilities for read/write Uint8Array
This commit is contained in:
parent
d06b5f2ed6
commit
dcda6459ac
66 changed files with 2039 additions and 287 deletions
|
@ -191,7 +191,7 @@
|
|||
"ignoreMissingScript": true,
|
||||
"allowWarningsInSuccessfulBuild": true,
|
||||
"enableParallelism": true,
|
||||
"incremental": true,
|
||||
// "incremental": true,
|
||||
"safeForSimultaneousRushProcesses": true
|
||||
},
|
||||
{
|
||||
|
|
94
common/config/rush/pnpm-lock.yaml
generated
94
common/config/rush/pnpm-lock.yaml
generated
|
@ -62,15 +62,15 @@ importers:
|
|||
'@yume-chan/event':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../event
|
||||
'@yume-chan/no-data-view':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../no-data-view
|
||||
'@yume-chan/stream-extra':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../stream-extra
|
||||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -105,9 +105,6 @@ importers:
|
|||
'@yume-chan/adb':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../adb
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@yume-chan/eslint-config':
|
||||
specifier: workspace:^1.0.0
|
||||
|
@ -136,9 +133,6 @@ importers:
|
|||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@yume-chan/eslint-config':
|
||||
specifier: workspace:^1.0.0
|
||||
|
@ -173,9 +167,6 @@ importers:
|
|||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -213,9 +204,6 @@ importers:
|
|||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.12.7
|
||||
|
@ -247,9 +235,6 @@ importers:
|
|||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -281,9 +266,6 @@ importers:
|
|||
'@types/w3c-web-usb':
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@yume-chan/eslint-config':
|
||||
specifier: workspace:^1.0.0
|
||||
|
@ -296,10 +278,6 @@ importers:
|
|||
version: 5.4.5
|
||||
|
||||
../../libraries/dataview-bigint-polyfill:
|
||||
dependencies:
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@yume-chan/eslint-config':
|
||||
specifier: workspace:^1.0.0
|
||||
|
@ -322,9 +300,6 @@ importers:
|
|||
'@yume-chan/async':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -361,11 +336,40 @@ importers:
|
|||
specifier: ^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:
|
||||
dependencies:
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -397,6 +401,9 @@ importers:
|
|||
|
||||
../../libraries/scrcpy:
|
||||
dependencies:
|
||||
'@yume-chan/no-data-view':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../no-data-view
|
||||
'@yume-chan/stream-extra':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../stream-extra
|
||||
|
@ -449,9 +456,6 @@ importers:
|
|||
tinyh264:
|
||||
specifier: ^0.0.7
|
||||
version: 0.0.7
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
yuv-buffer:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
|
@ -489,6 +493,9 @@ importers:
|
|||
'@yume-chan/event':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../event
|
||||
'@yume-chan/no-data-view':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../no-data-view
|
||||
'@yume-chan/scrcpy':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../scrcpy
|
||||
|
@ -498,9 +505,6 @@ importers:
|
|||
'@yume-chan/stream-extra':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../stream-extra
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -535,9 +539,6 @@ importers:
|
|||
'@yume-chan/struct':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../struct
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -566,12 +567,9 @@ importers:
|
|||
|
||||
../../libraries/struct:
|
||||
dependencies:
|
||||
'@yume-chan/dataview-bigint-polyfill':
|
||||
'@yume-chan/no-data-view':
|
||||
specifier: workspace:^0.0.23
|
||||
version: link:../dataview-bigint-polyfill
|
||||
tslib:
|
||||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
version: link:../no-data-view
|
||||
devDependencies:
|
||||
'@jest/globals':
|
||||
specifier: ^30.0.0-alpha.3
|
||||
|
@ -4571,6 +4569,10 @@ packages:
|
|||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: false
|
||||
|
||||
/tinybench@2.7.0:
|
||||
resolution: {integrity: sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==}
|
||||
dev: true
|
||||
|
||||
/tinyh264@0.0.7:
|
||||
resolution: {integrity: sha512-etkBRgYkSFBdAi2Cqk4sZgi+xWs/vhzNgvjO3z2i4WILeEmORiNqxuQ4URJatrWQ9LPNV3WPWAtzsh/LA/XL/g==}
|
||||
dev: false
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
||||
{
|
||||
"pnpmShrinkwrapHash": "5dfb0a8a0ad6b0505870eeb38140a9ba82571aee",
|
||||
"pnpmShrinkwrapHash": "0fb46a0dd9d3d20531a5eb59ce8ba9cacfe15a30",
|
||||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
||||
}
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/adb": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/adb": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||
|
|
|
@ -34,8 +34,7 @@
|
|||
"@types/w3c-web-usb": "^1.0.10",
|
||||
"@yume-chan/adb": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||
|
|
|
@ -37,8 +37,7 @@
|
|||
"@yume-chan/event": "workspace:^0.0.23",
|
||||
"@yume-chan/scrcpy": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -34,8 +34,7 @@
|
|||
"dependencies": {
|
||||
"@yume-chan/adb": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.7",
|
||||
|
|
|
@ -35,9 +35,9 @@
|
|||
"@yume-chan/async": "^2.2.0",
|
||||
"@yume-chan/dataview-bigint-polyfill": "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/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -97,10 +97,7 @@ export enum AdbSyncSendV2Flags {
|
|||
* 4
|
||||
*/
|
||||
Zstd = 1 << 2,
|
||||
/**
|
||||
* 0x80000000
|
||||
*/
|
||||
DryRun = (1 << 31) >>> 0,
|
||||
DryRun = 0x80000000,
|
||||
}
|
||||
|
||||
export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import {
|
||||
getBigUint64,
|
||||
setBigUint64,
|
||||
} from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js";
|
||||
getUint64BigEndian,
|
||||
setInt64BigEndian,
|
||||
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
|
||||
|
@ -11,7 +12,7 @@ import {
|
|||
* @param byteOffset The place in the buffer at which the value should be retrieved.
|
||||
*/
|
||||
export function getBigUint(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
byteOffset: number,
|
||||
length: number,
|
||||
): bigint {
|
||||
|
@ -22,8 +23,8 @@ export function getBigUint(
|
|||
|
||||
for (let i = byteOffset; i < byteOffset + length; i += 8) {
|
||||
result <<= 64n;
|
||||
const value = getBigUint64(dataView, i, false);
|
||||
result += value;
|
||||
const value = getUint64BigEndian(array, i);
|
||||
result |= value;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -37,7 +38,7 @@ export function getBigUint(
|
|||
* otherwise a little-endian value should be written.
|
||||
*/
|
||||
export function setBigUint(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
byteOffset: number,
|
||||
value: bigint,
|
||||
littleEndian?: boolean,
|
||||
|
@ -46,7 +47,7 @@ export function setBigUint(
|
|||
|
||||
if (littleEndian) {
|
||||
while (value > 0n) {
|
||||
setBigUint64(dataView, byteOffset, value, true);
|
||||
setInt64LittleEndian(array, byteOffset, value);
|
||||
byteOffset += 8;
|
||||
value >>= 64n;
|
||||
}
|
||||
|
@ -60,7 +61,7 @@ export function setBigUint(
|
|||
}
|
||||
|
||||
for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
|
||||
setBigUint64(dataView, byteOffset, uint64Array[i]!, false);
|
||||
setInt64BigEndian(array, byteOffset, uint64Array[i]!);
|
||||
byteOffset += 8;
|
||||
}
|
||||
}
|
||||
|
@ -95,9 +96,8 @@ const RsaPrivateKeyDOffset = 303;
|
|||
const RsaPrivateKeyDLength = 2048 / 8;
|
||||
|
||||
export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
|
||||
const view = new DataView(key.buffer, key.byteOffset, key.byteLength);
|
||||
const n = getBigUint(view, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
||||
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
||||
const n = getBigUint(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
||||
const d = getBigUint(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
||||
return [n, d];
|
||||
}
|
||||
|
||||
|
@ -193,12 +193,12 @@ export function adbGeneratePublicKey(
|
|||
outputOffset += 4;
|
||||
|
||||
// Write n
|
||||
setBigUint(outputView, outputOffset, n, true);
|
||||
setBigUint(output, outputOffset, n, true);
|
||||
outputOffset += 256;
|
||||
|
||||
// Calculate rr = (2^(rsa_size)) ^ 2 mod n
|
||||
const rr = 2n ** 4096n % n;
|
||||
outputOffset += setBigUint(outputView, outputOffset, rr, true);
|
||||
outputOffset += setBigUint(output, outputOffset, rr, true);
|
||||
|
||||
// exponent
|
||||
outputView.setUint32(outputOffset, 65537, true);
|
||||
|
@ -305,12 +305,11 @@ export function rsaSign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
|
|||
|
||||
// Encryption
|
||||
// signature = padded ** d % n
|
||||
const view = new DataView(padded.buffer);
|
||||
const signature = powMod(getBigUint(view, 0, view.byteLength), d, n);
|
||||
const signature = powMod(getBigUint(padded, 0, padded.length), d, n);
|
||||
|
||||
// `padded` is not used anymore,
|
||||
// re-use the buffer to store the result
|
||||
setBigUint(view, 0, signature, false);
|
||||
setBigUint(padded, 0, signature, false);
|
||||
|
||||
return padded;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ import {
|
|||
PromiseResolver,
|
||||
delay,
|
||||
} from "@yume-chan/async";
|
||||
import {
|
||||
getUint32BigEndian,
|
||||
setUint32LittleEndian,
|
||||
} from "@yume-chan/no-data-view";
|
||||
import {
|
||||
AbortController,
|
||||
Consumable,
|
||||
|
@ -10,7 +14,7 @@ import {
|
|||
type ReadableWritablePair,
|
||||
type WritableStreamDefaultWriter,
|
||||
} 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 { decodeUtf8, encodeUtf8 } from "../utils/index.js";
|
||||
|
@ -189,7 +193,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
"Invalid OKAY packet. Payload size should be 4",
|
||||
);
|
||||
}
|
||||
ackBytes = NumberFieldType.Uint32.deserialize(packet.payload, true);
|
||||
ackBytes = getUint32BigEndian(packet.payload, 0);
|
||||
} else {
|
||||
if (packet.payload.byteLength !== 0) {
|
||||
throw new Error(
|
||||
|
@ -228,10 +232,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
let payload: Uint8Array;
|
||||
if (this.options.initialDelayedAckBytes !== 0) {
|
||||
payload = new Uint8Array(4);
|
||||
payload[0] = ackBytes & 0xff;
|
||||
payload[1] = (ackBytes >> 8) & 0xff;
|
||||
payload[2] = (ackBytes >> 16) & 0xff;
|
||||
payload[3] = (ackBytes >> 24) & 0xff;
|
||||
setUint32LittleEndian(payload, 0, ackBytes);
|
||||
} else {
|
||||
payload = EMPTY_UINT8_ARRAY;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import type {
|
|||
ValueOrPromise,
|
||||
} from "@yume-chan/struct";
|
||||
import {
|
||||
BigIntFieldType,
|
||||
EMPTY_UINT8_ARRAY,
|
||||
SyncPromise,
|
||||
decodeUtf8,
|
||||
|
@ -29,6 +28,7 @@ import { AdbBanner } from "../banner.js";
|
|||
import type { AdbFeature } from "../features.js";
|
||||
import { NOOP, hexToNumber, numberToHex, unreachable } from "../utils/index.js";
|
||||
|
||||
import { getUint64LittleEndian } from "@yume-chan/no-data-view";
|
||||
import { AdbServerTransport } from "./transport.js";
|
||||
|
||||
export interface AdbServerConnectionOptions {
|
||||
|
@ -391,13 +391,7 @@ export class AdbServerClient {
|
|||
try {
|
||||
if (transportId === undefined) {
|
||||
const array = await readable.readExactly(8);
|
||||
// TODO: switch to a more performant algorithm.
|
||||
const dataView = new DataView(
|
||||
array.buffer,
|
||||
array.byteOffset,
|
||||
array.byteLength,
|
||||
);
|
||||
transportId = BigIntFieldType.Uint64.getter(dataView, 0, true);
|
||||
transportId = getUint64LittleEndian(array, 0);
|
||||
}
|
||||
|
||||
await AdbServerClient.readOkay(readable);
|
||||
|
|
|
@ -34,8 +34,7 @@
|
|||
"dependencies": {
|
||||
"@yume-chan/adb": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/w3c-web-usb": "^1.0.10",
|
||||
"tslib": "^2.6.2"
|
||||
"@types/w3c-web-usb": "^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||
|
|
|
@ -35,9 +35,7 @@
|
|||
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@yume-chan/eslint-config": "workspace:^1.0.0",
|
||||
"@yume-chan/tsconfig": "workspace:^1.0.0",
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/async": "^2.2.0",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/async": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
19
libraries/no-data-view/.npmignore
Normal file
19
libraries/no-data-view/.npmignore
Normal 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
|
21
libraries/no-data-view/LICENSE
Normal file
21
libraries/no-data-view/LICENSE
Normal 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.
|
21
libraries/no-data-view/README.md
Normal file
21
libraries/no-data-view/README.md
Normal 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.
|
243
libraries/no-data-view/benchmark.js
Normal file
243
libraries/no-data-view/benchmark.js
Normal 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);
|
||||
});
|
71
libraries/no-data-view/benchmark.md
Normal file
71
libraries/no-data-view/benchmark.md
Normal 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% |
|
14
libraries/no-data-view/jest.config.js
Normal file
14
libraries/no-data-view/jest.config.js
Normal 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",
|
||||
},
|
||||
};
|
45
libraries/no-data-view/package.json
Normal file
45
libraries/no-data-view/package.json
Normal 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"
|
||||
}
|
||||
}
|
7
libraries/no-data-view/src/index.ts
Normal file
7
libraries/no-data-view/src/index.ts
Normal 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";
|
149
libraries/no-data-view/src/int16.spec.ts
Normal file
149
libraries/no-data-view/src/int16.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
53
libraries/no-data-view/src/int16.ts
Normal file
53
libraries/no-data-view/src/int16.ts
Normal 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;
|
||||
}
|
||||
}
|
75
libraries/no-data-view/src/int32.spec.ts
Normal file
75
libraries/no-data-view/src/int32.spec.ts
Normal 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),
|
||||
);
|
||||
});
|
||||
});
|
36
libraries/no-data-view/src/int32.ts
Normal file
36
libraries/no-data-view/src/int32.ts
Normal 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]!;
|
||||
}
|
181
libraries/no-data-view/src/int64.spec.ts
Normal file
181
libraries/no-data-view/src/int64.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
109
libraries/no-data-view/src/int64.ts
Normal file
109
libraries/no-data-view/src/int64.ts
Normal 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);
|
||||
}
|
||||
}
|
45
libraries/no-data-view/src/int8.spec.ts
Normal file
45
libraries/no-data-view/src/int8.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
11
libraries/no-data-view/src/int8.ts
Normal file
11
libraries/no-data-view/src/int8.ts
Normal 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;
|
||||
}
|
79
libraries/no-data-view/src/uint16.spec.ts
Normal file
79
libraries/no-data-view/src/uint16.spec.ts
Normal 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),
|
||||
);
|
||||
});
|
||||
});
|
20
libraries/no-data-view/src/uint16.ts
Normal file
20
libraries/no-data-view/src/uint16.ts
Normal 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);
|
||||
}
|
149
libraries/no-data-view/src/uint32.spec.ts
Normal file
149
libraries/no-data-view/src/uint32.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
81
libraries/no-data-view/src/uint32.ts
Normal file
81
libraries/no-data-view/src/uint32.ts
Normal 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;
|
||||
}
|
||||
}
|
181
libraries/no-data-view/src/uint64.spec.ts
Normal file
181
libraries/no-data-view/src/uint64.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
109
libraries/no-data-view/src/uint64.ts
Normal file
109
libraries/no-data-view/src/uint64.ts
Normal 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);
|
||||
}
|
||||
}
|
3
libraries/no-data-view/tsconfig.build.json
Normal file
3
libraries/no-data-view/tsconfig.build.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./node_modules/@yume-chan/tsconfig/tsconfig.base.json"
|
||||
}
|
10
libraries/no-data-view/tsconfig.json
Normal file
10
libraries/no-data-view/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
]
|
||||
}
|
8
libraries/no-data-view/tsconfig.test.json
Normal file
8
libraries/no-data-view/tsconfig.test.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
],
|
||||
},
|
||||
"exclude": []
|
||||
}
|
|
@ -29,9 +29,7 @@
|
|||
"lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
"@types/audioworklet": "^0.0.54",
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
"@yume-chan/scrcpy": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"tinyh264": "^0.0.7",
|
||||
"tslib": "^2.6.2",
|
||||
"yuv-buffer": "^1.0.0",
|
||||
"yuv-canvas": "^1.2.11"
|
||||
},
|
||||
|
|
|
@ -34,10 +34,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@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-decoder-tinyh264": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
h264ParseConfiguration,
|
||||
h265ParseConfiguration,
|
||||
} from "@yume-chan/scrcpy";
|
||||
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
||||
import type {
|
||||
ScrcpyVideoDecoder,
|
||||
ScrcpyVideoDecoderCapability,
|
||||
|
@ -22,15 +23,6 @@ function toHex(value: number) {
|
|||
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 {
|
||||
static isSupported() {
|
||||
return typeof globalThis.VideoDecoder !== "undefined";
|
||||
|
@ -137,74 +129,83 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
this.#animationFrameId = requestAnimationFrame(this.#onFramePresented);
|
||||
};
|
||||
|
||||
#configureH264(data: Uint8Array) {
|
||||
const {
|
||||
profileIndex,
|
||||
constraintSet,
|
||||
levelIndex,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
} = h264ParseConfiguration(data);
|
||||
|
||||
this.#canvas.width = croppedWidth;
|
||||
this.#canvas.height = croppedHeight;
|
||||
this.#sizeChanged.fire({
|
||||
width: croppedWidth,
|
||||
height: croppedHeight,
|
||||
});
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
||||
// ISO Base Media File Format Name Space
|
||||
const codec =
|
||||
"avc1." +
|
||||
toHex(profileIndex) +
|
||||
toHex(constraintSet) +
|
||||
toHex(levelIndex);
|
||||
this.#decoder.configure({
|
||||
codec: codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
}
|
||||
|
||||
#configureH265(data: Uint8Array) {
|
||||
const {
|
||||
generalProfileSpace,
|
||||
generalProfileIndex,
|
||||
generalProfileCompatibilitySet,
|
||||
generalTierFlag,
|
||||
generalLevelIndex,
|
||||
generalConstraintSet,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
} = h265ParseConfiguration(data);
|
||||
|
||||
this.#canvas.width = croppedWidth;
|
||||
this.#canvas.height = croppedHeight;
|
||||
this.#sizeChanged.fire({
|
||||
width: croppedWidth,
|
||||
height: croppedHeight,
|
||||
});
|
||||
|
||||
const codec = [
|
||||
"hev1",
|
||||
["", "A", "B", "C"][generalProfileSpace]! +
|
||||
generalProfileIndex.toString(),
|
||||
getUint32LittleEndian(generalProfileCompatibilitySet, 0).toString(
|
||||
16,
|
||||
),
|
||||
(generalTierFlag ? "H" : "L") + generalLevelIndex.toString(),
|
||||
getUint32LittleEndian(generalConstraintSet, 0)
|
||||
.toString(16)
|
||||
.toUpperCase(),
|
||||
getUint32LittleEndian(generalConstraintSet, 4)
|
||||
.toString(16)
|
||||
.toUpperCase(),
|
||||
].join(".");
|
||||
this.#decoder.configure({
|
||||
codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
}
|
||||
|
||||
#configure(data: Uint8Array) {
|
||||
switch (this.#codec) {
|
||||
case ScrcpyVideoCodecId.H264: {
|
||||
const {
|
||||
profileIndex,
|
||||
constraintSet,
|
||||
levelIndex,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
} = h264ParseConfiguration(data);
|
||||
|
||||
this.#canvas.width = croppedWidth;
|
||||
this.#canvas.height = croppedHeight;
|
||||
this.#sizeChanged.fire({
|
||||
width: croppedWidth,
|
||||
height: croppedHeight,
|
||||
});
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
||||
// ISO Base Media File Format Name Space
|
||||
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
|
||||
.map(toHex)
|
||||
.join("")}`;
|
||||
this.#decoder.configure({
|
||||
codec: codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
case ScrcpyVideoCodecId.H264:
|
||||
this.#configureH264(data);
|
||||
break;
|
||||
}
|
||||
case ScrcpyVideoCodecId.H265: {
|
||||
const {
|
||||
generalProfileSpace,
|
||||
generalProfileIndex,
|
||||
generalProfileCompatibilitySet,
|
||||
generalTierFlag,
|
||||
generalLevelIndex,
|
||||
generalConstraintSet,
|
||||
croppedWidth,
|
||||
croppedHeight,
|
||||
} = h265ParseConfiguration(data);
|
||||
|
||||
this.#canvas.width = croppedWidth;
|
||||
this.#canvas.height = croppedHeight;
|
||||
this.#sizeChanged.fire({
|
||||
width: croppedWidth,
|
||||
height: croppedHeight,
|
||||
});
|
||||
|
||||
const codec = [
|
||||
"hev1",
|
||||
["", "A", "B", "C"][generalProfileSpace]! +
|
||||
generalProfileIndex.toString(),
|
||||
toUint32Le(generalProfileCompatibilitySet, 0).toString(16),
|
||||
(generalTierFlag ? "H" : "L") +
|
||||
generalLevelIndex.toString(),
|
||||
toUint32Le(generalConstraintSet, 0)
|
||||
.toString(16)
|
||||
.toUpperCase(),
|
||||
toUint32Le(generalConstraintSet, 4)
|
||||
.toString(16)
|
||||
.toUpperCase(),
|
||||
].join(".");
|
||||
this.#decoder.configure({
|
||||
codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
case ScrcpyVideoCodecId.H265:
|
||||
this.#configureH265(data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.#config = data;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"references": [
|
||||
{
|
||||
"path": "../no-data-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
},
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/no-data-view": "workspace:^0.0.23",
|
||||
"@yume-chan/stream-extra": "workspace:^0.0.23",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
|
|
|
@ -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 {
|
||||
if (value < min) {
|
||||
|
@ -16,7 +18,7 @@ export const ScrcpyFloatToUint16NumberType: NumberFieldType = {
|
|||
size: 2,
|
||||
signed: false,
|
||||
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
|
||||
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
|
||||
value = clamp(value, -1, 1);
|
||||
value = value === 1 ? 0xffff : value * 0x10000;
|
||||
NumberFieldType.Uint16.serialize(dataView, offset, value, littleEndian);
|
||||
dataView.setUint16(offset, value, littleEndian);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
TransformStream,
|
||||
} from "@yume-chan/stream-extra";
|
||||
import type { AsyncExactReadable, ValueOrPromise } from "@yume-chan/struct";
|
||||
import { NumberFieldType, decodeUtf8 } from "@yume-chan/struct";
|
||||
import { decodeUtf8 } from "@yume-chan/struct";
|
||||
|
||||
import type {
|
||||
ScrcpyBackOrScreenOnControlMessage,
|
||||
|
@ -23,6 +23,10 @@ import { ScrcpyVideoCodecId } from "../codec.js";
|
|||
import type { ScrcpyDisplay, ScrcpyEncoder, ScrcpyOptions } from "../types.js";
|
||||
import { toScrcpyOptionValue } from "../types.js";
|
||||
|
||||
import {
|
||||
getUint16BigEndian,
|
||||
getUint32BigEndian,
|
||||
} from "@yume-chan/no-data-view";
|
||||
import { CodecOptions } from "./codec-options.js";
|
||||
import type { ScrcpyOptionsInit1_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> {
|
||||
const buffer = await stream.readExactly(NumberFieldType.Uint16.size);
|
||||
return NumberFieldType.Uint16.deserialize(buffer, false);
|
||||
const buffer = await stream.readExactly(2);
|
||||
return getUint16BigEndian(buffer, 0);
|
||||
}
|
||||
|
||||
static async parseUint32BE(stream: AsyncExactReadable): Promise<number> {
|
||||
const buffer = await stream.readExactly(NumberFieldType.Uint32.size);
|
||||
return NumberFieldType.Uint32.deserialize(buffer, false);
|
||||
const buffer = await stream.readExactly(4);
|
||||
return getUint32BigEndian(buffer, 0);
|
||||
}
|
||||
|
||||
value: Required<ScrcpyOptionsInit1_16>;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Struct, {
|
||||
NumberFieldDefinition,
|
||||
NumberFieldType,
|
||||
} from "@yume-chan/struct";
|
||||
import { getInt16 } from "@yume-chan/no-data-view";
|
||||
import type { NumberFieldType } from "@yume-chan/struct";
|
||||
import Struct, { NumberFieldDefinition } from "@yume-chan/struct";
|
||||
|
||||
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
|
||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||
|
@ -12,7 +11,7 @@ export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
|
|||
size: 2,
|
||||
signed: true,
|
||||
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
|
||||
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
|
||||
value = clamp(value, -1, 1);
|
||||
value = value === 1 ? 0x7fff : value * 0x8000;
|
||||
NumberFieldType.Int16.serialize(dataView, offset, value, littleEndian);
|
||||
dataView.setInt16(offset, value, littleEndian);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -4,13 +4,14 @@ import {
|
|||
PushReadableStream,
|
||||
} from "@yume-chan/stream-extra";
|
||||
import type { ValueOrPromise } from "@yume-chan/struct";
|
||||
import Struct, { NumberFieldType, placeholder } from "@yume-chan/struct";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
|
||||
import type {
|
||||
AndroidMotionEventAction,
|
||||
ScrcpyInjectTouchControlMessage,
|
||||
} from "../control/index.js";
|
||||
|
||||
import { getUint32BigEndian } from "@yume-chan/no-data-view";
|
||||
import {
|
||||
CodecOptions,
|
||||
ScrcpyFloatToUint16FieldDefinition,
|
||||
|
@ -256,14 +257,9 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
||||
return (async (): Promise<ScrcpyAudioStreamMetadata> => {
|
||||
const buffered = new BufferedReadableStream(stream);
|
||||
const buffer = await buffered.readExactly(
|
||||
NumberFieldType.Uint32.size,
|
||||
);
|
||||
const buffer = await buffered.readExactly(4);
|
||||
|
||||
const codecMetadataValue = NumberFieldType.Uint32.deserialize(
|
||||
buffer,
|
||||
false,
|
||||
);
|
||||
const codecMetadataValue = getUint32BigEndian(buffer, 0);
|
||||
// Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false
|
||||
switch (codecMetadataValue) {
|
||||
case 0x00_00_00_00:
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/async": "^2.2.0",
|
||||
"@yume-chan/struct": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/struct": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -34,8 +34,7 @@
|
|||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/dataview-bigint-polyfill": "workspace:^0.0.23",
|
||||
"tslib": "^2.6.2"
|
||||
"@yume-chan/no-data-view": "workspace:^0.0.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0-alpha.3",
|
||||
|
|
|
@ -13,8 +13,13 @@ describe("StructFieldValue", () => {
|
|||
describe(".constructor", () => {
|
||||
it("should save parameters", () => {
|
||||
class MockStructFieldValue extends StructFieldValue<never> {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
void dataView;
|
||||
void array;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
@ -78,8 +83,13 @@ describe("StructFieldValue", () => {
|
|||
}
|
||||
|
||||
class MockStructFieldValue extends StructFieldValue<any> {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
void dataView;
|
||||
void array;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
@ -99,8 +109,13 @@ describe("StructFieldValue", () => {
|
|||
describe("#set", () => {
|
||||
it("should update its internal value", () => {
|
||||
class MockStructFieldValue extends StructFieldValue<any> {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
void dataView;
|
||||
void array;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
|
|
@ -67,5 +67,9 @@ export abstract class StructFieldValue<
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
|
|
|
@ -697,7 +697,7 @@ export class Struct<
|
|||
);
|
||||
let offset = 0;
|
||||
for (const { fieldValue, size } of fieldsInfo) {
|
||||
fieldValue.serialize(dataView, offset);
|
||||
fieldValue.serialize(dataView, output, offset);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import {
|
||||
getBigInt64,
|
||||
getBigUint64,
|
||||
setBigInt64,
|
||||
setBigUint64,
|
||||
} from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js";
|
||||
|
||||
getInt64,
|
||||
getUint64,
|
||||
setInt64,
|
||||
setUint64,
|
||||
} from "@yume-chan/no-data-view";
|
||||
import type {
|
||||
AsyncExactReadable,
|
||||
ExactReadable,
|
||||
|
@ -15,17 +14,17 @@ import { StructFieldDefinition, StructFieldValue } from "../basic/index.js";
|
|||
import { SyncPromise } from "../sync-promise.js";
|
||||
import type { ValueOrPromise } from "../utils.js";
|
||||
|
||||
type DataViewBigInt64Getter = (
|
||||
dataView: DataView,
|
||||
type GetBigInt64 = (
|
||||
array: Uint8Array,
|
||||
byteOffset: number,
|
||||
littleEndian: boolean | undefined,
|
||||
littleEndian: boolean,
|
||||
) => bigint;
|
||||
|
||||
type DataViewBigInt64Setter = (
|
||||
dataView: DataView,
|
||||
type SetBigInt64 = (
|
||||
array: Uint8Array,
|
||||
byteOffset: number,
|
||||
value: bigint,
|
||||
littleEndian: boolean | undefined,
|
||||
littleEndian: boolean,
|
||||
) => void;
|
||||
|
||||
export class BigIntFieldType {
|
||||
|
@ -33,23 +32,19 @@ export class BigIntFieldType {
|
|||
|
||||
readonly size: number;
|
||||
|
||||
readonly getter: DataViewBigInt64Getter;
|
||||
readonly getter: GetBigInt64;
|
||||
|
||||
readonly setter: DataViewBigInt64Setter;
|
||||
readonly setter: SetBigInt64;
|
||||
|
||||
constructor(
|
||||
size: number,
|
||||
getter: DataViewBigInt64Getter,
|
||||
setter: DataViewBigInt64Setter,
|
||||
) {
|
||||
constructor(size: number, getter: GetBigInt64, setter: SetBigInt64) {
|
||||
this.size = size;
|
||||
this.getter = getter;
|
||||
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<
|
||||
|
@ -95,12 +90,7 @@ export class BigIntFieldDefinition<
|
|||
return stream.readExactly(this.getSize());
|
||||
})
|
||||
.then((array) => {
|
||||
const view = new DataView(
|
||||
array.buffer,
|
||||
array.byteOffset,
|
||||
array.byteLength,
|
||||
);
|
||||
const value = this.type.getter(view, 0, options.littleEndian);
|
||||
const value = this.type.getter(array, 0, options.littleEndian);
|
||||
return this.create(options, struct, value as never);
|
||||
})
|
||||
.valueOrPromise();
|
||||
|
@ -110,9 +100,13 @@ export class BigIntFieldDefinition<
|
|||
export class BigIntFieldValue<
|
||||
TDefinition extends BigIntFieldDefinition<BigIntFieldType, unknown>,
|
||||
> extends StructFieldValue<TDefinition> {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
override serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
this.definition.type.setter(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
this.value as never,
|
||||
this.options.littleEndian,
|
||||
|
|
|
@ -189,7 +189,7 @@ describe("Types", () => {
|
|||
|
||||
const targetArray = new Uint8Array(size);
|
||||
const targetView = new DataView(targetArray.buffer);
|
||||
fieldValue.serialize(targetView, 0);
|
||||
fieldValue.serialize(targetView, targetArray, 0);
|
||||
|
||||
expect(targetArray).toEqual(sourceArray);
|
||||
});
|
||||
|
@ -219,7 +219,7 @@ describe("Types", () => {
|
|||
|
||||
const targetArray = new Uint8Array(size);
|
||||
const targetView = new DataView(targetArray.buffer);
|
||||
fieldValue.serialize(targetView, 0);
|
||||
fieldValue.serialize(targetView, targetArray, 0);
|
||||
|
||||
expect(targetArray).toEqual(sourceArray);
|
||||
});
|
||||
|
|
|
@ -186,15 +186,12 @@ export class BufferLikeFieldValue<
|
|||
this.array = undefined;
|
||||
}
|
||||
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
if (!this.array) {
|
||||
this.array = this.definition.type.toBuffer(this.value);
|
||||
}
|
||||
|
||||
new Uint8Array(
|
||||
dataView.buffer,
|
||||
dataView.byteOffset,
|
||||
dataView.byteLength,
|
||||
).set(this.array, offset);
|
||||
override serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
this.array ??= this.definition.type.toBuffer(this.value);
|
||||
array.set(this.array, offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,12 @@ class MockLengthFieldValue extends StructFieldValue<any> {
|
|||
void value;
|
||||
});
|
||||
|
||||
serialize = jest.fn((dataView: DataView, offset: number): void => {
|
||||
void dataView;
|
||||
void offset;
|
||||
});
|
||||
serialize = jest.fn(
|
||||
(dataView: DataView, array: Uint8Array, offset: number): void => {
|
||||
void dataView;
|
||||
void offset;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("Types", () => {
|
||||
|
@ -62,8 +64,13 @@ describe("Types", () => {
|
|||
|
||||
override getSize = jest.fn(() => this.size);
|
||||
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
serialize(
|
||||
dataView: DataView,
|
||||
array: Uint8Array,
|
||||
offset: number,
|
||||
): void {
|
||||
void dataView;
|
||||
void array;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
@ -181,11 +188,12 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const dataView = 0 as any;
|
||||
const array = 2 as any;
|
||||
const offset = 1 as any;
|
||||
|
||||
mockOriginalFieldValue.value = 10;
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toHaveReturnedWith(10);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
|
@ -195,13 +203,14 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
|
||||
mockOriginalFieldValue.set.mockClear();
|
||||
mockOriginalFieldValue.serialize.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith(100);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes(
|
||||
|
@ -209,6 +218,7 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
});
|
||||
|
@ -224,11 +234,12 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const dataView = 0 as any;
|
||||
const array = 2 as any;
|
||||
const offset = 1 as any;
|
||||
|
||||
mockOriginalFieldValue.value = "10";
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toHaveReturnedWith("10");
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
|
@ -238,13 +249,14 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
|
||||
mockOriginalFieldValue.set.mockClear();
|
||||
mockOriginalFieldValue.serialize.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith("100");
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledTimes(
|
||||
|
@ -252,6 +264,7 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
});
|
||||
|
@ -271,11 +284,12 @@ describe("Types", () => {
|
|||
radix;
|
||||
|
||||
const dataView = 0 as any;
|
||||
const array = 2 as any;
|
||||
const offset = 1 as any;
|
||||
|
||||
mockOriginalFieldValue.value = "10";
|
||||
mockArrayBufferFieldValue.size = 0;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.get).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.get).toHaveReturnedWith("10");
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
|
@ -285,13 +299,14 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
|
||||
mockOriginalFieldValue.set.mockClear();
|
||||
mockOriginalFieldValue.serialize.mockClear();
|
||||
mockArrayBufferFieldValue.size = 100;
|
||||
lengthFieldValue.serialize(dataView, offset);
|
||||
lengthFieldValue.serialize(dataView, array, offset);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledTimes(1);
|
||||
expect(mockOriginalFieldValue.set).toHaveBeenCalledWith(
|
||||
(100).toString(radix),
|
||||
|
@ -301,6 +316,7 @@ describe("Types", () => {
|
|||
);
|
||||
expect(mockOriginalFieldValue.serialize).toHaveBeenCalledWith(
|
||||
dataView,
|
||||
array,
|
||||
offset,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -180,8 +180,8 @@ export class VariableLengthBufferLikeFieldLengthValue extends StructFieldValue<
|
|||
// 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.serialize(dataView, offset);
|
||||
this.originalField.serialize(dataView, array, offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,7 +316,7 @@ describe("Types", () => {
|
|||
|
||||
const array = new Uint8Array(10);
|
||||
const dataView = new DataView(array.buffer);
|
||||
value.serialize(dataView, 2);
|
||||
value.serialize(dataView, array, 2);
|
||||
|
||||
expect(Array.from(array)).toEqual([
|
||||
0, 0, 42, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
import {
|
||||
getInt16,
|
||||
getInt32,
|
||||
getUint16,
|
||||
getUint32,
|
||||
} from "@yume-chan/no-data-view";
|
||||
import type {
|
||||
AsyncExactReadable,
|
||||
ExactReadable,
|
||||
|
@ -51,9 +57,7 @@ export namespace NumberFieldType {
|
|||
// PERF: Creating many `DataView`s over small buffers is 90% slower
|
||||
// than this. Even if the `DataView` is cached, `DataView#getUint16`
|
||||
// is still 1% slower than this.
|
||||
const a = (array[1]! << 8) | array[0]!;
|
||||
const b = (array[0]! << 8) | array[1]!;
|
||||
return littleEndian ? a : b;
|
||||
return getUint16(array, 0, littleEndian);
|
||||
},
|
||||
serialize(dataView, offset, value, littleEndian) {
|
||||
dataView.setUint16(offset, value, littleEndian);
|
||||
|
@ -64,8 +68,7 @@ export namespace NumberFieldType {
|
|||
signed: true,
|
||||
size: 2,
|
||||
deserialize(array, littleEndian) {
|
||||
const value = Uint16.deserialize(array, littleEndian);
|
||||
return (value << 16) >> 16;
|
||||
return getInt16(array, 0, littleEndian);
|
||||
},
|
||||
serialize(dataView, offset, value, littleEndian) {
|
||||
dataView.setInt16(offset, value, littleEndian);
|
||||
|
@ -76,8 +79,7 @@ export namespace NumberFieldType {
|
|||
signed: false,
|
||||
size: 4,
|
||||
deserialize(array, littleEndian) {
|
||||
const value = Int32.deserialize(array, littleEndian);
|
||||
return value >>> 0;
|
||||
return getUint32(array, 0, littleEndian);
|
||||
},
|
||||
serialize(dataView, offset, value, littleEndian) {
|
||||
dataView.setUint32(offset, value, littleEndian);
|
||||
|
@ -88,17 +90,7 @@ export namespace NumberFieldType {
|
|||
signed: true,
|
||||
size: 4,
|
||||
deserialize(array, littleEndian) {
|
||||
const a =
|
||||
(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;
|
||||
return getInt32(array, 0, littleEndian);
|
||||
},
|
||||
serialize(dataView, offset, value, littleEndian) {
|
||||
dataView.setInt32(offset, value, littleEndian);
|
||||
|
@ -162,7 +154,7 @@ export class NumberFieldDefinition<
|
|||
export class NumberFieldValue<
|
||||
TDefinition extends NumberFieldDefinition<NumberFieldType, unknown>,
|
||||
> extends StructFieldValue<TDefinition> {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
serialize(dataView: DataView, array: Uint8Array, offset: number): void {
|
||||
this.definition.type.serialize(
|
||||
dataView,
|
||||
offset,
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
{
|
||||
"references": [
|
||||
{
|
||||
"path": "../no-data-view/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
},
|
||||
|
|
|
@ -437,6 +437,12 @@
|
|||
"shouldPublish": true,
|
||||
"versionPolicyName": "adb"
|
||||
},
|
||||
{
|
||||
"packageName": "@yume-chan/no-data-view",
|
||||
"projectFolder": "libraries/no-data-view",
|
||||
"shouldPublish": true,
|
||||
"versionPolicyName": "adb"
|
||||
},
|
||||
{
|
||||
"packageName": "@yume-chan/scrcpy-decoder-tinyh264",
|
||||
"projectFolder": "libraries/scrcpy-decoder-tinyh264",
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"stripInternal": true,
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "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). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, // /* Enable all strict type-checking options. */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue