keichi / binary-parser

A blazing-fast declarative parser builder for binary data
MIT License
857 stars 133 forks source link

Parse order is wrong with bit fields followed by nest #240

Open nick-hunter opened 1 year ago

nick-hunter commented 1 year ago

I've been debugging some more dynamically generated parsers. After learning about the differences of bit8 and uint8 from issue #239 I switched some of uint8 to bit8 for sections where I'm not sure everything is byte aligned. I found some very unexpected behavior. See example below:

import { Parser } from "binary-parser";

const body = Buffer.from("082F00000682A20A70050E32", "hex");

const incorrect = new Parser()
    .endianness("big")
    .bit8("RATE")
    .bit8("FIELD")
    .nest("TIME", {
        type: new Parser().uint32("coarse").bit24("fine")
    })
// Result { TIME: { coarse: 137297920, fine: 10619504 }, RATE: 6, FIELD: 130 }

const correct = new Parser()
    .endianness("big")
    .uint8("RATE")
    .uint8("FIELD")
    .nest("TIME", {
        type: new Parser().uint32("coarse").bit24("fine")
    })
// Result { RATE: 8, FIELD: 47, TIME: { coarse: 1666, fine: 10619504 } }

Looking at the parser code you can see that the nested code is output before the bits.

Incorrect:

var dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
var offset = 0;
var vars = {};
vars.$parent = null;
vars.$root = vars;
vars.TIME = {};
vars.TIME.coarse = dataView.getUint32(offset, false);
offset += 4;
var $tmp0 = 0;
$tmp0 = dataView.getUint16(offset);
offset += 2;
vars.RATE = $tmp0 >> 8 & 0xff;
vars.FIELD = $tmp0 >> 0 & 0xff;
$tmp0 = (dataView.getUint16(offset) << 8) | dataView.getUint8(offset + 2);
offset += 3;
vars.TIME.fine = $tmp0 >> 0 & 0xffffff;
delete vars.$parent;
delete vars.$root;
return vars;

Correct:

var dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
var offset = 0;
var vars = {};
vars.$parent = null;
vars.$root = vars;
vars.RATE = dataView.getUint8(offset, false);
offset += 1;
vars.FIELD = dataView.getUint8(offset, false);
offset += 1;
vars.TIME = {};
vars.TIME.coarse = dataView.getUint32(offset, false);
offset += 4;
var $tmp0 = 0;
$tmp0 = (dataView.getUint16(offset) << 8) | dataView.getUint8(offset + 2);
offset += 3;
vars.TIME.fine = $tmp0 >> 0 & 0xffffff;
delete vars.$parent;
delete vars.$root;
return vars;