You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
10 KiB
397 lines
10 KiB
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; |
|
}
|
|
|