--- /dev/null
+var Chainsaw = require('chainsaw');
+var EventEmitter = require('events').EventEmitter;
+var Buffers = require('buffers');
+var Vars = require('./lib/vars.js');
+var Stream = require('stream').Stream;
+
+exports = module.exports = function (bufOrEm, eventName) {
+ if (Buffer.isBuffer(bufOrEm)) {
+ return exports.parse(bufOrEm);
+ }
+
+ var s = exports.stream();
+ if (bufOrEm && bufOrEm.pipe) {
+ bufOrEm.pipe(s);
+ }
+ else if (bufOrEm) {
+ bufOrEm.on(eventName || 'data', function (buf) {
+ s.write(buf);
+ });
+
+ bufOrEm.on('end', function () {
+ s.end();
+ });
+ }
+ return s;
+};
+
+exports.stream = function (input) {
+ if (input) return exports.apply(null, arguments);
+
+ var pending = null;
+ function getBytes (bytes, cb, skip) {
+ pending = {
+ bytes : bytes,
+ skip : skip,
+ cb : function (buf) {
+ pending = null;
+ cb(buf);
+ },
+ };
+ dispatch();
+ }
+
+ var offset = null;
+ function dispatch () {
+ if (!pending) {
+ if (caughtEnd) done = true;
+ return;
+ }
+ if (typeof pending === 'function') {
+ pending();
+ }
+ else {
+ var bytes = offset + pending.bytes;
+
+ if (buffers.length >= bytes) {
+ var buf;
+ if (offset == null) {
+ buf = buffers.splice(0, bytes);
+ if (!pending.skip) {
+ buf = buf.slice();
+ }
+ }
+ else {
+ if (!pending.skip) {
+ buf = buffers.slice(offset, bytes);
+ }
+ offset = bytes;
+ }
+
+ if (pending.skip) {
+ pending.cb();
+ }
+ else {
+ pending.cb(buf);
+ }
+ }
+ }
+ }
+
+ function builder (saw) {
+ function next () { if (!done) saw.next() }
+
+ var self = words(function (bytes, cb) {
+ return function (name) {
+ getBytes(bytes, function (buf) {
+ vars.set(name, cb(buf));
+ next();
+ });
+ };
+ });
+
+ self.tap = function (cb) {
+ saw.nest(cb, vars.store);
+ };
+
+ self.into = function (key, cb) {
+ if (!vars.get(key)) vars.set(key, {});
+ var parent = vars;
+ vars = Vars(parent.get(key));
+
+ saw.nest(function () {
+ cb.apply(this, arguments);
+ this.tap(function () {
+ vars = parent;
+ });
+ }, vars.store);
+ };
+
+ self.flush = function () {
+ vars.store = {};
+ next();
+ };
+
+ self.loop = function (cb) {
+ var end = false;
+
+ saw.nest(false, function loop () {
+ this.vars = vars.store;
+ cb.call(this, function () {
+ end = true;
+ next();
+ }, vars.store);
+ this.tap(function () {
+ if (end) saw.next()
+ else loop.call(this)
+ }.bind(this));
+ }, vars.store);
+ };
+
+ self.buffer = function (name, bytes) {
+ if (typeof bytes === 'string') {
+ bytes = vars.get(bytes);
+ }
+
+ getBytes(bytes, function (buf) {
+ vars.set(name, buf);
+ next();
+ });
+ };
+
+ self.skip = function (bytes) {
+ if (typeof bytes === 'string') {
+ bytes = vars.get(bytes);
+ }
+
+ getBytes(bytes, function () {
+ next();
+ });
+ };
+
+ self.scan = function find (name, search) {
+ if (typeof search === 'string') {
+ search = new Buffer(search);
+ }
+ else if (!Buffer.isBuffer(search)) {
+ throw new Error('search must be a Buffer or a string');
+ }
+
+ var taken = 0;
+ pending = function () {
+ var pos = buffers.indexOf(search, offset + taken);
+ var i = pos-offset-taken;
+ if (pos !== -1) {
+ pending = null;
+ if (offset != null) {
+ vars.set(
+ name,
+ buffers.slice(offset, offset + taken + i)
+ );
+ offset += taken + i + search.length;
+ }
+ else {
+ vars.set(
+ name,
+ buffers.slice(0, taken + i)
+ );
+ buffers.splice(0, taken + i + search.length);
+ }
+ next();
+ dispatch();
+ } else {
+ i = Math.max(buffers.length - search.length - offset - taken, 0);
+ }
+ taken += i;
+ };
+ dispatch();
+ };
+
+ self.peek = function (cb) {
+ offset = 0;
+ saw.nest(function () {
+ cb.call(this, vars.store);
+ this.tap(function () {
+ offset = null;
+ });
+ });
+ };
+
+ return self;
+ };
+
+ var stream = Chainsaw.light(builder);
+ stream.writable = true;
+
+ var buffers = Buffers();
+
+ stream.write = function (buf) {
+ buffers.push(buf);
+ dispatch();
+ };
+
+ var vars = Vars();
+
+ var done = false, caughtEnd = false;
+ stream.end = function () {
+ caughtEnd = true;
+ };
+
+ stream.pipe = Stream.prototype.pipe;
+ Object.getOwnPropertyNames(EventEmitter.prototype).forEach(function (name) {
+ stream[name] = EventEmitter.prototype[name];
+ });
+
+ return stream;
+};
+
+exports.parse = function parse (buffer) {
+ var self = words(function (bytes, cb) {
+ return function (name) {
+ if (offset + bytes <= buffer.length) {
+ var buf = buffer.slice(offset, offset + bytes);
+ offset += bytes;
+ vars.set(name, cb(buf));
+ }
+ else {
+ vars.set(name, null);
+ }
+ return self;
+ };
+ });
+
+ var offset = 0;
+ var vars = Vars();
+ self.vars = vars.store;
+
+ self.tap = function (cb) {
+ cb.call(self, vars.store);
+ return self;
+ };
+
+ self.into = function (key, cb) {
+ if (!vars.get(key)) {
+ vars.set(key, {});
+ }
+ var parent = vars;
+ vars = Vars(parent.get(key));
+ cb.call(self, vars.store);
+ vars = parent;
+ return self;
+ };
+
+ self.loop = function (cb) {
+ var end = false;
+ var ender = function () { end = true };
+ while (end === false) {
+ cb.call(self, ender, vars.store);
+ }
+ return self;
+ };
+
+ self.buffer = function (name, size) {
+ if (typeof size === 'string') {
+ size = vars.get(size);
+ }
+ var buf = buffer.slice(offset, Math.min(buffer.length, offset + size));
+ offset += size;
+ vars.set(name, buf);
+
+ return self;
+ };
+
+ self.skip = function (bytes) {
+ if (typeof bytes === 'string') {
+ bytes = vars.get(bytes);
+ }
+ offset += bytes;
+
+ return self;
+ };
+
+ self.scan = function (name, search) {
+ if (typeof search === 'string') {
+ search = new Buffer(search);
+ }
+ else if (!Buffer.isBuffer(search)) {
+ throw new Error('search must be a Buffer or a string');
+ }
+ vars.set(name, null);
+
+ // simple but slow string search
+ for (var i = 0; i + offset <= buffer.length - search.length + 1; i++) {
+ for (
+ var j = 0;
+ j < search.length && buffer[offset+i+j] === search[j];
+ j++
+ );
+ if (j === search.length) break;
+ }
+
+ vars.set(name, buffer.slice(offset, offset + i));
+ offset += i + search.length;
+ return self;
+ };
+
+ self.peek = function (cb) {
+ var was = offset;
+ cb.call(self, vars.store);
+ offset = was;
+ return self;
+ };
+
+ self.flush = function () {
+ vars.store = {};
+ return self;
+ };
+
+ self.eof = function () {
+ return offset >= buffer.length;
+ };
+
+ return self;
+};
+
+// convert byte strings to unsigned little endian numbers
+function decodeLEu (bytes) {
+ var acc = 0;
+ for (var i = 0; i < bytes.length; i++) {
+ acc += Math.pow(256,i) * bytes[i];
+ }
+ return acc;
+}
+
+// convert byte strings to unsigned big endian numbers
+function decodeBEu (bytes) {
+ var acc = 0;
+ for (var i = 0; i < bytes.length; i++) {
+ acc += Math.pow(256, bytes.length - i - 1) * bytes[i];
+ }
+ return acc;
+}
+
+// convert byte strings to signed big endian numbers
+function decodeBEs (bytes) {
+ var val = decodeBEu(bytes);
+ if ((bytes[0] & 0x80) == 0x80) {
+ val -= Math.pow(256, bytes.length);
+ }
+ return val;
+}
+
+// convert byte strings to signed little endian numbers
+function decodeLEs (bytes) {
+ var val = decodeLEu(bytes);
+ if ((bytes[bytes.length - 1] & 0x80) == 0x80) {
+ val -= Math.pow(256, bytes.length);
+ }
+ return val;
+}
+
+function words (decode) {
+ var self = {};
+
+ [ 1, 2, 4, 8 ].forEach(function (bytes) {
+ var bits = bytes * 8;
+
+ self['word' + bits + 'le']
+ = self['word' + bits + 'lu']
+ = decode(bytes, decodeLEu);
+
+ self['word' + bits + 'ls']
+ = decode(bytes, decodeLEs);
+
+ self['word' + bits + 'be']
+ = self['word' + bits + 'bu']
+ = decode(bytes, decodeBEu);
+
+ self['word' + bits + 'bs']
+ = decode(bytes, decodeBEs);
+ });
+
+ // word8be(n) == word8le(n) for all n
+ self.word8 = self.word8u = self.word8be;
+ self.word8s = self.word8bs;
+
+ return self;
+}