Finish conversion from 'binary' -> 'binary-parser'

* FTN packets
* SAUCE
This commit is contained in:
Bryan Ashby 2018-01-21 11:58:19 -07:00
parent c1f971d2d9
commit 8bfad971a1
5 changed files with 506 additions and 424 deletions

View file

@ -2,16 +2,17 @@
'use strict';
// ENiGMA½
const baseClient = require('../../client.js');
const Log = require('../../logger.js').log;
const LoginServerModule = require('../../login_server_module.js');
const Config = require('../../config.js').config;
const EnigAssert = require('../../enigma_assert.js');
const baseClient = require('../../client.js');
const Log = require('../../logger.js').log;
const LoginServerModule = require('../../login_server_module.js');
const Config = require('../../config.js').config;
const EnigAssert = require('../../enigma_assert.js');
const { stringFromNullTermBuffer } = require('../../string_util.js');
// deps
const net = require('net');
const buffers = require('buffers');
const binary = require('binary');
const { Parser } = require('binary-parser');
const util = require('util');
//var debug = require('debug')('telnet');
@ -218,46 +219,42 @@ OPTION_IMPLS[OPTIONS.TERMINAL_TYPE] = function(bufs, i, event) {
return MORE_DATA_REQUIRED;
}
let end = bufs.indexOf(IAC_SE_BUF, 5); // look past header bytes
const end = bufs.indexOf(IAC_SE_BUF, 5); // look past header bytes
if(-1 === end) {
return MORE_DATA_REQUIRED;
}
// eat up and process the header
let buf = bufs.splice(0, 4).toBuffer();
binary.parse(buf)
.word8('iac1')
.word8('sb')
.word8('ttype')
.word8('is')
.tap(function(vars) {
EnigAssert(vars.iac1 === COMMANDS.IAC);
EnigAssert(vars.sb === COMMANDS.SB);
EnigAssert(vars.ttype === OPTIONS.TERMINAL_TYPE);
EnigAssert(vars.is === SB_COMMANDS.IS);
});
// eat up the rest
end -= 4;
buf = bufs.splice(0, end).toBuffer();
//
// From this point -> |end| is our ttype
//
// Look for trailing NULL(s). Clients such as NetRunner do this.
// If none is found, we take the entire buffer
//
let trimAt = 0;
for(; trimAt < buf.length; ++trimAt) {
if(0x00 === buf[trimAt]) {
break;
}
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());
} catch(e) {
Log.debug( { error : e }, 'Failed parsing TTYP telnet command');
return event;
}
event.ttype = buf.toString('ascii', 0, trimAt);
EnigAssert(COMMANDS.IAC === ttypeCmd.iac1);
EnigAssert(COMMANDS.SB === ttypeCmd.sb);
EnigAssert(OPTIONS.TERMINAL_TYPE === ttypeCmd.opt);
EnigAssert(SB_COMMANDS.IS === ttypeCmd.is);
EnigAssert(ttypeCmd.ttype.length > 0);
// note we found IAC_SE above
// pop off the terminating IAC SE
bufs.splice(0, 2);
// some terminals such as NetRunner provide a NULL-terminated buffer
// slice to remove IAC
event.ttype = stringFromNullTermBuffer(ttypeCmd.ttype.slice(0, -1), 'ascii');
bufs.splice(0, end);
}
return event;
@ -272,25 +269,30 @@ OPTION_IMPLS[OPTIONS.WINDOW_SIZE] = function(bufs, i, event) {
return MORE_DATA_REQUIRED;
}
event.buf = bufs.splice(0, 9).toBuffer();
binary.parse(event.buf)
.word8('iac1')
.word8('sb')
.word8('naws')
.word16bu('width')
.word16bu('height')
.word8('iac2')
.word8('se')
.tap(function(vars) {
EnigAssert(vars.iac1 == COMMANDS.IAC);
EnigAssert(vars.sb == COMMANDS.SB);
EnigAssert(vars.naws == OPTIONS.WINDOW_SIZE);
EnigAssert(vars.iac2 == COMMANDS.IAC);
EnigAssert(vars.se == COMMANDS.SE);
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());
} catch(e) {
Log.debug( { error : e }, 'Failed parsing NAWS telnet command');
return event;
}
event.cols = event.columns = event.width = vars.width;
event.rows = event.height = vars.height;
});
EnigAssert(COMMANDS.IAC === nawsCmd.iac1);
EnigAssert(COMMANDS.SB === nawsCmd.sb);
EnigAssert(OPTIONS.WINDOW_SIZE === nawsCmd.opt);
EnigAssert(COMMANDS.IAC === nawsCmd.iac2);
EnigAssert(COMMANDS.SE === nawsCmd.se);
event.cols = event.columns = event.width = nawsCmd.width;
event.rows = event.height = nawsCmd.height;
}
return event;
};
@ -321,78 +323,109 @@ OPTION_IMPLS[OPTIONS.NEW_ENVIRONMENT] = function(bufs, i, event) {
return MORE_DATA_REQUIRED;
}
// eat up and process the header
let buf = bufs.splice(0, 4).toBuffer();
binary.parse(buf)
.word8('iac1')
.word8('sb')
.word8('newEnv')
.word8('isOrInfo') // initial=IS, updates=INFO
.tap(function(vars) {
EnigAssert(vars.iac1 === COMMANDS.IAC);
EnigAssert(vars.sb === COMMANDS.SB);
EnigAssert(vars.newEnv === OPTIONS.NEW_ENVIRONMENT || vars.newEnv === OPTIONS.NEW_ENVIRONMENT_DEP);
EnigAssert(vars.isOrInfo === SB_COMMANDS.IS || vars.isOrInfo === SB_COMMANDS.INFO);
// :TODO: It's likely that we could do all the env name/value parsing directly in Parser.
event.type = vars.isOrInfo;
if(vars.newEnv === OPTIONS.NEW_ENVIRONMENT_DEP) {
// :TODO: bring all this into Telnet class
Log.log.warn('Handling deprecated RFC 1408 NEW-ENVIRON');
}
});
// eat up the rest
end -= 4;
buf = bufs.splice(0, end).toBuffer();
//
// This part can become messy. The basic spec is:
// IAC SB NEW-ENVIRON IS type ... [ VALUE ... ] [ type ... [ VALUE ... ] [ ... ] ] IAC SE
//
// See RFC 1572 @ http://www.faqs.org/rfcs/rfc1572.html
//
// Start by splitting up the remaining buffer. Keep the delimiters
// as prefixes we can use for processing.
//
// :TODO: Currently not supporting ESCaped values (ESC + <type>). Probably not really in the wild, but we should be compliant
// :TODO: Could probably just convert this to use a regex & handle delims + escaped values... in any case, this is sloppy...
const params = [];
let p = 0;
let j;
let l;
for(j = 0, l = buf.length; j < l; ++j) {
if(NEW_ENVIRONMENT_DELIMITERS.indexOf(buf[j]) === -1) {
continue;
}
params.push(buf.slice(p, j));
p = j;
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());
} catch(e) {
Log.debug( { error : e }, 'Failed parsing NEW-ENVIRON telnet command');
return event;
}
// remainder
if(p < l) {
params.push(buf.slice(p, l));
EnigAssert(COMMANDS.IAC === envCmd.iac1);
EnigAssert(COMMANDS.SB === envCmd.sb);
EnigAssert(OPTIONS.NEW_ENVIRONMENT === envCmd.opt || OPTIONS.NEW_ENVIRONMENT_DEP === envCmd.opt);
EnigAssert(SB_COMMANDS.IS === envCmd.isOrInfo || SB_COMMANDS.INFO === envCmd.isOrInfo);
if(OPTIONS.NEW_ENVIRONMENT_DEP === envCmd.opt) {
// :TODO: we should probably support this for legacy clients?
Log.warn('Handling deprecated RFC 1408 NEW-ENVIRON');
}
const envBuf = envCmd.envBlock.slice(0, -1); // remove IAC
if(envBuf.length < 4) { // TYPE + single char name + sep + single char value
// empty env block
return event;
}
const States = {
Name : 1,
Value : 2,
};
let state = States.Name;
const setVars = {};
const delVars = [];
let varName;
event.envVars = {};
// :TODO: handle cases where a variable was present in a previous exchange, but missing here...e.g removed
for(j = 0; j < params.length; ++j) {
if(params[j].length < 2) {
continue;
}
// :TODO: handle ESC type!!!
while(envBuf.length) {
switch(state) {
case States.Name :
{
const type = parseInt(envBuf.splice(0, 1));
if(![ NEW_ENVIRONMENT_COMMANDS.VAR, NEW_ENVIRONMENT_COMMANDS.USERVAR, NEW_ENVIRONMENT_COMMANDS.ESC ].includes(type)) {
return event; // fail :(
}
let cmd = params[j].readUInt8();
if(cmd === NEW_ENVIRONMENT_COMMANDS.VAR || cmd === NEW_ENVIRONMENT_COMMANDS.USERVAR) {
varName = params[j].slice(1).toString('utf8'); // :TODO: what encoding should this really be?
} else {
event.envVars[varName] = params[j].slice(1).toString('utf8'); // :TODO: again, what encoding?
let nameEnd = envBuf.indexOf(NEW_ENVIRONMENT_COMMANDS.VALUE);
if(-1 === nameEnd) {
nameEnd = envBuf.length;
}
varName = envBuf.splice(0, nameEnd);
if(!varName) {
return event; // something is wrong.
}
varName = Buffer.from(varName).toString('ascii');
const next = parseInt(envBuf.splice(0, 1));
if(NEW_ENVIRONMENT_COMMANDS.VALUE === next) {
state = States.Value;
} else {
state = States.Name;
delVars.push(varName); // no value; del this var
}
}
break;
case States.Value :
{
let valueEnd = envBuf.indexOf(NEW_ENVIRONMENT_COMMANDS.VAR);
if(-1 === valueEnd) {
valueEnd = envBuf.indexOf(NEW_ENVIRONMENT_COMMANDS.USERVAR);
}
if(-1 === valueEnd) {
valueEnd = envBuf.length;
}
let value = envBuf.splice(0, valueEnd);
if(value) {
value = Buffer.from(value).toString('ascii');
setVars[varName] = value;
}
state = States.Name;
}
break;
}
}
// pop off remaining IAC SE
bufs.splice(0, 2);
// :TODO: Handle deleting previously set vars via delVars
event.type = envCmd.isOrInfo;
event.envVars = setVars;
}
return event;