Skip to content

Commit fd40a92

Browse files
committed
feat: 🎸 review XDR compliance with specifications
1 parent 15a9243 commit fd40a92

File tree

10 files changed

+4441
-13
lines changed

10 files changed

+4441
-13
lines changed

src/xdr/README.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# XDR Quick Reference Guide
2+
3+
## RFC Version Support
4+
5+
| Feature | RFC 1014 | RFC 1832 | RFC 4506 | Status |
6+
| ----------------------------- | -------- | -------- | -------- | -------------- |
7+
| Basic types (int, bool, enum) |||| ✅ Implemented |
8+
| Hyper integers (64-bit) |||| ✅ Implemented |
9+
| Float, Double |||| ✅ Implemented |
10+
| Quadruple (128-bit float) |||| ⚠️ Type only |
11+
| Opaque, String |||| ✅ Implemented |
12+
| Array, Struct, Union |||| ✅ Implemented |
13+
| Optional-data |||| ✅ Implemented |
14+
| Security guidelines |||| ✅ Implemented |
15+
16+
## Data Type Quick Reference
17+
18+
### Primitive Types
19+
20+
```typescript
21+
// Integer types
22+
{ type: 'int' } // 32-bit signed: -2^31 to 2^31-1
23+
{ type: 'unsigned_int' } // 32-bit unsigned: 0 to 2^32-1
24+
{ type: 'hyper' } // 64-bit signed
25+
{ type: 'unsigned_hyper' } // 64-bit unsigned
26+
{ type: 'boolean' } // Encoded as int (0/1)
27+
28+
// Floating-point types
29+
{ type: 'float' } // IEEE 754 single-precision (32-bit)
30+
{ type: 'double' } // IEEE 754 double-precision (64-bit)
31+
{ type: 'quadruple' } // IEEE 754 quad-precision (128-bit) - not implemented
32+
33+
// Special types
34+
{ type: 'void' } // No data
35+
{ type: 'enum', values: { RED: 0, GREEN: 1 } }
36+
```
37+
38+
### Composite Types
39+
40+
```typescript
41+
// Fixed-length opaque data
42+
{ type: 'opaque', size: 16 }
43+
44+
// Variable-length opaque data (max size optional)
45+
{ type: 'vopaque' }
46+
{ type: 'vopaque', size: 1024 }
47+
48+
// String (max size optional)
49+
{ type: 'string' }
50+
{ type: 'string', size: 255 }
51+
52+
// Fixed-length array
53+
{ type: 'array', elements: { type: 'int' }, size: 10 }
54+
55+
// Variable-length array (max size optional)
56+
{ type: 'varray', elements: { type: 'int' } }
57+
{ type: 'varray', elements: { type: 'int' }, size: 100 }
58+
59+
// Struct
60+
{
61+
type: 'struct',
62+
fields: [
63+
[{ type: 'int' }, 'id'],
64+
[{ type: 'string' }, 'name']
65+
]
66+
}
67+
68+
// Union
69+
{
70+
type: 'union',
71+
arms: [
72+
[0, { type: 'int' }],
73+
[1, { type: 'string' }]
74+
],
75+
default?: { type: 'void' }
76+
}
77+
78+
// Optional-data (NEW in RFC 1832)
79+
{ type: 'optional', element: { type: 'int' } }
80+
```
81+
82+
## Usage Examples
83+
84+
### Basic Encoding/Decoding
85+
86+
```typescript
87+
import {XdrEncoder, XdrDecoder, Writer, Reader} from '@jsonjoy.com/json-pack';
88+
89+
// Encode
90+
const writer = new Writer();
91+
const encoder = new XdrEncoder(writer);
92+
encoder.writeInt(42);
93+
encoder.writeString('hello');
94+
const encoded = writer.flush();
95+
96+
// Decode
97+
const reader = new Reader();
98+
const decoder = new XdrDecoder(reader);
99+
reader.reset(encoded);
100+
const num = decoder.readInt(); // 42
101+
const str = decoder.readString(); // "hello"
102+
```
103+
104+
### Schema-Based Encoding/Decoding
105+
106+
```typescript
107+
import {XdrSchemaEncoder, XdrSchemaDecoder, Writer, Reader} from '@jsonjoy.com/json-pack';
108+
109+
const schema = {
110+
type: 'struct',
111+
fields: [
112+
[{type: 'int'}, 'id'],
113+
[{type: 'string', size: 100}, 'name'],
114+
[{type: 'boolean'}, 'active'],
115+
],
116+
};
117+
118+
// Encode
119+
const writer = new Writer();
120+
const encoder = new XdrSchemaEncoder(writer);
121+
const data = {id: 1, name: 'Alice', active: true};
122+
const encoded = encoder.encode(data, schema);
123+
124+
// Decode
125+
const reader = new Reader();
126+
const decoder = new XdrSchemaDecoder(reader);
127+
const decoded = decoder.decode(encoded, schema);
128+
// { id: 1, name: 'Alice', active: true }
129+
```
130+
131+
### Optional-Data (RFC 1832)
132+
133+
```typescript
134+
const schema = {
135+
type: 'optional',
136+
element: {type: 'int'},
137+
};
138+
139+
// Encode optional value
140+
encoder.writeOptional(42, schema); // Encodes: TRUE + 42
141+
encoder.writeOptional(null, schema); // Encodes: FALSE
142+
encoder.writeOptional(undefined, schema); // Encodes: FALSE
143+
144+
// Decode optional value
145+
const value = decoder.readOptional(schema); // number | null
146+
```
147+
148+
### Union Types
149+
150+
```typescript
151+
import {XdrUnion} from '@jsonjoy.com/json-pack';
152+
153+
const schema = {
154+
type: 'union',
155+
arms: [
156+
[0, {type: 'int'}],
157+
[1, {type: 'string'}],
158+
],
159+
};
160+
161+
// Encode union
162+
const intValue = new XdrUnion(0, 42);
163+
const strValue = new XdrUnion(1, 'hello');
164+
encoder.encode(intValue, schema);
165+
166+
// Decode union
167+
const decoded = decoder.decode(data, schema); // XdrUnion instance
168+
console.log(decoded.discriminant); // 0 or 1
169+
console.log(decoded.value); // 42 or "hello"
170+
```
171+
172+
### Schema Validation
173+
174+
```typescript
175+
import {XdrSchemaValidator} from '@jsonjoy.com/json-pack';
176+
177+
const validator = new XdrSchemaValidator();
178+
179+
// Validate schema structure
180+
const isValidSchema = validator.validateSchema(schema); // boolean
181+
182+
// Validate value against schema
183+
const isValidValue = validator.validateValue(data, schema); // boolean
184+
```
185+
186+
## Security Best Practices
187+
188+
### Always Use Size Limits
189+
190+
```typescript
191+
// ❌ Bad - no maximum size
192+
{ type: 'string' }
193+
{ type: 'varray', elements: { type: 'int' } }
194+
195+
// ✅ Good - explicit maximum size
196+
{ type: 'string', size: 1024 }
197+
{ type: 'varray', elements: { type: 'int' }, size: 100 }
198+
```
199+
200+
### Validate Before Encoding
201+
202+
```typescript
203+
const validator = new XdrSchemaValidator();
204+
if (!validator.validateValue(data, schema)) {
205+
throw new Error('Invalid data for schema');
206+
}
207+
encoder.encode(data, schema);
208+
```
209+
210+
### Implement Depth Limits
211+
212+
```typescript
213+
class SafeDecoder extends XdrSchemaDecoder {
214+
private depth = 0;
215+
private maxDepth = 100;
216+
217+
decode(data: Uint8Array, schema: XdrSchema): unknown {
218+
if (++this.depth > this.maxDepth) {
219+
throw new Error('Max depth exceeded');
220+
}
221+
try {
222+
return super.decode(data, schema);
223+
} finally {
224+
this.depth--;
225+
}
226+
}
227+
}
228+
```
229+
230+
## Common Patterns
231+
232+
### Enum Pattern
233+
234+
```typescript
235+
const ColorEnum = {
236+
type: 'enum',
237+
values: {
238+
RED: 0,
239+
GREEN: 1,
240+
BLUE: 2,
241+
},
242+
} as const;
243+
244+
encoder.writeEnum('RED', ColorEnum);
245+
const color = decoder.readEnum(ColorEnum); // 'RED' | 0
246+
```
247+
248+
### Struct Pattern
249+
250+
```typescript
251+
interface User {
252+
id: number;
253+
name: string;
254+
email: string;
255+
active: boolean;
256+
}
257+
258+
const UserSchema = {
259+
type: 'struct',
260+
fields: [
261+
[{type: 'int'}, 'id'],
262+
[{type: 'string', size: 100}, 'name'],
263+
[{type: 'string', size: 255}, 'email'],
264+
[{type: 'boolean'}, 'active'],
265+
],
266+
} as const;
267+
```
268+
269+
### Variable-Length Array Pattern
270+
271+
```typescript
272+
const ListSchema = {
273+
type: 'varray',
274+
elements: {type: 'int'},
275+
size: 1000, // max 1000 elements
276+
};
277+
278+
encoder.encode([1, 2, 3, 4, 5], ListSchema);
279+
```
280+
281+
## Performance Tips
282+
283+
1. **Reuse encoder/decoder instances** - avoid creating new ones per operation
284+
2. **Use fixed-size types** when possible - faster than variable-length
285+
3. **Preallocate buffers** for known sizes
286+
4. **Batch operations** - encode multiple values before flushing
287+
5. **Use schema validation** only in development/testing
288+
289+
## Interoperability
290+
291+
This implementation is wire-compatible with:
292+
293+
- Sun RPC (ONC RPC)
294+
- NFS (Network File System)
295+
- Other RFC 4506-compliant libraries in any language
296+
297+
## Further Reading
298+
299+
- [SECURITY.md](./SECURITY.md) - Security considerations and best practices
300+
- [RFC_COMPLIANCE.md](./RFC_COMPLIANCE.md) - Detailed RFC compliance information
301+
- [CHANGELOG.md](./CHANGELOG.md) - Recent changes and additions
302+
- [RFC 4506](https://datatracker.ietf.org/doc/html/rfc4506) - Current XDR standard

src/xdr/XdrSchemaDecoder.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import {XdrUnion} from './XdrUnion';
44
import type {IReader, IReaderResettable} from '@jsonjoy.com/buffers/lib';
55
import type {
66
XdrSchema,
7-
XdrPrimitiveSchema,
8-
XdrWidePrimitiveSchema,
9-
XdrCompositeSchema,
107
XdrEnumSchema,
118
XdrOpaqueSchema,
129
XdrVarlenOpaqueSchema,
@@ -15,6 +12,7 @@ import type {
1512
XdrVarlenArraySchema,
1613
XdrStructSchema,
1714
XdrUnionSchema,
15+
XdrOptionalSchema,
1816
} from './types';
1917

2018
/**
@@ -81,6 +79,11 @@ export class XdrSchemaDecoder {
8179
return this.readStruct(schema as XdrStructSchema);
8280
case 'union':
8381
return this.readUnion(schema as XdrUnionSchema);
82+
case 'optional':
83+
return this.readOptional(schema as XdrOptionalSchema);
84+
case 'const':
85+
// Constants are not decoded; they have no runtime representation
86+
return undefined;
8487

8588
default:
8689
throw new Error(`Unknown schema type: ${(schema as any).type}`);
@@ -196,4 +199,15 @@ export class XdrSchemaDecoder {
196199

197200
throw new Error(`No matching union arm for discriminant: ${discriminant}`);
198201
}
202+
203+
/**
204+
* Reads optional-data according to the optional schema (RFC 1832 Section 3.19).
205+
* Optional-data is syntactic sugar for a union with boolean discriminant.
206+
* Returns null if opted is FALSE, otherwise returns the decoded value.
207+
*/
208+
private readOptional(schema: XdrOptionalSchema): unknown | null {
209+
const opted = this.decoder.readBoolean();
210+
if (!opted) return null;
211+
return this.readValue(schema.element);
212+
}
199213
}

0 commit comments

Comments
 (0)