From cd3b8d5e76d2a05a7e3d5d25f8efdb39737394f3 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Thu, 5 Dec 2019 20:48:13 -0700 Subject: [PATCH] Low hanging fruit: Don't re-create binary parsers constantly --- core/ftn_mail_packet.js | 138 ++++++++++++++++++----------------- core/sauce.js | 40 +++++----- core/servers/login/telnet.js | 69 +++++++++--------- 3 files changed, 126 insertions(+), 121 deletions(-) diff --git a/core/ftn_mail_packet.js b/core/ftn_mail_packet.js index 92e28270..781ed929 100644 --- a/core/ftn_mail_packet.js +++ b/core/ftn_mail_packet.js @@ -165,6 +165,74 @@ exports.PacketHeader = PacketHeader; // * Writeup on differences between type 2, 2.2, and 2+: // 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) { var self = this; @@ -175,39 +243,7 @@ function Packet(options) { let packetHeader; try { - packetHeader = 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') - .parse(packetBuffer); + packetHeader = PacketHeaderParser.parse(packetBuffer); } catch(e) { return Errors.Invalid(`Unable to parse FTN packet header: ${e.message}`); } @@ -544,41 +580,7 @@ function Packet(options) { let msgData; try { - msgData = 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, - }) - .parse(packetBuffer); + msgData = MessageHeaderParser.parse(packetBuffer); } catch(e) { return cb(Errors.Invalid(`Failed to parse FTN message header: ${e.message}`)); } diff --git a/core/sauce.js b/core/sauce.js index 7d5f52fd..40434861 100644 --- a/core/sauce.js +++ b/core/sauce.js @@ -26,6 +26,25 @@ exports.SAUCE_SIZE = SAUCE_SIZE; // 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) { if(data.length < SAUCE_SIZE) { return cb(Errors.DoesNotExist('No SAUCE record present')); @@ -33,30 +52,11 @@ function readSAUCE(data, cb) { let sauceRec; try { - sauceRec = 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 - .parse(data.slice(data.length - SAUCE_SIZE)); + sauceRec = SAUCEParser.parse(data.slice(data.length - SAUCE_SIZE)); } catch(e) { return cb(Errors.Invalid('Invalid SAUCE record')); } - if(!SAUCE_ID.equals(sauceRec.id)) { return cb(Errors.DoesNotExist('No SAUCE record present')); } diff --git a/core/servers/login/telnet.js b/core/servers/login/telnet.js index 1b892e71..20ec6cd9 100644 --- a/core/servers/login/telnet.js +++ b/core/servers/login/telnet.js @@ -192,6 +192,18 @@ OPTION_IMPLS[OPTIONS.SUPPRESS_GO_AHEAD] = function(bufs, i, 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) { if(event.commandCode !== COMMANDS.SB) { OPTION_IMPLS.NO_ARGS(bufs, i, event); @@ -208,18 +220,7 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) { let ttypeCmd; try { - ttypeCmd = 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') - .parse(bufs.toBuffer()); + ttypeCmd = TermTypeCmdParser.parse(bufs.toBuffer()); } catch(e) { Log.debug( { error : e }, 'Failed parsing TTYP telnet command'); return event; @@ -242,6 +243,15 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, 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) { if(event.commandCode !== COMMANDS.SB) { OPTION_IMPLS.NO_ARGS(bufs, i, event); @@ -253,15 +263,7 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) { let nawsCmd; try { - nawsCmd = new Parser() - .uint8('iac1') - .uint8('sb') - .uint8('opt') - .uint16be('width') - .uint16be('height') - .uint8('iac2') - .uint8('se') - .parse(bufs.splice(0, 9).toBuffer()); + nawsCmd = NawsCmdParser.parse(bufs.splice(0, 9).toBuffer()); } catch(e) { Log.debug( { error : e }, 'Failed parsing NAWS telnet command'); 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] //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: OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT_DEP] = OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) { @@ -306,18 +320,7 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) { let envCmd; try { - envCmd = 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') - .parse(bufs.splice(0, bufs.length).toBuffer()); + envCmd = EnvCmdParser.parse(bufs.splice(0, bufs.length).toBuffer()); } catch(e) { Log.debug( { error : e }, 'Failed parsing NEW-ENVIRON telnet command'); return event;