@k67/kaitai-struct-ts 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -49
- package/dist/index.d.mts +276 -202
- package/dist/index.d.ts +276 -202
- package/dist/index.js +276 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +276 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
@@ -151,6 +151,7 @@ var KaitaiStream = class _KaitaiStream {
|
|
151
151
|
if (buffer instanceof ArrayBuffer) {
|
152
152
|
this.buffer = new Uint8Array(buffer);
|
153
153
|
this.view = new DataView(buffer);
|
154
|
+
this._size = buffer.byteLength;
|
154
155
|
} else {
|
155
156
|
this.buffer = buffer;
|
156
157
|
this.view = new DataView(
|
@@ -158,6 +159,7 @@ var KaitaiStream = class _KaitaiStream {
|
|
158
159
|
buffer.byteOffset,
|
159
160
|
buffer.byteLength
|
160
161
|
);
|
162
|
+
this._size = buffer.byteLength;
|
161
163
|
}
|
162
164
|
}
|
163
165
|
/**
|
@@ -174,13 +176,13 @@ var KaitaiStream = class _KaitaiStream {
|
|
174
176
|
* Total size of the stream in bytes
|
175
177
|
*/
|
176
178
|
get size() {
|
177
|
-
return this.
|
179
|
+
return this._size;
|
178
180
|
}
|
179
181
|
/**
|
180
182
|
* Check if we've reached the end of the stream
|
181
183
|
*/
|
182
184
|
isEof() {
|
183
|
-
return this._pos >= this.
|
185
|
+
return this._pos >= this._size;
|
184
186
|
}
|
185
187
|
/**
|
186
188
|
* Seek to a specific position in the stream
|
@@ -600,6 +602,7 @@ var KsyParser = class {
|
|
600
602
|
throw new ParseError("KSY file must contain an object");
|
601
603
|
}
|
602
604
|
const schema = parsed;
|
605
|
+
this.processParametricTypes(schema);
|
603
606
|
if (validate) {
|
604
607
|
const result = this.validate(schema, { strict });
|
605
608
|
if (!result.valid) {
|
@@ -617,6 +620,65 @@ var KsyParser = class {
|
|
617
620
|
}
|
618
621
|
return schema;
|
619
622
|
}
|
623
|
+
/**
|
624
|
+
* Process parametric type syntax in schema.
|
625
|
+
* Converts "type_name(arg1, arg2)" to structured format.
|
626
|
+
*
|
627
|
+
* @param schema - Schema to process
|
628
|
+
* @private
|
629
|
+
*/
|
630
|
+
processParametricTypes(schema) {
|
631
|
+
if (schema.seq) {
|
632
|
+
for (const attr of schema.seq) {
|
633
|
+
if (typeof attr.type === "string") {
|
634
|
+
this.parseParametricType(attr);
|
635
|
+
}
|
636
|
+
}
|
637
|
+
}
|
638
|
+
if (schema.instances) {
|
639
|
+
for (const instance of Object.values(schema.instances)) {
|
640
|
+
if (typeof instance.type === "string") {
|
641
|
+
this.parseParametricType(instance);
|
642
|
+
}
|
643
|
+
}
|
644
|
+
}
|
645
|
+
if (schema.types) {
|
646
|
+
for (const type of Object.values(schema.types)) {
|
647
|
+
this.processParametricTypes(type);
|
648
|
+
}
|
649
|
+
}
|
650
|
+
}
|
651
|
+
/**
|
652
|
+
* Parse parametric type syntax from a type string.
|
653
|
+
* Converts "type_name(arg1, arg2)" to type + type-args.
|
654
|
+
*
|
655
|
+
* @param attr - Attribute to process
|
656
|
+
* @private
|
657
|
+
*/
|
658
|
+
parseParametricType(attr) {
|
659
|
+
if (typeof attr.type !== "string") return;
|
660
|
+
const match = attr.type.match(/^([a-z_][a-z0-9_]*)\((.*)\)$/i);
|
661
|
+
if (!match) return;
|
662
|
+
const [, typeName, argsStr] = match;
|
663
|
+
const args = [];
|
664
|
+
if (argsStr.trim()) {
|
665
|
+
const argParts = argsStr.split(",").map((s) => s.trim());
|
666
|
+
for (const arg of argParts) {
|
667
|
+
const num = Number(arg);
|
668
|
+
if (!isNaN(num)) {
|
669
|
+
args.push(num);
|
670
|
+
} else if (arg === "true") {
|
671
|
+
args.push(true);
|
672
|
+
} else if (arg === "false") {
|
673
|
+
args.push(false);
|
674
|
+
} else {
|
675
|
+
args.push(arg);
|
676
|
+
}
|
677
|
+
}
|
678
|
+
}
|
679
|
+
attr.type = typeName;
|
680
|
+
attr["type-args"] = args;
|
681
|
+
}
|
620
682
|
/**
|
621
683
|
* Validate a schema object.
|
622
684
|
*
|
@@ -1815,9 +1877,7 @@ var Evaluator = class {
|
|
1815
1877
|
evaluateEnumAccess(enumName, valueName, context) {
|
1816
1878
|
const value = context.getEnumValue(enumName, valueName);
|
1817
1879
|
if (value === void 0) {
|
1818
|
-
throw new ParseError(
|
1819
|
-
`Enum value "${enumName}::${valueName}" not found`
|
1820
|
-
);
|
1880
|
+
throw new ParseError(`Enum value "${enumName}::${valueName}" not found`);
|
1821
1881
|
}
|
1822
1882
|
return value;
|
1823
1883
|
}
|
@@ -1914,6 +1974,10 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1914
1974
|
constructor(schema, parentMeta) {
|
1915
1975
|
this.schema = schema;
|
1916
1976
|
this.parentMeta = parentMeta;
|
1977
|
+
// Performance optimization: cache for constant expressions
|
1978
|
+
// Limited size to prevent memory leaks
|
1979
|
+
this.expressionCache = /* @__PURE__ */ new Map();
|
1980
|
+
this.MAX_CACHE_SIZE = 1e3;
|
1917
1981
|
if (!schema.meta && !parentMeta) {
|
1918
1982
|
throw new ParseError("Schema must have meta section");
|
1919
1983
|
}
|
@@ -1926,12 +1990,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1926
1990
|
*
|
1927
1991
|
* @param stream - Binary stream to parse
|
1928
1992
|
* @param parent - Parent object (for nested types)
|
1993
|
+
* @param typeArgs - Arguments for parametric types
|
1929
1994
|
* @returns Parsed object
|
1930
1995
|
*/
|
1931
|
-
parse(stream, parent) {
|
1996
|
+
parse(stream, parent, typeArgs) {
|
1932
1997
|
const result = {};
|
1933
1998
|
const context = new Context(stream, result, parent, this.schema.enums);
|
1934
1999
|
context.current = result;
|
2000
|
+
if (typeArgs && this.schema.params) {
|
2001
|
+
for (let i = 0; i < this.schema.params.length && i < typeArgs.length; i++) {
|
2002
|
+
const param = this.schema.params[i];
|
2003
|
+
const argValue = typeArgs[i];
|
2004
|
+
const evaluatedArg = typeof argValue === "string" ? this.evaluateValue(argValue, context) : argValue;
|
2005
|
+
context.set(param.id, evaluatedArg);
|
2006
|
+
}
|
2007
|
+
}
|
1935
2008
|
if (this.schema.seq) {
|
1936
2009
|
for (const attr of this.schema.seq) {
|
1937
2010
|
const value = this.parseAttribute(attr, context);
|
@@ -1940,8 +2013,86 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1940
2013
|
}
|
1941
2014
|
}
|
1942
2015
|
}
|
2016
|
+
if (this.schema.instances) {
|
2017
|
+
this.setupInstances(result, stream, context);
|
2018
|
+
}
|
1943
2019
|
return result;
|
1944
2020
|
}
|
2021
|
+
/**
|
2022
|
+
* Set up lazy-evaluated instance getters.
|
2023
|
+
* Instances are computed on first access and cached.
|
2024
|
+
*
|
2025
|
+
* @param result - Result object to add getters to
|
2026
|
+
* @param stream - Stream for parsing
|
2027
|
+
* @param context - Execution context
|
2028
|
+
* @private
|
2029
|
+
*/
|
2030
|
+
setupInstances(result, stream, context) {
|
2031
|
+
if (!this.schema.instances) return;
|
2032
|
+
for (const [name, instance] of Object.entries(this.schema.instances)) {
|
2033
|
+
let cached = void 0;
|
2034
|
+
let evaluated = false;
|
2035
|
+
Object.defineProperty(result, name, {
|
2036
|
+
get: () => {
|
2037
|
+
if (!evaluated) {
|
2038
|
+
cached = this.parseInstance(
|
2039
|
+
instance,
|
2040
|
+
stream,
|
2041
|
+
context
|
2042
|
+
);
|
2043
|
+
evaluated = true;
|
2044
|
+
}
|
2045
|
+
return cached;
|
2046
|
+
},
|
2047
|
+
enumerable: true,
|
2048
|
+
configurable: true
|
2049
|
+
});
|
2050
|
+
}
|
2051
|
+
}
|
2052
|
+
/**
|
2053
|
+
* Parse an instance (lazy-evaluated field).
|
2054
|
+
*
|
2055
|
+
* @param instance - Instance specification
|
2056
|
+
* @param stream - Stream to read from
|
2057
|
+
* @param context - Execution context
|
2058
|
+
* @returns Parsed or calculated value
|
2059
|
+
* @private
|
2060
|
+
*/
|
2061
|
+
parseInstance(instance, stream, context) {
|
2062
|
+
if ("value" in instance) {
|
2063
|
+
return this.evaluateValue(
|
2064
|
+
instance.value,
|
2065
|
+
context
|
2066
|
+
);
|
2067
|
+
}
|
2068
|
+
const savedPos = stream.pos;
|
2069
|
+
try {
|
2070
|
+
if (instance.pos !== void 0) {
|
2071
|
+
const pos = this.evaluateValue(
|
2072
|
+
instance.pos,
|
2073
|
+
context
|
2074
|
+
);
|
2075
|
+
if (typeof pos === "number") {
|
2076
|
+
stream.seek(pos);
|
2077
|
+
} else if (typeof pos === "bigint") {
|
2078
|
+
stream.seek(Number(pos));
|
2079
|
+
} else {
|
2080
|
+
throw new ParseError(
|
2081
|
+
`pos must evaluate to a number, got ${typeof pos}`
|
2082
|
+
);
|
2083
|
+
}
|
2084
|
+
}
|
2085
|
+
const value = this.parseAttribute(
|
2086
|
+
instance,
|
2087
|
+
context
|
2088
|
+
);
|
2089
|
+
return value;
|
2090
|
+
} finally {
|
2091
|
+
if (instance.pos !== void 0) {
|
2092
|
+
stream.seek(savedPos);
|
2093
|
+
}
|
2094
|
+
}
|
2095
|
+
}
|
1945
2096
|
/**
|
1946
2097
|
* Parse a single attribute according to its specification.
|
1947
2098
|
*
|
@@ -1971,6 +2122,11 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
1971
2122
|
if (attr.io) {
|
1972
2123
|
throw new NotImplementedError("Custom I/O streams");
|
1973
2124
|
}
|
2125
|
+
if (!attr.type && !attr.size && !attr["size-eos"] && !attr.contents) {
|
2126
|
+
throw new ParseError(
|
2127
|
+
`Attribute "${attr.id || "unknown"}" must have type, size, size-eos, or contents`
|
2128
|
+
);
|
2129
|
+
}
|
1974
2130
|
if (attr.repeat) {
|
1975
2131
|
return this.parseRepeated(attr, context);
|
1976
2132
|
}
|
@@ -2020,7 +2176,13 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2020
2176
|
throw new ParseError("repeat-until expression is required");
|
2021
2177
|
}
|
2022
2178
|
let index = 0;
|
2179
|
+
const maxIterations = 1e6;
|
2023
2180
|
while (true) {
|
2181
|
+
if (index >= maxIterations) {
|
2182
|
+
throw new ParseError(
|
2183
|
+
`repeat-until exceeded maximum iterations (${maxIterations}). Possible infinite loop.`
|
2184
|
+
);
|
2185
|
+
}
|
2024
2186
|
context.set("_index", index);
|
2025
2187
|
const value = this.parseAttribute(
|
2026
2188
|
{ ...attr, repeat: void 0, "repeat-until": void 0 },
|
@@ -2095,16 +2257,34 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2095
2257
|
if (size < 0) {
|
2096
2258
|
throw new ParseError(`size must be non-negative, got ${size}`);
|
2097
2259
|
}
|
2260
|
+
if (stream.pos + size > stream.size) {
|
2261
|
+
throw new ParseError(
|
2262
|
+
`Not enough data: need ${size} bytes at position ${stream.pos}, but only ${stream.size - stream.pos} bytes available`
|
2263
|
+
);
|
2264
|
+
}
|
2098
2265
|
if (type === "str" || !type) {
|
2099
2266
|
const encoding = attr.encoding || this.schema.meta.encoding || "UTF-8";
|
2267
|
+
let data;
|
2100
2268
|
if (type === "str") {
|
2101
|
-
|
2269
|
+
data = stream.readBytes(size);
|
2270
|
+
if (attr.process) {
|
2271
|
+
data = this.applyProcessing(data, attr.process);
|
2272
|
+
}
|
2273
|
+
return new TextDecoder(encoding).decode(data);
|
2102
2274
|
} else {
|
2103
|
-
|
2275
|
+
data = stream.readBytes(size);
|
2276
|
+
if (attr.process) {
|
2277
|
+
data = this.applyProcessing(data, attr.process);
|
2278
|
+
}
|
2279
|
+
return data;
|
2104
2280
|
}
|
2105
2281
|
} else {
|
2106
|
-
|
2107
|
-
|
2282
|
+
let data = stream.readBytes(size);
|
2283
|
+
if (attr.process) {
|
2284
|
+
data = this.applyProcessing(data, attr.process);
|
2285
|
+
}
|
2286
|
+
const substream = new KaitaiStream(data);
|
2287
|
+
return this.parseType(type, substream, context, attr["type-args"]);
|
2108
2288
|
}
|
2109
2289
|
}
|
2110
2290
|
if (attr["size-eos"]) {
|
@@ -2119,7 +2299,7 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2119
2299
|
if (!type) {
|
2120
2300
|
throw new ParseError("Attribute must have either type, size, or contents");
|
2121
2301
|
}
|
2122
|
-
return this.parseType(type, stream, context);
|
2302
|
+
return this.parseType(type, stream, context, attr["type-args"]);
|
2123
2303
|
}
|
2124
2304
|
/**
|
2125
2305
|
* Parse a value of a specific type.
|
@@ -2127,12 +2307,17 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2127
2307
|
* @param type - Type name or switch specification
|
2128
2308
|
* @param stream - Stream to read from
|
2129
2309
|
* @param context - Execution context
|
2310
|
+
* @param typeArgs - Arguments for parametric types
|
2130
2311
|
* @returns Parsed value
|
2131
2312
|
* @private
|
2132
2313
|
*/
|
2133
|
-
parseType(type, stream, context) {
|
2314
|
+
parseType(type, stream, context, typeArgs) {
|
2134
2315
|
if (typeof type === "object") {
|
2135
|
-
|
2316
|
+
return this.parseSwitchType(
|
2317
|
+
type,
|
2318
|
+
stream,
|
2319
|
+
context
|
2320
|
+
);
|
2136
2321
|
}
|
2137
2322
|
if (isBuiltinType(type)) {
|
2138
2323
|
return this.parseBuiltinType(type, stream, context);
|
@@ -2143,11 +2328,46 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2143
2328
|
if (this.schema.enums && !typeSchema.enums) {
|
2144
2329
|
typeSchema.enums = this.schema.enums;
|
2145
2330
|
}
|
2331
|
+
if (this.schema.types && !typeSchema.types) {
|
2332
|
+
typeSchema.types = this.schema.types;
|
2333
|
+
}
|
2146
2334
|
const interpreter = new _TypeInterpreter(typeSchema, meta);
|
2147
|
-
return interpreter.parse(stream, context.current);
|
2335
|
+
return interpreter.parse(stream, context.current, typeArgs);
|
2148
2336
|
}
|
2149
2337
|
throw new ParseError(`Unknown type: ${type}`);
|
2150
2338
|
}
|
2339
|
+
/**
|
2340
|
+
* Parse a switch type (type selection based on expression).
|
2341
|
+
*
|
2342
|
+
* @param switchType - Switch type specification
|
2343
|
+
* @param stream - Stream to read from
|
2344
|
+
* @param context - Execution context
|
2345
|
+
* @returns Parsed value
|
2346
|
+
* @private
|
2347
|
+
*/
|
2348
|
+
parseSwitchType(switchType, stream, context) {
|
2349
|
+
const switchOn = switchType["switch-on"];
|
2350
|
+
const cases = switchType["cases"];
|
2351
|
+
const defaultType = switchType["default"];
|
2352
|
+
if (!switchOn || typeof switchOn !== "string") {
|
2353
|
+
throw new ParseError("switch-on expression is required for switch types");
|
2354
|
+
}
|
2355
|
+
if (!cases) {
|
2356
|
+
throw new ParseError("cases are required for switch types");
|
2357
|
+
}
|
2358
|
+
const switchValue = this.evaluateValue(switchOn, context);
|
2359
|
+
const switchKey = String(switchValue);
|
2360
|
+
let selectedType = cases[switchKey];
|
2361
|
+
if (selectedType === void 0 && defaultType) {
|
2362
|
+
selectedType = defaultType;
|
2363
|
+
}
|
2364
|
+
if (selectedType === void 0) {
|
2365
|
+
throw new ParseError(
|
2366
|
+
`No matching case for switch value "${switchKey}" and no default type specified`
|
2367
|
+
);
|
2368
|
+
}
|
2369
|
+
return this.parseType(selectedType, stream, context);
|
2370
|
+
}
|
2151
2371
|
/**
|
2152
2372
|
* Parse a built-in type.
|
2153
2373
|
*
|
@@ -2163,6 +2383,13 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2163
2383
|
const meta = this.schema.meta || this.parentMeta;
|
2164
2384
|
const metaEndian = meta?.endian;
|
2165
2385
|
const endian = typeEndian || (typeof metaEndian === "string" ? metaEndian : "le");
|
2386
|
+
if (type.startsWith("b") && type.length > 1) {
|
2387
|
+
const bits = parseInt(type.substring(1), 10);
|
2388
|
+
if (!isNaN(bits) && bits >= 1 && bits <= 64) {
|
2389
|
+
const value = endian === "be" ? stream.readBitsIntBe(bits) : stream.readBitsIntLe(bits);
|
2390
|
+
return bits <= 32 ? Number(value) : value;
|
2391
|
+
}
|
2392
|
+
}
|
2166
2393
|
if (isIntegerType(type)) {
|
2167
2394
|
return this.readInteger(base, endian, stream);
|
2168
2395
|
}
|
@@ -2225,11 +2452,27 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2225
2452
|
}
|
2226
2453
|
}
|
2227
2454
|
/**
|
2228
|
-
*
|
2229
|
-
*
|
2230
|
-
*
|
2455
|
+
* Apply processing transformation to data.
|
2456
|
+
* Supports basic transformations like zlib decompression.
|
2457
|
+
*
|
2458
|
+
* @param data - Data to process
|
2459
|
+
* @param process - Processing specification
|
2460
|
+
* @returns Processed data
|
2461
|
+
* @private
|
2462
|
+
*/
|
2463
|
+
applyProcessing(data, process) {
|
2464
|
+
const processType = typeof process === "string" ? process : process.algorithm;
|
2465
|
+
if (processType) {
|
2466
|
+
throw new NotImplementedError(
|
2467
|
+
`Processing type "${processType}" is not yet implemented. Supported in future versions with zlib, encryption, etc.`
|
2468
|
+
);
|
2469
|
+
}
|
2470
|
+
return data;
|
2471
|
+
}
|
2472
|
+
/**
|
2473
|
+
* Evaluate a value that can be an expression or literal.
|
2231
2474
|
*
|
2232
|
-
* @param value -
|
2475
|
+
* @param value - Value to evaluate (expression string, number, or boolean)
|
2233
2476
|
* @param context - Execution context
|
2234
2477
|
* @returns Evaluated result
|
2235
2478
|
* @private
|
@@ -2242,8 +2485,21 @@ var TypeInterpreter = class _TypeInterpreter {
|
|
2242
2485
|
return value;
|
2243
2486
|
}
|
2244
2487
|
if (typeof value === "string") {
|
2488
|
+
if (!value.includes("_") && !value.includes(".")) {
|
2489
|
+
const cached = this.expressionCache.get(value);
|
2490
|
+
if (cached !== void 0) {
|
2491
|
+
return cached;
|
2492
|
+
}
|
2493
|
+
}
|
2245
2494
|
try {
|
2246
|
-
|
2495
|
+
const result = evaluateExpression(value, context);
|
2496
|
+
if (!value.includes("_") && !value.includes(".")) {
|
2497
|
+
if (this.expressionCache.size >= this.MAX_CACHE_SIZE) {
|
2498
|
+
this.expressionCache.clear();
|
2499
|
+
}
|
2500
|
+
this.expressionCache.set(value, result);
|
2501
|
+
}
|
2502
|
+
return result;
|
2247
2503
|
} catch (error) {
|
2248
2504
|
throw new ParseError(
|
2249
2505
|
`Failed to evaluate expression "${value}": ${error instanceof Error ? error.message : String(error)}`
|
@@ -2383,7 +2639,7 @@ export {
|
|
2383
2639
|
* @module kaitai-struct-ts
|
2384
2640
|
* @author Fabiano Pinto
|
2385
2641
|
* @license MIT
|
2386
|
-
* @version 0.
|
2642
|
+
* @version 0.6.0
|
2387
2643
|
*
|
2388
2644
|
* @description
|
2389
2645
|
* A runtime interpreter for Kaitai Struct binary format definitions in TypeScript.
|