mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 10:49:24 +02:00
feat: migrate to Uint8Array
This commit is contained in:
parent
cb988f5563
commit
96b5807691
39 changed files with 429 additions and 752 deletions
|
@ -24,7 +24,7 @@ const classNames = mergeStyleSets({
|
||||||
});
|
});
|
||||||
|
|
||||||
function serializePacket(packet: AdbPacketInit) {
|
function serializePacket(packet: AdbPacketInit) {
|
||||||
const command = decodeUtf8(new Uint32Array([packet.command]).buffer);
|
const command = decodeUtf8(new Uint32Array([packet.command]));
|
||||||
|
|
||||||
const parts = [
|
const parts = [
|
||||||
command,
|
command,
|
||||||
|
@ -35,7 +35,7 @@ function serializePacket(packet: AdbPacketInit) {
|
||||||
if (packet.payload) {
|
if (packet.payload) {
|
||||||
parts.push(
|
parts.push(
|
||||||
Array.from(
|
Array.from(
|
||||||
new Uint8Array(packet.payload),
|
packet.payload,
|
||||||
byte => byte.toString(16).padStart(2, '0')
|
byte => byte.toString(16).padStart(2, '0')
|
||||||
).join(' ')
|
).join(' ')
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,16 +36,16 @@ export class AdbTerminal extends AutoDisposable {
|
||||||
|
|
||||||
this._socketAbortController = new AbortController();
|
this._socketAbortController = new AbortController();
|
||||||
|
|
||||||
value.stdout.pipeTo(new WritableStream({
|
value.stdout.pipeTo(new WritableStream<Uint8Array>({
|
||||||
write: (chunk) => {
|
write: (chunk) => {
|
||||||
this.terminal.write(new Uint8Array(chunk));
|
this.terminal.write(chunk);
|
||||||
},
|
},
|
||||||
}), {
|
}), {
|
||||||
signal: this._socketAbortController.signal,
|
signal: this._socketAbortController.signal,
|
||||||
});
|
});
|
||||||
value.stderr.pipeTo(new WritableStream({
|
value.stderr.pipeTo(new WritableStream<Uint8Array>({
|
||||||
write: (chunk) => {
|
write: (chunk) => {
|
||||||
this.terminal.write(new Uint8Array(chunk));
|
this.terminal.write(chunk);
|
||||||
},
|
},
|
||||||
}), {
|
}), {
|
||||||
signal: this._socketAbortController.signal,
|
signal: this._socketAbortController.signal,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { getFileTypeIconNameFromExtensionOrType } from '@fluentui/react-file-typ
|
||||||
import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons';
|
import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons';
|
||||||
import { useConst } from '@fluentui/react-hooks';
|
import { useConst } from '@fluentui/react-hooks';
|
||||||
import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, LinuxFileType, ReadableStream, TransformStream } from '@yume-chan/adb';
|
import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, LinuxFileType, ReadableStream, TransformStream } from '@yume-chan/adb';
|
||||||
import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
|
|
||||||
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
|
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
|
@ -21,7 +20,7 @@ import { asyncEffect, formatSize, formatSpeed, Icons, pickFile, RouteStackProps
|
||||||
* Because of internal buffer of upstream/downstream streams,
|
* Because of internal buffer of upstream/downstream streams,
|
||||||
* the progress value won't be 100% accurate. But it's usually good enough.
|
* the progress value won't be 100% accurate. But it's usually good enough.
|
||||||
*/
|
*/
|
||||||
export class ProgressStream extends TransformStream<ArrayBuffer, ArrayBuffer> {
|
export class ProgressStream extends TransformStream<Uint8Array, Uint8Array> {
|
||||||
public constructor(onProgress: (value: number) => void) {
|
public constructor(onProgress: (value: number) => void) {
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
super({
|
super({
|
||||||
|
@ -171,13 +170,7 @@ class FileManagerState {
|
||||||
item.name,
|
item.name,
|
||||||
{ size: item.size }
|
{ size: item.size }
|
||||||
);
|
);
|
||||||
await readable
|
await readable.pipeTo(writeable);
|
||||||
.pipeThrough(new TransformStream({
|
|
||||||
transform(chunk, controller) {
|
|
||||||
controller.enqueue(new Uint8Array(chunk));
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.pipeTo(writeable);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -477,7 +470,6 @@ class FileManagerState {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
||||||
.pipeThrough(new ExtractViewBufferStream())
|
|
||||||
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
||||||
.pipeThrough(new ProgressStream(action((uploaded) => {
|
.pipeThrough(new ProgressStream(action((uploaded) => {
|
||||||
this.uploadedSize = uploaded;
|
this.uploadedSize = uploaded;
|
||||||
|
@ -558,12 +550,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
|
||||||
const previewImage = useCallback(async (path: string) => {
|
const previewImage = useCallback(async (path: string) => {
|
||||||
const sync = await globalState.device!.sync();
|
const sync = await globalState.device!.sync();
|
||||||
try {
|
try {
|
||||||
const readable = (await sync.read(path))
|
const readable = sync.read(path);
|
||||||
.pipeThrough(new TransformStream({
|
|
||||||
transform(chunk, controller) {
|
|
||||||
controller.enqueue(new Uint8Array(chunk));
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
const response = new Response(readable);
|
const response = new Response(readable);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
|
@ -24,7 +24,7 @@ class FrameBufferState {
|
||||||
setImage(image: AdbFrameBuffer) {
|
setImage(image: AdbFrameBuffer) {
|
||||||
this.width = image.width;
|
this.width = image.width;
|
||||||
this.height = image.height;
|
this.height = image.height;
|
||||||
this.imageData = new ImageData(image.data, image.width, image.height);
|
this.imageData = new ImageData(new Uint8ClampedArray(image.data), image.width, image.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDemoModeVisible() {
|
toggleDemoModeVisible() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
|
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
|
||||||
import { ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, ReadableStream } from "@yume-chan/adb";
|
import { ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, ReadableStream } from "@yume-chan/adb";
|
||||||
import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
|
|
||||||
import { action, makeAutoObservable, observable, runInAction } from "mobx";
|
import { action, makeAutoObservable, observable, runInAction } from "mobx";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
|
@ -59,7 +58,6 @@ class InstallPageState {
|
||||||
});
|
});
|
||||||
|
|
||||||
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
||||||
.pipeThrough(new ExtractViewBufferStream())
|
|
||||||
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
||||||
.pipeThrough(new ProgressStream(action((uploaded) => {
|
.pipeThrough(new ProgressStream(action((uploaded) => {
|
||||||
if (uploaded !== file.size) {
|
if (uploaded !== file.size) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { ProgressStream } from "./file-manager";
|
||||||
const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString();
|
const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString();
|
||||||
|
|
||||||
class FetchWithProgress {
|
class FetchWithProgress {
|
||||||
public readonly promise: Promise<ArrayBuffer>;
|
public readonly promise: Promise<Uint8Array>;
|
||||||
|
|
||||||
private _downloaded = 0;
|
private _downloaded = 0;
|
||||||
public get downloaded() { return this._downloaded; }
|
public get downloaded() { return this._downloaded; }
|
||||||
|
@ -56,7 +56,7 @@ class FetchWithProgress {
|
||||||
result.set(chunk, position);
|
result.set(chunk, position);
|
||||||
position += chunk.byteLength;
|
position += chunk.byteLength;
|
||||||
}
|
}
|
||||||
return result.buffer;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +387,7 @@ class ScrcpyPageState {
|
||||||
this.debouncedServerDownloadedSize = this.serverDownloadedSize;
|
this.debouncedServerDownloadedSize = this.serverDownloadedSize;
|
||||||
}), 1000);
|
}), 1000);
|
||||||
|
|
||||||
let serverBuffer: ArrayBuffer;
|
let serverBuffer: Uint8Array;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverBuffer = await fetchServer(action(([downloaded, total]) => {
|
serverBuffer = await fetchServer(action(([downloaded, total]) => {
|
||||||
|
@ -408,7 +408,7 @@ class ScrcpyPageState {
|
||||||
}), 1000);
|
}), 1000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await new ReadableStream({
|
await new ReadableStream<Uint8Array>({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
controller.enqueue(serverBuffer);
|
controller.enqueue(serverBuffer);
|
||||||
controller.close();
|
controller.close();
|
||||||
|
@ -450,7 +450,7 @@ class ScrcpyPageState {
|
||||||
|
|
||||||
// Run scrcpy once will delete the server file
|
// Run scrcpy once will delete the server file
|
||||||
// Re-push it
|
// Re-push it
|
||||||
await new ReadableStream({
|
await new ReadableStream<Uint8Array>({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
controller.enqueue(serverBuffer);
|
controller.enqueue(serverBuffer);
|
||||||
controller.close();
|
controller.close();
|
||||||
|
@ -492,7 +492,7 @@ class ScrcpyPageState {
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
client.stdout.pipeTo(new WritableStream({
|
client.stdout.pipeTo(new WritableStream<string>({
|
||||||
write: action((line) => {
|
write: action((line) => {
|
||||||
this.log.push(line);
|
this.log.push(line);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,19 +1,4 @@
|
||||||
import { AdbBackend, ReadableStream, ReadableWritablePair, TransformStream, WritableStream } from '@yume-chan/adb';
|
import { AdbBackend, ReadableStream, WritableStream } from '@yume-chan/adb';
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform an `ArrayBufferView` stream to an `ArrayBuffer` stream.
|
|
||||||
*
|
|
||||||
* The view must wrap the whole buffer (`byteOffset === 0` && `byteLength === buffer.byteLength`).
|
|
||||||
*/
|
|
||||||
export class ExtractViewBufferStream extends TransformStream<ArrayBufferView, ArrayBuffer>{
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
transform(chunk, controller) {
|
|
||||||
controller.enqueue(chunk.buffer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface TCPSocket {
|
interface TCPSocket {
|
||||||
|
@ -44,28 +29,6 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbDirectSocketsBackendStreams implements ReadableWritablePair<ArrayBuffer, ArrayBuffer>{
|
|
||||||
private socket: TCPSocket;
|
|
||||||
|
|
||||||
private _readableTransformStream: ExtractViewBufferStream;
|
|
||||||
public get readable(): ReadableStream<ArrayBuffer> {
|
|
||||||
return this._readableTransformStream.readable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get writable(): WritableStream<ArrayBuffer> {
|
|
||||||
return this.socket.writable;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(socket: TCPSocket) {
|
|
||||||
this.socket = socket;
|
|
||||||
|
|
||||||
// Although Direct Sockets spec didn't say,
|
|
||||||
// WebTransport spec and File spec all have the `Uint8Array` wraps the whole `ArrayBuffer`.
|
|
||||||
this._readableTransformStream = new ExtractViewBufferStream();
|
|
||||||
this.socket.readable.pipeTo(this._readableTransformStream.writable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class AdbDirectSocketsBackend implements AdbBackend {
|
export default class AdbDirectSocketsBackend implements AdbBackend {
|
||||||
public static isSupported(): boolean {
|
public static isSupported(): boolean {
|
||||||
return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket;
|
return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket;
|
||||||
|
@ -87,12 +50,10 @@ export default class AdbDirectSocketsBackend implements AdbBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect() {
|
public async connect() {
|
||||||
const socket = await navigator.openTCPSocket({
|
return await navigator.openTCPSocket({
|
||||||
remoteAddress: this.host,
|
remoteAddress: this.host,
|
||||||
remotePort: this.port,
|
remotePort: this.port,
|
||||||
noDelay: true,
|
noDelay: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new AdbDirectSocketsBackendStreams(socket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,15 @@ export const WebUsbDeviceFilter: USBDeviceFilter = {
|
||||||
protocolCode: 1,
|
protocolCode: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer, ArrayBuffer>{
|
export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array, Uint8Array>{
|
||||||
private _readable: ReadableStream<ArrayBuffer>;
|
private _readable: ReadableStream<Uint8Array>;
|
||||||
public get readable() { return this._readable; }
|
public get readable() { return this._readable; }
|
||||||
|
|
||||||
private _writable: WritableStream<ArrayBuffer>;
|
private _writable: WritableStream<Uint8Array>;
|
||||||
public get writable() { return this._writable; }
|
public get writable() { return this._writable; }
|
||||||
|
|
||||||
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
|
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
|
||||||
this._readable = new ReadableStream({
|
this._readable = new ReadableStream<Uint8Array>({
|
||||||
pull: async (controller) => {
|
pull: async (controller) => {
|
||||||
let result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
|
let result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer,
|
||||||
result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
|
result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { buffer } = result.data!;
|
const view = result.data!;
|
||||||
controller.enqueue(buffer);
|
controller.enqueue(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
||||||
},
|
},
|
||||||
cancel: async () => {
|
cancel: async () => {
|
||||||
await device.close();
|
await device.close();
|
||||||
|
@ -35,7 +35,7 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer,
|
||||||
size(chunk) { return chunk.byteLength; },
|
size(chunk) { return chunk.byteLength; },
|
||||||
});
|
});
|
||||||
|
|
||||||
this._writable = new WritableStream({
|
this._writable = new WritableStream<Uint8Array>({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
await device.transferOut(outEndpoint.endpointNumber, chunk);
|
await device.transferOut(outEndpoint.endpointNumber, chunk);
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,10 +21,10 @@ export default class AdbWsBackend implements AdbBackend {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const readable = new ReadableStream({
|
const readable = new ReadableStream<Uint8Array>({
|
||||||
start: (controller) => {
|
start: (controller) => {
|
||||||
socket.onmessage = ({ data }: { data: ArrayBuffer; }) => {
|
socket.onmessage = ({ data }: { data: ArrayBuffer; }) => {
|
||||||
controller.enqueue(data);
|
controller.enqueue(new Uint8Array(data));
|
||||||
};
|
};
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
controller.close();
|
controller.close();
|
||||||
|
@ -35,7 +35,7 @@ export default class AdbWsBackend implements AdbBackend {
|
||||||
size(chunk) { return chunk.byteLength; },
|
size(chunk) { return chunk.byteLength; },
|
||||||
});
|
});
|
||||||
|
|
||||||
const writable = new WritableStream({
|
const writable = new WritableStream<Uint8Array>({
|
||||||
write: (chunk) => {
|
write: (chunk) => {
|
||||||
socket.send(chunk);
|
socket.send(chunk);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
// cspell: ignore RSASSA
|
// cspell: ignore RSASSA
|
||||||
|
|
||||||
import { type AdbCredentialStore, calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, encodeBase64 } from "@yume-chan/adb";
|
import { calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, encodeBase64, type AdbCredentialStore } from "@yume-chan/adb";
|
||||||
|
|
||||||
const Utf8Encoder = new TextEncoder();
|
const Utf8Encoder = new TextEncoder();
|
||||||
const Utf8Decoder = new TextDecoder();
|
const Utf8Decoder = new TextDecoder();
|
||||||
|
|
||||||
export function encodeUtf8(input: string): ArrayBuffer {
|
export function encodeUtf8(input: string): Uint8Array {
|
||||||
return Utf8Encoder.encode(input).buffer;
|
return Utf8Encoder.encode(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeUtf8(buffer: ArrayBuffer): string {
|
export function decodeUtf8(array: Uint8Array): string {
|
||||||
return Utf8Decoder.decode(buffer);
|
return Utf8Decoder.decode(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AdbWebCredentialStore implements AdbCredentialStore {
|
export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||||
|
@ -20,14 +20,14 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||||
this.localStorageKey = localStorageKey;
|
this.localStorageKey = localStorageKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public *iterateKeys(): Generator<ArrayBuffer, void, void> {
|
public *iterateKeys(): Generator<Uint8Array, void, void> {
|
||||||
const privateKey = window.localStorage.getItem(this.localStorageKey);
|
const privateKey = window.localStorage.getItem(this.localStorageKey);
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
yield decodeBase64(privateKey);
|
yield decodeBase64(privateKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async generateKey(): Promise<ArrayBuffer> {
|
public async generateKey(): Promise<Uint8Array> {
|
||||||
const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
|
const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
|
||||||
{
|
{
|
||||||
name: 'RSASSA-PKCS1-v1_5',
|
name: 'RSASSA-PKCS1-v1_5',
|
||||||
|
@ -40,7 +40,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||||
['sign', 'verify']
|
['sign', 'verify']
|
||||||
);
|
);
|
||||||
|
|
||||||
const privateKey = await crypto.subtle.exportKey('pkcs8', cryptoKey!);
|
const privateKey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', cryptoKey!));
|
||||||
window.localStorage.setItem(this.localStorageKey, decodeUtf8(encodeBase64(privateKey)));
|
window.localStorage.setItem(this.localStorageKey, decodeUtf8(encodeBase64(privateKey)));
|
||||||
|
|
||||||
// The authentication module in core doesn't need public keys.
|
// The authentication module in core doesn't need public keys.
|
||||||
|
@ -49,7 +49,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||||
// so also save the public key for their convenience.
|
// so also save the public key for their convenience.
|
||||||
const publicKeyLength = calculatePublicKeyLength();
|
const publicKeyLength = calculatePublicKeyLength();
|
||||||
const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength);
|
const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength);
|
||||||
const publicKeyBuffer = new ArrayBuffer(publicKeyBase64Length);
|
const publicKeyBuffer = new Uint8Array(publicKeyBase64Length);
|
||||||
calculatePublicKey(privateKey, publicKeyBuffer);
|
calculatePublicKey(privateKey, publicKeyBuffer);
|
||||||
encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer);
|
encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer);
|
||||||
window.localStorage.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer));
|
window.localStorage.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer));
|
||||||
|
|
|
@ -52,9 +52,11 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
|
||||||
state: undefined,
|
state: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async close() {
|
close: async () => {
|
||||||
// Close `stderr` on exit.
|
// Close `stderr` on exit.
|
||||||
stderrController.close();
|
stderrController.close();
|
||||||
|
|
||||||
|
this._exit.resolve(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export function adbSyncPull(
|
||||||
writer: WritableStreamDefaultWriter<Uint8Array>,
|
writer: WritableStreamDefaultWriter<Uint8Array>,
|
||||||
path: string,
|
path: string,
|
||||||
): ReadableStream<Uint8Array> {
|
): ReadableStream<Uint8Array> {
|
||||||
return new ReadableStream({
|
return new ReadableStream<Uint8Array>({
|
||||||
async start() {
|
async start() {
|
||||||
await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
|
await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function adbSyncPush(
|
||||||
packetSize: number = ADB_SYNC_MAX_PACKET_SIZE,
|
packetSize: number = ADB_SYNC_MAX_PACKET_SIZE,
|
||||||
): WritableStream<Uint8Array> {
|
): WritableStream<Uint8Array> {
|
||||||
const { readable, writable } = new ChunkStream(packetSize);
|
const { readable, writable } = new ChunkStream(packetSize);
|
||||||
readable.pipeTo(new WritableStream({
|
readable.pipeTo(new WritableStream<Uint8Array>({
|
||||||
async start() {
|
async start() {
|
||||||
const pathAndMode = `${filename},${mode.toString()}`;
|
const pathAndMode = `${filename},${mode.toString()}`;
|
||||||
await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode);
|
await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode);
|
||||||
|
|
|
@ -30,20 +30,20 @@ export async function adbSyncWriteRequest(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let buffer: Uint8Array;
|
let buffer: Uint8Array;
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'number') {
|
||||||
buffer = new Uint8Array(AdbSyncNumberRequest.serialize({
|
buffer = AdbSyncNumberRequest.serialize({
|
||||||
id,
|
id,
|
||||||
arg: value,
|
arg: value,
|
||||||
}));
|
});
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
buffer = new Uint8Array(AdbSyncDataRequest.serialize({
|
buffer = AdbSyncDataRequest.serialize({
|
||||||
id,
|
id,
|
||||||
data: encodeUtf8(value),
|
data: encodeUtf8(value),
|
||||||
}));
|
});
|
||||||
} else {
|
} else {
|
||||||
buffer = new Uint8Array(AdbSyncDataRequest.serialize({
|
buffer = AdbSyncDataRequest.serialize({
|
||||||
id,
|
id,
|
||||||
data: value,
|
data: value,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
await writer.write(buffer);
|
await writer.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
|
||||||
transform: async (init, controller) => {
|
transform: async (init, controller) => {
|
||||||
let checksum: number;
|
let checksum: number;
|
||||||
if (this.calculateChecksum && init.payload) {
|
if (this.calculateChecksum && init.payload) {
|
||||||
const array = new Uint8Array(init.payload);
|
const array = init.payload;
|
||||||
checksum = array.reduce((result, item) => result + item, 0);
|
checksum = array.reduce((result, item) => result + item, 0);
|
||||||
} else {
|
} else {
|
||||||
checksum = 0;
|
checksum = 0;
|
||||||
|
@ -49,7 +49,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
|
||||||
payloadLength: init.payload.byteLength,
|
payloadLength: init.payload.byteLength,
|
||||||
};
|
};
|
||||||
|
|
||||||
controller.enqueue(new Uint8Array(AdbPacketHeader.serialize(packet)));
|
controller.enqueue(AdbPacketHeader.serialize(packet));
|
||||||
|
|
||||||
if (packet.payloadLength) {
|
if (packet.payloadLength) {
|
||||||
controller.enqueue(packet.payload);
|
controller.enqueue(packet.payload);
|
||||||
|
|
|
@ -62,7 +62,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
{ signal: this._abortController.signal, preventCancel: true }
|
{ signal: this._abortController.signal, preventCancel: true }
|
||||||
)
|
)
|
||||||
.pipeThrough(new StructDeserializeStream(AdbPacket))
|
.pipeThrough(new StructDeserializeStream(AdbPacket))
|
||||||
.pipeTo(new WritableStream({
|
.pipeTo(new WritableStream<AdbPacket>({
|
||||||
write: async (packet) => {
|
write: async (packet) => {
|
||||||
try {
|
try {
|
||||||
this.logger?.onIncomingPacket?.(packet);
|
this.logger?.onIncomingPacket?.(packet);
|
||||||
|
|
|
@ -36,7 +36,7 @@ export class BufferedStream {
|
||||||
const buffer = this.buffer;
|
const buffer = this.buffer;
|
||||||
if (buffer.byteLength > length) {
|
if (buffer.byteLength > length) {
|
||||||
this.buffer = buffer.subarray(length);
|
this.buffer = buffer.subarray(length);
|
||||||
return buffer.slice(0, length);
|
return buffer.subarray(0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
array = new Uint8Array(length);
|
array = new Uint8Array(length);
|
||||||
|
@ -60,11 +60,11 @@ export class BufferedStream {
|
||||||
|
|
||||||
if (value.byteLength > length) {
|
if (value.byteLength > length) {
|
||||||
this.buffer = value.subarray(length);
|
this.buffer = value.subarray(length);
|
||||||
return value.slice(0, length);
|
return value.subarray(0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
array = new Uint8Array(length);
|
array = new Uint8Array(length);
|
||||||
array.set(new Uint8Array(value), 0);
|
array.set(value, 0);
|
||||||
index = value.byteLength;
|
index = value.byteLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ export class StructSerializeStream<T extends Struct<any, any, any, any>>
|
||||||
constructor(struct: T) {
|
constructor(struct: T) {
|
||||||
super({
|
super({
|
||||||
transform(chunk, controller) {
|
transform(chunk, controller) {
|
||||||
controller.enqueue(new Uint8Array(struct.serialize(chunk)));
|
controller.enqueue(struct.serialize(chunk));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ export class WrapReadableStream<T, R extends ReadableStream<T>, S> extends Reada
|
||||||
pull: async (controller) => {
|
pull: async (controller) => {
|
||||||
const result = await this.reader.read();
|
const result = await this.reader.read();
|
||||||
if (result.done) {
|
if (result.done) {
|
||||||
|
wrapper.close?.(this.state);
|
||||||
controller.close();
|
controller.close();
|
||||||
} else {
|
} else {
|
||||||
controller.enqueue(result.value);
|
controller.enqueue(result.value);
|
||||||
|
@ -153,9 +154,9 @@ export class ChunkStream extends TransformStream<Uint8Array, Uint8Array>{
|
||||||
public constructor(size: number) {
|
public constructor(size: number) {
|
||||||
super({
|
super({
|
||||||
transform(chunk, controller) {
|
transform(chunk, controller) {
|
||||||
for (let start = 0; start < chunk.length; start += size) {
|
for (let start = 0; start < chunk.byteLength;) {
|
||||||
const end = start + size;
|
const end = start + size;
|
||||||
controller.enqueue(chunk.slice(start, end));
|
controller.enqueue(chunk.subarray(start, end));
|
||||||
start = end;
|
start = end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@ export function encodeUtf8(input: string): Uint8Array {
|
||||||
return Utf8Encoder.encode(input);
|
return Utf8Encoder.encode(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeUtf8(buffer: Uint8Array): string {
|
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
|
||||||
return Utf8Decoder.decode(buffer);
|
return Utf8Decoder.decode(buffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ export class BugReportZ extends AdbCommandBase {
|
||||||
return version.major > 1 || version.minor >= 2;
|
return version.major > 1 || version.minor >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public stream(): ReadableStream<ArrayBuffer> {
|
public stream(): ReadableStream<Uint8Array> {
|
||||||
return new WrapReadableStream<ArrayBuffer, ReadableStream<ArrayBuffer>, undefined>({
|
return new WrapReadableStream<Uint8Array, ReadableStream<Uint8Array>, undefined>({
|
||||||
start: async () => {
|
start: async () => {
|
||||||
const process = await this.adb.subprocess.spawn(['bugreportz', '-s']);
|
const process = await this.adb.subprocess.spawn(['bugreportz', '-s']);
|
||||||
return {
|
return {
|
||||||
|
@ -86,7 +86,7 @@ export class BugReportZ extends AdbCommandBase {
|
||||||
await process.stdout
|
await process.stdout
|
||||||
.pipeThrough(new DecodeUtf8Stream())
|
.pipeThrough(new DecodeUtf8Stream())
|
||||||
.pipeThrough(new SplitLineStream())
|
.pipeThrough(new SplitLineStream())
|
||||||
.pipeTo(new WritableStream({
|
.pipeTo(new WritableStream<string>({
|
||||||
write(line) {
|
write(line) {
|
||||||
// (Not 100% sure) `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
|
// (Not 100% sure) `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
|
||||||
let match = line.match(BugReportZ.PROGRESS_REGEX);
|
let match = line.match(BugReportZ.PROGRESS_REGEX);
|
||||||
|
|
|
@ -124,7 +124,7 @@ export class ScrcpyClient {
|
||||||
private _videoStream: TransformStream<VideoStreamPacket, VideoStreamPacket>;
|
private _videoStream: TransformStream<VideoStreamPacket, VideoStreamPacket>;
|
||||||
public get videoStream() { return this._videoStream.readable; }
|
public get videoStream() { return this._videoStream.readable; }
|
||||||
|
|
||||||
private _controlStreamWriter: WritableStreamDefaultWriter<ArrayBuffer> | undefined;
|
private _controlStreamWriter: WritableStreamDefaultWriter<Uint8Array> | undefined;
|
||||||
|
|
||||||
private readonly clipboardChangeEvent = new EventEmitter<string>();
|
private readonly clipboardChangeEvent = new EventEmitter<string>();
|
||||||
public get onClipboardChange() { return this.clipboardChangeEvent.event; }
|
public get onClipboardChange() { return this.clipboardChangeEvent.event; }
|
||||||
|
@ -179,7 +179,7 @@ export class ScrcpyClient {
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
const type = await buffered.read(1);
|
const type = await buffered.read(1);
|
||||||
switch (new Uint8Array(type)[0]) {
|
switch (type[0]) {
|
||||||
case 0:
|
case 0:
|
||||||
const { content } = await ClipboardMessage.deserialize(buffered);
|
const { content } = await ClipboardMessage.deserialize(buffered);
|
||||||
this.clipboardChangeEvent.fire(content!);
|
this.clipboardChangeEvent.fire(content!);
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class TinyH264Decoder implements H264Decoder {
|
||||||
|
|
||||||
this._renderer = document.createElement('canvas');
|
this._renderer = document.createElement('canvas');
|
||||||
|
|
||||||
this._writable = new WritableStream({
|
this._writable = new WritableStream<VideoStreamPacket>({
|
||||||
write: async (packet) => {
|
write: async (packet) => {
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case 'configuration':
|
case 'configuration':
|
||||||
|
@ -49,7 +49,7 @@ export class TinyH264Decoder implements H264Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = await this._initializer.promise;
|
const wrapper = await this._initializer.promise;
|
||||||
wrapper.feed(packet.data);
|
wrapper.feed(packet.data.slice().buffer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class WebCodecsDecoder implements H264Decoder {
|
||||||
error() { },
|
error() { },
|
||||||
});
|
});
|
||||||
|
|
||||||
this._writable = new WritableStream({
|
this._writable = new WritableStream<VideoStreamPacket>({
|
||||||
write: async (packet) => {
|
write: async (packet) => {
|
||||||
switch (packet.type) {
|
switch (packet.type) {
|
||||||
case 'configuration':
|
case 'configuration':
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForw
|
||||||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message";
|
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message";
|
||||||
import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18";
|
import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18";
|
||||||
import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22";
|
import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22";
|
||||||
import { toScrcpyOptionValue, type VideoStreamPacket, type ScrcpyOptions, type ScrcpyOptionValue } from "../common";
|
import { toScrcpyOptionValue, type ScrcpyOptions, type ScrcpyOptionValue, type VideoStreamPacket } from "../common";
|
||||||
import { parse_sequence_parameter_set } from "./sps";
|
import { parse_sequence_parameter_set } from "./sps";
|
||||||
|
|
||||||
export enum ScrcpyLogLevel {
|
export enum ScrcpyLogLevel {
|
||||||
|
@ -114,7 +114,7 @@ export const VideoPacket =
|
||||||
new Struct()
|
new Struct()
|
||||||
.int64('pts')
|
.int64('pts')
|
||||||
.uint32('size')
|
.uint32('size')
|
||||||
.arrayBuffer('data', { lengthField: 'size' });
|
.uint8Array('data', { lengthField: 'size' });
|
||||||
|
|
||||||
export const NoPts = BigInt(-1);
|
export const NoPts = BigInt(-1);
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ export const ScrcpyInjectScrollControlMessage1_16 =
|
||||||
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
|
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
|
||||||
public value: Partial<T>;
|
public value: Partial<T>;
|
||||||
|
|
||||||
private _streamHeader: ArrayBuffer | undefined;
|
private _streamHeader: Uint8Array | undefined;
|
||||||
|
|
||||||
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
|
||||||
if (new.target === ScrcpyOptions1_16 &&
|
if (new.target === ScrcpyOptions1_16 &&
|
||||||
|
@ -223,7 +223,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
||||||
|
|
||||||
const { pts, data } = await VideoPacket.deserialize(stream);
|
const { pts, data } = await VideoPacket.deserialize(stream);
|
||||||
if (pts === NoPts) {
|
if (pts === NoPts) {
|
||||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice(0));
|
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
profile_idc: profileIndex,
|
profile_idc: profileIndex,
|
||||||
|
@ -267,12 +267,11 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let frameData: ArrayBuffer;
|
let frameData: Uint8Array;
|
||||||
if (this._streamHeader) {
|
if (this._streamHeader) {
|
||||||
frameData = new ArrayBuffer(this._streamHeader.byteLength + data.byteLength);
|
frameData = new Uint8Array(this._streamHeader.byteLength + data.byteLength);
|
||||||
const array = new Uint8Array(frameData);
|
frameData.set(this._streamHeader);
|
||||||
array.set(new Uint8Array(this._streamHeader));
|
frameData.set(data!, this._streamHeader.byteLength);
|
||||||
array.set(new Uint8Array(data!), this._streamHeader.byteLength);
|
|
||||||
this._streamHeader = undefined;
|
this._streamHeader = undefined;
|
||||||
} else {
|
} else {
|
||||||
frameData = data;
|
frameData = data;
|
||||||
|
@ -296,7 +295,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
||||||
|
|
||||||
public serializeInjectScrollControlMessage(
|
public serializeInjectScrollControlMessage(
|
||||||
message: ScrcpyInjectScrollControlMessage1_22,
|
message: ScrcpyInjectScrollControlMessage1_22,
|
||||||
): ArrayBuffer {
|
): Uint8Array {
|
||||||
return ScrcpyInjectScrollControlMessage1_16.serialize(message);
|
return ScrcpyInjectScrollControlMessage1_16.serialize(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import Struct from "@yume-chan/struct";
|
import Struct from "@yume-chan/struct";
|
||||||
import { type ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
import { ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnection } from "../connection";
|
||||||
import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16";
|
import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16";
|
||||||
import { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21";
|
import { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21";
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ export class ScrcpyOptions1_22<T extends ScrcpyOptionsInit1_22 = ScrcpyOptionsIn
|
||||||
|
|
||||||
public override serializeInjectScrollControlMessage(
|
public override serializeInjectScrollControlMessage(
|
||||||
message: ScrcpyInjectScrollControlMessage1_22,
|
message: ScrcpyInjectScrollControlMessage1_22,
|
||||||
): ArrayBuffer {
|
): Uint8Array {
|
||||||
return ScrcpyInjectScrollControlMessage1_22.serialize(message);
|
return ScrcpyInjectScrollControlMessage1_22.serialize(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ export interface VideoStreamConfigurationPacket {
|
||||||
|
|
||||||
export interface VideoStreamFramePacket {
|
export interface VideoStreamFramePacket {
|
||||||
type: 'frame';
|
type: 'frame';
|
||||||
data: ArrayBuffer;
|
data: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VideoStreamPacket = VideoStreamConfigurationPacket | VideoStreamFramePacket;
|
export type VideoStreamPacket = VideoStreamConfigurationPacket | VideoStreamFramePacket;
|
||||||
|
@ -53,9 +53,9 @@ export interface ScrcpyOptions<T> {
|
||||||
|
|
||||||
serializeBackOrScreenOnControlMessage(
|
serializeBackOrScreenOnControlMessage(
|
||||||
message: ScrcpyBackOrScreenOnEvent1_18,
|
message: ScrcpyBackOrScreenOnEvent1_18,
|
||||||
): ArrayBuffer | undefined;
|
): Uint8Array | undefined;
|
||||||
|
|
||||||
serializeInjectScrollControlMessage(
|
serializeInjectScrollControlMessage(
|
||||||
message: ScrcpyInjectScrollControlMessage1_22,
|
message: ScrcpyInjectScrollControlMessage1_22,
|
||||||
): ArrayBuffer;
|
): Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ export function pushServer(
|
||||||
) {
|
) {
|
||||||
const { path = DEFAULT_SERVER_PATH } = options;
|
const { path = DEFAULT_SERVER_PATH } = options;
|
||||||
|
|
||||||
return new WrapWritableStream<ArrayBuffer, WritableStream<ArrayBuffer>, AdbSync>({
|
return new WrapWritableStream<Uint8Array, WritableStream<Uint8Array>, AdbSync>({
|
||||||
async start() {
|
async start() {
|
||||||
const sync = await device.sync();
|
const sync = await device.sync();
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic';
|
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic';
|
||||||
import { StructDefaultOptions, StructValue } from './basic';
|
import { StructDefaultOptions, StructValue } from './basic';
|
||||||
import { Syncbird } from "./syncbird";
|
import { Syncbird } from "./syncbird";
|
||||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, ArrayBufferViewFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
|
import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, FixedLengthBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldOptions } from './types';
|
||||||
import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils";
|
import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils";
|
||||||
|
|
||||||
export interface StructLike<TValue> {
|
export interface StructLike<TValue> {
|
||||||
|
@ -57,12 +57,12 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
TType extends BufferFieldSubType<any, any>,
|
||||||
TTypeScriptType = TType['TTypeScriptType'],
|
TTypeScriptType = TType['TTypeScriptType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
type: TType,
|
type: TType,
|
||||||
options: FixedLengthArrayBufferLikeFieldOptions,
|
options: FixedLengthBufferLikeFieldOptions,
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TFields,
|
TFields,
|
||||||
|
@ -70,9 +70,9 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TName,
|
TName,
|
||||||
FixedLengthArrayBufferLikeFieldDefinition<
|
FixedLengthBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
FixedLengthArrayBufferLikeFieldOptions
|
FixedLengthBufferLikeFieldOptions
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
@ -81,8 +81,8 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
*/
|
*/
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
TType extends BufferFieldSubType<any, any>,
|
||||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>,
|
TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
|
||||||
TTypeScriptType = TType['TTypeScriptType'],
|
TTypeScriptType = TType['TTypeScriptType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
|
@ -95,7 +95,7 @@ interface ArrayBufferLikeFieldCreator<
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TName,
|
TName,
|
||||||
VariableLengthArrayBufferLikeFieldDefinition<
|
VariableLengthBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TOptions
|
TOptions
|
||||||
>
|
>
|
||||||
|
@ -110,14 +110,14 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
TOmitInitKey extends PropertyKey,
|
TOmitInitKey extends PropertyKey,
|
||||||
TExtra extends object,
|
TExtra extends object,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TType extends ArrayBufferLikeFieldType<any, any>
|
TType extends BufferFieldSubType<any, any>
|
||||||
> {
|
> {
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TTypeScriptType = TType['TTypeScriptType'],
|
TTypeScriptType = TType['TTypeScriptType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
options: FixedLengthArrayBufferLikeFieldOptions,
|
options: FixedLengthBufferLikeFieldOptions,
|
||||||
typescriptType?: TTypeScriptType,
|
typescriptType?: TTypeScriptType,
|
||||||
): AddFieldDescriptor<
|
): AddFieldDescriptor<
|
||||||
TFields,
|
TFields,
|
||||||
|
@ -125,16 +125,16 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TName,
|
TName,
|
||||||
FixedLengthArrayBufferLikeFieldDefinition<
|
FixedLengthBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
FixedLengthArrayBufferLikeFieldOptions
|
FixedLengthBufferLikeFieldOptions
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
<
|
<
|
||||||
TName extends PropertyKey,
|
TName extends PropertyKey,
|
||||||
TLengthField extends LengthField<TFields>,
|
TLengthField extends LengthField<TFields>,
|
||||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields, TLengthField>,
|
TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,
|
||||||
TTypeScriptType = TType['TTypeScriptType'],
|
TTypeScriptType = TType['TTypeScriptType'],
|
||||||
>(
|
>(
|
||||||
name: TName,
|
name: TName,
|
||||||
|
@ -146,7 +146,7 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
TName,
|
TName,
|
||||||
VariableLengthArrayBufferLikeFieldDefinition<
|
VariableLengthBufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TOptions
|
TOptions
|
||||||
>
|
>
|
||||||
|
@ -421,59 +421,33 @@ export class Struct<
|
||||||
TPostDeserialized
|
TPostDeserialized
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
type: ArrayBufferLikeFieldType,
|
type: BufferFieldSubType,
|
||||||
options: FixedLengthArrayBufferLikeFieldOptions | VariableLengthArrayBufferLikeFieldOptions
|
options: FixedLengthBufferLikeFieldOptions | VariableLengthBufferLikeFieldOptions
|
||||||
): any => {
|
): any => {
|
||||||
if ('length' in options) {
|
if ('length' in options) {
|
||||||
return this.field(
|
return this.field(
|
||||||
name,
|
name,
|
||||||
new FixedLengthArrayBufferLikeFieldDefinition(type, options),
|
new FixedLengthBufferLikeFieldDefinition(type, options),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return this.field(
|
return this.field(
|
||||||
name,
|
name,
|
||||||
new VariableLengthArrayBufferLikeFieldDefinition(type, options),
|
new VariableLengthBufferLikeFieldDefinition(type, options),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public arrayBuffer: BoundArrayBufferLikeFieldDefinitionCreator<
|
|
||||||
TFields,
|
|
||||||
TOmitInitKey,
|
|
||||||
TExtra,
|
|
||||||
TPostDeserialized,
|
|
||||||
ArrayBufferFieldType
|
|
||||||
> = (
|
|
||||||
name: PropertyKey,
|
|
||||||
options: any
|
|
||||||
): any => {
|
|
||||||
return this.arrayBufferLike(name, ArrayBufferFieldType.instance, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
|
public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
TFields,
|
TFields,
|
||||||
TOmitInitKey,
|
TOmitInitKey,
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
ArrayBufferViewFieldType<Uint8Array>
|
Uint8ArrayBufferFieldSubType
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
options: any
|
options: any
|
||||||
): any => {
|
): any => {
|
||||||
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8Array, options);
|
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options);
|
||||||
};
|
|
||||||
|
|
||||||
public uint8ClampedArray: BoundArrayBufferLikeFieldDefinitionCreator<
|
|
||||||
TFields,
|
|
||||||
TOmitInitKey,
|
|
||||||
TExtra,
|
|
||||||
TPostDeserialized,
|
|
||||||
ArrayBufferViewFieldType<Uint8ClampedArray>
|
|
||||||
> = (
|
|
||||||
name: PropertyKey,
|
|
||||||
options: any
|
|
||||||
): any => {
|
|
||||||
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8ClampedArray, options);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public string: BoundArrayBufferLikeFieldDefinitionCreator<
|
public string: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||||
|
@ -481,12 +455,12 @@ export class Struct<
|
||||||
TOmitInitKey,
|
TOmitInitKey,
|
||||||
TExtra,
|
TExtra,
|
||||||
TPostDeserialized,
|
TPostDeserialized,
|
||||||
StringFieldType
|
StringBufferFieldSubType
|
||||||
> = (
|
> = (
|
||||||
name: PropertyKey,
|
name: PropertyKey,
|
||||||
options: any
|
options: any
|
||||||
): any => {
|
): any => {
|
||||||
return this.arrayBufferLike(name, StringFieldType.instance, options);
|
return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,207 +0,0 @@
|
||||||
import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic';
|
|
||||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, ArrayBufferViewFieldType } from './array-buffer';
|
|
||||||
|
|
||||||
class MockDeserializationStream implements StructDeserializeStream {
|
|
||||||
public buffer = new ArrayBuffer(0);
|
|
||||||
|
|
||||||
public read = jest.fn((length: number) => this.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Types', () => {
|
|
||||||
describe('ArrayBufferLike', () => {
|
|
||||||
describe('ArrayBufferFieldType', () => {
|
|
||||||
it('should have a static instance', () => {
|
|
||||||
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#toArrayBuffer` should return the same `ArrayBuffer`', () => {
|
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
|
||||||
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#fromArrayBuffer` should return the same `ArrayBuffer`', () => {
|
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
|
||||||
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
|
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
|
||||||
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Uint8ClampedArrayFieldType', () => {
|
|
||||||
it('should have a static instance', () => {
|
|
||||||
expect(ArrayBufferViewFieldType.instance).toBeInstanceOf(ArrayBufferViewFieldType);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#toArrayBuffer` should return its `buffer`', () => {
|
|
||||||
const array = new Uint8ClampedArray(10);
|
|
||||||
const buffer = array.buffer;
|
|
||||||
expect(ArrayBufferViewFieldType.instance.toArrayBuffer(array)).toBe(buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
|
|
||||||
const arrayBuffer = new ArrayBuffer(10);
|
|
||||||
const array = ArrayBufferViewFieldType.instance.fromArrayBuffer(arrayBuffer);
|
|
||||||
expect(array).toHaveProperty('buffer', arrayBuffer);
|
|
||||||
expect(array).toHaveProperty('byteOffset', 0);
|
|
||||||
expect(array).toHaveProperty('byteLength', 10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#getSize` should return the `byteLength` of the `Uint8ClampedArray`', () => {
|
|
||||||
const array = new Uint8ClampedArray(10);
|
|
||||||
expect(ArrayBufferViewFieldType.instance.getSize(array)).toBe(10);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('StringFieldType', () => {
|
|
||||||
it('should have a static instance', () => {
|
|
||||||
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#toArrayBuffer` should return the decoded string', () => {
|
|
||||||
const text = 'foo';
|
|
||||||
const arrayBuffer = Buffer.from(text, 'utf-8');
|
|
||||||
expect(StringFieldType.instance.toArrayBuffer(text)).toEqual(arrayBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
|
|
||||||
const text = 'foo';
|
|
||||||
const arrayBuffer = Buffer.from(text, 'utf-8');
|
|
||||||
expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(text);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('`#getSize` should return -1', () => {
|
|
||||||
expect(StringFieldType.instance.getSize()).toBe(-1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
class MockArrayBufferFieldDefinition<TType extends ArrayBufferLikeFieldType>
|
|
||||||
extends ArrayBufferLikeFieldDefinition<TType, number> {
|
|
||||||
public getSize(): number {
|
|
||||||
return this.options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ArrayBufferLikeFieldDefinition', () => {
|
|
||||||
it('should work with `ArrayBufferFieldType`', async () => {
|
|
||||||
const size = 10;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const buffer = new ArrayBuffer(size);
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
expect(context.read).toBeCalledTimes(1);
|
|
||||||
expect(context.read).toBeCalledWith(size);
|
|
||||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
|
||||||
|
|
||||||
expect(fieldValue.get()).toBe(buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with `Uint8ClampedArrayFieldType`', async () => {
|
|
||||||
const size = 10;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferViewFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const buffer = new ArrayBuffer(size);
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
expect(context.read).toBeCalledTimes(1);
|
|
||||||
expect(context.read).toBeCalledWith(size);
|
|
||||||
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
|
|
||||||
|
|
||||||
const value = fieldValue.get();
|
|
||||||
expect(value).toBeInstanceOf(Uint8ClampedArray);
|
|
||||||
expect(value).toHaveProperty('buffer', buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when `#getSize` returns `0`', async () => {
|
|
||||||
const size = 0;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const buffer = new ArrayBuffer(size);
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
expect(context.read).toBeCalledTimes(0);
|
|
||||||
expect(fieldValue['arrayBuffer']).toBeInstanceOf(ArrayBuffer);
|
|
||||||
expect(fieldValue['arrayBuffer']).toHaveProperty('byteLength', 0);
|
|
||||||
|
|
||||||
const value = fieldValue.get();
|
|
||||||
expect(value).toBeInstanceOf(ArrayBuffer);
|
|
||||||
expect(value).toHaveProperty('byteLength', 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ArrayBufferLikeFieldValue', () => {
|
|
||||||
describe('#set', () => {
|
|
||||||
it('should clear `arrayBuffer` field', async () => {
|
|
||||||
const size = 0;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const buffer = new ArrayBuffer(size);
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
|
|
||||||
const newValue = new ArrayBuffer(20);
|
|
||||||
fieldValue.set(newValue);
|
|
||||||
expect(fieldValue.get()).toBe(newValue);
|
|
||||||
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#serialize', () => {
|
|
||||||
it('should be able to serialize with cached `arrayBuffer`', async () => {
|
|
||||||
const size = 0;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
|
||||||
const buffer = sourceArray.buffer;
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
|
|
||||||
const targetArray = new Uint8Array(size);
|
|
||||||
const targetView = new DataView(targetArray.buffer);
|
|
||||||
fieldValue.serialize(targetView, 0);
|
|
||||||
|
|
||||||
expect(targetArray).toEqual(sourceArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to serialize a modified value', async () => {
|
|
||||||
const size = 0;
|
|
||||||
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
|
|
||||||
|
|
||||||
const context = new MockDeserializationStream();
|
|
||||||
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
|
||||||
const buffer = sourceArray.buffer;
|
|
||||||
context.buffer = buffer;
|
|
||||||
const struct = new StructValue();
|
|
||||||
|
|
||||||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
|
||||||
|
|
||||||
fieldValue.set(sourceArray.buffer);
|
|
||||||
|
|
||||||
const targetArray = new Uint8Array(size);
|
|
||||||
const targetView = new DataView(targetArray.buffer);
|
|
||||||
fieldValue.serialize(targetView, 0);
|
|
||||||
|
|
||||||
expect(targetArray).toEqual(sourceArray);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,202 +0,0 @@
|
||||||
// cspell: ignore syncbird
|
|
||||||
|
|
||||||
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic';
|
|
||||||
import { Syncbird } from "../syncbird";
|
|
||||||
import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../utils";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for all types that
|
|
||||||
* can be converted from an ArrayBuffer when deserialized,
|
|
||||||
* and need to be converted to an ArrayBuffer when serializing
|
|
||||||
*
|
|
||||||
* @template TValue The actual TypeScript type of this type
|
|
||||||
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
|
||||||
* specified by user when creating field definitions.
|
|
||||||
*/
|
|
||||||
export abstract class ArrayBufferLikeFieldType<TValue = unknown, TTypeScriptType = TValue> {
|
|
||||||
public readonly TTypeScriptType!: TTypeScriptType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When implemented in derived classes, converts the type-specific `value` to an `ArrayBuffer`
|
|
||||||
*
|
|
||||||
* This function should be "pure", i.e.,
|
|
||||||
* same `value` should always be converted to `ArrayBuffer`s that have same content.
|
|
||||||
*/
|
|
||||||
public abstract toArrayBuffer(value: TValue): ArrayBuffer;
|
|
||||||
|
|
||||||
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
|
||||||
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer): TValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
|
||||||
*
|
|
||||||
* If the size can't be calculated without first converting the `value` back to an `ArrayBuffer`,
|
|
||||||
* implementer should returns `-1` so the caller will get its size by first converting it to
|
|
||||||
* an `ArrayBuffer` (and cache the result).
|
|
||||||
*/
|
|
||||||
public abstract getSize(value: TValue): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
|
|
||||||
export class ArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
|
|
||||||
public static readonly instance = new ArrayBufferFieldType();
|
|
||||||
|
|
||||||
protected constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public toArrayBuffer(value: ArrayBuffer): ArrayBuffer {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public fromArrayBuffer(arrayBuffer: ArrayBuffer): ArrayBuffer {
|
|
||||||
return arrayBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSize(value: ArrayBuffer): number {
|
|
||||||
return value.byteLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArrayBufferViewConstructor<T> = new (array: ArrayLike<number> | ArrayBufferLike) => T;
|
|
||||||
|
|
||||||
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */
|
|
||||||
export class ArrayBufferViewFieldType<T extends ArrayBufferView>
|
|
||||||
extends ArrayBufferLikeFieldType<T, T> {
|
|
||||||
public static readonly uint8Array = new ArrayBufferViewFieldType(Uint8Array);
|
|
||||||
public static readonly uint8ClampedArray = new ArrayBufferViewFieldType(Uint8ClampedArray);
|
|
||||||
|
|
||||||
protected type: ArrayBufferViewConstructor<T>;
|
|
||||||
|
|
||||||
public constructor(type: ArrayBufferViewConstructor<T>) {
|
|
||||||
super();
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public toArrayBuffer(value: T): ArrayBuffer {
|
|
||||||
return value.buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public fromArrayBuffer(arrayBuffer: ArrayBuffer): T {
|
|
||||||
return new this.type(arrayBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSize(value: T): number {
|
|
||||||
return value.byteLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** An ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
|
|
||||||
export class StringFieldType<TTypeScriptType = string>
|
|
||||||
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
|
|
||||||
public static readonly instance = new StringFieldType();
|
|
||||||
|
|
||||||
public toArrayBuffer(value: string): ArrayBuffer {
|
|
||||||
return encodeUtf8(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public fromArrayBuffer(arrayBuffer: ArrayBuffer): string {
|
|
||||||
return decodeUtf8(arrayBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSize(): number {
|
|
||||||
// Return `-1`, so `ArrayBufferLikeFieldDefinition` will
|
|
||||||
// convert this `value` into an `ArrayBuffer` (and cache the result),
|
|
||||||
// Then get the size from that `ArrayBuffer`
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmptyArrayBuffer = new ArrayBuffer(0);
|
|
||||||
|
|
||||||
export abstract class ArrayBufferLikeFieldDefinition<
|
|
||||||
TType extends ArrayBufferLikeFieldType<any, any> = ArrayBufferLikeFieldType<unknown, unknown>,
|
|
||||||
TOptions = void,
|
|
||||||
TOmitInitKey extends PropertyKey = never,
|
|
||||||
> extends StructFieldDefinition<
|
|
||||||
TOptions,
|
|
||||||
TType['TTypeScriptType'],
|
|
||||||
TOmitInitKey
|
|
||||||
>{
|
|
||||||
public readonly type: TType;
|
|
||||||
|
|
||||||
public constructor(type: TType, options: TOptions) {
|
|
||||||
super(options);
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getDeserializeSize(struct: StructValue): number {
|
|
||||||
return this.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When implemented in derived classes, creates a `StructFieldValue` for the current field definition.
|
|
||||||
*/
|
|
||||||
public create(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
struct: StructValue,
|
|
||||||
value: TType['TTypeScriptType'],
|
|
||||||
arrayBuffer?: ArrayBuffer,
|
|
||||||
): ArrayBufferLikeFieldValue<this> {
|
|
||||||
return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override deserialize(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
stream: StructDeserializeStream,
|
|
||||||
struct: StructValue,
|
|
||||||
): ArrayBufferLikeFieldValue<this>;
|
|
||||||
public override deserialize(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
stream: StructAsyncDeserializeStream,
|
|
||||||
struct: StructValue,
|
|
||||||
): Promise<ArrayBufferLikeFieldValue<this>>;
|
|
||||||
public override deserialize(
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
|
||||||
struct: StructValue,
|
|
||||||
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
|
|
||||||
return Syncbird.try(() => {
|
|
||||||
const size = this.getDeserializeSize(struct);
|
|
||||||
if (size === 0) {
|
|
||||||
return EmptyArrayBuffer;
|
|
||||||
} else {
|
|
||||||
return stream.read(size);
|
|
||||||
}
|
|
||||||
}).then(arrayBuffer => {
|
|
||||||
const value = this.type.fromArrayBuffer(arrayBuffer);
|
|
||||||
return this.create(options, struct, value, arrayBuffer);
|
|
||||||
}).valueOrPromise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ArrayBufferLikeFieldValue<
|
|
||||||
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType<unknown, unknown>, any, any>,
|
|
||||||
> extends StructFieldValue<TDefinition> {
|
|
||||||
protected arrayBuffer: ArrayBuffer | undefined;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
definition: TDefinition,
|
|
||||||
options: Readonly<StructOptions>,
|
|
||||||
struct: StructValue,
|
|
||||||
value: TDefinition['TValue'],
|
|
||||||
arrayBuffer?: ArrayBuffer,
|
|
||||||
) {
|
|
||||||
super(definition, options, struct, value);
|
|
||||||
this.arrayBuffer = arrayBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override set(value: TDefinition['TValue']): void {
|
|
||||||
super.set(value);
|
|
||||||
this.arrayBuffer = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public serialize(dataView: DataView, offset: number): void {
|
|
||||||
if (!this.arrayBuffer) {
|
|
||||||
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
new Uint8Array(dataView.buffer)
|
|
||||||
.set(new Uint8Array(this.arrayBuffer), offset);
|
|
||||||
}
|
|
||||||
}
|
|
163
libraries/struct/src/types/buffer/base.spec.ts
Normal file
163
libraries/struct/src/types/buffer/base.spec.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../../basic';
|
||||||
|
import { BufferFieldSubType, BufferLikeFieldDefinition, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base';
|
||||||
|
|
||||||
|
class MockDeserializationStream implements StructDeserializeStream {
|
||||||
|
public array = new Uint8Array(0);
|
||||||
|
|
||||||
|
public read = jest.fn((length: number) => this.array);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Types', () => {
|
||||||
|
describe('Buffer', () => {
|
||||||
|
describe('Uint8ArrayBufferFieldSubType', () => {
|
||||||
|
it('should have a static instance', () => {
|
||||||
|
expect(Uint8ArrayBufferFieldSubType.Instance).toBeInstanceOf(Uint8ArrayBufferFieldSubType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#toBuffer` should return the same `Uint8Array`', () => {
|
||||||
|
const array = new Uint8Array(10);
|
||||||
|
expect(Uint8ArrayBufferFieldSubType.Instance.toBuffer(array)).toBe(array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#fromBuffer` should return the same `Uint8Array`', () => {
|
||||||
|
const buffer = new Uint8Array(10);
|
||||||
|
expect(Uint8ArrayBufferFieldSubType.Instance.toValue(buffer)).toBe(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#getSize` should return the `byteLength` of the `Uint8Array`', () => {
|
||||||
|
const arrayBuffer = new Uint8Array(10);
|
||||||
|
expect(Uint8ArrayBufferFieldSubType.Instance.getSize(arrayBuffer)).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('StringBufferFieldSubType', () => {
|
||||||
|
it('should have a static instance', () => {
|
||||||
|
expect(StringBufferFieldSubType.Instance).toBeInstanceOf(StringBufferFieldSubType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#toBuffer` should return the decoded string', () => {
|
||||||
|
const text = 'foo';
|
||||||
|
const array = new Uint8Array(Buffer.from(text, 'utf-8'));
|
||||||
|
expect(StringBufferFieldSubType.Instance.toBuffer(text)).toEqual(array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#fromBuffer` should return the encoded ArrayBuffer', () => {
|
||||||
|
const text = 'foo';
|
||||||
|
const array = new Uint8Array(Buffer.from(text, 'utf-8'));
|
||||||
|
expect(StringBufferFieldSubType.Instance.toValue(array)).toBe(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('`#getSize` should return -1', () => {
|
||||||
|
expect(StringBufferFieldSubType.Instance.getSize()).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class MockArrayBufferFieldDefinition<TType extends BufferFieldSubType>
|
||||||
|
extends BufferLikeFieldDefinition<TType, number> {
|
||||||
|
public getSize(): number {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BufferLikeFieldDefinition', () => {
|
||||||
|
it('should work with `Uint8ArrayBufferFieldSubType`', async () => {
|
||||||
|
const size = 10;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||||
|
|
||||||
|
const context = new MockDeserializationStream();
|
||||||
|
const array = new Uint8Array(size);
|
||||||
|
context.array = array;
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(context.read).toBeCalledTimes(1);
|
||||||
|
expect(context.read).toBeCalledWith(size);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', array);
|
||||||
|
|
||||||
|
expect(fieldValue.get()).toBe(array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work when `#getSize` returns `0`', async () => {
|
||||||
|
const size = 0;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||||
|
|
||||||
|
const context = new MockDeserializationStream();
|
||||||
|
const buffer = new Uint8Array(size);
|
||||||
|
context.array = buffer;
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
expect(context.read).toBeCalledTimes(0);
|
||||||
|
expect(fieldValue['array']).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(fieldValue['array']).toHaveProperty('byteLength', 0);
|
||||||
|
|
||||||
|
const value = fieldValue.get();
|
||||||
|
expect(value).toBeInstanceOf(Uint8Array);
|
||||||
|
expect(value).toHaveProperty('byteLength', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ArrayBufferLikeFieldValue', () => {
|
||||||
|
describe('#set', () => {
|
||||||
|
it('should clear `arrayBuffer` field', async () => {
|
||||||
|
const size = 0;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||||
|
|
||||||
|
const context = new MockDeserializationStream();
|
||||||
|
const array = new Uint8Array(size);
|
||||||
|
context.array = array;
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
|
||||||
|
const newValue = new Uint8Array(20);
|
||||||
|
fieldValue.set(newValue);
|
||||||
|
expect(fieldValue.get()).toBe(newValue);
|
||||||
|
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#serialize', () => {
|
||||||
|
it('should be able to serialize with cached `arrayBuffer`', async () => {
|
||||||
|
const size = 0;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||||
|
|
||||||
|
const context = new MockDeserializationStream();
|
||||||
|
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
||||||
|
const array = sourceArray;
|
||||||
|
context.array = array;
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
|
||||||
|
const targetArray = new Uint8Array(size);
|
||||||
|
const targetView = new DataView(targetArray.buffer);
|
||||||
|
fieldValue.serialize(targetView, 0);
|
||||||
|
|
||||||
|
expect(targetArray).toEqual(sourceArray);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to serialize a modified value', async () => {
|
||||||
|
const size = 0;
|
||||||
|
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||||
|
|
||||||
|
const context = new MockDeserializationStream();
|
||||||
|
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
|
||||||
|
const array = sourceArray;
|
||||||
|
context.array = array;
|
||||||
|
const struct = new StructValue();
|
||||||
|
|
||||||
|
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||||
|
|
||||||
|
fieldValue.set(sourceArray);
|
||||||
|
|
||||||
|
const targetArray = new Uint8Array(size);
|
||||||
|
const targetView = new DataView(targetArray.buffer);
|
||||||
|
fieldValue.serialize(targetView, 0);
|
||||||
|
|
||||||
|
expect(targetArray).toEqual(sourceArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,8 +6,8 @@ import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../../utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for all types that
|
* Base class for all types that
|
||||||
* can be converted from an ArrayBuffer when deserialized,
|
* can be converted from an `Uint8Array` when deserialized,
|
||||||
* and need to be converted to an ArrayBuffer when serializing
|
* and need to be converted to an `Uint8Array` when serializing
|
||||||
*
|
*
|
||||||
* @template TValue The actual TypeScript type of this type
|
* @template TValue The actual TypeScript type of this type
|
||||||
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
|
||||||
|
@ -17,27 +17,27 @@ export abstract class BufferFieldSubType<TValue = unknown, TTypeScriptType = TVa
|
||||||
public readonly TTypeScriptType!: TTypeScriptType;
|
public readonly TTypeScriptType!: TTypeScriptType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, converts the type-specific `value` to an `ArrayBuffer`
|
* When implemented in derived classes, converts the type-specific `value` to an `Uint8Array`
|
||||||
*
|
*
|
||||||
* This function should be "pure", i.e.,
|
* This function should be "pure", i.e.,
|
||||||
* same `value` should always be converted to `ArrayBuffer`s that have same content.
|
* same `value` should always be converted to `Uint8Array`s that have same content.
|
||||||
*/
|
*/
|
||||||
public abstract toBuffer(value: TValue): Uint8Array;
|
public abstract toBuffer(value: TValue): Uint8Array;
|
||||||
|
|
||||||
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
/** When implemented in derived classes, converts the `Uint8Array` to a type-specific value */
|
||||||
public abstract fromBuffer(array: Uint8Array): TValue;
|
public abstract toValue(array: Uint8Array): TValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
|
||||||
*
|
*
|
||||||
* If the size can't be calculated without first converting the `value` back to an `ArrayBuffer`,
|
* If the size can't be calculated without first converting the `value` back to an `Uint8Array`,
|
||||||
* implementer should returns `-1` so the caller will get its size by first converting it to
|
* implementer can returns `-1`, so the caller will get its size by first converting it to
|
||||||
* an `ArrayBuffer` (and cache the result).
|
* an `Uint8Array` (and cache the result).
|
||||||
*/
|
*/
|
||||||
public abstract getSize(value: TValue): number;
|
public abstract getSize(value: TValue): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
|
/** An `BufferFieldSubType` that's actually an `Uint8Array` */
|
||||||
export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> {
|
export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> {
|
||||||
public static readonly Instance = new Uint8ArrayBufferFieldSubType();
|
public static readonly Instance = new Uint8ArrayBufferFieldSubType();
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array>
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fromBuffer(buffer: Uint8Array): Uint8Array {
|
public toValue(buffer: Uint8Array): Uint8Array {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
|
/** An `BufferFieldSubType` that converts between `Uint8Array` and `string` */
|
||||||
export class StringBufferFieldSubType<TTypeScriptType = string>
|
export class StringBufferFieldSubType<TTypeScriptType = string>
|
||||||
extends BufferFieldSubType<string, TTypeScriptType> {
|
extends BufferFieldSubType<string, TTypeScriptType> {
|
||||||
public static readonly Instance = new StringBufferFieldSubType();
|
public static readonly Instance = new StringBufferFieldSubType();
|
||||||
|
@ -67,21 +67,21 @@ export class StringBufferFieldSubType<TTypeScriptType = string>
|
||||||
return encodeUtf8(value);
|
return encodeUtf8(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fromBuffer(array: Uint8Array): string {
|
public toValue(array: Uint8Array): string {
|
||||||
return decodeUtf8(array);
|
return decodeUtf8(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSize(): number {
|
public getSize(): number {
|
||||||
// Return `-1`, so `ArrayBufferLikeFieldDefinition` will
|
// Return `-1`, so `BufferLikeFieldDefinition` will
|
||||||
// convert this `value` into an `ArrayBuffer` (and cache the result),
|
// convert this `value` into an `Uint8Array` (and cache the result),
|
||||||
// Then get the size from that `ArrayBuffer`
|
// Then get the size from that `Uint8Array`
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmptyArrayBuffer = new ArrayBuffer(0);
|
const EmptyBuffer = new Uint8Array(0);
|
||||||
|
|
||||||
export abstract class ArrayBufferLikeFieldDefinition<
|
export abstract class BufferLikeFieldDefinition<
|
||||||
TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>,
|
TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>,
|
||||||
TOptions = void,
|
TOptions = void,
|
||||||
TOmitInitKey extends PropertyKey = never,
|
TOmitInitKey extends PropertyKey = never,
|
||||||
|
@ -108,67 +108,67 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
value: TType['TTypeScriptType'],
|
value: TType['TTypeScriptType'],
|
||||||
arrayBuffer?: ArrayBuffer,
|
array?: Uint8Array,
|
||||||
): ArrayBufferLikeFieldValue<this> {
|
): BufferLikeFieldValue<this> {
|
||||||
return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer);
|
return new BufferLikeFieldValue(this, options, struct, value, array);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override deserialize(
|
public override deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
stream: StructDeserializeStream,
|
stream: StructDeserializeStream,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
): ArrayBufferLikeFieldValue<this>;
|
): BufferLikeFieldValue<this>;
|
||||||
public override deserialize(
|
public override deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
stream: StructAsyncDeserializeStream,
|
stream: StructAsyncDeserializeStream,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
): Promise<ArrayBufferLikeFieldValue<this>>;
|
): Promise<BufferLikeFieldValue<this>>;
|
||||||
public override deserialize(
|
public override deserialize(
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
|
): ValueOrPromise<BufferLikeFieldValue<this>> {
|
||||||
return Syncbird.try(() => {
|
return Syncbird.try(() => {
|
||||||
const size = this.getDeserializeSize(struct);
|
const size = this.getDeserializeSize(struct);
|
||||||
if (size === 0) {
|
if (size === 0) {
|
||||||
return EmptyArrayBuffer;
|
return EmptyBuffer;
|
||||||
} else {
|
} else {
|
||||||
return stream.read(size);
|
return stream.read(size);
|
||||||
}
|
}
|
||||||
}).then(arrayBuffer => {
|
}).then(array => {
|
||||||
const value = this.type.fromBuffer(arrayBuffer);
|
const value = this.type.toValue(array);
|
||||||
return this.create(options, struct, value, arrayBuffer);
|
return this.create(options, struct, value, array);
|
||||||
}).valueOrPromise();
|
}).valueOrPromise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ArrayBufferLikeFieldValue<
|
export class BufferLikeFieldValue<
|
||||||
TDefinition extends ArrayBufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>,
|
TDefinition extends BufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>,
|
||||||
> extends StructFieldValue<TDefinition> {
|
> extends StructFieldValue<TDefinition> {
|
||||||
protected arrayBuffer: ArrayBuffer | undefined;
|
protected array: Uint8Array | undefined;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
definition: TDefinition,
|
definition: TDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
value: TDefinition['TValue'],
|
value: TDefinition['TValue'],
|
||||||
arrayBuffer?: ArrayBuffer,
|
array?: Uint8Array,
|
||||||
) {
|
) {
|
||||||
super(definition, options, struct, value);
|
super(definition, options, struct, value);
|
||||||
this.arrayBuffer = arrayBuffer;
|
this.array = array;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override set(value: TDefinition['TValue']): void {
|
public override set(value: TDefinition['TValue']): void {
|
||||||
super.set(value);
|
super.set(value);
|
||||||
this.arrayBuffer = undefined;
|
this.array = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public serialize(dataView: DataView, offset: number): void {
|
public serialize(dataView: DataView, offset: number): void {
|
||||||
if (!this.arrayBuffer) {
|
if (!this.array) {
|
||||||
this.arrayBuffer = this.definition.type.toBuffer(this.value);
|
this.array = this.definition.type.toBuffer(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Uint8Array(dataView.buffer)
|
new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength)
|
||||||
.set(new Uint8Array(this.arrayBuffer), offset);
|
.set(this.array, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { ArrayBufferFieldType } from './array-buffer';
|
import { Uint8ArrayBufferFieldSubType } from './base';
|
||||||
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
|
import { FixedLengthBufferLikeFieldDefinition } from './fixed-length';
|
||||||
|
|
||||||
describe('Types', () => {
|
describe('Types', () => {
|
||||||
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||||
describe('#getSize', () => {
|
describe('#getSize', () => {
|
||||||
it('should return size in its options', () => {
|
it('should return size in its options', () => {
|
||||||
const definition = new FixedLengthArrayBufferLikeFieldDefinition(
|
const definition = new FixedLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ length: 10 },
|
{ length: 10 },
|
||||||
);
|
);
|
||||||
expect(definition.getSize()).toBe(10);
|
expect(definition.getSize()).toBe(10);
|
17
libraries/struct/src/types/buffer/fixed-length.ts
Normal file
17
libraries/struct/src/types/buffer/fixed-length.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { BufferFieldSubType, BufferLikeFieldDefinition } from './base';
|
||||||
|
|
||||||
|
export interface FixedLengthBufferLikeFieldOptions {
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FixedLengthBufferLikeFieldDefinition<
|
||||||
|
TType extends BufferFieldSubType = BufferFieldSubType,
|
||||||
|
TOptions extends FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions,
|
||||||
|
> extends BufferLikeFieldDefinition<
|
||||||
|
TType,
|
||||||
|
TOptions
|
||||||
|
> {
|
||||||
|
public getSize(): number {
|
||||||
|
return this.options.length;
|
||||||
|
}
|
||||||
|
};
|
3
libraries/struct/src/types/buffer/index.ts
Normal file
3
libraries/struct/src/types/buffer/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './base';
|
||||||
|
export * from './fixed-length';
|
||||||
|
export * from './variable-length';
|
|
@ -1,21 +1,21 @@
|
||||||
import { StructDefaultOptions, StructFieldValue, StructValue } from '../basic';
|
import { StructDefaultOptions, StructFieldValue, StructValue } from '../../basic';
|
||||||
import { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer';
|
import { BufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base';
|
||||||
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
|
import { VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldLengthValue, VariableLengthBufferLikeStructFieldValue } from './variable-length';
|
||||||
|
|
||||||
class MockOriginalFieldValue extends StructFieldValue {
|
class MockOriginalFieldValue extends StructFieldValue {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super({} as any, {} as any, {} as any, {});
|
super({} as any, {} as any, {} as any, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public value: string | number = 0;
|
public override value: string | number = 0;
|
||||||
|
|
||||||
public get = jest.fn((): string | number => this.value);
|
public override get = jest.fn((): string | number => this.value);
|
||||||
|
|
||||||
public size = 0;
|
public size = 0;
|
||||||
|
|
||||||
public getSize = jest.fn((): number => this.size);
|
public override getSize = jest.fn((): number => this.size);
|
||||||
|
|
||||||
public set = jest.fn((value: string | number) => { });
|
public override set = jest.fn((value: string | number) => { });
|
||||||
|
|
||||||
public serialize = jest.fn((dataView: DataView, offset: number): void => { });
|
public serialize = jest.fn((dataView: DataView, offset: number): void => { });
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ describe('Types', () => {
|
||||||
|
|
||||||
public size = 0;
|
public size = 0;
|
||||||
|
|
||||||
public getSize = jest.fn(() => this.size);
|
public override getSize = jest.fn(() => this.size);
|
||||||
|
|
||||||
public serialize(dataView: DataView, offset: number): void {
|
public serialize(dataView: DataView, offset: number): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
|
@ -40,7 +40,7 @@ describe('Types', () => {
|
||||||
it('should return size of its original field value', () => {
|
it('should return size of its original field value', () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -60,7 +60,7 @@ describe('Types', () => {
|
||||||
it('should return size of its `arrayBufferField`', async () => {
|
it('should return size of its `arrayBufferField`', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -82,7 +82,7 @@ describe('Types', () => {
|
||||||
it('should return size of its `arrayBufferField` as string', async () => {
|
it('should return size of its `arrayBufferField` as string', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -106,7 +106,7 @@ describe('Types', () => {
|
||||||
it('should does nothing', async () => {
|
it('should does nothing', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -124,7 +124,7 @@ describe('Types', () => {
|
||||||
it('should call `serialize` of its `originalField`', async () => {
|
it('should call `serialize` of its `originalField`', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -155,7 +155,7 @@ describe('Types', () => {
|
||||||
it('should stringify its length if `originalField` is a string', async () => {
|
it('should stringify its length if `originalField` is a string', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -186,7 +186,7 @@ describe('Types', () => {
|
||||||
it('should stringify its length in specified base if `originalField` is a string', async () => {
|
it('should stringify its length in specified base if `originalField` is a string', async () => {
|
||||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
mockOriginalFieldValue,
|
mockOriginalFieldValue,
|
||||||
mockArrayBufferFieldValue,
|
mockArrayBufferFieldValue,
|
||||||
);
|
);
|
||||||
|
@ -228,14 +228,14 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(0);
|
const value = new Uint8Array(0);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -257,14 +257,14 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -276,7 +276,7 @@ describe('Types', () => {
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
expect(arrayBufferFieldValue).toHaveProperty('array', value);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -287,14 +287,14 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(0);
|
const value = new Uint8Array(0);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -307,18 +307,18 @@ describe('Types', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getSize', () => {
|
describe('#getSize', () => {
|
||||||
class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
|
class MockArrayBufferFieldType extends BufferFieldSubType<Uint8Array> {
|
||||||
public toArrayBuffer = jest.fn((value: ArrayBuffer): ArrayBuffer => {
|
public override toBuffer = jest.fn((value: Uint8Array): Uint8Array => {
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
|
||||||
public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer): ArrayBuffer => {
|
public override toValue = jest.fn((arrayBuffer: Uint8Array): Uint8Array => {
|
||||||
return arrayBuffer;
|
return arrayBuffer;
|
||||||
});
|
});
|
||||||
|
|
||||||
public size = 0;
|
public size = 0;
|
||||||
|
|
||||||
public getSize = jest.fn((value: ArrayBuffer): number => {
|
public override getSize = jest.fn((value: Uint8Array): number => {
|
||||||
return this.size;
|
return this.size;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -331,14 +331,14 @@ describe('Types', () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
arrayBufferFieldType,
|
arrayBufferFieldType,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -347,8 +347,8 @@ describe('Types', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -360,14 +360,14 @@ describe('Types', () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
arrayBufferFieldType,
|
arrayBufferFieldType,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -376,8 +376,8 @@ describe('Types', () => {
|
||||||
|
|
||||||
arrayBufferFieldType.size = 100;
|
arrayBufferFieldType.size = 100;
|
||||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||||
|
@ -391,14 +391,14 @@ describe('Types', () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
arrayBufferFieldType,
|
arrayBufferFieldType,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -407,8 +407,8 @@ describe('Types', () => {
|
||||||
|
|
||||||
arrayBufferFieldType.size = -1;
|
arrayBufferFieldType.size = -1;
|
||||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(1);
|
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(1);
|
||||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||||
|
@ -423,14 +423,14 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -451,14 +451,14 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
|
|
||||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||||
arrayBufferFieldDefinition,
|
arrayBufferFieldDefinition,
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -476,8 +476,8 @@ describe('Types', () => {
|
||||||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||||
describe('#getSize', () => {
|
describe('#getSize', () => {
|
||||||
it('should always return `0`', () => {
|
it('should always return `0`', () => {
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField: 'foo' },
|
{ lengthField: 'foo' },
|
||||||
);
|
);
|
||||||
expect(definition.getSize()).toBe(0);
|
expect(definition.getSize()).toBe(0);
|
||||||
|
@ -492,8 +492,8 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -514,8 +514,8 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -537,8 +537,8 @@ describe('Types', () => {
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const base = 8;
|
const base = 8;
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField, lengthFieldBase: base },
|
{ lengthField, lengthFieldBase: base },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -561,12 +561,12 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
const arrayBufferFieldValue = definition.create(
|
const arrayBufferFieldValue = definition.create(
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
||||||
|
@ -588,12 +588,12 @@ describe('Types', () => {
|
||||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||||
struct.set(lengthField, originalLengthFieldValue);
|
struct.set(lengthField, originalLengthFieldValue);
|
||||||
|
|
||||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||||
ArrayBufferFieldType.instance,
|
Uint8ArrayBufferFieldSubType.Instance,
|
||||||
{ lengthField },
|
{ lengthField },
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = new ArrayBuffer(100);
|
const value = new Uint8Array(100);
|
||||||
const arrayBufferFieldValue = definition.create(
|
const arrayBufferFieldValue = definition.create(
|
||||||
StructDefaultOptions,
|
StructDefaultOptions,
|
||||||
struct,
|
struct,
|
|
@ -1,10 +1,10 @@
|
||||||
import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic';
|
import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../../basic';
|
||||||
import type { KeysOfType } from '../utils';
|
import type { KeysOfType } from '../../utils';
|
||||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
|
import { BufferFieldSubType, BufferLikeFieldDefinition, BufferLikeFieldValue } from './base';
|
||||||
|
|
||||||
export type LengthField<TFields> = KeysOfType<TFields, number | string>;
|
export type LengthField<TFields> = KeysOfType<TFields, number | string>;
|
||||||
|
|
||||||
export interface VariableLengthArrayBufferLikeFieldOptions<
|
export interface VariableLengthBufferLikeFieldOptions<
|
||||||
TFields = object,
|
TFields = object,
|
||||||
TLengthField extends LengthField<TFields> = any,
|
TLengthField extends LengthField<TFields> = any,
|
||||||
> {
|
> {
|
||||||
|
@ -13,10 +13,10 @@ export interface VariableLengthArrayBufferLikeFieldOptions<
|
||||||
lengthFieldBase?: number;
|
lengthFieldBase?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VariableLengthArrayBufferLikeFieldDefinition<
|
export class VariableLengthBufferLikeFieldDefinition<
|
||||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
TType extends BufferFieldSubType = BufferFieldSubType,
|
||||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
|
TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions
|
||||||
> extends ArrayBufferLikeFieldDefinition<
|
> extends BufferLikeFieldDefinition<
|
||||||
TType,
|
TType,
|
||||||
TOptions,
|
TOptions,
|
||||||
TOptions['lengthField']
|
TOptions['lengthField']
|
||||||
|
@ -37,43 +37,43 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
value: TType['TTypeScriptType'],
|
value: TType['TTypeScriptType'],
|
||||||
arrayBuffer?: ArrayBuffer
|
array?: Uint8Array
|
||||||
): VariableLengthArrayBufferLikeStructFieldValue<this> {
|
): VariableLengthBufferLikeStructFieldValue<this> {
|
||||||
return new VariableLengthArrayBufferLikeStructFieldValue(
|
return new VariableLengthBufferLikeStructFieldValue(
|
||||||
this,
|
this,
|
||||||
options,
|
options,
|
||||||
struct,
|
struct,
|
||||||
value,
|
value,
|
||||||
arrayBuffer,
|
array,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VariableLengthArrayBufferLikeStructFieldValue<
|
export class VariableLengthBufferLikeStructFieldValue<
|
||||||
TDefinition extends VariableLengthArrayBufferLikeFieldDefinition = VariableLengthArrayBufferLikeFieldDefinition,
|
TDefinition extends VariableLengthBufferLikeFieldDefinition = VariableLengthBufferLikeFieldDefinition,
|
||||||
> extends ArrayBufferLikeFieldValue<TDefinition> {
|
> extends BufferLikeFieldValue<TDefinition> {
|
||||||
protected length: number | undefined;
|
protected length: number | undefined;
|
||||||
|
|
||||||
protected lengthFieldValue: VariableLengthArrayBufferLikeFieldLengthValue;
|
protected lengthFieldValue: VariableLengthBufferLikeFieldLengthValue;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
definition: TDefinition,
|
definition: TDefinition,
|
||||||
options: Readonly<StructOptions>,
|
options: Readonly<StructOptions>,
|
||||||
struct: StructValue,
|
struct: StructValue,
|
||||||
value: TDefinition['TValue'],
|
value: TDefinition['TValue'],
|
||||||
arrayBuffer?: ArrayBuffer,
|
array?: Uint8Array,
|
||||||
) {
|
) {
|
||||||
super(definition, options, struct, value, arrayBuffer);
|
super(definition, options, struct, value, array);
|
||||||
|
|
||||||
if (arrayBuffer) {
|
if (array) {
|
||||||
this.length = arrayBuffer.byteLength;
|
this.length = array.byteLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch the associated length field.
|
// Patch the associated length field.
|
||||||
const lengthField = this.definition.options.lengthField;
|
const lengthField = this.definition.options.lengthField;
|
||||||
|
|
||||||
const originalValue = struct.get(lengthField);
|
const originalValue = struct.get(lengthField);
|
||||||
this.lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
this.lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||||
originalValue,
|
originalValue,
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
|
@ -84,8 +84,8 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
||||||
if (this.length === undefined) {
|
if (this.length === undefined) {
|
||||||
this.length = this.definition.type.getSize(this.value);
|
this.length = this.definition.type.getSize(this.value);
|
||||||
if (this.length === -1) {
|
if (this.length === -1) {
|
||||||
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value);
|
this.array = this.definition.type.toBuffer(this.value);
|
||||||
this.length = this.arrayBuffer.byteLength;
|
this.length = this.array.byteLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,24 +94,24 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
||||||
|
|
||||||
public override set(value: unknown) {
|
public override set(value: unknown) {
|
||||||
super.set(value);
|
super.set(value);
|
||||||
this.arrayBuffer = undefined;
|
this.array = undefined;
|
||||||
this.length = undefined;
|
this.length = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not using `VariableLengthArrayBufferLikeStructFieldValue` directly makes writing tests much easier...
|
// Not using `VariableLengthBufferLikeStructFieldValue` directly makes writing tests much easier...
|
||||||
type VariableLengthArrayBufferLikeFieldValueLike =
|
type VariableLengthBufferLikeFieldValueLike =
|
||||||
StructFieldValue<StructFieldDefinition<VariableLengthArrayBufferLikeFieldOptions, any, any>>;
|
StructFieldValue<StructFieldDefinition<VariableLengthBufferLikeFieldOptions, any, any>>;
|
||||||
|
|
||||||
export class VariableLengthArrayBufferLikeFieldLengthValue
|
export class VariableLengthBufferLikeFieldLengthValue
|
||||||
extends StructFieldValue {
|
extends StructFieldValue {
|
||||||
protected originalField: StructFieldValue;
|
protected originalField: StructFieldValue;
|
||||||
|
|
||||||
protected arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike;
|
protected arrayBufferField: VariableLengthBufferLikeFieldValueLike;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
originalField: StructFieldValue,
|
originalField: StructFieldValue,
|
||||||
arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike,
|
arrayBufferField: VariableLengthBufferLikeFieldValueLike,
|
||||||
) {
|
) {
|
||||||
super(originalField.definition, originalField.options, originalField.struct, 0);
|
super(originalField.definition, originalField.options, originalField.struct, 0);
|
||||||
this.originalField = originalField;
|
this.originalField = originalField;
|
|
@ -1,17 +0,0 @@
|
||||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType } from './array-buffer';
|
|
||||||
|
|
||||||
export interface FixedLengthArrayBufferLikeFieldOptions {
|
|
||||||
length: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FixedLengthArrayBufferLikeFieldDefinition<
|
|
||||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
|
||||||
TOptions extends FixedLengthArrayBufferLikeFieldOptions = FixedLengthArrayBufferLikeFieldOptions,
|
|
||||||
> extends ArrayBufferLikeFieldDefinition<
|
|
||||||
TType,
|
|
||||||
TOptions
|
|
||||||
> {
|
|
||||||
public getSize(): number {
|
|
||||||
return this.options.length;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,5 +1,3 @@
|
||||||
export * from './array-buffer';
|
|
||||||
export * from './bigint';
|
export * from './bigint';
|
||||||
export * from './fixed-length-array-buffer';
|
export * from './buffer';
|
||||||
export * from './number';
|
export * from './number';
|
||||||
export * from './variable-length-array-buffer';
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue