284 lines
6.5 KiB
JavaScript
284 lines
6.5 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
var Config = require('./config.js').config;
|
|
var ftn = require('./ftn_util.js');
|
|
|
|
var _ = require('lodash');
|
|
var assert = require('assert');
|
|
var binary = require('binary');
|
|
var fs = require('fs');
|
|
var util = require('util');
|
|
var async = require('async');
|
|
|
|
//
|
|
// References
|
|
// https://github.com/M-griffin/PyPacketMail/blob/master/PyPacketMail.py
|
|
//
|
|
|
|
function FTNMailPacket(options) {
|
|
|
|
//
|
|
// Map of networkName -> { zone, net, node, point, ... }
|
|
//
|
|
//
|
|
// :TODO: ...
|
|
// options.nodeAddresses
|
|
|
|
var self = this;
|
|
this.nodeAddresses = options.nodeAddresses || {};
|
|
|
|
/*
|
|
this.loadNodeAddresses = function() {
|
|
if(Config.networks) {
|
|
for(var name in Config.networks) {
|
|
if(!Config.networks[name].address) {
|
|
continue;
|
|
}
|
|
|
|
this.nodeAddresses[name] = Config.networks[name].address;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.loadNodeAddresses();
|
|
*/
|
|
|
|
this.getPacketHeaderAddress = function() {
|
|
return {
|
|
zone : self.packetHeader.destZone,
|
|
net : self.packetHeader.destNet,
|
|
node : self.packetHeader.destNode,
|
|
point : self.packetHeader.destPoint,
|
|
};
|
|
};
|
|
|
|
this.getNetworkNameForAddress = function(addr) {
|
|
var nodeAddr;
|
|
for(var network in self.nodeAddresses) {
|
|
nodeAddr = self.nodeAddresses[network];
|
|
if(nodeAddr.zone === addr.zone &&
|
|
nodeAddr.net === addr.net &&
|
|
nodeAddr.node === addr.node &&
|
|
nodeAddr.point === addr.point)
|
|
{
|
|
return network;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.parseFtnPacketHeader = function(packetBuffer, cb) {
|
|
assert(Buffer.isBuffer(packetBuffer));
|
|
|
|
if(packetBuffer.length < 58) {
|
|
cb(new Error('Buffer too small'));
|
|
return;
|
|
}
|
|
|
|
binary.parse(packetBuffer)
|
|
.word16lu('origNode')
|
|
.word16lu('destNode')
|
|
.word16lu('year')
|
|
.word16lu('month')
|
|
.word16lu('day')
|
|
.word16lu('hour')
|
|
.word16lu('minute')
|
|
.word16lu('second')
|
|
.word16lu('baud')
|
|
.word16lu('packetType')
|
|
.word16lu('originNet')
|
|
.word16lu('destNet')
|
|
.word8('prodCodeLo')
|
|
.word8('revisionMajor') // aka serialNo
|
|
.buffer('password', 8) // null terminated C style string
|
|
.word16lu('origZone')
|
|
.word16lu('destZone')
|
|
// :TODO: Document the various specs/fields more
|
|
.word16lu('auxNet')
|
|
.word16lu('capWordA')
|
|
.word8('prodCodeHi')
|
|
.word8('revisionMinor')
|
|
.word16lu('capWordB')
|
|
.word16lu('originZone2')
|
|
.word16lu('destZone2')
|
|
.word16lu('originPoint')
|
|
.word16lu('destPoint')
|
|
.word32lu('prodData')
|
|
.tap(function tapped(packetHeader) {
|
|
packetHeader.password = ftn.stringFromFTN(packetHeader.password);
|
|
|
|
// :TODO: Don't hard code magic # here
|
|
if(2 !== packetHeader.packetType) {
|
|
cb(new Error('Packet is not Type-2'));
|
|
return;
|
|
}
|
|
|
|
// :TODO: validate & pass error if failure
|
|
cb(null, packetHeader);
|
|
});
|
|
};
|
|
|
|
this.parseFtnMessageBody = function(msgBodyBuffer, cb) {
|
|
|
|
};
|
|
|
|
this.parseFtnMessages = function(buffer, cb) {
|
|
var nullTermBuf = new Buffer( [ 0 ] );
|
|
var fidoMessages = [];
|
|
|
|
binary.stream(buffer).loop(function looper(end, vars) {
|
|
this
|
|
.word16lu('messageType')
|
|
.word16lu('originNode')
|
|
.word16lu('destNode')
|
|
.word16lu('originNet')
|
|
.word16lu('destNet')
|
|
.word8('attrFlags1')
|
|
.word8('attrFlags2')
|
|
.word16lu('cost')
|
|
.scan('modDateTime', nullTermBuf)
|
|
.scan('toUserName', nullTermBuf)
|
|
.scan('fromUserName', nullTermBuf)
|
|
.scan('subject', nullTermBuf)
|
|
.scan('message', nullTermBuf)
|
|
.tap(function tapped(msgData) {
|
|
if(!msgData.originNode) {
|
|
end();
|
|
cb(null, fidoMessages);
|
|
return;
|
|
}
|
|
|
|
// buffer to string conversion
|
|
// :TODO: What is the real encoding here?
|
|
[ 'modDateTime', 'toUserName', 'fromUserName', 'subject', ].forEach(function field(f) {
|
|
msgData[f] = msgData[f].toString();
|
|
});
|
|
|
|
|
|
|
|
fidoMessages.push(_.clone(msgData));
|
|
});
|
|
});
|
|
};
|
|
|
|
/*
|
|
this.loadMessageHeader = function(msgHeaderBuffer, cb) {
|
|
assert(Buffer.isBuffer(msgHeaderBuffer));
|
|
|
|
if(msgHeaderBuffer.length < 14) {
|
|
cb(new Error('Buffer too small'));
|
|
return;
|
|
}
|
|
|
|
binary.parse(msgHeaderBuffer)
|
|
.word16lu('messageType')
|
|
.word16lu('originNode')
|
|
.word16lu('destNode')
|
|
.word16lu('originNet')
|
|
.word16lu('destNet')
|
|
.word8('attrFlags1')
|
|
.word8('attrFlags2')
|
|
.word16lu('cost')
|
|
.tap(function tapped(msgHeader) {
|
|
console.log(msgHeader)
|
|
|
|
var nullTermBuf = new Buffer( [ 0 ] );
|
|
var offset = 14;
|
|
binary.parse(msgHeaderBuffer.slice(offset))
|
|
.scan('modDateTime', nullTermBuf)
|
|
.scan('toUserName', nullTermBuf)
|
|
.tap(function tapped(varMsgHeader) {
|
|
console.log(varMsgHeader.modDateTime.toString())
|
|
console.log(varMsgHeader.toUserName.toString())
|
|
});
|
|
|
|
cb(null, msgHeader);
|
|
});
|
|
};
|
|
|
|
this.loadMessage = function(buf, cb) {
|
|
var bufPosition = 0;
|
|
async.waterfall(
|
|
[
|
|
function loadHdr(callback) {
|
|
self.loadMessageHeader(buf.slice(bufPosition), function headerLoaded(err, msgHeader) {
|
|
callback(err, msgHeader);
|
|
});
|
|
}
|
|
]
|
|
);
|
|
};
|
|
*/
|
|
|
|
this.loadMessagesFromPacketBuffer = function(packetBuffer, cb) {
|
|
async.waterfall(
|
|
[
|
|
function parseHeader(callback) {
|
|
self.parseFtnPacketHeader(packetBuffer, function headerParsed(err, packetHeader) {
|
|
self.packetHeader = packetHeader;
|
|
callback(err);
|
|
});
|
|
},
|
|
function validateDesinationAddress(callback) {
|
|
self.localNetworkName = self.getNetworkNameForAddress(self.getPacketHeaderAddress());
|
|
callback(self.localNetworkName ? null : new Error('Packet not addressed do this system'));
|
|
},
|
|
function parseMessages(callback) {
|
|
self.parseFtnMessages(packetBuffer.slice(58), function messagesParsed(err, fidoMessages) {
|
|
callback(err, fidoMessages);
|
|
});
|
|
},
|
|
function createMessageObjects(fidoMessages, callback) {
|
|
fidoMessages.forEach(function msg(fmsg) {
|
|
console.log(fmsg.subject);
|
|
});
|
|
}
|
|
],
|
|
function complete(err) {
|
|
cb(err);
|
|
}
|
|
);
|
|
};
|
|
|
|
}
|
|
|
|
FTNMailPacket.prototype.parse = function(path, cb) {
|
|
var self = this;
|
|
|
|
async.waterfall(
|
|
[
|
|
function readFromFile(callback) {
|
|
fs.readFile(path, function packetData(err, data) {
|
|
callback(err, data);
|
|
});
|
|
},
|
|
function extractMessages(data, callback) {
|
|
self.loadMessagesFromPacketBuffer(data, function extracted(err, messages) {
|
|
callback(err, messages);
|
|
});
|
|
}
|
|
],
|
|
function complete(err, messages) {
|
|
cb(err, messages);
|
|
}
|
|
);
|
|
};
|
|
|
|
|
|
var mailPacket = new FTNMailPacket(
|
|
{
|
|
nodeAddresses : {
|
|
fidoNet : {
|
|
zone : 46,
|
|
net : 1,
|
|
node : 140,
|
|
point : 0,
|
|
domain : ''
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
mailPacket.parse('/home/bashby/ownCloud/Projects/ENiGMA½ BBS/FTNPackets/27000425.pkt', function parsed(err, messages) {
|
|
|
|
}); |