From f4fc10eb4dc3c123a8ee74ce1b1b7bfbb05ecc4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:07:00 +0000 Subject: [PATCH 1/3] Initial plan From 2bc56b1e67d0392add584b83baaefe3c34db67b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Sep 2025 18:17:08 +0000 Subject: [PATCH 2/3] feat: implement XDR decoder classes with comprehensive tests Co-authored-by: streamich <9773803+streamich@users.noreply.github.com> --- src/xdr/XdrDecoder.ts | 199 +++++++++ src/xdr/XdrSchemaDecoder.ts | 199 +++++++++ src/xdr/__tests__/XdrDecoder.spec.ts | 367 ++++++++++++++++ src/xdr/__tests__/XdrSchemaDecoder.spec.ts | 471 +++++++++++++++++++++ src/xdr/index.ts | 2 + 5 files changed, 1238 insertions(+) create mode 100644 src/xdr/XdrDecoder.ts create mode 100644 src/xdr/XdrSchemaDecoder.ts create mode 100644 src/xdr/__tests__/XdrDecoder.spec.ts create mode 100644 src/xdr/__tests__/XdrSchemaDecoder.spec.ts diff --git a/src/xdr/XdrDecoder.ts b/src/xdr/XdrDecoder.ts new file mode 100644 index 00000000..a5340968 --- /dev/null +++ b/src/xdr/XdrDecoder.ts @@ -0,0 +1,199 @@ +import {Reader} from '@jsonjoy.com/buffers/lib/Reader'; +import type {IReader, IReaderResettable} from '@jsonjoy.com/buffers/lib'; +import type {BinaryJsonDecoder} from '../types'; + +/** + * XDR (External Data Representation) binary decoder for basic value decoding. + * Implements XDR binary decoding according to RFC 4506. + * + * Key XDR decoding principles: + * - All data types are aligned to 4-byte boundaries + * - Multi-byte quantities are transmitted in big-endian byte order + * - Strings and opaque data are padded to 4-byte boundaries + * - Variable-length arrays and strings are preceded by their length + */ +export class XdrDecoder + implements BinaryJsonDecoder +{ + public constructor(public reader: R = new Reader() as any) {} + + public read(uint8: Uint8Array): unknown { + this.reader.reset(uint8); + return this.readAny(); + } + + public decode(uint8: Uint8Array): unknown { + this.reader.reset(uint8); + return this.readAny(); + } + + public readAny(): unknown { + // Basic implementation - in practice this would need schema info + // For now, we'll throw as this should be used with schema decoder + throw new Error('XdrDecoder.readAny() requires explicit type methods or use XdrSchemaDecoder'); + } + + /** + * Reads an XDR void value (no data is actually read). + */ + public readVoid(): void { + // Void values have no representation in XDR + } + + /** + * Reads an XDR boolean value as a 4-byte integer. + * Returns true for non-zero values, false for zero. + */ + public readBoolean(): boolean { + return this.readInt() !== 0; + } + + /** + * Reads an XDR signed 32-bit integer in big-endian format. + */ + public readInt(): number { + const reader = this.reader; + const value = reader.view.getInt32(reader.x, false); // false = big-endian + reader.x += 4; + return value; + } + + /** + * Reads an XDR unsigned 32-bit integer in big-endian format. + */ + public readUnsignedInt(): number { + const reader = this.reader; + const value = reader.view.getUint32(reader.x, false); // false = big-endian + reader.x += 4; + return value; + } + + /** + * Reads an XDR signed 64-bit integer (hyper) in big-endian format. + */ + public readHyper(): bigint { + const reader = this.reader; + const value = reader.view.getBigInt64(reader.x, false); // false = big-endian + reader.x += 8; + return value; + } + + /** + * Reads an XDR unsigned 64-bit integer (unsigned hyper) in big-endian format. + */ + public readUnsignedHyper(): bigint { + const reader = this.reader; + const value = reader.view.getBigUint64(reader.x, false); // false = big-endian + reader.x += 8; + return value; + } + + /** + * Reads an XDR float value using IEEE 754 single-precision in big-endian format. + */ + public readFloat(): number { + const reader = this.reader; + const value = reader.view.getFloat32(reader.x, false); // false = big-endian + reader.x += 4; + return value; + } + + /** + * Reads an XDR double value using IEEE 754 double-precision in big-endian format. + */ + public readDouble(): number { + const reader = this.reader; + const value = reader.view.getFloat64(reader.x, false); // false = big-endian + reader.x += 8; + return value; + } + + /** + * Reads an XDR quadruple value (128-bit float). + * Note: JavaScript doesn't have native 128-bit float support. + */ + public readQuadruple(): number { + throw new Error('not implemented'); + } + + /** + * Reads XDR opaque data with known fixed length. + * Data is padded to 4-byte boundary but only the actual data is returned. + */ + public readOpaque(size: number): Uint8Array { + const reader = this.reader; + const data = new Uint8Array(size); + + // Read actual data + for (let i = 0; i < size; i++) { + data[i] = reader.u8(); + } + + // Skip padding bytes to reach 4-byte boundary + const paddedSize = Math.ceil(size / 4) * 4; + const padding = paddedSize - size; + reader.skip(padding); + + return data; + } + + /** + * Reads XDR variable-length opaque data. + * Length is read first, followed by data padded to 4-byte boundary. + */ + public readVarlenOpaque(): Uint8Array { + const size = this.readUnsignedInt(); + return this.readOpaque(size); + } + + /** + * Reads an XDR string with UTF-8 encoding. + * Length is read first, followed by UTF-8 bytes padded to 4-byte boundary. + */ + public readString(): string { + const size = this.readUnsignedInt(); + const reader = this.reader; + + // Read UTF-8 bytes + const utf8Bytes = new Uint8Array(size); + for (let i = 0; i < size; i++) { + utf8Bytes[i] = reader.u8(); + } + + // Skip padding bytes to reach 4-byte boundary + const paddedSize = Math.ceil(size / 4) * 4; + const padding = paddedSize - size; + reader.skip(padding); + + // Decode UTF-8 to string + return new TextDecoder('utf-8').decode(utf8Bytes); + } + + /** + * Reads an XDR enum value as an unsigned integer. + */ + public readEnum(): number { + return this.readInt(); + } + + /** + * Reads a fixed-size array of elements. + * Caller must provide the decode function for each element. + */ + public readArray(size: number, elementReader: () => T): T[] { + const array: T[] = []; + for (let i = 0; i < size; i++) { + array.push(elementReader()); + } + return array; + } + + /** + * Reads a variable-length array of elements. + * Length is read first, followed by elements. + */ + public readVarlenArray(elementReader: () => T): T[] { + const size = this.readUnsignedInt(); + return this.readArray(size, elementReader); + } +} \ No newline at end of file diff --git a/src/xdr/XdrSchemaDecoder.ts b/src/xdr/XdrSchemaDecoder.ts new file mode 100644 index 00000000..dd71786a --- /dev/null +++ b/src/xdr/XdrSchemaDecoder.ts @@ -0,0 +1,199 @@ +import {Reader} from '@jsonjoy.com/buffers/lib/Reader'; +import {XdrDecoder} from './XdrDecoder'; +import {XdrUnion} from './XdrUnion'; +import type {IReader, IReaderResettable} from '@jsonjoy.com/buffers/lib'; +import type { + XdrSchema, + XdrPrimitiveSchema, + XdrWidePrimitiveSchema, + XdrCompositeSchema, + XdrEnumSchema, + XdrOpaqueSchema, + XdrVarlenOpaqueSchema, + XdrStringSchema, + XdrArraySchema, + XdrVarlenArraySchema, + XdrStructSchema, + XdrUnionSchema, +} from './types'; + +/** + * XDR (External Data Representation) schema-aware decoder. + * Decodes values according to provided XDR schemas with proper validation. + * Based on RFC 4506 specification. + */ +export class XdrSchemaDecoder { + private decoder: XdrDecoder; + + constructor(public readonly reader: IReader & IReaderResettable = new Reader()) { + this.decoder = new XdrDecoder(reader); + } + + /** + * Decodes a value according to the provided schema. + */ + public decode(data: Uint8Array, schema: XdrSchema): unknown { + this.reader.reset(data); + return this.readValue(schema); + } + + /** + * Reads a value according to its schema. + */ + private readValue(schema: XdrSchema): unknown { + switch (schema.type) { + // Primitive types + case 'void': + return this.decoder.readVoid(); + case 'int': + return this.decoder.readInt(); + case 'unsigned_int': + return this.decoder.readUnsignedInt(); + case 'boolean': + return this.decoder.readBoolean(); + case 'hyper': + return this.decoder.readHyper(); + case 'unsigned_hyper': + return this.decoder.readUnsignedHyper(); + case 'float': + return this.decoder.readFloat(); + case 'double': + return this.decoder.readDouble(); + case 'quadruple': + return this.decoder.readQuadruple(); + case 'enum': + return this.readEnum(schema as XdrEnumSchema); + + // Wide primitive types + case 'opaque': + return this.readOpaque(schema as XdrOpaqueSchema); + case 'vopaque': + return this.readVarlenOpaque(schema as XdrVarlenOpaqueSchema); + case 'string': + return this.readString(schema as XdrStringSchema); + + // Composite types + case 'array': + return this.readArray(schema as XdrArraySchema); + case 'varray': + return this.readVarlenArray(schema as XdrVarlenArraySchema); + case 'struct': + return this.readStruct(schema as XdrStructSchema); + case 'union': + return this.readUnion(schema as XdrUnionSchema); + + default: + throw new Error(`Unknown schema type: ${(schema as any).type}`); + } + } + + /** + * Reads an enum value according to the enum schema. + */ + private readEnum(schema: XdrEnumSchema): string | number { + const value = this.decoder.readEnum(); + + // Find the enum name for this value + for (const [name, enumValue] of Object.entries(schema.values)) { + if (enumValue === value) { + return name; + } + } + + // If no matching name found, return the numeric value + return value; + } + + /** + * Reads opaque data according to the opaque schema. + */ + private readOpaque(schema: XdrOpaqueSchema): Uint8Array { + return this.decoder.readOpaque(schema.size); + } + + /** + * Reads variable-length opaque data according to the schema. + */ + private readVarlenOpaque(schema: XdrVarlenOpaqueSchema): Uint8Array { + const data = this.decoder.readVarlenOpaque(); + + // Check size constraint if specified + if (schema.size !== undefined && data.length > schema.size) { + throw new Error(`Variable-length opaque data size ${data.length} exceeds maximum ${schema.size}`); + } + + return data; + } + + /** + * Reads a string according to the string schema. + */ + private readString(schema: XdrStringSchema): string { + const str = this.decoder.readString(); + + // Check size constraint if specified + if (schema.size !== undefined && str.length > schema.size) { + throw new Error(`String length ${str.length} exceeds maximum ${schema.size}`); + } + + return str; + } + + /** + * Reads a fixed-size array according to the array schema. + */ + private readArray(schema: XdrArraySchema): unknown[] { + return this.decoder.readArray(schema.size, () => this.readValue(schema.elements)); + } + + /** + * Reads a variable-length array according to the schema. + */ + private readVarlenArray(schema: XdrVarlenArraySchema): unknown[] { + const array = this.decoder.readVarlenArray(() => this.readValue(schema.elements)); + + // Check size constraint if specified + if (schema.size !== undefined && array.length > schema.size) { + throw new Error(`Variable-length array size ${array.length} exceeds maximum ${schema.size}`); + } + + return array; + } + + /** + * Reads a struct according to the struct schema. + */ + private readStruct(schema: XdrStructSchema): Record { + const struct: Record = {}; + + for (const [fieldSchema, fieldName] of schema.fields) { + struct[fieldName] = this.readValue(fieldSchema); + } + + return struct; + } + + /** + * Reads a union according to the union schema. + */ + private readUnion(schema: XdrUnionSchema): XdrUnion { + // Read discriminant + const discriminant = this.decoder.readInt(); + + // Find matching arm + for (const [armDiscriminant, armSchema] of schema.arms) { + if (armDiscriminant === discriminant) { + const value = this.readValue(armSchema); + return new XdrUnion(discriminant, value); + } + } + + // If no matching arm found, try default + if (schema.default) { + const value = this.readValue(schema.default); + return new XdrUnion(discriminant, value); + } + + throw new Error(`No matching union arm for discriminant: ${discriminant}`); + } +} \ No newline at end of file diff --git a/src/xdr/__tests__/XdrDecoder.spec.ts b/src/xdr/__tests__/XdrDecoder.spec.ts new file mode 100644 index 00000000..2f357102 --- /dev/null +++ b/src/xdr/__tests__/XdrDecoder.spec.ts @@ -0,0 +1,367 @@ +import {Reader} from '@jsonjoy.com/buffers/lib/Reader'; +import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; +import {XdrEncoder} from '../XdrEncoder'; +import {XdrDecoder} from '../XdrDecoder'; + +describe('XdrDecoder', () => { + let reader: Reader; + let writer: Writer; + let encoder: XdrEncoder; + let decoder: XdrDecoder; + + beforeEach(() => { + reader = new Reader(); + writer = new Writer(); + encoder = new XdrEncoder(writer); + decoder = new XdrDecoder(reader); + }); + + describe('primitive types', () => { + test('decodes void', () => { + encoder.writeVoid(); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readVoid(); + expect(result).toBeUndefined(); + }); + + test('decodes boolean true', () => { + encoder.writeBoolean(true); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readBoolean(); + expect(result).toBe(true); + }); + + test('decodes boolean false', () => { + encoder.writeBoolean(false); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readBoolean(); + expect(result).toBe(false); + }); + + test('decodes positive int', () => { + const value = 42; + encoder.writeInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readInt(); + expect(result).toBe(value); + }); + + test('decodes negative int', () => { + const value = -1; + encoder.writeInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readInt(); + expect(result).toBe(value); + }); + + test('decodes large positive int', () => { + const value = 0x12345678; + encoder.writeInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readInt(); + expect(result).toBe(value); + }); + + test('decodes unsigned int', () => { + const value = 0xffffffff; + encoder.writeUnsignedInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readUnsignedInt(); + expect(result).toBe(value); + }); + + test('decodes hyper from bigint', () => { + const value = BigInt('0x123456789abcdef0'); + encoder.writeHyper(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readHyper(); + expect(result).toBe(value); + }); + + test('decodes negative hyper from bigint', () => { + const value = -BigInt('0x123456789abcdef0'); + encoder.writeHyper(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readHyper(); + expect(result).toBe(value); + }); + + test('decodes unsigned hyper from bigint', () => { + const value = BigInt('0xffffffffffffffff'); + encoder.writeUnsignedHyper(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readUnsignedHyper(); + expect(result).toBe(value); + }); + + test('decodes float', () => { + const value = 3.14; + encoder.writeFloat(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readFloat(); + expect(result).toBeCloseTo(value, 6); + }); + + test('decodes double', () => { + const value = Math.PI; + encoder.writeDouble(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readDouble(); + expect(result).toBeCloseTo(value, 15); + }); + + test('throws on quadruple', () => { + expect(() => decoder.readQuadruple()).toThrow('not implemented'); + }); + }); + + describe('opaque data', () => { + test('decodes fixed opaque data', () => { + const data = new Uint8Array([1, 2, 3, 4, 5]); + encoder.writeOpaque(data); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readOpaque(data.length); + expect(result).toEqual(data); + }); + + test('decodes fixed opaque data with padding', () => { + const data = new Uint8Array([1, 2, 3]); // 3 bytes -> 4 bytes with padding + encoder.writeOpaque(data); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readOpaque(data.length); + expect(result).toEqual(data); + }); + + test('decodes variable-length opaque data', () => { + const data = new Uint8Array([1, 2, 3, 4, 5]); + encoder.writeVarlenOpaque(data); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readVarlenOpaque(); + expect(result).toEqual(data); + }); + + test('decodes empty variable-length opaque data', () => { + const data = new Uint8Array([]); + encoder.writeVarlenOpaque(data); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readVarlenOpaque(); + expect(result).toEqual(data); + }); + }); + + describe('strings', () => { + test('decodes simple string', () => { + const value = 'hello'; + encoder.writeStr(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readString(); + expect(result).toBe(value); + }); + + test('decodes empty string', () => { + const value = ''; + encoder.writeStr(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readString(); + expect(result).toBe(value); + }); + + test('decodes UTF-8 string', () => { + const value = 'πŸš€ Hello, δΈ–η•Œ!'; + encoder.writeStr(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readString(); + expect(result).toBe(value); + }); + + test('decodes string that fits exactly in 4-byte boundary', () => { + const value = 'test'; // 4 bytes + encoder.writeStr(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readString(); + expect(result).toBe(value); + }); + }); + + describe('enum', () => { + test('decodes enum value', () => { + const value = 42; + encoder.writeInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readEnum(); + expect(result).toBe(value); + }); + }); + + describe('arrays', () => { + test('decodes fixed-size array', () => { + const values = [1, 2, 3]; + values.forEach(v => encoder.writeInt(v)); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readArray(values.length, () => decoder.readInt()); + expect(result).toEqual(values); + }); + + test('decodes empty fixed-size array', () => { + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readArray(0, () => decoder.readInt()); + expect(result).toEqual([]); + }); + + test('decodes variable-length array', () => { + const values = [1, 2, 3, 4]; + encoder.writeUnsignedInt(values.length); + values.forEach(v => encoder.writeInt(v)); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readVarlenArray(() => decoder.readInt()); + expect(result).toEqual(values); + }); + + test('decodes empty variable-length array', () => { + encoder.writeUnsignedInt(0); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readVarlenArray(() => decoder.readInt()); + expect(result).toEqual([]); + }); + }); + + describe('decode method', () => { + test('decode method calls readAny which throws', () => { + const encoded = new Uint8Array([0, 0, 0, 42]); + expect(() => decoder.decode(encoded)).toThrow('XdrDecoder.readAny() requires explicit type methods'); + }); + + test('read method calls readAny which throws', () => { + const encoded = new Uint8Array([0, 0, 0, 42]); + expect(() => decoder.read(encoded)).toThrow('XdrDecoder.readAny() requires explicit type methods'); + }); + }); + + describe('edge cases', () => { + test('handles 32-bit integer boundaries', () => { + const values = [-2147483648, 2147483647, 0]; + + for (const value of values) { + writer.reset(); + encoder.writeInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readInt(); + expect(result).toBe(value); + } + }); + + test('handles 32-bit unsigned integer boundaries', () => { + const values = [0, 4294967295]; + + for (const value of values) { + writer.reset(); + encoder.writeUnsignedInt(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readUnsignedInt(); + expect(result).toBe(value); + } + }); + + test('handles special float values', () => { + const values = [0, -0, Infinity, -Infinity]; + + for (const value of values) { + writer.reset(); + encoder.writeFloat(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readFloat(); + expect(result).toBe(value); + } + }); + + test('handles NaN float value', () => { + writer.reset(); + encoder.writeFloat(NaN); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readFloat(); + expect(result).toBeNaN(); + }); + + test('handles special double values', () => { + const values = [0, -0, Infinity, -Infinity]; + + for (const value of values) { + writer.reset(); + encoder.writeDouble(value); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readDouble(); + expect(result).toBe(value); + } + }); + + test('handles NaN double value', () => { + writer.reset(); + encoder.writeDouble(NaN); + const encoded = writer.flush(); + + reader.reset(encoded); + const result = decoder.readDouble(); + expect(result).toBeNaN(); + }); + }); +}); \ No newline at end of file diff --git a/src/xdr/__tests__/XdrSchemaDecoder.spec.ts b/src/xdr/__tests__/XdrSchemaDecoder.spec.ts new file mode 100644 index 00000000..af6ea2a5 --- /dev/null +++ b/src/xdr/__tests__/XdrSchemaDecoder.spec.ts @@ -0,0 +1,471 @@ +import {Reader} from '@jsonjoy.com/buffers/lib/Reader'; +import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; +import {XdrEncoder} from '../XdrEncoder'; +import {XdrSchemaEncoder} from '../XdrSchemaEncoder'; +import {XdrSchemaDecoder} from '../XdrSchemaDecoder'; +import {XdrUnion} from '../XdrUnion'; +import type {XdrSchema} from '../types'; + +describe('XdrSchemaDecoder', () => { + let reader: Reader; + let writer: Writer; + let encoder: XdrEncoder; + let schemaEncoder: XdrSchemaEncoder; + let decoder: XdrSchemaDecoder; + + beforeEach(() => { + reader = new Reader(); + writer = new Writer(); + encoder = new XdrEncoder(writer); + schemaEncoder = new XdrSchemaEncoder(writer); + decoder = new XdrSchemaDecoder(reader); + }); + + describe('primitive types with schema', () => { + test('decodes void with void schema', () => { + const schema: XdrSchema = {type: 'void'}; + const encoded = schemaEncoder.encode(null, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBeUndefined(); + }); + + test('decodes int with int schema', () => { + const schema: XdrSchema = {type: 'int'}; + const value = 42; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('decodes unsigned int with unsigned_int schema', () => { + const schema: XdrSchema = {type: 'unsigned_int'}; + const value = 4294967295; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('decodes boolean with boolean schema', () => { + const schema: XdrSchema = {type: 'boolean'}; + + let encoded = schemaEncoder.encode(true, schema); + let result = decoder.decode(encoded, schema); + expect(result).toBe(true); + + encoded = schemaEncoder.encode(false, schema); + result = decoder.decode(encoded, schema); + expect(result).toBe(false); + }); + + test('decodes hyper with hyper schema', () => { + const schema: XdrSchema = {type: 'hyper'}; + const value = BigInt('0x123456789abcdef0'); + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('decodes unsigned hyper with unsigned_hyper schema', () => { + const schema: XdrSchema = {type: 'unsigned_hyper'}; + const value = BigInt('0xffffffffffffffff'); + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('decodes float with float schema', () => { + const schema: XdrSchema = {type: 'float'}; + const value = 3.14; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBeCloseTo(value, 6); + }); + + test('decodes double with double schema', () => { + const schema: XdrSchema = {type: 'double'}; + const value = Math.PI; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBeCloseTo(value, 15); + }); + + test('throws on quadruple with quadruple schema', () => { + const schema: XdrSchema = {type: 'quadruple'}; + const value = 1.0; + + expect(() => schemaEncoder.encode(value, schema)).toThrow('not implemented'); + }); + }); + + describe('enum schemas', () => { + test('decodes valid enum value by name', () => { + const schema: XdrSchema = { + type: 'enum', + values: {RED: 0, GREEN: 1, BLUE: 2}, + }; + const encoded = schemaEncoder.encode('GREEN', schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe('GREEN'); + }); + + test('returns numeric value for unknown enum', () => { + const schema: XdrSchema = { + type: 'enum', + values: {RED: 0, GREEN: 1, BLUE: 2}, + }; + + // Manually encode a value that's not in the enum + encoder.writeInt(99); + const encoded = writer.flush(); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(99); + }); + }); + + describe('opaque schemas', () => { + test('decodes opaque data with correct size', () => { + const schema: XdrSchema = {type: 'opaque', size: 5}; + const value = new Uint8Array([1, 2, 3, 4, 5]); + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes variable-length opaque data', () => { + const schema: XdrSchema = {type: 'vopaque'}; + const value = new Uint8Array([1, 2, 3, 4, 5]); + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes variable-length opaque data with size limit', () => { + const schema: XdrSchema = {type: 'vopaque', size: 10}; + const value = new Uint8Array([1, 2, 3, 4, 5]); + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('throws on variable-length opaque data too large', () => { + const schema: XdrSchema = {type: 'vopaque', size: 3}; + + // Manually encode data larger than limit + const data = new Uint8Array([1, 2, 3, 4, 5]); + encoder.writeVarlenOpaque(data); + const encoded = writer.flush(); + + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 3'); + }); + }); + + describe('string schemas', () => { + test('decodes string with string schema', () => { + const schema: XdrSchema = {type: 'string'}; + const value = 'Hello, XDR!'; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('decodes string with size limit', () => { + const schema: XdrSchema = {type: 'string', size: 20}; + const value = 'Hello'; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toBe(value); + }); + + test('throws on string too long', () => { + const schema: XdrSchema = {type: 'string', size: 3}; + + // Manually encode a string longer than limit + const str = 'toolong'; + encoder.writeStr(str); + const encoded = writer.flush(); + + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 3'); + }); + }); + + describe('array schemas', () => { + test('decodes fixed-size array', () => { + const schema: XdrSchema = { + type: 'array', + elements: {type: 'int'}, + size: 3, + }; + const value = [1, 2, 3]; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes variable-length array', () => { + const schema: XdrSchema = { + type: 'varray', + elements: {type: 'int'}, + }; + const value = [1, 2, 3, 4]; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes empty variable-length array', () => { + const schema: XdrSchema = { + type: 'varray', + elements: {type: 'int'}, + }; + const value: number[] = []; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('throws on variable-length array too large', () => { + const schema: XdrSchema = { + type: 'varray', + elements: {type: 'int'}, + size: 2, + }; + + // Manually encode array larger than limit + const values = [1, 2, 3]; + encoder.writeUnsignedInt(values.length); + values.forEach(v => encoder.writeInt(v)); + const encoded = writer.flush(); + + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 2'); + }); + + test('decodes nested arrays', () => { + const schema: XdrSchema = { + type: 'array', + elements: { + type: 'array', + elements: {type: 'int'}, + size: 2, + }, + size: 2, + }; + const value = [[1, 2], [3, 4]]; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + }); + + describe('struct schemas', () => { + test('decodes simple struct', () => { + const schema: XdrSchema = { + type: 'struct', + fields: [ + [{type: 'int'}, 'id'], + [{type: 'string'}, 'name'], + ], + }; + const value = {id: 42, name: 'test'}; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes nested struct', () => { + const schema: XdrSchema = { + type: 'struct', + fields: [ + [{type: 'int'}, 'id'], + [ + { + type: 'struct', + fields: [ + [{type: 'string'}, 'first'], + [{type: 'string'}, 'last'], + ], + }, + 'name', + ], + ], + }; + const value = { + id: 42, + name: {first: 'John', last: 'Doe'}, + }; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + + test('decodes empty struct', () => { + const schema: XdrSchema = { + type: 'struct', + fields: [], + }; + const value = {}; + const encoded = schemaEncoder.encode(value, schema); + + const result = decoder.decode(encoded, schema); + expect(result).toEqual(value); + }); + }); + + describe('union schemas', () => { + test('decodes union value with numeric discriminant', () => { + const schema: XdrSchema = { + type: 'union', + arms: [ + [0, {type: 'int'}], + [1, {type: 'string'}], + ], + }; + + // Test first arm + let encoded = schemaEncoder.encode(new XdrUnion(0, 42), schema); + let result = decoder.decode(encoded, schema) as XdrUnion; + expect(result).toBeInstanceOf(XdrUnion); + expect(result.discriminant).toBe(0); + expect(result.value).toBe(42); + + // Test second arm + encoded = schemaEncoder.encode(new XdrUnion(1, 'hello'), schema); + result = decoder.decode(encoded, schema) as XdrUnion; + expect(result).toBeInstanceOf(XdrUnion); + expect(result.discriminant).toBe(1); + expect(result.value).toBe('hello'); + }); + + test('decodes union value with default', () => { + const schema: XdrSchema = { + type: 'union', + arms: [ + [0, {type: 'int'}], + [1, {type: 'string'}], + ], + default: {type: 'boolean'}, + }; + + // Manually encode unknown discriminant + encoder.writeInt(99); // discriminant + encoder.writeBoolean(true); // default value + const encoded = writer.flush(); + + const result = decoder.decode(encoded, schema) as XdrUnion; + expect(result).toBeInstanceOf(XdrUnion); + expect(result.discriminant).toBe(99); + expect(result.value).toBe(true); + }); + + test('throws on union value with no matching arm', () => { + const schema: XdrSchema = { + type: 'union', + arms: [ + [0, {type: 'int'}], + [1, {type: 'string'}], + ], + }; + + // Manually encode unknown discriminant without default + encoder.writeInt(99); + encoder.writeInt(42); // some value + const encoded = writer.flush(); + + expect(() => decoder.decode(encoded, schema)).toThrow('No matching union arm for discriminant: 99'); + }); + }); + + describe('invalid schemas', () => { + test('throws on unknown schema type', () => { + const schema = {type: 'invalid'} as any; + const encoded = new Uint8Array([0, 0, 0, 42]); + + expect(() => decoder.decode(encoded, schema)).toThrow('Unknown schema type: invalid'); + }); + }); + + describe('complex nested schemas', () => { + test('decodes complex nested structure', () => { + const schema: XdrSchema = { + type: 'struct', + fields: [ + [{type: 'int'}, 'version'], + [ + { + type: 'varray', + elements: { + type: 'struct', + fields: [ + [{type: 'string'}, 'name'], + [{type: 'enum', values: {ACTIVE: 1, INACTIVE: 0}}, 'status'], + [ + { + type: 'union', + arms: [ + [0, {type: 'int'}], + [1, {type: 'string'}], + ], + }, + 'data', + ], + ], + }, + }, + 'items', + ], + ], + }; + + const value = { + version: 1, + items: [ + { + name: 'item1', + status: 'ACTIVE', + data: new XdrUnion(0, 42), + }, + { + name: 'item2', + status: 'INACTIVE', + data: new XdrUnion(1, 'test'), + }, + ], + }; + + const encoded = schemaEncoder.encode(value, schema); + const result = decoder.decode(encoded, schema) as any; + + expect(result.version).toBe(1); + expect(result.items).toHaveLength(2); + expect(result.items[0].name).toBe('item1'); + expect(result.items[0].status).toBe('ACTIVE'); + expect(result.items[0].data).toBeInstanceOf(XdrUnion); + expect(result.items[0].data.discriminant).toBe(0); + expect(result.items[0].data.value).toBe(42); + expect(result.items[1].name).toBe('item2'); + expect(result.items[1].status).toBe('INACTIVE'); + expect(result.items[1].data).toBeInstanceOf(XdrUnion); + expect(result.items[1].data.discriminant).toBe(1); + expect(result.items[1].data.value).toBe('test'); + }); + }); +}); \ No newline at end of file diff --git a/src/xdr/index.ts b/src/xdr/index.ts index 46950b30..a4ce226e 100644 --- a/src/xdr/index.ts +++ b/src/xdr/index.ts @@ -7,6 +7,8 @@ export * from './types'; export * from './XdrEncoder'; +export * from './XdrDecoder'; export * from './XdrSchemaEncoder'; +export * from './XdrSchemaDecoder'; export * from './XdrSchemaValidator'; export * from './XdrUnion'; From 5bcc10db353bbc88878a2bb72135b82def2d5dbe Mon Sep 17 00:00:00 2001 From: Va Da Date: Tue, 16 Sep 2025 18:25:51 +0000 Subject: [PATCH 3/3] style: run Prettier --- src/xdr/XdrDecoder.ts | 14 ++-- src/xdr/XdrSchemaDecoder.ts | 28 +++---- src/xdr/__tests__/XdrDecoder.spec.ts | 76 +++++++++---------- src/xdr/__tests__/XdrSchemaDecoder.spec.ts | 87 +++++++++++----------- 4 files changed, 104 insertions(+), 101 deletions(-) diff --git a/src/xdr/XdrDecoder.ts b/src/xdr/XdrDecoder.ts index a5340968..6e158d13 100644 --- a/src/xdr/XdrDecoder.ts +++ b/src/xdr/XdrDecoder.ts @@ -123,17 +123,17 @@ export class XdrDecoder schema.size) { throw new Error(`Variable-length opaque data size ${data.length} exceeds maximum ${schema.size}`); } - + return data; } @@ -130,12 +130,12 @@ export class XdrSchemaDecoder { */ private readString(schema: XdrStringSchema): string { const str = this.decoder.readString(); - + // Check size constraint if specified if (schema.size !== undefined && str.length > schema.size) { throw new Error(`String length ${str.length} exceeds maximum ${schema.size}`); } - + return str; } @@ -151,12 +151,12 @@ export class XdrSchemaDecoder { */ private readVarlenArray(schema: XdrVarlenArraySchema): unknown[] { const array = this.decoder.readVarlenArray(() => this.readValue(schema.elements)); - + // Check size constraint if specified if (schema.size !== undefined && array.length > schema.size) { throw new Error(`Variable-length array size ${array.length} exceeds maximum ${schema.size}`); } - + return array; } @@ -165,11 +165,11 @@ export class XdrSchemaDecoder { */ private readStruct(schema: XdrStructSchema): Record { const struct: Record = {}; - + for (const [fieldSchema, fieldName] of schema.fields) { struct[fieldName] = this.readValue(fieldSchema); } - + return struct; } @@ -179,7 +179,7 @@ export class XdrSchemaDecoder { private readUnion(schema: XdrUnionSchema): XdrUnion { // Read discriminant const discriminant = this.decoder.readInt(); - + // Find matching arm for (const [armDiscriminant, armSchema] of schema.arms) { if (armDiscriminant === discriminant) { @@ -187,13 +187,13 @@ export class XdrSchemaDecoder { return new XdrUnion(discriminant, value); } } - + // If no matching arm found, try default if (schema.default) { const value = this.readValue(schema.default); return new XdrUnion(discriminant, value); } - + throw new Error(`No matching union arm for discriminant: ${discriminant}`); } -} \ No newline at end of file +} diff --git a/src/xdr/__tests__/XdrDecoder.spec.ts b/src/xdr/__tests__/XdrDecoder.spec.ts index 2f357102..18974191 100644 --- a/src/xdr/__tests__/XdrDecoder.spec.ts +++ b/src/xdr/__tests__/XdrDecoder.spec.ts @@ -20,7 +20,7 @@ describe('XdrDecoder', () => { test('decodes void', () => { encoder.writeVoid(); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readVoid(); expect(result).toBeUndefined(); @@ -29,7 +29,7 @@ describe('XdrDecoder', () => { test('decodes boolean true', () => { encoder.writeBoolean(true); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readBoolean(); expect(result).toBe(true); @@ -38,7 +38,7 @@ describe('XdrDecoder', () => { test('decodes boolean false', () => { encoder.writeBoolean(false); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readBoolean(); expect(result).toBe(false); @@ -48,7 +48,7 @@ describe('XdrDecoder', () => { const value = 42; encoder.writeInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readInt(); expect(result).toBe(value); @@ -58,7 +58,7 @@ describe('XdrDecoder', () => { const value = -1; encoder.writeInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readInt(); expect(result).toBe(value); @@ -68,7 +68,7 @@ describe('XdrDecoder', () => { const value = 0x12345678; encoder.writeInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readInt(); expect(result).toBe(value); @@ -78,7 +78,7 @@ describe('XdrDecoder', () => { const value = 0xffffffff; encoder.writeUnsignedInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readUnsignedInt(); expect(result).toBe(value); @@ -88,7 +88,7 @@ describe('XdrDecoder', () => { const value = BigInt('0x123456789abcdef0'); encoder.writeHyper(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readHyper(); expect(result).toBe(value); @@ -98,7 +98,7 @@ describe('XdrDecoder', () => { const value = -BigInt('0x123456789abcdef0'); encoder.writeHyper(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readHyper(); expect(result).toBe(value); @@ -108,7 +108,7 @@ describe('XdrDecoder', () => { const value = BigInt('0xffffffffffffffff'); encoder.writeUnsignedHyper(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readUnsignedHyper(); expect(result).toBe(value); @@ -118,7 +118,7 @@ describe('XdrDecoder', () => { const value = 3.14; encoder.writeFloat(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readFloat(); expect(result).toBeCloseTo(value, 6); @@ -128,7 +128,7 @@ describe('XdrDecoder', () => { const value = Math.PI; encoder.writeDouble(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readDouble(); expect(result).toBeCloseTo(value, 15); @@ -144,7 +144,7 @@ describe('XdrDecoder', () => { const data = new Uint8Array([1, 2, 3, 4, 5]); encoder.writeOpaque(data); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readOpaque(data.length); expect(result).toEqual(data); @@ -154,7 +154,7 @@ describe('XdrDecoder', () => { const data = new Uint8Array([1, 2, 3]); // 3 bytes -> 4 bytes with padding encoder.writeOpaque(data); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readOpaque(data.length); expect(result).toEqual(data); @@ -164,7 +164,7 @@ describe('XdrDecoder', () => { const data = new Uint8Array([1, 2, 3, 4, 5]); encoder.writeVarlenOpaque(data); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readVarlenOpaque(); expect(result).toEqual(data); @@ -174,7 +174,7 @@ describe('XdrDecoder', () => { const data = new Uint8Array([]); encoder.writeVarlenOpaque(data); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readVarlenOpaque(); expect(result).toEqual(data); @@ -186,7 +186,7 @@ describe('XdrDecoder', () => { const value = 'hello'; encoder.writeStr(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readString(); expect(result).toBe(value); @@ -196,7 +196,7 @@ describe('XdrDecoder', () => { const value = ''; encoder.writeStr(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readString(); expect(result).toBe(value); @@ -206,7 +206,7 @@ describe('XdrDecoder', () => { const value = 'πŸš€ Hello, δΈ–η•Œ!'; encoder.writeStr(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readString(); expect(result).toBe(value); @@ -216,7 +216,7 @@ describe('XdrDecoder', () => { const value = 'test'; // 4 bytes encoder.writeStr(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readString(); expect(result).toBe(value); @@ -228,7 +228,7 @@ describe('XdrDecoder', () => { const value = 42; encoder.writeInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readEnum(); expect(result).toBe(value); @@ -238,9 +238,9 @@ describe('XdrDecoder', () => { describe('arrays', () => { test('decodes fixed-size array', () => { const values = [1, 2, 3]; - values.forEach(v => encoder.writeInt(v)); + values.forEach((v) => encoder.writeInt(v)); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readArray(values.length, () => decoder.readInt()); expect(result).toEqual(values); @@ -248,7 +248,7 @@ describe('XdrDecoder', () => { test('decodes empty fixed-size array', () => { const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readArray(0, () => decoder.readInt()); expect(result).toEqual([]); @@ -257,9 +257,9 @@ describe('XdrDecoder', () => { test('decodes variable-length array', () => { const values = [1, 2, 3, 4]; encoder.writeUnsignedInt(values.length); - values.forEach(v => encoder.writeInt(v)); + values.forEach((v) => encoder.writeInt(v)); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readVarlenArray(() => decoder.readInt()); expect(result).toEqual(values); @@ -268,7 +268,7 @@ describe('XdrDecoder', () => { test('decodes empty variable-length array', () => { encoder.writeUnsignedInt(0); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readVarlenArray(() => decoder.readInt()); expect(result).toEqual([]); @@ -290,12 +290,12 @@ describe('XdrDecoder', () => { describe('edge cases', () => { test('handles 32-bit integer boundaries', () => { const values = [-2147483648, 2147483647, 0]; - + for (const value of values) { writer.reset(); encoder.writeInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readInt(); expect(result).toBe(value); @@ -304,12 +304,12 @@ describe('XdrDecoder', () => { test('handles 32-bit unsigned integer boundaries', () => { const values = [0, 4294967295]; - + for (const value of values) { writer.reset(); encoder.writeUnsignedInt(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readUnsignedInt(); expect(result).toBe(value); @@ -318,12 +318,12 @@ describe('XdrDecoder', () => { test('handles special float values', () => { const values = [0, -0, Infinity, -Infinity]; - + for (const value of values) { writer.reset(); encoder.writeFloat(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readFloat(); expect(result).toBe(value); @@ -334,7 +334,7 @@ describe('XdrDecoder', () => { writer.reset(); encoder.writeFloat(NaN); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readFloat(); expect(result).toBeNaN(); @@ -342,12 +342,12 @@ describe('XdrDecoder', () => { test('handles special double values', () => { const values = [0, -0, Infinity, -Infinity]; - + for (const value of values) { writer.reset(); encoder.writeDouble(value); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readDouble(); expect(result).toBe(value); @@ -358,10 +358,10 @@ describe('XdrDecoder', () => { writer.reset(); encoder.writeDouble(NaN); const encoded = writer.flush(); - + reader.reset(encoded); const result = decoder.readDouble(); expect(result).toBeNaN(); }); }); -}); \ No newline at end of file +}); diff --git a/src/xdr/__tests__/XdrSchemaDecoder.spec.ts b/src/xdr/__tests__/XdrSchemaDecoder.spec.ts index af6ea2a5..041f2a8c 100644 --- a/src/xdr/__tests__/XdrSchemaDecoder.spec.ts +++ b/src/xdr/__tests__/XdrSchemaDecoder.spec.ts @@ -25,7 +25,7 @@ describe('XdrSchemaDecoder', () => { test('decodes void with void schema', () => { const schema: XdrSchema = {type: 'void'}; const encoded = schemaEncoder.encode(null, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBeUndefined(); }); @@ -34,7 +34,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'int'}; const value = 42; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); @@ -43,18 +43,18 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'unsigned_int'}; const value = 4294967295; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); test('decodes boolean with boolean schema', () => { const schema: XdrSchema = {type: 'boolean'}; - + let encoded = schemaEncoder.encode(true, schema); let result = decoder.decode(encoded, schema); expect(result).toBe(true); - + encoded = schemaEncoder.encode(false, schema); result = decoder.decode(encoded, schema); expect(result).toBe(false); @@ -64,7 +64,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'hyper'}; const value = BigInt('0x123456789abcdef0'); const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); @@ -73,7 +73,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'unsigned_hyper'}; const value = BigInt('0xffffffffffffffff'); const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); @@ -82,7 +82,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'float'}; const value = 3.14; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBeCloseTo(value, 6); }); @@ -91,7 +91,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'double'}; const value = Math.PI; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBeCloseTo(value, 15); }); @@ -99,7 +99,7 @@ describe('XdrSchemaDecoder', () => { test('throws on quadruple with quadruple schema', () => { const schema: XdrSchema = {type: 'quadruple'}; const value = 1.0; - + expect(() => schemaEncoder.encode(value, schema)).toThrow('not implemented'); }); }); @@ -111,7 +111,7 @@ describe('XdrSchemaDecoder', () => { values: {RED: 0, GREEN: 1, BLUE: 2}, }; const encoded = schemaEncoder.encode('GREEN', schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe('GREEN'); }); @@ -121,11 +121,11 @@ describe('XdrSchemaDecoder', () => { type: 'enum', values: {RED: 0, GREEN: 1, BLUE: 2}, }; - + // Manually encode a value that's not in the enum encoder.writeInt(99); const encoded = writer.flush(); - + const result = decoder.decode(encoded, schema); expect(result).toBe(99); }); @@ -136,7 +136,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'opaque', size: 5}; const value = new Uint8Array([1, 2, 3, 4, 5]); const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -145,7 +145,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'vopaque'}; const value = new Uint8Array([1, 2, 3, 4, 5]); const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -154,19 +154,19 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'vopaque', size: 10}; const value = new Uint8Array([1, 2, 3, 4, 5]); const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); test('throws on variable-length opaque data too large', () => { const schema: XdrSchema = {type: 'vopaque', size: 3}; - + // Manually encode data larger than limit const data = new Uint8Array([1, 2, 3, 4, 5]); encoder.writeVarlenOpaque(data); const encoded = writer.flush(); - + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 3'); }); }); @@ -176,7 +176,7 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'string'}; const value = 'Hello, XDR!'; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); @@ -185,19 +185,19 @@ describe('XdrSchemaDecoder', () => { const schema: XdrSchema = {type: 'string', size: 20}; const value = 'Hello'; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toBe(value); }); test('throws on string too long', () => { const schema: XdrSchema = {type: 'string', size: 3}; - + // Manually encode a string longer than limit const str = 'toolong'; encoder.writeStr(str); const encoded = writer.flush(); - + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 3'); }); }); @@ -211,7 +211,7 @@ describe('XdrSchemaDecoder', () => { }; const value = [1, 2, 3]; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -223,7 +223,7 @@ describe('XdrSchemaDecoder', () => { }; const value = [1, 2, 3, 4]; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -235,7 +235,7 @@ describe('XdrSchemaDecoder', () => { }; const value: number[] = []; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -246,13 +246,13 @@ describe('XdrSchemaDecoder', () => { elements: {type: 'int'}, size: 2, }; - + // Manually encode array larger than limit const values = [1, 2, 3]; encoder.writeUnsignedInt(values.length); - values.forEach(v => encoder.writeInt(v)); + values.forEach((v) => encoder.writeInt(v)); const encoded = writer.flush(); - + expect(() => decoder.decode(encoded, schema)).toThrow('exceeds maximum 2'); }); @@ -266,9 +266,12 @@ describe('XdrSchemaDecoder', () => { }, size: 2, }; - const value = [[1, 2], [3, 4]]; + const value = [ + [1, 2], + [3, 4], + ]; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -285,7 +288,7 @@ describe('XdrSchemaDecoder', () => { }; const value = {id: 42, name: 'test'}; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -312,7 +315,7 @@ describe('XdrSchemaDecoder', () => { name: {first: 'John', last: 'Doe'}, }; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -324,7 +327,7 @@ describe('XdrSchemaDecoder', () => { }; const value = {}; const encoded = schemaEncoder.encode(value, schema); - + const result = decoder.decode(encoded, schema); expect(result).toEqual(value); }); @@ -339,14 +342,14 @@ describe('XdrSchemaDecoder', () => { [1, {type: 'string'}], ], }; - + // Test first arm let encoded = schemaEncoder.encode(new XdrUnion(0, 42), schema); let result = decoder.decode(encoded, schema) as XdrUnion; expect(result).toBeInstanceOf(XdrUnion); expect(result.discriminant).toBe(0); expect(result.value).toBe(42); - + // Test second arm encoded = schemaEncoder.encode(new XdrUnion(1, 'hello'), schema); result = decoder.decode(encoded, schema) as XdrUnion; @@ -364,12 +367,12 @@ describe('XdrSchemaDecoder', () => { ], default: {type: 'boolean'}, }; - + // Manually encode unknown discriminant encoder.writeInt(99); // discriminant encoder.writeBoolean(true); // default value const encoded = writer.flush(); - + const result = decoder.decode(encoded, schema) as XdrUnion; expect(result).toBeInstanceOf(XdrUnion); expect(result.discriminant).toBe(99); @@ -384,12 +387,12 @@ describe('XdrSchemaDecoder', () => { [1, {type: 'string'}], ], }; - + // Manually encode unknown discriminant without default encoder.writeInt(99); encoder.writeInt(42); // some value const encoded = writer.flush(); - + expect(() => decoder.decode(encoded, schema)).toThrow('No matching union arm for discriminant: 99'); }); }); @@ -398,7 +401,7 @@ describe('XdrSchemaDecoder', () => { test('throws on unknown schema type', () => { const schema = {type: 'invalid'} as any; const encoded = new Uint8Array([0, 0, 0, 42]); - + expect(() => decoder.decode(encoded, schema)).toThrow('Unknown schema type: invalid'); }); }); @@ -453,7 +456,7 @@ describe('XdrSchemaDecoder', () => { const encoded = schemaEncoder.encode(value, schema); const result = decoder.decode(encoded, schema) as any; - + expect(result.version).toBe(1); expect(result.items).toHaveLength(2); expect(result.items[0].name).toBe('item1'); @@ -468,4 +471,4 @@ describe('XdrSchemaDecoder', () => { expect(result.items[1].data.value).toBe('test'); }); }); -}); \ No newline at end of file +});