Skip to content

Commit 3b60a67

Browse files
Copilotstreamich
andcommitted
feat: implement bin templates for Uint8Array generation
Co-authored-by: streamich <9773803+streamich@users.noreply.github.com>
1 parent d400d71 commit 3b60a67

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

src/structured/TemplateJson.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {clone} from '../util';
44
import * as templates from './templates';
55
import type {
66
ArrayTemplate,
7+
BinTemplate,
78
BooleanTemplate,
89
FloatTemplate,
910
IntegerTemplate,
@@ -70,6 +71,8 @@ export class TemplateJson {
7071
return this.generateFloat(template as FloatTemplate);
7172
case 'bool':
7273
return this.generateBoolean(template as BooleanTemplate);
74+
case 'bin':
75+
return this.generateBin(template as BinTemplate);
7376
case 'nil':
7477
return null;
7578
case 'lit':
@@ -153,6 +156,16 @@ export class TemplateJson {
153156
return value !== undefined ? value : Math.random() < 0.5;
154157
}
155158

159+
protected generateBin(template: BinTemplate): Uint8Array {
160+
const [, min = 0, max = 5, omin = 0, omax = 255] = template;
161+
const length = this.minmax(min, max);
162+
const result = new Uint8Array(length);
163+
for (let i = 0; i < length; i++) {
164+
result[i] = int(omin, omax);
165+
}
166+
return result;
167+
}
168+
156169
protected generateLiteral(template: LiteralTemplate): unknown {
157170
return clone(template[1]);
158171
}

src/structured/__tests__/TemplateJson.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,66 @@ describe('TemplateJson', () => {
112112
});
113113
});
114114

115+
describe('bin', () => {
116+
test('uses default binary schema, if not provided', () => {
117+
resetMathRandom();
118+
const bin = TemplateJson.gen('bin');
119+
expect(bin instanceof Uint8Array).toBe(true);
120+
expect((bin as Uint8Array).length).toBeGreaterThanOrEqual(0);
121+
expect((bin as Uint8Array).length).toBeLessThanOrEqual(5);
122+
});
123+
124+
test('can specify length range', () => {
125+
resetMathRandom();
126+
const bin = TemplateJson.gen(['bin', 2, 4]) as Uint8Array;
127+
expect(bin instanceof Uint8Array).toBe(true);
128+
expect(bin.length).toBeGreaterThanOrEqual(2);
129+
expect(bin.length).toBeLessThanOrEqual(4);
130+
});
131+
132+
test('can specify octet value range', () => {
133+
resetMathRandom();
134+
const bin = TemplateJson.gen(['bin', 5, 5, 100, 150]) as Uint8Array;
135+
expect(bin instanceof Uint8Array).toBe(true);
136+
expect(bin.length).toBe(5);
137+
for (let i = 0; i < bin.length; i++) {
138+
expect(bin[i]).toBeGreaterThanOrEqual(100);
139+
expect(bin[i]).toBeLessThanOrEqual(150);
140+
}
141+
});
142+
143+
test('handles edge cases', () => {
144+
// Empty array
145+
const empty = TemplateJson.gen(['bin', 0, 0]) as Uint8Array;
146+
expect(empty instanceof Uint8Array).toBe(true);
147+
expect(empty.length).toBe(0);
148+
149+
// Single byte with fixed value range
150+
resetMathRandom();
151+
const single = TemplateJson.gen(['bin', 1, 1, 42, 42]) as Uint8Array;
152+
expect(single instanceof Uint8Array).toBe(true);
153+
expect(single.length).toBe(1);
154+
expect(single[0]).toBe(42);
155+
});
156+
157+
test('uses default octet range when not specified', () => {
158+
resetMathRandom();
159+
const bin = TemplateJson.gen(['bin', 3, 3]) as Uint8Array;
160+
expect(bin instanceof Uint8Array).toBe(true);
161+
expect(bin.length).toBe(3);
162+
for (let i = 0; i < bin.length; i++) {
163+
expect(bin[i]).toBeGreaterThanOrEqual(0);
164+
expect(bin[i]).toBeLessThanOrEqual(255);
165+
}
166+
});
167+
168+
test('respects maxNodes limit', () => {
169+
const bin = TemplateJson.gen(['bin', 10, 20], {maxNodes: 5}) as Uint8Array;
170+
expect(bin instanceof Uint8Array).toBe(true);
171+
expect(bin.length).toBeLessThanOrEqual(10);
172+
});
173+
});
174+
115175
describe('nil', () => {
116176
test('always returns null', () => {
117177
expect(TemplateJson.gen('nil')).toBe(null);
@@ -375,6 +435,16 @@ describe('TemplateJson', () => {
375435
const result = TemplateJson.gen(['or', ['lit', 'only']]);
376436
expect(result).toBe('only');
377437
});
438+
439+
test('works with bin templates', () => {
440+
resetMathRandom();
441+
const result = TemplateJson.gen(['or', 'str', 'int', ['bin', 2, 2]]);
442+
// Result should be one of the template types
443+
const isString = typeof result === 'string';
444+
const isNumber = typeof result === 'number';
445+
const isBin = result instanceof Uint8Array;
446+
expect(isString || isNumber || isBin).toBe(true);
447+
});
378448
});
379449

380450
describe('maxNodeCount', () => {
@@ -449,6 +519,39 @@ describe('TemplateJson', () => {
449519
expect(typeof result).toBe('number');
450520
expect(Number.isInteger(result)).toBe(true);
451521
});
522+
523+
test('handles bin templates in complex structures', () => {
524+
resetMathRandom();
525+
const template: any = [
526+
'obj',
527+
[
528+
['name', 'str'],
529+
['data', ['bin', 3, 3]],
530+
['metadata', [
531+
'obj',
532+
[
533+
['hash', ['bin', 32, 32]],
534+
['signature', ['bin', 64, 64, 0, 127]],
535+
],
536+
]],
537+
],
538+
];
539+
const result = TemplateJson.gen(template) as any;
540+
expect(typeof result).toBe('object');
541+
expect(typeof result.name).toBe('string');
542+
expect(result.data instanceof Uint8Array).toBe(true);
543+
expect(result.data.length).toBe(3);
544+
expect(typeof result.metadata).toBe('object');
545+
expect(result.metadata.hash instanceof Uint8Array).toBe(true);
546+
expect(result.metadata.hash.length).toBe(32);
547+
expect(result.metadata.signature instanceof Uint8Array).toBe(true);
548+
expect(result.metadata.signature.length).toBe(64);
549+
// Check signature values are in the specified range
550+
for (let i = 0; i < result.metadata.signature.length; i++) {
551+
expect(result.metadata.signature[i]).toBeGreaterThanOrEqual(0);
552+
expect(result.metadata.signature[i]).toBeLessThanOrEqual(127);
553+
}
554+
});
452555
});
453556
});
454557

src/structured/types.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ export type TemplateNode =
1212
| FloatTemplate
1313
| StringTemplate
1414
| BooleanTemplate
15+
| BinTemplate
1516
| NullTemplate
1617
| ArrayTemplate
1718
| ObjectTemplate
1819
| MapTemplate
1920
| OrTemplate;
2021

21-
export type TemplateShorthand = 'num' | 'int' | 'float' | 'str' | 'bool' | 'nil' | 'arr' | 'obj' | 'map';
22+
export type TemplateShorthand = 'num' | 'int' | 'float' | 'str' | 'bool' | 'bin' | 'nil' | 'arr' | 'obj' | 'map';
2223

2324
/**
2425
* Recursive reference allows for recursive template construction, for example:
@@ -80,6 +81,30 @@ export type StringTemplate = [type: 'str', token?: Token];
8081
*/
8182
export type BooleanTemplate = [type: 'bool', value?: boolean];
8283

84+
/**
85+
* Binary template. Generates a random Uint8Array. The template allows
86+
* specifying the length of binary data and the range of values in each octet.
87+
*/
88+
export type BinTemplate = [
89+
type: 'bin',
90+
/**
91+
* The minimum length of binary data. Defaults to 0.
92+
*/
93+
min?: number,
94+
/**
95+
* The maximum length of binary data. Defaults to 5.
96+
*/
97+
max?: number,
98+
/**
99+
* The minimum octet value. Defaults to 0.
100+
*/
101+
omin?: number,
102+
/**
103+
* The maximum octet value. Defaults to 255.
104+
*/
105+
omax?: number,
106+
];
107+
83108
/**
84109
* Null template. Generates a `null` value. If a specific value is provided, it
85110
* will always return that value; otherwise, it returns `null`.

0 commit comments

Comments
 (0)