// -*- js-indent: 2 -*- // Interpreter for bit syntax AST. // Grammar: // // pattern := segment ("," segment)* // segment := (value | var) (":" size)? ("/" specifier ("-" specifier)*)? | string // var := "_" | identifier // size := integer | var // specifier := "little" | "big" | "signed" | "unsigned" | "unit" ":" 0..256 | type // type := "integer" | "binary" | "float" // // where integer has the obvious meaning, and identifier is anything // other than "_" that fits the JavaScript identifier specification. // // We'll use an object to represent each segment, and an array of // segments for a pattern. We won't try to optimise for groups of // patterns; we'll just step through each to see if it works. We rely // a hypothetical prior step to check that it's a valid pattern. // ? compile to intermediate instructions ? // A segment looks like // { // type: string, // 'string' is special case // size: integer | true, // true means 'all remaining' // name: string | null, // (may be '_') // value: value | null, // either name OR value // unit: integer, // signed: boolean, // bigendian: boolean // } 'use strict'; var ints = require('buffer-more-ints'), debug = require('debug')('bitsyntax-Interpreter'); function parse_int(bin, off, sizeInBytes, bigendian, signed) { switch (sizeInBytes) { case 1: return (signed) ? bin.readInt8(off) : bin.readUInt8(off); case 2: return (bigendian) ? (signed) ? bin.readInt16BE(off) : bin.readUInt16BE(off) : (signed) ? bin.readInt16LE(off) : bin.readUInt16LE(off); case 4: return (bigendian) ? (signed) ? bin.readInt32BE(off) : bin.readUInt32BE(off) : (signed) ? bin.readInt32LE(off) : bin.readUInt32LE(off); case 8: return (bigendian) ? ((signed) ? ints.readInt64BE : ints.readUInt64BE)(bin, off) : ((signed) ? ints.readInt64LE : ints.readUInt64LE)(bin, off); default: throw "Integers must be 8-, 16-, 32- or 64-bit"; } } function parse_float(bin, off, sizeInBytes, bigendian) { switch (sizeInBytes) { case 4: return (bigendian) ? bin.readFloatBE(off) : bin.readFloatLE(off); case 8: return (bigendian) ? bin.readDoubleBE(off) : bin.readDoubleLE(off); default: throw "Floats must be 32- or 64-bit"; } } function size_of(segment, bound) { var size = segment.size; if (typeof size === 'string') { return bound[size]; } else { return size; } } function new_scope(env) { function scope() {}; scope.prototype = env; return new scope(); } function bindings(scope) { var s = {}; for (var k in scope) { if (scope.hasOwnProperty(k)) { s[k] = scope[k]; } } return s; } function match(pattern, binary, boundvars) { var offset = 0, vars = new_scope(boundvars); var binsize = binary.length * 8; function skip_bits(segment) { debug("skip bits"); debug(segment); var size = size_of(segment, vars); if (size === true) { if (offset % 8 === 0) { offset = binsize; return true; } else { return false; } } var bits = segment.unit * size; if (offset + bits > binsize) { return false; } else { offset += bits; } } function get_integer(segment) { debug("get_integer"); debug(segment); // let's do only multiples of eight bits for now var unit = segment.unit, size = size_of(segment, vars); var bitsize = size * unit; var byteoffset = offset / 8; // NB assumes aligned offset += bitsize; if (bitsize % 8 > 0 || (offset > binsize)) { return false; } else { return parse_int(binary, byteoffset, bitsize / 8, segment.bigendian, segment.signed); } } function get_float(segment) { debug("get_float"); debug(segment); var unit = segment.unit; var size = size_of(segment, vars); var bitsize = size * unit; var byteoffset = offset / 8; // assume aligned offset += bitsize; if (offset > binsize) { return false; } else { return parse_float(binary, byteoffset, bitsize / 8, segment.bigendian); } } function get_binary(segment) { debug("get_binary"); debug(segment); var unit = segment.unit, size = size_of(segment, vars); var byteoffset = offset / 8; // NB alignment if (size === true) { offset = binsize; return binary.slice(byteoffset); } else { var bitsize = size * unit; if (bitsize % 8 > 0 || (offset + bitsize) > binsize) { return false; } else { offset += bitsize; return binary.slice(byteoffset, byteoffset + bitsize / 8); } } } function get_string(segment) { debug("get_string"); debug(segment); var len = segment.value.length; var byteoffset = offset / 8; offset += len * 8; if (offset > binsize) { return false; } // FIXME bytes vs UTF8 characters return binary.slice(byteoffset, byteoffset + len).toString('utf8'); } var patternlen = pattern.length; for (var i = 0; i < patternlen; i++) { var segment = pattern[i]; var result = false; if (segment.name === '_') { result = skip_bits(segment); } else { switch (segment.type) { case 'string': result = get_string(segment); break; case 'integer': result = get_integer(segment); break; case 'float': result = get_float(segment); break; case 'binary': result = get_binary(segment); break; } if (result === false) { return false; } else if (segment.name) { vars[segment.name] = result; } else if (segment.value != result) { return false; } } } if (offset == binsize) { return bindings(vars); } else { return false; } } module.exports.match = match; module.exports.parse_int = parse_int; module.exports.parse_float = parse_float;