Low hanging fruit: Don't re-create binary parsers constantly

This commit is contained in:
Bryan Ashby 2019-12-05 20:48:13 -07:00
parent 2c9a68d0b1
commit cd3b8d5e76
No known key found for this signature in database
GPG Key ID: B49EB437951D2542
3 changed files with 126 additions and 121 deletions

View File

@ -165,6 +165,74 @@ exports.PacketHeader = PacketHeader;
// * Writeup on differences between type 2, 2.2, and 2+: // * Writeup on differences between type 2, 2.2, and 2+:
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt // http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
// //
const PacketHeaderParser = new Parser()
.uint16le('origNode')
.uint16le('destNode')
.uint16le('year')
.uint16le('month')
.uint16le('day')
.uint16le('hour')
.uint16le('minute')
.uint16le('second')
.uint16le('baud')
.uint16le('packetType')
.uint16le('origNet')
.uint16le('destNet')
.int8('prodCodeLo')
.int8('prodRevLo') // aka serialNo
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
.uint16le('origZone')
.uint16le('destZone')
//
// The following is "filler" in FTS-0001, specifics in
// FSC-0045 and FSC-0048
//
.uint16le('auxNet')
.uint16le('capWordValidate')
.int8('prodCodeHi')
.int8('prodRevHi')
.uint16le('capWord')
.uint16le('origZone2')
.uint16le('destZone2')
.uint16le('origPoint')
.uint16le('destPoint')
.uint32le('prodData');
const MessageHeaderParser = new Parser()
.uint16le('messageType')
.uint16le('ftn_msg_orig_node')
.uint16le('ftn_msg_dest_node')
.uint16le('ftn_msg_orig_net')
.uint16le('ftn_msg_dest_net')
.uint16le('ftn_attr_flags')
.uint16le('ftn_cost')
//
// It would be nice to just string() these, but we want CP437 which requires
// iconv. Another option would be to use a formatter, but until issue 33
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
//
.array('modDateTime', {
type : 'uint8',
length : 20, // FTS-0001.016: 20 bytes
})
.array('toUserName', {
type : 'uint8',
// :TODO: array needs some soft of 'limit' field
readUntil : b => 0x00 === b,
})
.array('fromUserName', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('subject', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('message', {
type : 'uint8',
readUntil : b => 0x00 === b,
});
function Packet(options) { function Packet(options) {
var self = this; var self = this;
@ -175,39 +243,7 @@ function Packet(options) {
let packetHeader; let packetHeader;
try { try {
packetHeader = new Parser() packetHeader = PacketHeaderParser.parse(packetBuffer);
.uint16le('origNode')
.uint16le('destNode')
.uint16le('year')
.uint16le('month')
.uint16le('day')
.uint16le('hour')
.uint16le('minute')
.uint16le('second')
.uint16le('baud')
.uint16le('packetType')
.uint16le('origNet')
.uint16le('destNet')
.int8('prodCodeLo')
.int8('prodRevLo') // aka serialNo
.buffer('password', { length : 8 }) // can't use string; need CP437 - see https://github.com/keichi/binary-parser/issues/33
.uint16le('origZone')
.uint16le('destZone')
//
// The following is "filler" in FTS-0001, specifics in
// FSC-0045 and FSC-0048
//
.uint16le('auxNet')
.uint16le('capWordValidate')
.int8('prodCodeHi')
.int8('prodRevHi')
.uint16le('capWord')
.uint16le('origZone2')
.uint16le('destZone2')
.uint16le('origPoint')
.uint16le('destPoint')
.uint32le('prodData')
.parse(packetBuffer);
} catch(e) { } catch(e) {
return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`); return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`);
} }
@ -544,41 +580,7 @@ function Packet(options) {
let msgData; let msgData;
try { try {
msgData = new Parser() msgData = MessageHeaderParser.parse(packetBuffer);
.uint16le('messageType')
.uint16le('ftn_msg_orig_node')
.uint16le('ftn_msg_dest_node')
.uint16le('ftn_msg_orig_net')
.uint16le('ftn_msg_dest_net')
.uint16le('ftn_attr_flags')
.uint16le('ftn_cost')
//
// It would be nice to just string() these, but we want CP437 which requires
// iconv. Another option would be to use a formatter, but until issue 33
// (https://github.com/keichi/binary-parser/issues/33) is fixed, this is cumbersome.
//
.array('modDateTime', {
type : 'uint8',
length : 20, // FTS-0001.016: 20 bytes
})
.array('toUserName', {
type : 'uint8',
// :TODO: array needs some soft of 'limit' field
readUntil : b => 0x00 === b,
})
.array('fromUserName', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('subject', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.array('message', {
type : 'uint8',
readUntil : b => 0x00 === b,
})
.parse(packetBuffer);
} catch(e) { } catch(e) {
return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`)); return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`));
} }

View File

@ -26,6 +26,25 @@ exports.SAUCE_SIZE = SAUCE_SIZE;
// //
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ]; const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
const SAUCEParser = new Parser()
.buffer('id', { length : 5 } )
.buffer('version', { length : 2 } )
.buffer('title', { length: 35 } )
.buffer('author', { length : 20 } )
.buffer('group', { length: 20 } )
.buffer('date', { length: 8 } )
.uint32le('fileSize')
.int8('dataType')
.int8('fileType')
.uint16le('tinfo1')
.uint16le('tinfo2')
.uint16le('tinfo3')
.uint16le('tinfo4')
.int8('numComments')
.int8('flags')
// :TODO: does this need to be optional?
.buffer('tinfos', { length: 22 } ); // SAUCE 00.5
function readSAUCE(data, cb) { function readSAUCE(data, cb) {
if(data.length < SAUCE_SIZE) { if(data.length < SAUCE_SIZE) {
return cb(Errors.DoesNotExist('No SAUCE record present')); return cb(Errors.DoesNotExist('No SAUCE record present'));
@ -33,30 +52,11 @@ function readSAUCE(data, cb) {
let sauceRec; let sauceRec;
try { try {
sauceRec = new Parser() sauceRec = SAUCEParser.parse(data.slice(data.length - SAUCE_SIZE));
.buffer('id', { length : 5 } )
.buffer('version', { length : 2 } )
.buffer('title', { length: 35 } )
.buffer('author', { length : 20 } )
.buffer('group', { length: 20 } )
.buffer('date', { length: 8 } )
.uint32le('fileSize')
.int8('dataType')
.int8('fileType')
.uint16le('tinfo1')
.uint16le('tinfo2')
.uint16le('tinfo3')
.uint16le('tinfo4')
.int8('numComments')
.int8('flags')
// :TODO: does this need to be optional?
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
.parse(data.slice(data.length - SAUCE_SIZE));
} catch(e) { } catch(e) {
return cb(Errors.Invalid('Invalid SAUCE record')); return cb(Errors.Invalid('Invalid SAUCE record'));
} }
if(!SAUCE_ID.equals(sauceRec.id)) { if(!SAUCE_ID.equals(sauceRec.id)) {
return cb(Errors.DoesNotExist('No SAUCE record present')); return cb(Errors.DoesNotExist('No SAUCE record present'));
} }

View File

@ -192,6 +192,18 @@ OPTION_IMPLS[OPTIONS.SUPPRESS_GO_AHEAD] = function(bufs, i, event) {
return event; return event;
}; };
const TermTypeCmdParser = new Parser()
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint8('is')
.array('ttype', {
type : 'uint8',
readUntil : b => 255 === b, // 255=COMMANDS.IAC
})
// note we read iac2 above
.uint8('se');
OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) { OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
if(event.commandCode !== COMMANDS.SB) { if(event.commandCode !== COMMANDS.SB) {
OPTION_IMPLS.NO_ARGS(bufs, i, event); OPTION_IMPLS.NO_ARGS(bufs, i, event);
@ -208,18 +220,7 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
let ttypeCmd; let ttypeCmd;
try { try {
ttypeCmd = new Parser() ttypeCmd = TermTypeCmdParser.parse(bufs.toBuffer());
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint8('is')
.array('ttype', {
type : 'uint8',
readUntil : b => 255 === b, // 255=COMMANDS.IAC
})
// note we read iac2 above
.uint8('se')
.parse(bufs.toBuffer());
} catch(e) { } catch(e) {
Log.debug( { error : e }, 'Failed parsing TTYP telnet command'); Log.debug( { error : e }, 'Failed parsing TTYP telnet command');
return event; return event;
@ -242,6 +243,15 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
return event; return event;
}; };
const NawsCmdParser = new Parser()
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint16be('width')
.uint16be('height')
.uint8('iac2')
.uint8('se');
OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) { OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
if(event.commandCode !== COMMANDS.SB) { if(event.commandCode !== COMMANDS.SB) {
OPTION_IMPLS.NO_ARGS(bufs, i, event); OPTION_IMPLS.NO_ARGS(bufs, i, event);
@ -253,15 +263,7 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
let nawsCmd; let nawsCmd;
try { try {
nawsCmd = new Parser() nawsCmd = NawsCmdParser.parse(bufs.splice(0, 9).toBuffer());
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint16be('width')
.uint16be('height')
.uint8('iac2')
.uint8('se')
.parse(bufs.splice(0, 9).toBuffer());
} catch(e) { } catch(e) {
Log.debug( { error : e }, 'Failed parsing NAWS telnet command'); Log.debug( { error : e }, 'Failed parsing NAWS telnet command');
return event; return event;
@ -282,6 +284,18 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
// Build an array of delimiters for parsing NEW_ENVIRONMENT[_DEP] // Build an array of delimiters for parsing NEW_ENVIRONMENT[_DEP]
//const NEW_ENVIRONMENT_DELIMITERS = _.values(NEW_ENVIRONMENT_COMMANDS); //const NEW_ENVIRONMENT_DELIMITERS = _.values(NEW_ENVIRONMENT_COMMANDS);
const EnvCmdParser = new Parser()
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint8('isOrInfo') // IS=initial, INFO=updates
.array('envBlock', {
type : 'uint8',
readUntil : b => 255 === b, // 255=COMMANDS.IAC
})
// note we consume IAC above
.uint8('se');
// Handle the deprecated RFC 1408 & the updated RFC 1572: // Handle the deprecated RFC 1408 & the updated RFC 1572:
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT_DEP] = OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT_DEP] =
OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) { OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
@ -306,18 +320,7 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
let envCmd; let envCmd;
try { try {
envCmd = new Parser() envCmd = EnvCmdParser.parse(bufs.splice(0, bufs.length).toBuffer());
.uint8('iac1')
.uint8('sb')
.uint8('opt')
.uint8('isOrInfo') // IS=initial, INFO=updates
.array('envBlock', {
type : 'uint8',
readUntil : b => 255 === b, // 255=COMMANDS.IAC
})
// note we consume IAC above
.uint8('se')
.parse(bufs.splice(0, bufs.length).toBuffer());
} catch(e) { } catch(e) {
Log.debug( { error : e }, 'Failed parsing NEW-ENVIRON telnet command'); Log.debug( { error : e }, 'Failed parsing NEW-ENVIRON telnet command');
return event; return event;