mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +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) {
|
||||
const command = decodeUtf8(new Uint32Array([packet.command]).buffer);
|
||||
const command = decodeUtf8(new Uint32Array([packet.command]));
|
||||
|
||||
const parts = [
|
||||
command,
|
||||
|
@ -35,7 +35,7 @@ function serializePacket(packet: AdbPacketInit) {
|
|||
if (packet.payload) {
|
||||
parts.push(
|
||||
Array.from(
|
||||
new Uint8Array(packet.payload),
|
||||
packet.payload,
|
||||
byte => byte.toString(16).padStart(2, '0')
|
||||
).join(' ')
|
||||
);
|
||||
|
|
|
@ -36,16 +36,16 @@ export class AdbTerminal extends AutoDisposable {
|
|||
|
||||
this._socketAbortController = new AbortController();
|
||||
|
||||
value.stdout.pipeTo(new WritableStream({
|
||||
value.stdout.pipeTo(new WritableStream<Uint8Array>({
|
||||
write: (chunk) => {
|
||||
this.terminal.write(new Uint8Array(chunk));
|
||||
this.terminal.write(chunk);
|
||||
},
|
||||
}), {
|
||||
signal: this._socketAbortController.signal,
|
||||
});
|
||||
value.stderr.pipeTo(new WritableStream({
|
||||
value.stderr.pipeTo(new WritableStream<Uint8Array>({
|
||||
write: (chunk) => {
|
||||
this.terminal.write(new Uint8Array(chunk));
|
||||
this.terminal.write(chunk);
|
||||
},
|
||||
}), {
|
||||
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 { useConst } from '@fluentui/react-hooks';
|
||||
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 { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
|
@ -21,7 +20,7 @@ import { asyncEffect, formatSize, formatSpeed, Icons, pickFile, RouteStackProps
|
|||
* Because of internal buffer of upstream/downstream streams,
|
||||
* 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) {
|
||||
let progress = 0;
|
||||
super({
|
||||
|
@ -171,13 +170,7 @@ class FileManagerState {
|
|||
item.name,
|
||||
{ size: item.size }
|
||||
);
|
||||
await readable
|
||||
.pipeThrough(new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue(new Uint8Array(chunk));
|
||||
},
|
||||
}))
|
||||
.pipeTo(writeable);
|
||||
await readable.pipeTo(writeable);
|
||||
} catch (e) {
|
||||
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
|
@ -477,7 +470,6 @@ class FileManagerState {
|
|||
|
||||
try {
|
||||
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
||||
.pipeThrough(new ExtractViewBufferStream())
|
||||
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
||||
.pipeThrough(new ProgressStream(action((uploaded) => {
|
||||
this.uploadedSize = uploaded;
|
||||
|
@ -558,12 +550,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
|
|||
const previewImage = useCallback(async (path: string) => {
|
||||
const sync = await globalState.device!.sync();
|
||||
try {
|
||||
const readable = (await sync.read(path))
|
||||
.pipeThrough(new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue(new Uint8Array(chunk));
|
||||
},
|
||||
}));
|
||||
const readable = sync.read(path);
|
||||
const response = new Response(readable);
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
|
|
@ -24,7 +24,7 @@ class FrameBufferState {
|
|||
setImage(image: AdbFrameBuffer) {
|
||||
this.width = image.width;
|
||||
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() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
|
||||
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 { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
|
@ -59,7 +58,6 @@ class InstallPageState {
|
|||
});
|
||||
|
||||
await (file.stream() as unknown as ReadableStream<Uint8Array>)
|
||||
.pipeThrough(new ExtractViewBufferStream())
|
||||
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
|
||||
.pipeThrough(new ProgressStream(action((uploaded) => {
|
||||
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();
|
||||
|
||||
class FetchWithProgress {
|
||||
public readonly promise: Promise<ArrayBuffer>;
|
||||
public readonly promise: Promise<Uint8Array>;
|
||||
|
||||
private _downloaded = 0;
|
||||
public get downloaded() { return this._downloaded; }
|
||||
|
@ -56,7 +56,7 @@ class FetchWithProgress {
|
|||
result.set(chunk, position);
|
||||
position += chunk.byteLength;
|
||||
}
|
||||
return result.buffer;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,7 @@ class ScrcpyPageState {
|
|||
this.debouncedServerDownloadedSize = this.serverDownloadedSize;
|
||||
}), 1000);
|
||||
|
||||
let serverBuffer: ArrayBuffer;
|
||||
let serverBuffer: Uint8Array;
|
||||
|
||||
try {
|
||||
serverBuffer = await fetchServer(action(([downloaded, total]) => {
|
||||
|
@ -408,7 +408,7 @@ class ScrcpyPageState {
|
|||
}), 1000);
|
||||
|
||||
try {
|
||||
await new ReadableStream({
|
||||
await new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(serverBuffer);
|
||||
controller.close();
|
||||
|
@ -450,7 +450,7 @@ class ScrcpyPageState {
|
|||
|
||||
// Run scrcpy once will delete the server file
|
||||
// Re-push it
|
||||
await new ReadableStream({
|
||||
await new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(serverBuffer);
|
||||
controller.close();
|
||||
|
@ -492,7 +492,7 @@ class ScrcpyPageState {
|
|||
options
|
||||
);
|
||||
|
||||
client.stdout.pipeTo(new WritableStream({
|
||||
client.stdout.pipeTo(new WritableStream<string>({
|
||||
write: action((line) => {
|
||||
this.log.push(line);
|
||||
}),
|
||||
|
|
|
@ -1,19 +1,4 @@
|
|||
import { AdbBackend, ReadableStream, ReadableWritablePair, TransformStream, 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
import { AdbBackend, ReadableStream, WritableStream } from '@yume-chan/adb';
|
||||
|
||||
declare global {
|
||||
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 {
|
||||
public static isSupported(): boolean {
|
||||
return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket;
|
||||
|
@ -87,12 +50,10 @@ export default class AdbDirectSocketsBackend implements AdbBackend {
|
|||
}
|
||||
|
||||
public async connect() {
|
||||
const socket = await navigator.openTCPSocket({
|
||||
return await navigator.openTCPSocket({
|
||||
remoteAddress: this.host,
|
||||
remotePort: this.port,
|
||||
noDelay: true,
|
||||
});
|
||||
|
||||
return new AdbDirectSocketsBackendStreams(socket);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,15 @@ export const WebUsbDeviceFilter: USBDeviceFilter = {
|
|||
protocolCode: 1,
|
||||
};
|
||||
|
||||
export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer, ArrayBuffer>{
|
||||
private _readable: ReadableStream<ArrayBuffer>;
|
||||
export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array, Uint8Array>{
|
||||
private _readable: ReadableStream<Uint8Array>;
|
||||
public get readable() { return this._readable; }
|
||||
|
||||
private _writable: WritableStream<ArrayBuffer>;
|
||||
private _writable: WritableStream<Uint8Array>;
|
||||
public get writable() { return this._writable; }
|
||||
|
||||
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
|
||||
this._readable = new ReadableStream({
|
||||
this._readable = new ReadableStream<Uint8Array>({
|
||||
pull: async (controller) => {
|
||||
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);
|
||||
}
|
||||
|
||||
const { buffer } = result.data!;
|
||||
controller.enqueue(buffer);
|
||||
const view = result.data!;
|
||||
controller.enqueue(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
||||
},
|
||||
cancel: async () => {
|
||||
await device.close();
|
||||
|
@ -35,7 +35,7 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer,
|
|||
size(chunk) { return chunk.byteLength; },
|
||||
});
|
||||
|
||||
this._writable = new WritableStream({
|
||||
this._writable = new WritableStream<Uint8Array>({
|
||||
write: async (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) => {
|
||||
socket.onmessage = ({ data }: { data: ArrayBuffer; }) => {
|
||||
controller.enqueue(data);
|
||||
controller.enqueue(new Uint8Array(data));
|
||||
};
|
||||
socket.onclose = () => {
|
||||
controller.close();
|
||||
|
@ -35,7 +35,7 @@ export default class AdbWsBackend implements AdbBackend {
|
|||
size(chunk) { return chunk.byteLength; },
|
||||
});
|
||||
|
||||
const writable = new WritableStream({
|
||||
const writable = new WritableStream<Uint8Array>({
|
||||
write: (chunk) => {
|
||||
socket.send(chunk);
|
||||
},
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// 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 Utf8Decoder = new TextDecoder();
|
||||
|
||||
export function encodeUtf8(input: string): ArrayBuffer {
|
||||
return Utf8Encoder.encode(input).buffer;
|
||||
export function encodeUtf8(input: string): Uint8Array {
|
||||
return Utf8Encoder.encode(input);
|
||||
}
|
||||
|
||||
export function decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return Utf8Decoder.decode(buffer);
|
||||
export function decodeUtf8(array: Uint8Array): string {
|
||||
return Utf8Decoder.decode(array);
|
||||
}
|
||||
|
||||
export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||
|
@ -20,14 +20,14 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
|||
this.localStorageKey = localStorageKey;
|
||||
}
|
||||
|
||||
public *iterateKeys(): Generator<ArrayBuffer, void, void> {
|
||||
public *iterateKeys(): Generator<Uint8Array, void, void> {
|
||||
const privateKey = window.localStorage.getItem(this.localStorageKey);
|
||||
if (privateKey) {
|
||||
yield decodeBase64(privateKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async generateKey(): Promise<ArrayBuffer> {
|
||||
public async generateKey(): Promise<Uint8Array> {
|
||||
const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSASSA-PKCS1-v1_5',
|
||||
|
@ -40,7 +40,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
|||
['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)));
|
||||
|
||||
// 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.
|
||||
const publicKeyLength = calculatePublicKeyLength();
|
||||
const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength);
|
||||
const publicKeyBuffer = new ArrayBuffer(publicKeyBase64Length);
|
||||
const publicKeyBuffer = new Uint8Array(publicKeyBase64Length);
|
||||
calculatePublicKey(privateKey, publicKeyBuffer);
|
||||
encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer);
|
||||
window.localStorage.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer));
|
||||
|
|
|
@ -52,9 +52,11 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
|
|||
state: undefined,
|
||||
};
|
||||
},
|
||||
async close() {
|
||||
close: async () => {
|
||||
// Close `stderr` on exit.
|
||||
stderrController.close();
|
||||
|
||||
this._exit.resolve(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ export function adbSyncPull(
|
|||
writer: WritableStreamDefaultWriter<Uint8Array>,
|
||||
path: string,
|
||||
): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream({
|
||||
return new ReadableStream<Uint8Array>({
|
||||
async start() {
|
||||
await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@ export function adbSyncPush(
|
|||
packetSize: number = ADB_SYNC_MAX_PACKET_SIZE,
|
||||
): WritableStream<Uint8Array> {
|
||||
const { readable, writable } = new ChunkStream(packetSize);
|
||||
readable.pipeTo(new WritableStream({
|
||||
readable.pipeTo(new WritableStream<Uint8Array>({
|
||||
async start() {
|
||||
const pathAndMode = `${filename},${mode.toString()}`;
|
||||
await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode);
|
||||
|
|
|
@ -30,20 +30,20 @@ export async function adbSyncWriteRequest(
|
|||
): Promise<void> {
|
||||
let buffer: Uint8Array;
|
||||
if (typeof value === 'number') {
|
||||
buffer = new Uint8Array(AdbSyncNumberRequest.serialize({
|
||||
buffer = AdbSyncNumberRequest.serialize({
|
||||
id,
|
||||
arg: value,
|
||||
}));
|
||||
});
|
||||
} else if (typeof value === 'string') {
|
||||
buffer = new Uint8Array(AdbSyncDataRequest.serialize({
|
||||
buffer = AdbSyncDataRequest.serialize({
|
||||
id,
|
||||
data: encodeUtf8(value),
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
buffer = new Uint8Array(AdbSyncDataRequest.serialize({
|
||||
buffer = AdbSyncDataRequest.serialize({
|
||||
id,
|
||||
data: value,
|
||||
}));
|
||||
});
|
||||
}
|
||||
await writer.write(buffer);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
|
|||
transform: async (init, controller) => {
|
||||
let checksum: number;
|
||||
if (this.calculateChecksum && init.payload) {
|
||||
const array = new Uint8Array(init.payload);
|
||||
const array = init.payload;
|
||||
checksum = array.reduce((result, item) => result + item, 0);
|
||||
} else {
|
||||
checksum = 0;
|
||||
|
@ -49,7 +49,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
|
|||
payloadLength: init.payload.byteLength,
|
||||
};
|
||||
|
||||
controller.enqueue(new Uint8Array(AdbPacketHeader.serialize(packet)));
|
||||
controller.enqueue(AdbPacketHeader.serialize(packet));
|
||||
|
||||
if (packet.payloadLength) {
|
||||
controller.enqueue(packet.payload);
|
||||
|
|
|
@ -62,7 +62,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
{ signal: this._abortController.signal, preventCancel: true }
|
||||
)
|
||||
.pipeThrough(new StructDeserializeStream(AdbPacket))
|
||||
.pipeTo(new WritableStream({
|
||||
.pipeTo(new WritableStream<AdbPacket>({
|
||||
write: async (packet) => {
|
||||
try {
|
||||
this.logger?.onIncomingPacket?.(packet);
|
||||
|
|
|
@ -36,7 +36,7 @@ export class BufferedStream {
|
|||
const buffer = this.buffer;
|
||||
if (buffer.byteLength > length) {
|
||||
this.buffer = buffer.subarray(length);
|
||||
return buffer.slice(0, length);
|
||||
return buffer.subarray(0, length);
|
||||
}
|
||||
|
||||
array = new Uint8Array(length);
|
||||
|
@ -60,11 +60,11 @@ export class BufferedStream {
|
|||
|
||||
if (value.byteLength > length) {
|
||||
this.buffer = value.subarray(length);
|
||||
return value.slice(0, length);
|
||||
return value.subarray(0, length);
|
||||
}
|
||||
|
||||
array = new Uint8Array(length);
|
||||
array.set(new Uint8Array(value), 0);
|
||||
array.set(value, 0);
|
||||
index = value.byteLength;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ export class StructSerializeStream<T extends Struct<any, any, any, any>>
|
|||
constructor(struct: T) {
|
||||
super({
|
||||
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) => {
|
||||
const result = await this.reader.read();
|
||||
if (result.done) {
|
||||
wrapper.close?.(this.state);
|
||||
controller.close();
|
||||
} else {
|
||||
controller.enqueue(result.value);
|
||||
|
@ -153,9 +154,9 @@ export class ChunkStream extends TransformStream<Uint8Array, Uint8Array>{
|
|||
public constructor(size: number) {
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
for (let start = 0; start < chunk.length; start += size) {
|
||||
for (let start = 0; start < chunk.byteLength;) {
|
||||
const end = start + size;
|
||||
controller.enqueue(chunk.slice(start, end));
|
||||
controller.enqueue(chunk.subarray(start, end));
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,6 @@ export function encodeUtf8(input: string): Uint8Array {
|
|||
return Utf8Encoder.encode(input);
|
||||
}
|
||||
|
||||
export function decodeUtf8(buffer: Uint8Array): string {
|
||||
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
|
||||
return Utf8Decoder.decode(buffer);
|
||||
}
|
||||
|
|
|
@ -50,8 +50,8 @@ export class BugReportZ extends AdbCommandBase {
|
|||
return version.major > 1 || version.minor >= 2;
|
||||
}
|
||||
|
||||
public stream(): ReadableStream<ArrayBuffer> {
|
||||
return new WrapReadableStream<ArrayBuffer, ReadableStream<ArrayBuffer>, undefined>({
|
||||
public stream(): ReadableStream<Uint8Array> {
|
||||
return new WrapReadableStream<Uint8Array, ReadableStream<Uint8Array>, undefined>({
|
||||
start: async () => {
|
||||
const process = await this.adb.subprocess.spawn(['bugreportz', '-s']);
|
||||
return {
|
||||
|
@ -86,7 +86,7 @@ export class BugReportZ extends AdbCommandBase {
|
|||
await process.stdout
|
||||
.pipeThrough(new DecodeUtf8Stream())
|
||||
.pipeThrough(new SplitLineStream())
|
||||
.pipeTo(new WritableStream({
|
||||
.pipeTo(new WritableStream<string>({
|
||||
write(line) {
|
||||
// (Not 100% sure) `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
|
||||
let match = line.match(BugReportZ.PROGRESS_REGEX);
|
||||
|
|
|
@ -124,7 +124,7 @@ export class ScrcpyClient {
|
|||
private _videoStream: TransformStream<VideoStreamPacket, VideoStreamPacket>;
|
||||
public get videoStream() { return this._videoStream.readable; }
|
||||
|
||||
private _controlStreamWriter: WritableStreamDefaultWriter<ArrayBuffer> | undefined;
|
||||
private _controlStreamWriter: WritableStreamDefaultWriter<Uint8Array> | undefined;
|
||||
|
||||
private readonly clipboardChangeEvent = new EventEmitter<string>();
|
||||
public get onClipboardChange() { return this.clipboardChangeEvent.event; }
|
||||
|
@ -179,7 +179,7 @@ export class ScrcpyClient {
|
|||
try {
|
||||
while (true) {
|
||||
const type = await buffered.read(1);
|
||||
switch (new Uint8Array(type)[0]) {
|
||||
switch (type[0]) {
|
||||
case 0:
|
||||
const { content } = await ClipboardMessage.deserialize(buffered);
|
||||
this.clipboardChangeEvent.fire(content!);
|
||||
|
|
|
@ -37,7 +37,7 @@ export class TinyH264Decoder implements H264Decoder {
|
|||
|
||||
this._renderer = document.createElement('canvas');
|
||||
|
||||
this._writable = new WritableStream({
|
||||
this._writable = new WritableStream<VideoStreamPacket>({
|
||||
write: async (packet) => {
|
||||
switch (packet.type) {
|
||||
case 'configuration':
|
||||
|
@ -49,7 +49,7 @@ export class TinyH264Decoder implements H264Decoder {
|
|||
}
|
||||
|
||||
const wrapper = await this._initializer.promise;
|
||||
wrapper.feed(packet.data);
|
||||
wrapper.feed(packet.data.slice().buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export class WebCodecsDecoder implements H264Decoder {
|
|||
error() { },
|
||||
});
|
||||
|
||||
this._writable = new WritableStream({
|
||||
this._writable = new WritableStream<VideoStreamPacket>({
|
||||
write: async (packet) => {
|
||||
switch (packet.type) {
|
||||
case 'configuration':
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForw
|
|||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message";
|
||||
import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18";
|
||||
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";
|
||||
|
||||
export enum ScrcpyLogLevel {
|
||||
|
@ -114,7 +114,7 @@ export const VideoPacket =
|
|||
new Struct()
|
||||
.int64('pts')
|
||||
.uint32('size')
|
||||
.arrayBuffer('data', { lengthField: 'size' });
|
||||
.uint8Array('data', { lengthField: 'size' });
|
||||
|
||||
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> {
|
||||
public value: Partial<T>;
|
||||
|
||||
private _streamHeader: ArrayBuffer | undefined;
|
||||
private _streamHeader: Uint8Array | undefined;
|
||||
|
||||
public constructor(value: Partial<ScrcpyOptionsInit1_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);
|
||||
if (pts === NoPts) {
|
||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice(0));
|
||||
const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
|
||||
|
||||
const {
|
||||
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) {
|
||||
frameData = new ArrayBuffer(this._streamHeader.byteLength + data.byteLength);
|
||||
const array = new Uint8Array(frameData);
|
||||
array.set(new Uint8Array(this._streamHeader));
|
||||
array.set(new Uint8Array(data!), this._streamHeader.byteLength);
|
||||
frameData = new Uint8Array(this._streamHeader.byteLength + data.byteLength);
|
||||
frameData.set(this._streamHeader);
|
||||
frameData.set(data!, this._streamHeader.byteLength);
|
||||
this._streamHeader = undefined;
|
||||
} else {
|
||||
frameData = data;
|
||||
|
@ -296,7 +295,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
|
|||
|
||||
public serializeInjectScrollControlMessage(
|
||||
message: ScrcpyInjectScrollControlMessage1_22,
|
||||
): ArrayBuffer {
|
||||
): Uint8Array {
|
||||
return ScrcpyInjectScrollControlMessage1_16.serialize(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Adb } from "@yume-chan/adb";
|
||||
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 { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21";
|
||||
|
||||
|
@ -74,7 +74,7 @@ export class ScrcpyOptions1_22<T extends ScrcpyOptionsInit1_22 = ScrcpyOptionsIn
|
|||
|
||||
public override serializeInjectScrollControlMessage(
|
||||
message: ScrcpyInjectScrollControlMessage1_22,
|
||||
): ArrayBuffer {
|
||||
): Uint8Array {
|
||||
return ScrcpyInjectScrollControlMessage1_22.serialize(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export interface VideoStreamConfigurationPacket {
|
|||
|
||||
export interface VideoStreamFramePacket {
|
||||
type: 'frame';
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export type VideoStreamPacket = VideoStreamConfigurationPacket | VideoStreamFramePacket;
|
||||
|
@ -53,9 +53,9 @@ export interface ScrcpyOptions<T> {
|
|||
|
||||
serializeBackOrScreenOnControlMessage(
|
||||
message: ScrcpyBackOrScreenOnEvent1_18,
|
||||
): ArrayBuffer | undefined;
|
||||
): Uint8Array | undefined;
|
||||
|
||||
serializeInjectScrollControlMessage(
|
||||
message: ScrcpyInjectScrollControlMessage1_22,
|
||||
): ArrayBuffer;
|
||||
): Uint8Array;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export function pushServer(
|
|||
) {
|
||||
const { path = DEFAULT_SERVER_PATH } = options;
|
||||
|
||||
return new WrapWritableStream<ArrayBuffer, WritableStream<ArrayBuffer>, AdbSync>({
|
||||
return new WrapWritableStream<Uint8Array, WritableStream<Uint8Array>, AdbSync>({
|
||||
async start() {
|
||||
const sync = await device.sync();
|
||||
return {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic';
|
||||
import { StructDefaultOptions, StructValue } from './basic';
|
||||
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";
|
||||
|
||||
export interface StructLike<TValue> {
|
||||
|
@ -57,12 +57,12 @@ interface ArrayBufferLikeFieldCreator<
|
|||
*/
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
||||
TType extends BufferFieldSubType<any, any>,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
name: TName,
|
||||
type: TType,
|
||||
options: FixedLengthArrayBufferLikeFieldOptions,
|
||||
options: FixedLengthBufferLikeFieldOptions,
|
||||
typescriptType?: TTypeScriptType,
|
||||
): AddFieldDescriptor<
|
||||
TFields,
|
||||
|
@ -70,9 +70,9 @@ interface ArrayBufferLikeFieldCreator<
|
|||
TExtra,
|
||||
TPostDeserialized,
|
||||
TName,
|
||||
FixedLengthArrayBufferLikeFieldDefinition<
|
||||
FixedLengthBufferLikeFieldDefinition<
|
||||
TType,
|
||||
FixedLengthArrayBufferLikeFieldOptions
|
||||
FixedLengthBufferLikeFieldOptions
|
||||
>
|
||||
>;
|
||||
|
||||
|
@ -81,8 +81,8 @@ interface ArrayBufferLikeFieldCreator<
|
|||
*/
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TType extends ArrayBufferLikeFieldType<any, any>,
|
||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>,
|
||||
TType extends BufferFieldSubType<any, any>,
|
||||
TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
name: TName,
|
||||
|
@ -95,7 +95,7 @@ interface ArrayBufferLikeFieldCreator<
|
|||
TExtra,
|
||||
TPostDeserialized,
|
||||
TName,
|
||||
VariableLengthArrayBufferLikeFieldDefinition<
|
||||
VariableLengthBufferLikeFieldDefinition<
|
||||
TType,
|
||||
TOptions
|
||||
>
|
||||
|
@ -110,14 +110,14 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
|||
TOmitInitKey extends PropertyKey,
|
||||
TExtra extends object,
|
||||
TPostDeserialized,
|
||||
TType extends ArrayBufferLikeFieldType<any, any>
|
||||
TType extends BufferFieldSubType<any, any>
|
||||
> {
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
name: TName,
|
||||
options: FixedLengthArrayBufferLikeFieldOptions,
|
||||
options: FixedLengthBufferLikeFieldOptions,
|
||||
typescriptType?: TTypeScriptType,
|
||||
): AddFieldDescriptor<
|
||||
TFields,
|
||||
|
@ -125,16 +125,16 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
|||
TExtra,
|
||||
TPostDeserialized,
|
||||
TName,
|
||||
FixedLengthArrayBufferLikeFieldDefinition<
|
||||
FixedLengthBufferLikeFieldDefinition<
|
||||
TType,
|
||||
FixedLengthArrayBufferLikeFieldOptions
|
||||
FixedLengthBufferLikeFieldOptions
|
||||
>
|
||||
>;
|
||||
|
||||
<
|
||||
TName extends PropertyKey,
|
||||
TLengthField extends LengthField<TFields>,
|
||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields, TLengthField>,
|
||||
TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,
|
||||
TTypeScriptType = TType['TTypeScriptType'],
|
||||
>(
|
||||
name: TName,
|
||||
|
@ -146,7 +146,7 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
|
|||
TExtra,
|
||||
TPostDeserialized,
|
||||
TName,
|
||||
VariableLengthArrayBufferLikeFieldDefinition<
|
||||
VariableLengthBufferLikeFieldDefinition<
|
||||
TType,
|
||||
TOptions
|
||||
>
|
||||
|
@ -421,59 +421,33 @@ export class Struct<
|
|||
TPostDeserialized
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
type: ArrayBufferLikeFieldType,
|
||||
options: FixedLengthArrayBufferLikeFieldOptions | VariableLengthArrayBufferLikeFieldOptions
|
||||
type: BufferFieldSubType,
|
||||
options: FixedLengthBufferLikeFieldOptions | VariableLengthBufferLikeFieldOptions
|
||||
): any => {
|
||||
if ('length' in options) {
|
||||
return this.field(
|
||||
name,
|
||||
new FixedLengthArrayBufferLikeFieldDefinition(type, options),
|
||||
new FixedLengthBufferLikeFieldDefinition(type, options),
|
||||
);
|
||||
} else {
|
||||
return this.field(
|
||||
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<
|
||||
TFields,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
ArrayBufferViewFieldType<Uint8Array>
|
||||
Uint8ArrayBufferFieldSubType
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
options: any
|
||||
): any => {
|
||||
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8Array, options);
|
||||
};
|
||||
|
||||
public uint8ClampedArray: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
ArrayBufferViewFieldType<Uint8ClampedArray>
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
options: any
|
||||
): any => {
|
||||
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8ClampedArray, options);
|
||||
return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options);
|
||||
};
|
||||
|
||||
public string: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
|
@ -481,12 +455,12 @@ export class Struct<
|
|||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
StringFieldType
|
||||
StringBufferFieldSubType
|
||||
> = (
|
||||
name: PropertyKey,
|
||||
options: 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
|
||||
* can be converted from an ArrayBuffer when deserialized,
|
||||
* and need to be converted to an ArrayBuffer when serializing
|
||||
* can be converted from an `Uint8Array` when deserialized,
|
||||
* and need to be converted to an `Uint8Array` when serializing
|
||||
*
|
||||
* @template TValue The actual TypeScript type of this type
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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.,
|
||||
* 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;
|
||||
|
||||
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
|
||||
public abstract fromBuffer(array: Uint8Array): TValue;
|
||||
/** When implemented in derived classes, converts the `Uint8Array` to a type-specific value */
|
||||
public abstract toValue(array: Uint8Array): 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).
|
||||
* If the size can't be calculated without first converting the `value` back to an `Uint8Array`,
|
||||
* implementer can returns `-1`, so the caller will get its size by first converting it to
|
||||
* an `Uint8Array` (and cache the result).
|
||||
*/
|
||||
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> {
|
||||
public static readonly Instance = new Uint8ArrayBufferFieldSubType();
|
||||
|
||||
|
@ -49,7 +49,7 @@ export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array>
|
|||
return value;
|
||||
}
|
||||
|
||||
public fromBuffer(buffer: Uint8Array): Uint8Array {
|
||||
public toValue(buffer: Uint8Array): Uint8Array {
|
||||
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>
|
||||
extends BufferFieldSubType<string, TTypeScriptType> {
|
||||
public static readonly Instance = new StringBufferFieldSubType();
|
||||
|
@ -67,21 +67,21 @@ export class StringBufferFieldSubType<TTypeScriptType = string>
|
|||
return encodeUtf8(value);
|
||||
}
|
||||
|
||||
public fromBuffer(array: Uint8Array): string {
|
||||
public toValue(array: Uint8Array): string {
|
||||
return decodeUtf8(array);
|
||||
}
|
||||
|
||||
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`, so `BufferLikeFieldDefinition` will
|
||||
// convert this `value` into an `Uint8Array` (and cache the result),
|
||||
// Then get the size from that `Uint8Array`
|
||||
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>,
|
||||
TOptions = void,
|
||||
TOmitInitKey extends PropertyKey = never,
|
||||
|
@ -108,67 +108,67 @@ export abstract class ArrayBufferLikeFieldDefinition<
|
|||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: TType['TTypeScriptType'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
): ArrayBufferLikeFieldValue<this> {
|
||||
return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer);
|
||||
array?: Uint8Array,
|
||||
): BufferLikeFieldValue<this> {
|
||||
return new BufferLikeFieldValue(this, options, struct, value, array);
|
||||
}
|
||||
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructDeserializeStream,
|
||||
struct: StructValue,
|
||||
): ArrayBufferLikeFieldValue<this>;
|
||||
): BufferLikeFieldValue<this>;
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): Promise<ArrayBufferLikeFieldValue<this>>;
|
||||
): Promise<BufferLikeFieldValue<this>>;
|
||||
public override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
||||
struct: StructValue,
|
||||
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
|
||||
): ValueOrPromise<BufferLikeFieldValue<this>> {
|
||||
return Syncbird.try(() => {
|
||||
const size = this.getDeserializeSize(struct);
|
||||
if (size === 0) {
|
||||
return EmptyArrayBuffer;
|
||||
return EmptyBuffer;
|
||||
} else {
|
||||
return stream.read(size);
|
||||
}
|
||||
}).then(arrayBuffer => {
|
||||
const value = this.type.fromBuffer(arrayBuffer);
|
||||
return this.create(options, struct, value, arrayBuffer);
|
||||
}).then(array => {
|
||||
const value = this.type.toValue(array);
|
||||
return this.create(options, struct, value, array);
|
||||
}).valueOrPromise();
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayBufferLikeFieldValue<
|
||||
TDefinition extends ArrayBufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>,
|
||||
export class BufferLikeFieldValue<
|
||||
TDefinition extends BufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>,
|
||||
> extends StructFieldValue<TDefinition> {
|
||||
protected arrayBuffer: ArrayBuffer | undefined;
|
||||
protected array: Uint8Array | undefined;
|
||||
|
||||
public constructor(
|
||||
definition: TDefinition,
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: TDefinition['TValue'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
array?: Uint8Array,
|
||||
) {
|
||||
super(definition, options, struct, value);
|
||||
this.arrayBuffer = arrayBuffer;
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public override set(value: TDefinition['TValue']): void {
|
||||
super.set(value);
|
||||
this.arrayBuffer = undefined;
|
||||
this.array = undefined;
|
||||
}
|
||||
|
||||
public serialize(dataView: DataView, offset: number): void {
|
||||
if (!this.arrayBuffer) {
|
||||
this.arrayBuffer = this.definition.type.toBuffer(this.value);
|
||||
if (!this.array) {
|
||||
this.array = this.definition.type.toBuffer(this.value);
|
||||
}
|
||||
|
||||
new Uint8Array(dataView.buffer)
|
||||
.set(new Uint8Array(this.arrayBuffer), offset);
|
||||
new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength)
|
||||
.set(this.array, offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ArrayBufferFieldType } from './array-buffer';
|
||||
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer';
|
||||
import { Uint8ArrayBufferFieldSubType } from './base';
|
||||
import { FixedLengthBufferLikeFieldDefinition } from './fixed-length';
|
||||
|
||||
describe('Types', () => {
|
||||
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||
describe('#getSize', () => {
|
||||
it('should return size in its options', () => {
|
||||
const definition = new FixedLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new FixedLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ length: 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 { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer';
|
||||
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer';
|
||||
import { StructDefaultOptions, StructFieldValue, StructValue } from '../../basic';
|
||||
import { BufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base';
|
||||
import { VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldLengthValue, VariableLengthBufferLikeStructFieldValue } from './variable-length';
|
||||
|
||||
class MockOriginalFieldValue extends StructFieldValue {
|
||||
public constructor() {
|
||||
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 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 => { });
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ describe('Types', () => {
|
|||
|
||||
public size = 0;
|
||||
|
||||
public getSize = jest.fn(() => this.size);
|
||||
public override getSize = jest.fn(() => this.size);
|
||||
|
||||
public serialize(dataView: DataView, offset: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
|
@ -40,7 +40,7 @@ describe('Types', () => {
|
|||
it('should return size of its original field value', () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -60,7 +60,7 @@ describe('Types', () => {
|
|||
it('should return size of its `arrayBufferField`', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -82,7 +82,7 @@ describe('Types', () => {
|
|||
it('should return size of its `arrayBufferField` as string', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -106,7 +106,7 @@ describe('Types', () => {
|
|||
it('should does nothing', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -124,7 +124,7 @@ describe('Types', () => {
|
|||
it('should call `serialize` of its `originalField`', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -155,7 +155,7 @@ describe('Types', () => {
|
|||
it('should stringify its length if `originalField` is a string', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -186,7 +186,7 @@ describe('Types', () => {
|
|||
it('should stringify its length in specified base if `originalField` is a string', async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
);
|
||||
|
@ -228,14 +228,14 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(0);
|
||||
const value = new Uint8Array(0);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -257,14 +257,14 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -276,7 +276,7 @@ describe('Types', () => {
|
|||
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('value', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('array', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
});
|
||||
|
||||
|
@ -287,14 +287,14 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(0);
|
||||
const value = new Uint8Array(0);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -307,18 +307,18 @@ describe('Types', () => {
|
|||
});
|
||||
|
||||
describe('#getSize', () => {
|
||||
class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
|
||||
public toArrayBuffer = jest.fn((value: ArrayBuffer): ArrayBuffer => {
|
||||
class MockArrayBufferFieldType extends BufferFieldSubType<Uint8Array> {
|
||||
public override toBuffer = jest.fn((value: Uint8Array): Uint8Array => {
|
||||
return value;
|
||||
});
|
||||
|
||||
public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer): ArrayBuffer => {
|
||||
public override toValue = jest.fn((arrayBuffer: Uint8Array): Uint8Array => {
|
||||
return arrayBuffer;
|
||||
});
|
||||
|
||||
public size = 0;
|
||||
|
||||
public getSize = jest.fn((value: ArrayBuffer): number => {
|
||||
public override getSize = jest.fn((value: Uint8Array): number => {
|
||||
return this.size;
|
||||
});
|
||||
}
|
||||
|
@ -331,14 +331,14 @@ describe('Types', () => {
|
|||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -347,8 +347,8 @@ describe('Types', () => {
|
|||
);
|
||||
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
|
@ -360,14 +360,14 @@ describe('Types', () => {
|
|||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -376,8 +376,8 @@ describe('Types', () => {
|
|||
|
||||
arrayBufferFieldType.size = 100;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
|
@ -391,14 +391,14 @@ describe('Types', () => {
|
|||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
arrayBufferFieldType,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -407,8 +407,8 @@ describe('Types', () => {
|
|||
|
||||
arrayBufferFieldType.size = -1;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty('length', 100);
|
||||
|
@ -423,14 +423,14 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -451,14 +451,14 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -476,8 +476,8 @@ describe('Types', () => {
|
|||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||
describe('#getSize', () => {
|
||||
it('should always return `0`', () => {
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField: 'foo' },
|
||||
);
|
||||
expect(definition.getSize()).toBe(0);
|
||||
|
@ -492,8 +492,8 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
|
@ -514,8 +514,8 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
|
@ -537,8 +537,8 @@ describe('Types', () => {
|
|||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const base = 8;
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField, lengthFieldBase: base },
|
||||
);
|
||||
|
||||
|
@ -561,12 +561,12 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -588,12 +588,12 @@ describe('Types', () => {
|
|||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthArrayBufferLikeFieldDefinition(
|
||||
ArrayBufferFieldType.instance,
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
{ lengthField },
|
||||
);
|
||||
|
||||
const value = new ArrayBuffer(100);
|
||||
const value = new Uint8Array(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
struct,
|
|
@ -1,10 +1,10 @@
|
|||
import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic';
|
||||
import type { KeysOfType } from '../utils';
|
||||
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer';
|
||||
import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../../basic';
|
||||
import type { KeysOfType } from '../../utils';
|
||||
import { BufferFieldSubType, BufferLikeFieldDefinition, BufferLikeFieldValue } from './base';
|
||||
|
||||
export type LengthField<TFields> = KeysOfType<TFields, number | string>;
|
||||
|
||||
export interface VariableLengthArrayBufferLikeFieldOptions<
|
||||
export interface VariableLengthBufferLikeFieldOptions<
|
||||
TFields = object,
|
||||
TLengthField extends LengthField<TFields> = any,
|
||||
> {
|
||||
|
@ -13,10 +13,10 @@ export interface VariableLengthArrayBufferLikeFieldOptions<
|
|||
lengthFieldBase?: number;
|
||||
}
|
||||
|
||||
export class VariableLengthArrayBufferLikeFieldDefinition<
|
||||
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
|
||||
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions
|
||||
> extends ArrayBufferLikeFieldDefinition<
|
||||
export class VariableLengthBufferLikeFieldDefinition<
|
||||
TType extends BufferFieldSubType = BufferFieldSubType,
|
||||
TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions
|
||||
> extends BufferLikeFieldDefinition<
|
||||
TType,
|
||||
TOptions,
|
||||
TOptions['lengthField']
|
||||
|
@ -37,43 +37,43 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
|
|||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: TType['TTypeScriptType'],
|
||||
arrayBuffer?: ArrayBuffer
|
||||
): VariableLengthArrayBufferLikeStructFieldValue<this> {
|
||||
return new VariableLengthArrayBufferLikeStructFieldValue(
|
||||
array?: Uint8Array
|
||||
): VariableLengthBufferLikeStructFieldValue<this> {
|
||||
return new VariableLengthBufferLikeStructFieldValue(
|
||||
this,
|
||||
options,
|
||||
struct,
|
||||
value,
|
||||
arrayBuffer,
|
||||
array,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class VariableLengthArrayBufferLikeStructFieldValue<
|
||||
TDefinition extends VariableLengthArrayBufferLikeFieldDefinition = VariableLengthArrayBufferLikeFieldDefinition,
|
||||
> extends ArrayBufferLikeFieldValue<TDefinition> {
|
||||
export class VariableLengthBufferLikeStructFieldValue<
|
||||
TDefinition extends VariableLengthBufferLikeFieldDefinition = VariableLengthBufferLikeFieldDefinition,
|
||||
> extends BufferLikeFieldValue<TDefinition> {
|
||||
protected length: number | undefined;
|
||||
|
||||
protected lengthFieldValue: VariableLengthArrayBufferLikeFieldLengthValue;
|
||||
protected lengthFieldValue: VariableLengthBufferLikeFieldLengthValue;
|
||||
|
||||
public constructor(
|
||||
definition: TDefinition,
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: TDefinition['TValue'],
|
||||
arrayBuffer?: ArrayBuffer,
|
||||
array?: Uint8Array,
|
||||
) {
|
||||
super(definition, options, struct, value, arrayBuffer);
|
||||
super(definition, options, struct, value, array);
|
||||
|
||||
if (arrayBuffer) {
|
||||
this.length = arrayBuffer.byteLength;
|
||||
if (array) {
|
||||
this.length = array.byteLength;
|
||||
}
|
||||
|
||||
// Patch the associated length field.
|
||||
const lengthField = this.definition.options.lengthField;
|
||||
|
||||
const originalValue = struct.get(lengthField);
|
||||
this.lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue(
|
||||
this.lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
originalValue,
|
||||
this,
|
||||
);
|
||||
|
@ -84,8 +84,8 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
|||
if (this.length === undefined) {
|
||||
this.length = this.definition.type.getSize(this.value);
|
||||
if (this.length === -1) {
|
||||
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value);
|
||||
this.length = this.arrayBuffer.byteLength;
|
||||
this.array = this.definition.type.toBuffer(this.value);
|
||||
this.length = this.array.byteLength;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,24 +94,24 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
|
|||
|
||||
public override set(value: unknown) {
|
||||
super.set(value);
|
||||
this.arrayBuffer = undefined;
|
||||
this.array = undefined;
|
||||
this.length = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Not using `VariableLengthArrayBufferLikeStructFieldValue` directly makes writing tests much easier...
|
||||
type VariableLengthArrayBufferLikeFieldValueLike =
|
||||
StructFieldValue<StructFieldDefinition<VariableLengthArrayBufferLikeFieldOptions, any, any>>;
|
||||
// Not using `VariableLengthBufferLikeStructFieldValue` directly makes writing tests much easier...
|
||||
type VariableLengthBufferLikeFieldValueLike =
|
||||
StructFieldValue<StructFieldDefinition<VariableLengthBufferLikeFieldOptions, any, any>>;
|
||||
|
||||
export class VariableLengthArrayBufferLikeFieldLengthValue
|
||||
export class VariableLengthBufferLikeFieldLengthValue
|
||||
extends StructFieldValue {
|
||||
protected originalField: StructFieldValue;
|
||||
|
||||
protected arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike;
|
||||
protected arrayBufferField: VariableLengthBufferLikeFieldValueLike;
|
||||
|
||||
public constructor(
|
||||
originalField: StructFieldValue,
|
||||
arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike,
|
||||
arrayBufferField: VariableLengthBufferLikeFieldValueLike,
|
||||
) {
|
||||
super(originalField.definition, originalField.options, originalField.struct, 0);
|
||||
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 './fixed-length-array-buffer';
|
||||
export * from './buffer';
|
||||
export * from './number';
|
||||
export * from './variable-length-array-buffer';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue