import { bipedal } from "./bipedal.js"; import type { Field } from "./field.js"; export interface Converter { convert: (value: From) => To; back: (value: To) => From; } export interface BufferLengthConverter extends Converter { field: K; } export interface BufferLike { (length: number): Field; ( length: number, converter: Converter, ): Field; (lengthField: K): Field>; ( lengthField: K, converter: Converter, ): Field>; ( length: BufferLengthConverter, ): Field>; ( length: BufferLengthConverter, converter: Converter, ): Field>; ( length: Field, ): Field; ( length: Field, converter: Converter, ): Field; } export const EmptyUint8Array = new Uint8Array(0); export const buffer: BufferLike = function ( lengthOrField: | string | number | Field | BufferLengthConverter, converter?: Converter, ): Field> { if (typeof lengthOrField === "number") { if (converter) { if (lengthOrField === 0) { return { size: 0, serialize: () => {}, deserialize: () => converter.convert(EmptyUint8Array), }; } return { size: lengthOrField, serialize: (value, { buffer, index }) => { buffer.set( converter.back(value).slice(0, lengthOrField), index, ); }, deserialize: bipedal(function* (then, { reader }) { const array = yield* then( reader.readExactly(lengthOrField), ); return converter.convert(array); }), }; } if (lengthOrField === 0) { return { size: 0, serialize: () => {}, deserialize: () => EmptyUint8Array, }; } return { size: lengthOrField, serialize: (value, { buffer, index }) => { buffer.set( (value as Uint8Array).slice(0, lengthOrField), index, ); }, deserialize: ({ reader }) => reader.readExactly(lengthOrField), }; } // Some Field type might be `function`s if ( (typeof lengthOrField === "object" || typeof lengthOrField === "function") && "serialize" in lengthOrField ) { if (converter) { return { size: 0, dynamicSize(value) { const array = converter.back(value); const lengthFieldSize = lengthOrField.dynamicSize?.(array.length) ?? lengthOrField.size; return lengthFieldSize + array.length; }, serialize(value, context) { const array = converter.back(value); const lengthFieldSize = lengthOrField.dynamicSize?.(array.length) ?? lengthOrField.size; lengthOrField.serialize(array.length, context); context.buffer.set(array, context.index + lengthFieldSize); }, deserialize: bipedal(function* (then, context) { const length = yield* then( lengthOrField.deserialize(context), ); const array = yield* then( context.reader.readExactly(length), ); return converter.convert(array); }), }; } return { size: 0, dynamicSize(value) { const lengthFieldSize = lengthOrField.dynamicSize?.((value as Uint8Array).length) ?? lengthOrField.size; return lengthFieldSize + (value as Uint8Array).length; }, serialize(value, context) { const lengthFieldSize = lengthOrField.dynamicSize?.((value as Uint8Array).length) ?? lengthOrField.size; lengthOrField.serialize((value as Uint8Array).length, context); context.buffer.set( value as Uint8Array, context.index + lengthFieldSize, ); }, deserialize: bipedal(function* (then, context) { const length = yield* then(lengthOrField.deserialize(context)); return context.reader.readExactly(length); }), }; } if (typeof lengthOrField === "string") { if (converter) { return { size: 0, preSerialize: (value, runtimeStruct) => { runtimeStruct[lengthOrField] = converter.back(value).length; }, dynamicSize: (value) => { return converter.back(value).length; }, serialize: (value, { buffer, index }) => { buffer.set(converter.back(value), index); }, deserialize: bipedal(function* ( then, { reader, runtimeStruct }, ) { const length = runtimeStruct[lengthOrField] as number; if (length === 0) { return converter.convert(EmptyUint8Array); } const value = yield* then(reader.readExactly(length)); return converter.convert(value); }), }; } return { size: 0, preSerialize: (value, runtimeStruct) => { runtimeStruct[lengthOrField] = (value as Uint8Array).length; }, dynamicSize: (value) => { return (value as Uint8Array).length; }, serialize: (value, { buffer, index }) => { buffer.set(value as Uint8Array, index); }, deserialize: ({ reader, runtimeStruct }) => { const length = runtimeStruct[lengthOrField] as number; if (length === 0) { return EmptyUint8Array; } return reader.readExactly(length); }, }; } if (converter) { return { size: 0, preSerialize: (value, runtimeStruct) => { const length = converter.back(value).length; runtimeStruct[lengthOrField.field] = lengthOrField.back(length); }, dynamicSize: (value) => { return converter.back(value).length; }, serialize: (value, { buffer, index }) => { buffer.set(converter.back(value), index); }, deserialize: bipedal(function* (then, { reader, runtimeStruct }) { const rawLength = runtimeStruct[lengthOrField.field]; const length = lengthOrField.convert(rawLength); if (length === 0) { return converter.convert(EmptyUint8Array); } const value = yield* then(reader.readExactly(length)); return converter.convert(value); }), }; } return { size: 0, preSerialize: (value, runtimeStruct) => { runtimeStruct[lengthOrField.field] = lengthOrField.back( (value as Uint8Array).length, ); }, dynamicSize: (value) => { return (value as Uint8Array).length; }, serialize: (value, { buffer, index }) => { buffer.set(value as Uint8Array, index); }, deserialize: ({ reader, runtimeStruct }) => { const rawLength = runtimeStruct[lengthOrField.field]; const length = lengthOrField.convert(rawLength); if (length === 0) { return EmptyUint8Array; } return reader.readExactly(length); }, }; } as never;