feat: migrate to Uint8Array

This commit is contained in:
Simon Chan 2022-02-23 23:09:56 +08:00
parent cb988f5563
commit 96b5807691
39 changed files with 429 additions and 752 deletions

View file

@ -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(' ')
);

View file

@ -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,

View file

@ -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);

View file

@ -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() {

View file

@ -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) {

View file

@ -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);
}),

View file

@ -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);
}
}

View file

@ -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);
},

View file

@ -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);
},

View file

@ -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));

View file

@ -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);
}
});
}

View file

@ -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);
},

View file

@ -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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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!);

View file

@ -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;
}
}

View file

@ -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':

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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 {

View file

@ -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);
};
/**

View file

@ -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);
});
});
});
});
});

View file

@ -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);
}
}

View 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);
});
});
});
});
});

View file

@ -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);
}
}

View file

@ -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);

View 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;
}
};

View file

@ -0,0 +1,3 @@
export * from './base';
export * from './fixed-length';
export * from './variable-length';

View file

@ -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,

View file

@ -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;

View file

@ -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;
}
};

View file

@ -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';