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; }