Skip to content

Commit 0b4bc49

Browse files
committed
Allow long bit field sequences
1 parent be95cb8 commit 0b4bc49

File tree

2 files changed

+141
-33
lines changed

2 files changed

+141
-33
lines changed

lib/binary_parser.ts

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -978,11 +978,11 @@ export class Parser {
978978
this.generateWrapper(ctx);
979979
break;
980980
}
981-
this.generateAssert(ctx);
981+
if (this.type !== "bit") this.generateAssert(ctx);
982982
}
983983

984984
const varName = ctx.generateVariable(this.varName);
985-
if (this.options.formatter) {
985+
if (this.options.formatter && this.type !== "bit") {
986986
this.generateFormatter(ctx, varName, this.options.formatter);
987987
}
988988

@@ -1036,53 +1036,99 @@ export class Parser {
10361036
private generateBit(ctx: Context) {
10371037
// TODO find better method to handle nested bit fields
10381038
const parser = JSON.parse(JSON.stringify(this));
1039+
parser.options = this.options;
1040+
parser.generateAssert = this.generateAssert.bind(this);
1041+
parser.generateFormatter = this.generateFormatter.bind(this);
10391042
parser.varName = ctx.generateVariable(parser.varName);
10401043
ctx.bitFields.push(parser);
10411044

10421045
if (
10431046
!this.next ||
10441047
(this.next && ["bit", "nest"].indexOf(this.next.type) < 0)
10451048
) {
1046-
let sum = 0;
1047-
ctx.bitFields.forEach(
1048-
(parser) => (sum += parser.options.length as number)
1049-
);
1050-
10511049
const val = ctx.generateTmpVariable();
10521050

1053-
if (sum <= 8) {
1054-
ctx.pushCode(`var ${val} = dataView.getUint8(offset);`);
1055-
sum = 8;
1056-
} else if (sum <= 16) {
1057-
ctx.pushCode(`var ${val} = dataView.getUint16(offset);`);
1058-
sum = 16;
1059-
} else if (sum <= 24) {
1060-
const val1 = ctx.generateTmpVariable();
1061-
const val2 = ctx.generateTmpVariable();
1062-
ctx.pushCode(`var ${val1} = dataView.getUint16(offset);`);
1063-
ctx.pushCode(`var ${val2} = dataView.getUint8(offset + 2);`);
1064-
ctx.pushCode(`var ${val} = (${val1} << 8) | ${val2};`);
1065-
sum = 24;
1066-
} else if (sum <= 32) {
1067-
ctx.pushCode(`var ${val} = dataView.getUint32(offset);`);
1068-
sum = 32;
1069-
} else {
1070-
throw new Error(
1071-
"Currently, bit field sequence longer than 4-bytes is not supported."
1072-
);
1073-
}
1074-
ctx.pushCode(`offset += ${sum / 8};`);
1051+
ctx.pushCode(`var ${val} = 0;`);
1052+
1053+
const getMaxBits = (from = 0) => {
1054+
let sum = 0;
1055+
for (let i = from; i < ctx.bitFields.length; i++) {
1056+
const length = ctx.bitFields[i].options.length as number;
1057+
if (sum + length > 32) break;
1058+
sum += length;
1059+
}
1060+
return sum;
1061+
};
1062+
1063+
const getBytes = (sum: number) => {
1064+
if (sum <= 8) {
1065+
ctx.pushCode(`${val} = dataView.getUint8(offset);`);
1066+
sum = 8;
1067+
} else if (sum <= 16) {
1068+
ctx.pushCode(`${val} = dataView.getUint16(offset);`);
1069+
sum = 16;
1070+
} else if (sum <= 24) {
1071+
ctx.pushCode(
1072+
`${val} = (dataView.getUint16(offset) << 8) | dataView.getUint8(offset + 2);`
1073+
);
1074+
sum = 24;
1075+
} else {
1076+
ctx.pushCode(`${val} = dataView.getUint32(offset);`);
1077+
sum = 32;
1078+
}
1079+
ctx.pushCode(`offset += ${sum / 8};`);
1080+
return sum;
1081+
};
10751082

10761083
let bitOffset = 0;
10771084
const isBigEndian = this.endian === "be";
10781085

1079-
ctx.bitFields.forEach((parser) => {
1080-
const length = parser.options.length as number;
1086+
let sum = 0;
1087+
let rem = 0;
1088+
1089+
ctx.bitFields.forEach((parser, i) => {
1090+
let length = parser.options.length as number;
1091+
if (length > rem) {
1092+
if (rem) {
1093+
const mask = -1 >>> (32 - rem);
1094+
ctx.pushCode(
1095+
`${parser.varName} = (${val} & 0x${mask.toString(16)}) << ${
1096+
length - rem
1097+
};`
1098+
);
1099+
length -= rem;
1100+
}
1101+
bitOffset = 0;
1102+
rem = sum = getBytes(getMaxBits(i) - rem);
1103+
}
10811104
const offset = isBigEndian ? sum - bitOffset - length : bitOffset;
1082-
const mask = (1 << length) - 1;
1105+
const mask = -1 >>> (32 - length);
1106+
1107+
ctx.pushCode(
1108+
`${parser.varName} ${
1109+
length < (parser.options.length as number) ? "|=" : "="
1110+
} ${val} >> ${offset} & 0x${mask.toString(16)};`
1111+
);
1112+
1113+
// Ensure value is unsigned
1114+
if ((parser.options.length as number) === 32) {
1115+
ctx.pushCode(`${parser.varName} >>>= 0`);
1116+
}
1117+
1118+
if (parser.options.assert) {
1119+
parser.generateAssert(ctx);
1120+
}
1121+
1122+
if (parser.options.formatter) {
1123+
parser.generateFormatter(
1124+
ctx,
1125+
parser.varName,
1126+
parser.options.formatter
1127+
);
1128+
}
10831129

1084-
ctx.pushCode(`${parser.varName} = ${val} >> ${offset} & ${mask};`);
10851130
bitOffset += length;
1131+
rem -= length;
10861132
});
10871133

10881134
ctx.bitFields = [];

test/primitive_parser.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ function primitiveParserTests(
120120
const bytes = [];
121121

122122
s = s.replace(/\s/g, "");
123+
deepStrictEqual(s.length % 8, 0);
123124
for (let i = 0; i < s.length; i += 8) {
124125
bytes.push(parseInt(s.slice(i, i + 8), 2));
125126
}
@@ -212,6 +213,49 @@ function primitiveParserTests(
212213
e: 1,
213214
});
214215
});
216+
it("should parse 32-bit fields", () => {
217+
const parser1 = new Parser().bit32("a");
218+
const buf1 = binaryLiteral("10110101011101010111001010011101");
219+
deepStrictEqual(parser1.parse(buf1), { a: 3044373149 });
220+
const parser2 = new Parser().bit6("a").bit32("b").bit2("c");
221+
const buf2 = binaryLiteral(
222+
"101101 10110101011101010111001010011101 11"
223+
);
224+
deepStrictEqual(parser2.parse(buf2), { a: 45, b: 3044373149, c: 3 });
225+
});
226+
it("should parse arbitrarily large bit field sequence", () => {
227+
const parser1 = new Parser()
228+
.bit1("a")
229+
.bit24("b")
230+
.bit4("c")
231+
.bit2("d")
232+
.bit9("e");
233+
const buf = binaryLiteral(
234+
"1 101010101010101010101010 1111 01 110100110"
235+
);
236+
deepStrictEqual(parser1.parse(buf), {
237+
a: 1,
238+
b: 11184810,
239+
c: 15,
240+
d: 1,
241+
e: 422,
242+
});
243+
244+
const parser2 = new Parser()
245+
.endianness("little")
246+
.bit1("a")
247+
.bit24("b")
248+
.bit4("c")
249+
.bit2("d")
250+
.bit9("e");
251+
deepStrictEqual(parser2.parse(buf), {
252+
a: 1,
253+
b: 11184829,
254+
c: 10,
255+
d: 2,
256+
e: 422,
257+
});
258+
});
215259
it("should parse nested bit fields", () => {
216260
const parser = new Parser().bit1("a").nest("x", {
217261
type: new Parser().bit2("b").bit4("c").bit1("d"),
@@ -228,6 +272,24 @@ function primitiveParserTests(
228272
},
229273
});
230274
});
275+
276+
it("should assert bit fields", () => {
277+
const parser = new Parser()
278+
.bit4("a", { assert: (v) => v === 10 })
279+
.bit3("b", { assert: (v) => v === 7 })
280+
.bit1("c", { assert: (v) => v === 1 });
281+
const buf = binaryLiteral("1010 111 1");
282+
deepStrictEqual(parser.parse(buf), { a: 10, b: 7, c: 1 });
283+
});
284+
285+
it("should format bit fields", () => {
286+
const parser = new Parser()
287+
.bit4("a", { formatter: (v) => v * 2 })
288+
.bit3("b", { formatter: (v) => v * 3 })
289+
.bit1("c", { formatter: (v) => v * 4 });
290+
const buf = binaryLiteral("1010 111 1");
291+
deepStrictEqual(parser.parse(buf), { a: 20, b: 21, c: 4 });
292+
});
231293
});
232294

233295
describe("String parser", () => {

0 commit comments

Comments
 (0)