Skip to content

Commit 2bc56b1

Browse files
Copilotstreamich
andcommitted
feat: implement XDR decoder classes with comprehensive tests
Co-authored-by: streamich <[email protected]>
1 parent f4fc10e commit 2bc56b1

File tree

5 files changed

+1238
-0
lines changed

5 files changed

+1238
-0
lines changed

src/xdr/XdrDecoder.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import {Reader} from '@jsonjoy.com/buffers/lib/Reader';
2+
import type {IReader, IReaderResettable} from '@jsonjoy.com/buffers/lib';
3+
import type {BinaryJsonDecoder} from '../types';
4+
5+
/**
6+
* XDR (External Data Representation) binary decoder for basic value decoding.
7+
* Implements XDR binary decoding according to RFC 4506.
8+
*
9+
* Key XDR decoding principles:
10+
* - All data types are aligned to 4-byte boundaries
11+
* - Multi-byte quantities are transmitted in big-endian byte order
12+
* - Strings and opaque data are padded to 4-byte boundaries
13+
* - Variable-length arrays and strings are preceded by their length
14+
*/
15+
export class XdrDecoder<R extends IReader & IReaderResettable = IReader & IReaderResettable>
16+
implements BinaryJsonDecoder
17+
{
18+
public constructor(public reader: R = new Reader() as any) {}
19+
20+
public read(uint8: Uint8Array): unknown {
21+
this.reader.reset(uint8);
22+
return this.readAny();
23+
}
24+
25+
public decode(uint8: Uint8Array): unknown {
26+
this.reader.reset(uint8);
27+
return this.readAny();
28+
}
29+
30+
public readAny(): unknown {
31+
// Basic implementation - in practice this would need schema info
32+
// For now, we'll throw as this should be used with schema decoder
33+
throw new Error('XdrDecoder.readAny() requires explicit type methods or use XdrSchemaDecoder');
34+
}
35+
36+
/**
37+
* Reads an XDR void value (no data is actually read).
38+
*/
39+
public readVoid(): void {
40+
// Void values have no representation in XDR
41+
}
42+
43+
/**
44+
* Reads an XDR boolean value as a 4-byte integer.
45+
* Returns true for non-zero values, false for zero.
46+
*/
47+
public readBoolean(): boolean {
48+
return this.readInt() !== 0;
49+
}
50+
51+
/**
52+
* Reads an XDR signed 32-bit integer in big-endian format.
53+
*/
54+
public readInt(): number {
55+
const reader = this.reader;
56+
const value = reader.view.getInt32(reader.x, false); // false = big-endian
57+
reader.x += 4;
58+
return value;
59+
}
60+
61+
/**
62+
* Reads an XDR unsigned 32-bit integer in big-endian format.
63+
*/
64+
public readUnsignedInt(): number {
65+
const reader = this.reader;
66+
const value = reader.view.getUint32(reader.x, false); // false = big-endian
67+
reader.x += 4;
68+
return value;
69+
}
70+
71+
/**
72+
* Reads an XDR signed 64-bit integer (hyper) in big-endian format.
73+
*/
74+
public readHyper(): bigint {
75+
const reader = this.reader;
76+
const value = reader.view.getBigInt64(reader.x, false); // false = big-endian
77+
reader.x += 8;
78+
return value;
79+
}
80+
81+
/**
82+
* Reads an XDR unsigned 64-bit integer (unsigned hyper) in big-endian format.
83+
*/
84+
public readUnsignedHyper(): bigint {
85+
const reader = this.reader;
86+
const value = reader.view.getBigUint64(reader.x, false); // false = big-endian
87+
reader.x += 8;
88+
return value;
89+
}
90+
91+
/**
92+
* Reads an XDR float value using IEEE 754 single-precision in big-endian format.
93+
*/
94+
public readFloat(): number {
95+
const reader = this.reader;
96+
const value = reader.view.getFloat32(reader.x, false); // false = big-endian
97+
reader.x += 4;
98+
return value;
99+
}
100+
101+
/**
102+
* Reads an XDR double value using IEEE 754 double-precision in big-endian format.
103+
*/
104+
public readDouble(): number {
105+
const reader = this.reader;
106+
const value = reader.view.getFloat64(reader.x, false); // false = big-endian
107+
reader.x += 8;
108+
return value;
109+
}
110+
111+
/**
112+
* Reads an XDR quadruple value (128-bit float).
113+
* Note: JavaScript doesn't have native 128-bit float support.
114+
*/
115+
public readQuadruple(): number {
116+
throw new Error('not implemented');
117+
}
118+
119+
/**
120+
* Reads XDR opaque data with known fixed length.
121+
* Data is padded to 4-byte boundary but only the actual data is returned.
122+
*/
123+
public readOpaque(size: number): Uint8Array {
124+
const reader = this.reader;
125+
const data = new Uint8Array(size);
126+
127+
// Read actual data
128+
for (let i = 0; i < size; i++) {
129+
data[i] = reader.u8();
130+
}
131+
132+
// Skip padding bytes to reach 4-byte boundary
133+
const paddedSize = Math.ceil(size / 4) * 4;
134+
const padding = paddedSize - size;
135+
reader.skip(padding);
136+
137+
return data;
138+
}
139+
140+
/**
141+
* Reads XDR variable-length opaque data.
142+
* Length is read first, followed by data padded to 4-byte boundary.
143+
*/
144+
public readVarlenOpaque(): Uint8Array {
145+
const size = this.readUnsignedInt();
146+
return this.readOpaque(size);
147+
}
148+
149+
/**
150+
* Reads an XDR string with UTF-8 encoding.
151+
* Length is read first, followed by UTF-8 bytes padded to 4-byte boundary.
152+
*/
153+
public readString(): string {
154+
const size = this.readUnsignedInt();
155+
const reader = this.reader;
156+
157+
// Read UTF-8 bytes
158+
const utf8Bytes = new Uint8Array(size);
159+
for (let i = 0; i < size; i++) {
160+
utf8Bytes[i] = reader.u8();
161+
}
162+
163+
// Skip padding bytes to reach 4-byte boundary
164+
const paddedSize = Math.ceil(size / 4) * 4;
165+
const padding = paddedSize - size;
166+
reader.skip(padding);
167+
168+
// Decode UTF-8 to string
169+
return new TextDecoder('utf-8').decode(utf8Bytes);
170+
}
171+
172+
/**
173+
* Reads an XDR enum value as an unsigned integer.
174+
*/
175+
public readEnum(): number {
176+
return this.readInt();
177+
}
178+
179+
/**
180+
* Reads a fixed-size array of elements.
181+
* Caller must provide the decode function for each element.
182+
*/
183+
public readArray<T>(size: number, elementReader: () => T): T[] {
184+
const array: T[] = [];
185+
for (let i = 0; i < size; i++) {
186+
array.push(elementReader());
187+
}
188+
return array;
189+
}
190+
191+
/**
192+
* Reads a variable-length array of elements.
193+
* Length is read first, followed by elements.
194+
*/
195+
public readVarlenArray<T>(elementReader: () => T): T[] {
196+
const size = this.readUnsignedInt();
197+
return this.readArray(size, elementReader);
198+
}
199+
}

0 commit comments

Comments
 (0)