ya-struct 0.0.8 → 0.0.10
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/dist/lib/layout.js +8 -0
- package/dist/lib/parser.js +1 -0
- package/dist/lib/types/string.js +1 -1
- package/dist/lib/types/struct.d.ts +2 -1
- package/dist/lib/types/struct.js +37 -4
- package/lib/layout.ts +8 -0
- package/lib/parser.ts +1 -0
- package/lib/types/string.ts +1 -1
- package/lib/types/struct.ts +37 -4
- package/package.json +1 -1
- package/test/nested-structs.ts +78 -0
- package/tsconfig.json +2 -1
package/dist/lib/layout.js
CHANGED
|
@@ -111,6 +111,14 @@ const layoutStruct = ({
|
|
|
111
111
|
|
|
112
112
|
break;
|
|
113
113
|
}
|
|
114
|
+
case "struct": {
|
|
115
|
+
|
|
116
|
+
if (currentOffsetInBits % 64 !== 0) {
|
|
117
|
+
throw Error("nested struct alignment handling not implemented yet");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
114
122
|
case "string": {
|
|
115
123
|
// no special alignment needed
|
|
116
124
|
break;
|
package/dist/lib/parser.js
CHANGED
|
@@ -51,6 +51,7 @@ const define = /*<const T extends TFieldType>*/({ definition }/*: { definition:
|
|
|
51
51
|
if (l.type === "struct") {
|
|
52
52
|
valueParser = createStructParser({
|
|
53
53
|
layoutedFields: l.fields,
|
|
54
|
+
structOffsetInBits: l.offsetInBits,
|
|
54
55
|
endianness: abi.endianness
|
|
55
56
|
}) /*as unknown*/ /*as TValueParser<TParsedValueOfDefinition<T>>*/;
|
|
56
57
|
} else {
|
package/dist/lib/types/string.js
CHANGED
|
@@ -37,7 +37,7 @@ const createStringParser = ({ length }/*: { length: number }*/)/*: TStringParser
|
|
|
37
37
|
|
|
38
38
|
const encoded = encoder.encode(value);
|
|
39
39
|
|
|
40
|
-
if (encoded.length + 1
|
|
40
|
+
if (encoded.length + 1 > length) {
|
|
41
41
|
throw Error("string too long to fit in target");
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -2,10 +2,11 @@ import type { TEndianness } from "../common.js";
|
|
|
2
2
|
import type { TLayoutedField } from "../layout.js";
|
|
3
3
|
import type { TValueParser } from "./value.js";
|
|
4
4
|
type TStructParser = TValueParser<Record<string, unknown>>;
|
|
5
|
-
declare const createStructParser: ({ layoutedFields, endianness }: {
|
|
5
|
+
declare const createStructParser: ({ layoutedFields, structOffsetInBits, endianness }: {
|
|
6
6
|
layoutedFields: (TLayoutedField & {
|
|
7
7
|
type: "struct";
|
|
8
8
|
})["fields"];
|
|
9
|
+
structOffsetInBits: number;
|
|
9
10
|
endianness: TEndianness;
|
|
10
11
|
}) => TStructParser;
|
|
11
12
|
export { createStructParser };
|
package/dist/lib/types/struct.js
CHANGED
|
@@ -9,11 +9,20 @@ import { createArrayParser } from "./array.js";
|
|
|
9
9
|
|
|
10
10
|
/*type TStructParser = TValueParser<Record<string, unknown>>;*/
|
|
11
11
|
|
|
12
|
+
const subData = ({ data, offsetInBits, sizeInBits }/*: { data: Uint8Array, offsetInBits: number, sizeInBits: number }*/) => {
|
|
13
|
+
return {
|
|
14
|
+
data: new Uint8Array(data.buffer, data.byteOffset + Math.floor(offsetInBits / 8), Math.ceil(sizeInBits / 8)),
|
|
15
|
+
offsetInBits: offsetInBits % 8
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
12
19
|
const createStructParser = ({
|
|
13
20
|
layoutedFields,
|
|
21
|
+
structOffsetInBits,
|
|
14
22
|
endianness
|
|
15
23
|
}/*: {
|
|
16
24
|
layoutedFields: (TLayoutedField & { type: "struct" })["fields"];
|
|
25
|
+
structOffsetInBits: number;
|
|
17
26
|
endianness: TEndianness
|
|
18
27
|
}*/)/*: TStructParser*/ => {
|
|
19
28
|
|
|
@@ -37,6 +46,7 @@ const createStructParser = ({
|
|
|
37
46
|
if (field.definition.type === "struct") {
|
|
38
47
|
return createStructParser({
|
|
39
48
|
layoutedFields: field.definition.fields,
|
|
49
|
+
structOffsetInBits: field.definition.offsetInBits,
|
|
40
50
|
endianness
|
|
41
51
|
});
|
|
42
52
|
}
|
|
@@ -70,19 +80,35 @@ const createStructParser = ({
|
|
|
70
80
|
layoutedFields.forEach((field, idx) => {
|
|
71
81
|
const fieldParser = fieldParsers[idx];
|
|
72
82
|
|
|
83
|
+
// field definition is absolute, subtracting struct offset gives bit offset inside struct
|
|
84
|
+
// adding data bit offset gives bit offset inside provided data
|
|
85
|
+
const offsetToProvidedDataInBits = field.definition.offsetInBits - structOffsetInBits + offsetInBits;
|
|
86
|
+
|
|
73
87
|
const offsetInBitsInByte = field.definition.offsetInBits % 8;
|
|
74
88
|
if (offsetInBitsInByte !== 0) {
|
|
75
89
|
throw Error("not implemented yet: unaligned field parsing");
|
|
76
90
|
}
|
|
77
91
|
|
|
78
|
-
const
|
|
79
|
-
|
|
92
|
+
const {
|
|
93
|
+
data: fieldData,
|
|
94
|
+
offsetInBits: fieldOffsetInBits
|
|
95
|
+
} = subData({
|
|
96
|
+
data,
|
|
97
|
+
offsetInBits: offsetToProvidedDataInBits,
|
|
98
|
+
sizeInBits: field.definition.sizeInBits
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
result[field.name] = fieldParser.parse({ data: fieldData, offsetInBits: fieldOffsetInBits });
|
|
80
102
|
});
|
|
81
103
|
|
|
82
104
|
return result;
|
|
83
105
|
};
|
|
84
106
|
|
|
85
107
|
const format/*: TStructParser["format"]*/ = ({ value, target, offsetInBits }) => {
|
|
108
|
+
if (offsetInBits % 8 !== 0) {
|
|
109
|
+
throw Error("unaligned struct formatting not supported yet");
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
layoutedFields.forEach((field, idx) => {
|
|
87
113
|
const fieldValue = value[field.name];
|
|
88
114
|
const fieldParser = fieldParsers[idx];
|
|
@@ -92,10 +118,17 @@ const createStructParser = ({
|
|
|
92
118
|
throw Error("not implemented yet: unaligned field formatting");
|
|
93
119
|
}
|
|
94
120
|
|
|
95
|
-
const
|
|
121
|
+
const {
|
|
122
|
+
data: fieldTarget,
|
|
123
|
+
offsetInBits: fieldOffsetInBits
|
|
124
|
+
} = subData({
|
|
125
|
+
data: target,
|
|
126
|
+
offsetInBits: field.definition.offsetInBits - structOffsetInBits + offsetInBits,
|
|
127
|
+
sizeInBits: field.definition.sizeInBits
|
|
128
|
+
});
|
|
96
129
|
|
|
97
130
|
try {
|
|
98
|
-
fieldParser.format({ value: fieldValue, target: fieldTarget, offsetInBits });
|
|
131
|
+
fieldParser.format({ value: fieldValue, target: fieldTarget, offsetInBits: fieldOffsetInBits });
|
|
99
132
|
} catch (ex) {
|
|
100
133
|
throw Error(`failed to format field "${field.name}"`, { cause: ex });
|
|
101
134
|
}
|
package/lib/layout.ts
CHANGED
|
@@ -111,6 +111,14 @@ const layoutStruct = ({
|
|
|
111
111
|
|
|
112
112
|
break;
|
|
113
113
|
}
|
|
114
|
+
case "struct": {
|
|
115
|
+
|
|
116
|
+
if (currentOffsetInBits % 64 !== 0) {
|
|
117
|
+
throw Error("nested struct alignment handling not implemented yet");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
114
122
|
case "string": {
|
|
115
123
|
// no special alignment needed
|
|
116
124
|
break;
|
package/lib/parser.ts
CHANGED
|
@@ -51,6 +51,7 @@ const define = <const T extends TFieldType>({ definition }: { definition: T }) =
|
|
|
51
51
|
if (l.type === "struct") {
|
|
52
52
|
valueParser = createStructParser({
|
|
53
53
|
layoutedFields: l.fields,
|
|
54
|
+
structOffsetInBits: l.offsetInBits,
|
|
54
55
|
endianness: abi.endianness
|
|
55
56
|
}) as unknown as TValueParser<TParsedValueOfDefinition<T>>;
|
|
56
57
|
} else {
|
package/lib/types/string.ts
CHANGED
|
@@ -37,7 +37,7 @@ const createStringParser = ({ length }: { length: number }): TStringParser => {
|
|
|
37
37
|
|
|
38
38
|
const encoded = encoder.encode(value);
|
|
39
39
|
|
|
40
|
-
if (encoded.length + 1
|
|
40
|
+
if (encoded.length + 1 > length) {
|
|
41
41
|
throw Error("string too long to fit in target");
|
|
42
42
|
}
|
|
43
43
|
|
package/lib/types/struct.ts
CHANGED
|
@@ -9,11 +9,20 @@ import type { TFieldType } from "./index.ts";
|
|
|
9
9
|
|
|
10
10
|
type TStructParser = TValueParser<Record<string, unknown>>;
|
|
11
11
|
|
|
12
|
+
const subData = ({ data, offsetInBits, sizeInBits }: { data: Uint8Array, offsetInBits: number, sizeInBits: number }) => {
|
|
13
|
+
return {
|
|
14
|
+
data: new Uint8Array(data.buffer, data.byteOffset + Math.floor(offsetInBits / 8), Math.ceil(sizeInBits / 8)),
|
|
15
|
+
offsetInBits: offsetInBits % 8
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
12
19
|
const createStructParser = ({
|
|
13
20
|
layoutedFields,
|
|
21
|
+
structOffsetInBits,
|
|
14
22
|
endianness
|
|
15
23
|
}: {
|
|
16
24
|
layoutedFields: (TLayoutedField & { type: "struct" })["fields"];
|
|
25
|
+
structOffsetInBits: number;
|
|
17
26
|
endianness: TEndianness
|
|
18
27
|
}): TStructParser => {
|
|
19
28
|
|
|
@@ -37,6 +46,7 @@ const createStructParser = ({
|
|
|
37
46
|
if (field.definition.type === "struct") {
|
|
38
47
|
return createStructParser({
|
|
39
48
|
layoutedFields: field.definition.fields,
|
|
49
|
+
structOffsetInBits: field.definition.offsetInBits,
|
|
40
50
|
endianness
|
|
41
51
|
});
|
|
42
52
|
}
|
|
@@ -70,19 +80,35 @@ const createStructParser = ({
|
|
|
70
80
|
layoutedFields.forEach((field, idx) => {
|
|
71
81
|
const fieldParser = fieldParsers[idx];
|
|
72
82
|
|
|
83
|
+
// field definition is absolute, subtracting struct offset gives bit offset inside struct
|
|
84
|
+
// adding data bit offset gives bit offset inside provided data
|
|
85
|
+
const offsetToProvidedDataInBits = field.definition.offsetInBits - structOffsetInBits + offsetInBits;
|
|
86
|
+
|
|
73
87
|
const offsetInBitsInByte = field.definition.offsetInBits % 8;
|
|
74
88
|
if (offsetInBitsInByte !== 0) {
|
|
75
89
|
throw Error("not implemented yet: unaligned field parsing");
|
|
76
90
|
}
|
|
77
91
|
|
|
78
|
-
const
|
|
79
|
-
|
|
92
|
+
const {
|
|
93
|
+
data: fieldData,
|
|
94
|
+
offsetInBits: fieldOffsetInBits
|
|
95
|
+
} = subData({
|
|
96
|
+
data,
|
|
97
|
+
offsetInBits: offsetToProvidedDataInBits,
|
|
98
|
+
sizeInBits: field.definition.sizeInBits
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
result[field.name] = fieldParser.parse({ data: fieldData, offsetInBits: fieldOffsetInBits });
|
|
80
102
|
});
|
|
81
103
|
|
|
82
104
|
return result;
|
|
83
105
|
};
|
|
84
106
|
|
|
85
107
|
const format: TStructParser["format"] = ({ value, target, offsetInBits }) => {
|
|
108
|
+
if (offsetInBits % 8 !== 0) {
|
|
109
|
+
throw Error("unaligned struct formatting not supported yet");
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
layoutedFields.forEach((field, idx) => {
|
|
87
113
|
const fieldValue = value[field.name];
|
|
88
114
|
const fieldParser = fieldParsers[idx];
|
|
@@ -92,10 +118,17 @@ const createStructParser = ({
|
|
|
92
118
|
throw Error("not implemented yet: unaligned field formatting");
|
|
93
119
|
}
|
|
94
120
|
|
|
95
|
-
const
|
|
121
|
+
const {
|
|
122
|
+
data: fieldTarget,
|
|
123
|
+
offsetInBits: fieldOffsetInBits
|
|
124
|
+
} = subData({
|
|
125
|
+
data: target,
|
|
126
|
+
offsetInBits: field.definition.offsetInBits - structOffsetInBits + offsetInBits,
|
|
127
|
+
sizeInBits: field.definition.sizeInBits
|
|
128
|
+
});
|
|
96
129
|
|
|
97
130
|
try {
|
|
98
|
-
fieldParser.format({ value: fieldValue, target: fieldTarget, offsetInBits });
|
|
131
|
+
fieldParser.format({ value: fieldValue, target: fieldTarget, offsetInBits: fieldOffsetInBits });
|
|
99
132
|
} catch (ex) {
|
|
100
133
|
throw Error(`failed to format field "${field.name}"`, { cause: ex });
|
|
101
134
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import nodeAssert from "node:assert";
|
|
2
|
+
import { define } from "../lib/parser.ts";
|
|
3
|
+
import { types } from "../lib/types/index.ts";
|
|
4
|
+
|
|
5
|
+
const abi = {
|
|
6
|
+
compiler: "gcc",
|
|
7
|
+
dataModel: "LP64",
|
|
8
|
+
endianness: "little",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
const innerStructDefinition = {
|
|
12
|
+
type: "struct",
|
|
13
|
+
packed: false,
|
|
14
|
+
fixedAbi: {},
|
|
15
|
+
fields: [
|
|
16
|
+
{ name: "a", definition: types.UInt64 },
|
|
17
|
+
{ name: "b", definition: types.UInt32 },
|
|
18
|
+
{ name: "c", definition: types.UInt32 },
|
|
19
|
+
],
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
const outerStructDefinition = {
|
|
23
|
+
type: "struct",
|
|
24
|
+
packed: false,
|
|
25
|
+
fixedAbi: {},
|
|
26
|
+
fields: [
|
|
27
|
+
{ name: "x", definition: types.UInt64 },
|
|
28
|
+
{ name: "inner", definition: innerStructDefinition },
|
|
29
|
+
],
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
const expectedEncoded = () => {
|
|
33
|
+
const data = new Uint8Array(24);
|
|
34
|
+
const view = new DataView(data.buffer);
|
|
35
|
+
|
|
36
|
+
view.setBigUint64(0, 1n, true);
|
|
37
|
+
view.setBigUint64(8, 2n, true);
|
|
38
|
+
view.setUint32(16, 3, true);
|
|
39
|
+
view.setUint32(20, 4, true);
|
|
40
|
+
|
|
41
|
+
return data;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe("nested-structs", () => {
|
|
45
|
+
it("should format nested structs", () => {
|
|
46
|
+
const parser = define({ definition: outerStructDefinition }).parser({ abi });
|
|
47
|
+
|
|
48
|
+
const encoded = parser.format({
|
|
49
|
+
value: {
|
|
50
|
+
x: 1n,
|
|
51
|
+
inner: {
|
|
52
|
+
a: 2n,
|
|
53
|
+
b: 3n,
|
|
54
|
+
c: 4n,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
nodeAssert.deepStrictEqual(encoded, expectedEncoded());
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should parse nested structs", () => {
|
|
63
|
+
const parser = define({ definition: outerStructDefinition }).parser({ abi });
|
|
64
|
+
|
|
65
|
+
const parsed = parser.parse({
|
|
66
|
+
data: expectedEncoded(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
nodeAssert.deepStrictEqual(parsed, {
|
|
70
|
+
x: 1n,
|
|
71
|
+
inner: {
|
|
72
|
+
a: 2n,
|
|
73
|
+
b: 3n,
|
|
74
|
+
c: 4n,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|