* New Address class for FTN addresses + experiment with ES6 classes

* Move a lot of address functionality/parsing/etc. to Address
* WIP on ftn_bso scan/tosser
* PATH and SEEN-BY creation, parsing, etc.
This commit is contained in:
Bryan Ashby 2016-02-15 17:56:05 -07:00
parent 7b5ab029f9
commit 13d5c4d8f4
6 changed files with 605 additions and 139 deletions

169
core/ftn_address.js Normal file
View File

@ -0,0 +1,169 @@
/* jslint node: true */
'use strict';
let _ = require('lodash');
const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-\.]+)?$/i;
const FTN_PATTERN_REGEXP = /^([0-9\*]+:)?([0-9\*]+)(\/[0-9\*]+)?(\.[0-9\*]+)?(@[a-z0-9\-\.\*]+)?$/i;
module.exports = class Address {
constructor(addr) {
if(addr) {
if(_.isObject(addr)) {
Object.assign(this, addr);
} else if(_.isString(addr)) {
const temp = Address.fromString(addr);
if(temp) {
Object.assign(this, temp);
}
}
}
}
isEqual(other) {
if(_.isString(other)) {
other = Address.fromString(other);
}
return (
this.net === other.net &&
this.node === other.node &&
this.zone === other.zone &&
this.point === other.point &&
this.domain === other.domain
);
}
isMatch(pattern) {
const m = FTN_PATTERN_REGEXP.exec(pattern);
if(m) {
let addr = { };
if(m[1]) {
addr.zone = m[1].slice(0, -1)
if('*' !== addr.zone) {
addr.zone = parseInt(addr.zone);
}
} else {
addr.zone = '*';
}
if(m[2]) {
addr.net = m[2];
if('*' !== addr.net) {
addr.net = parseInt(addr.net);
}
} else {
addr.net = '*';
}
if(m[3]) {
addr.node = m[3].substr(1);
if('*' !== addr.node) {
addr.node = parseInt(addr.node);
}
} else {
addr.node = '*';
}
if(m[4]) {
addr.point = m[4].substr(1);
if('*' !== addr.point) {
addr.point = parseInt(addr.point);
}
} else {
addr.point = '*';
}
if(m[5]) {
addr.domain = m[5].substr(1);
} else {
addr.domain = '*';
}
return (
('*' === addr.net || this.net === addr.net) &&
('*' === addr.node || this.node === addr.node) &&
('*' === addr.zone || this.zone === addr.zone) &&
('*' === addr.point || this.point === addr.point) &&
('*' === addr.domain || this.domain === addr.domain)
);
}
return false;
}
static fromString(addrStr) {
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
if(m) {
// start with a 2D
let addr = {
net : parseInt(m[2]),
node : parseInt(m[3].substr(1)),
};
// 3D: Addition of zone if present
if(m[1]) {
addr.zone = parseInt(m[1].slice(0, -1));
}
// 4D if optional point is present
if(m[4]) {
addr.point = parseInt(m[4].substr(1));
}
// 5D with @domain
if(m[5]) {
addr.domain = m[5].substr(1);
}
return new Address(addr);
}
}
toString(dimensions) {
dimensions = dimensions || '5D';
let addrStr = `${this.zone}:${this.net}`;
// allow for e.g. '4D' or 5
const dim = parseInt(dimensions.toString()[0]);
if(dim >= 3) {
addrStr += `/${this.node}`;
}
// missing & .0 are equiv for point
if(dim >= 4 && this.point) {
addrStr += `.${this.point}`;
}
if(5 === dim && this.domain) {
addrStr += `@${this.domain.toLowerCase()}`;
}
return addrStr;
}
static getComparator() {
return function(left, right) {
let c = (left.zone || 0) - (right.zone || 0);
if(0 !== c) {
return c;
}
c = (left.net || 0) - (right.net || 0);
if(0 !== c) {
return c;
}
c = (left.node || 0) - (right.node || 0);
if(0 !== c) {
return c;
}
return (left.domain || '').localeCompare(right.domain || '');
}
}
}

View File

@ -2,19 +2,24 @@
'use strict';
//var MailPacket = require('./mail_packet.js');
var ftn = require('./ftn_util.js');
var Message = require('./message.js');
var sauce = require('./sauce.js');
let ftn = require('./ftn_util.js');
let Message = require('./message.js');
let sauce = require('./sauce.js');
let Address = require('./ftn_address.js');
let strUtil = require('./string_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');
var iconv = require('iconv-lite');
var buffers = require('buffers');
var moment = require('moment');
let _ = require('lodash');
let assert = require('assert');
let binary = require('binary');
let fs = require('fs');
let util = require('util');
let async = require('async');
let iconv = require('iconv-lite');
let buffers = require('buffers');
let moment = require('moment');
exports.PacketHeader = PacketHeader;
exports.Packet = Packet;
/*
:TODO: things
@ -35,6 +40,88 @@ const FTN_MESSAGE_SAUCE_HEADER = new Buffer('SAUCE00');
const FTN_MESSAGE_KLUDGE_PREFIX = '\x01';
function PacketHeader(options) {
}
PacketHeader.prototype.init = function(origAddr, destAddr, created, version) {
version = version || '2+';
const EMPTY_ADDRESS = {
node : 0,
net : 0,
zone : 0,
point : 0,
};
this.packetVersion = version;
this.setOrigAddress(origAddr || EMPTY_ADDRESS);
this.setDestAddress(destAddr || EMPTY_ADDRESS);
this.setCreated(created || moment());
// uncommon to set the following explicitly
this.prodCodeLo = 0xfe; // http://ftsc.org/docs/fta-1005.003
this.prodRevLo = 0;
this.baud = 0;
this.packetType = FTN_PACKET_HEADER_TYPE;
this.password = '';
this.prodData = 0x47694e45; // "ENiG"
this.capWord = 0x0001;
this.capWordValidate = ((this.capWord & 0xff) << 8) | ((this.capWord >> 8) & 0xff);
return this;
};
PacketHeader.prototype.setOrigAddress = function(address) {
this.origNode = address.node;
// See FSC-48
if(address.point) {
this.origNet = -1;
this.auxNet = address.net;
} else {
this.origNet = address.net;
this.auxNet = 0;
}
this.origZone = address.zone;
this.origZone2 = address.zone;
this.origPoint = address.point || 0;
return this;
};
PacketHeader.prototype.setDestAddress = function(address) {
this.destNode = address.node;
this.destNet = address.net;
this.destZone = address.zone;
this.destZone2 = address.zone;
this.destPoint = address.point || 0;
return this;
};
PacketHeader.prototype.setCreated = function(created) {
if(!moment.isMoment(created)) {
created = moment(created);
}
this.year = created.year();
this.month = created.month();
this.day = created.day();
this.hour = created.hour();
this.minute = created.minute();
this.second = created.second();
return this;
};
PacketHeader.prototype.setPassword = function(password) {
this.password = password.substr(0, 8);
}
//
// Read/Write FTN packets with support for the following formats:
//
@ -47,7 +134,7 @@ const FTN_MESSAGE_KLUDGE_PREFIX = '\x01';
// * Writeup on differences between type 2, 2.2, and 2+:
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
//
function FTNPacket() {
function Packet() {
var self = this;
@ -96,7 +183,8 @@ function FTNPacket() {
.word32lu('prodData')
.tap(packetHeader => {
// Convert password from NULL padded array to string
packetHeader.password = ftn.stringFromFTN(packetHeader.password);
//packetHeader.password = ftn.stringFromFTN(packetHeader.password);
packetHeader.password = strUtil.stringFromNullTermBuffer(packetHeader.password, 'CP437');
if(FTN_PACKET_HEADER_TYPE !== packetHeader.packetType) {
cb(new Error('Unsupported header type: ' + packetHeader.packetType));
@ -106,7 +194,7 @@ function FTNPacket() {
//
// What kind of packet do we really have here?
//
// :TODO: adjust values based on version discoverd
// :TODO: adjust values based on version discovered
if(FTN_PACKET_BAUD_TYPE_2_2 === packetHeader.baud) {
packetHeader.packetVersion = '2.2';
@ -147,15 +235,16 @@ function FTNPacket() {
//
packetHeader.created = moment(packetHeader);
cb(null, packetHeader);
let ph = new PacketHeader();
_.assign(ph, packetHeader);
cb(null, ph);
});
};
this.writePacketHeader = function(packetHeader, ws) {
let buffer = new Buffer(FTN_PACKET_HEADER_SIZE);
// :TODO: write 2, 2.2, or 2+ packet based on packetHeader.packetVersion (def=2+)
buffer.writeUInt16LE(packetHeader.origNode, 0);
buffer.writeUInt16LE(packetHeader.destNode, 2);
buffer.writeUInt16LE(packetHeader.created.year(), 4);
@ -177,7 +266,8 @@ function FTNPacket() {
buffer.writeUInt16LE(packetHeader.origZone, 34);
buffer.writeUInt16LE(packetHeader.destZone, 36);
switch(packetHeader.packetType) {
// :TODO: update header information appropriately for 2 and 2.2
switch(packetHeader.packetVersion) {
case '2' :
// filler...
packetHeader.auxNet = 0;
@ -196,9 +286,20 @@ function FTNPacket() {
packetHeader.hour = 0;
packetHeader.minute = 0;
packetHeader.second = 0;
// :TODO: copy over fields from 2+ -> overriden fields here!
packetHeader.baud = FTN_PACKET_BAUD_TYPE_2_2;
break;
case '2+' :
const capWordValidateSwapped =
((packetHeader.capWordValidate & 0xff) << 8) |
((packetHeader.capWordValidate >> 8) & 0xff);
packetHeader.capWordValidate = capWordValidateSwapped;
// :TODO: set header appropriate if point
break;
}
@ -355,8 +456,9 @@ function FTNPacket() {
.word16lu('ftn_dest_node')
.word16lu('ftn_orig_network')
.word16lu('ftn_dest_network')
.word8('ftn_attr_flags1')
.word8('ftn_attr_flags2')
.word16lu('ftn_attr_flags')
//.word8('ftn_attr_flags1')
//.word8('ftn_attr_flags2')
.word16lu('ftn_cost')
.scan('modDateTime', NULL_TERM_BUFFER) // :TODO: 20 bytes max
.scan('toUserName', NULL_TERM_BUFFER) // :TODO: 36 bytes max
@ -404,8 +506,9 @@ function FTNPacket() {
msg.meta.FtnProperty.ftn_dest_node = msgData.ftn_dest_node;
msg.meta.FtnProperty.ftn_orig_network = msgData.ftn_orig_network;
msg.meta.FtnProperty.ftn_dest_network = msgData.ftn_dest_network;
msg.meta.FtnProperty.ftn_attr_flags1 = msgData.ftn_attr_flags1;
msg.meta.FtnProperty.ftn_attr_flags2 = msgData.ftn_attr_flags2;
msg.meta.FtnProperty.ftn_attr_flags = msgData.ftn_attr_flags;
//msg.meta.FtnProperty.ftn_attr_flags1 = msgData.ftn_attr_flags1;
//msg.meta.FtnProperty.ftn_attr_flags2 = msgData.ftn_attr_flags2;
msg.meta.FtnProperty.ftn_cost = msgData.ftn_cost;
self.processMessageBody(msgData.message, function processed(messageBodyData) {
@ -457,8 +560,9 @@ function FTNPacket() {
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_dest_node, 4);
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_orig_network, 6);
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_dest_network, 8);
basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags1, 10);
basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags2, 11);
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_attr_flags, 10);
//basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags1, 10);
//basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags2, 11);
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_cost, 12);
const dateTimeBuffer = new Buffer(ftn.getDateTimeString(message.modTimestamp) + '\0');
@ -522,20 +626,25 @@ function FTNPacket() {
//
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// Origin line should be near the bottom of a message
//
appendMeta('', message.meta.FtnProperty.ftn_tear_line);
//
// Tear line should be near the bottom of a message
//
appendMeta('', message.meta.FtnProperty.ftn_origin);
if(message.meta.FtnProperty.ftn_tear_line) {
msgBody += `${message.meta.FtnProperty.ftn_tear_line}\r`;
}
//
// Origin line should be near the bottom of a message
//
if(message.meta.FtnProperty.ftn_origin) {
msgBody += `${message.meta.FtnProperty.ftn_origin}\r`;
}
//
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// SEEN-BY and PATH should be the last lines of a message
//
appendMeta('SEEN-BY', message.meta.FtnProperty.ftn_seen_by); // note: no ^A (0x01)
appendMeta('\x01PATH', message.meta.FtnKludge['PATH']);
ws.write(iconv.encode(msgBody + '\0', 'CP437'));
@ -564,7 +673,31 @@ function FTNPacket() {
};
}
FTNPacket.prototype.read = function(pathOrBuffer, iterator, cb) {
//
// Message attributes defined in FTS-0001.016
// http://ftsc.org/docs/fts-0001.016
//
Packet.Attribute = {
Private : 0x0001,
Crash : 0x0002,
Received : 0x0004,
Sent : 0x0008,
FileAttached : 0x0010,
InTransit : 0x0020,
Orphan : 0x0040,
KillSent : 0x0080,
Local : 0x0100,
Hold : 0x0200,
Reserved0 : 0x0400,
FileRequest : 0x0800,
ReturnReceiptRequest : 0x1000,
ReturnReceipt : 0x2000,
AuditRequest : 0x4000,
FileUpdateRequest : 0x8000,
};
Object.freeze(Packet.Attribute);
Packet.prototype.read = function(pathOrBuffer, iterator, cb) {
var self = this;
async.series(
@ -591,32 +724,67 @@ FTNPacket.prototype.read = function(pathOrBuffer, iterator, cb) {
);
};
FTNPacket.prototype.write = function(path, packetHeader, messages, cb) {
packetHeader.packetType = packetHeader.packetType || '2+';
packetHeader.created = packetHeader.created || moment();
packetHeader.baud = packetHeader.baud || 0;
// :TODO: Other defaults?
if(!_.isArray(messages)) {
messages = [ messages ] ;
Packet.prototype.writeStream = function(ws, messages, options) {
if(!_.isBoolean(options.terminatePacket)) {
options.terminatePacket = true;
}
if(_.isObject(options.packetHeader)) {
/*
let packetHeader = options.packetHeader;
// default header
packetHeader.packetVersion = packetHeader.packetVersion || '2+';
packetHeader.created = packetHeader.created || moment();
packetHeader.baud = packetHeader.baud || 0;
*/
this.writePacketHeader(options.packetHeader, ws);
}
let ws = fs.createWriteStream(path);
this.writePacketHeader(packetHeader, ws);
messages.forEach(msg => {
this.writeMessage(msg, ws);
});
ws.write(new Buffer( [ 0 ] )); // final extra null term
if(true === options.terminatePacket) {
ws.write(new Buffer( [ 0 ] )); // final extra null term
}
}
Packet.prototype.write = function(path, packetHeader, messages) {
if(!_.isArray(messages)) {
messages = [ messages ];
}
this.writeStream(
fs.createWriteStream(path), // :TODO: specify mode/etc.
messages,
{ packetHeader : packetHeader, terminatePacket : true }
);
};
var ftnPacket = new FTNPacket();
const LOCAL_ADDRESS = {
zone : 46,
net : 1,
node : 232,
domain : 'l33t.codes',
};
const REMOTE_ADDRESS = {
zone : 1,
net : 2,
node : 218,
};
var packetHeader1 = new PacketHeader();
packetHeader1.init(LOCAL_ADDRESS, REMOTE_ADDRESS);
console.log(packetHeader1);
var packet = new Packet();
var theHeader;
var written = false;
let messagesToWrite = [];
ftnPacket.read(
packet.read(
process.argv[2],
function iterator(dataType, data) {
if('header' === dataType) {
@ -633,7 +801,7 @@ ftnPacket.read(
written = true;
let messages = [ msg ];
ftnPacket.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messages, err => {
Packet.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messages, err => {
});
@ -650,8 +818,13 @@ ftnPacket.read(
console.log(ftn.getMessageIdentifier(msg, address));
console.log(ftn.getProductIdentifier())
//console.log(ftn.getOrigin(address))
console.log(ftn.parseAddress('46:1/232.4@l33t.codes'))
//console.log(ftn.parseAddress('46:1/232.4@l33t.codes'))
console.log(Address.fromString('46:1/232.4@l33t.codes'));
console.log(ftn.getUTCTimeZoneOffset())
console.log(ftn.getUpdatedSeenByEntries(
msg.meta.FtnProperty.ftn_seen_by, '1/107 4/22 4/25 4/10'));
console.log(ftn.getUpdatedPathEntries(
msg.meta.FtnKludge['PATH'], '1:365/50'))
}
},
function completion(err) {
@ -660,7 +833,7 @@ ftnPacket.read(
console.log(messagesToWrite.length)
ftnPacket.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messagesToWrite, err => {
packet.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messagesToWrite, err => {
});
}
);

View File

@ -2,7 +2,7 @@
'use strict';
var Config = require('./config.js').config;
var Address = require('./ftn_address.js');
var _ = require('lodash');
var assert = require('assert');
@ -18,20 +18,22 @@ var os = require('os');
var packageJson = require('../package.json');
// :TODO: Remove "Ftn" from most of these -- it's implied in the module
exports.stringFromFTN = stringFromFTN;
exports.stringToNullPaddedBuffer = stringToNullPaddedBuffer;
exports.createMessageUuid = createMessageUuid;
exports.parseAddress = parseAddress;
exports.formatAddress = formatAddress;
exports.getDateFromFtnDateTime = getDateFromFtnDateTime;
exports.getDateTimeString = getDateTimeString;
exports.getDateFromFtnDateTime = getDateFromFtnDateTime;
exports.getDateTimeString = getDateTimeString;
exports.getMessageIdentifier = getMessageIdentifier;
exports.getProductIdentifier = getProductIdentifier;
exports.getUTCTimeZoneOffset = getUTCTimeZoneOffset;
exports.getOrigin = getOrigin;
exports.getMessageIdentifier = getMessageIdentifier;
exports.getProductIdentifier = getProductIdentifier;
exports.getUTCTimeZoneOffset = getUTCTimeZoneOffset;
exports.getOrigin = getOrigin;
exports.getTearLine = getTearLine;
exports.getAbbreviatedNetNodeList = getAbbreviatedNetNodeList;
exports.parseAbbreviatedNetNodeList = parseAbbreviatedNetNodeList;
exports.getUpdatedSeenByEntries = getUpdatedSeenByEntries;
exports.getUpdatedPathEntries = getUpdatedPathEntries;
exports.getQuotePrefix = getQuotePrefix;
exports.getQuotePrefix = getQuotePrefix;
//
// Namespace for RFC-4122 name based UUIDs generated from
@ -40,23 +42,10 @@ exports.getQuotePrefix = getQuotePrefix;
const ENIGMA_FTN_MSGID_NAMESPACE = uuid.parse('a5c7ae11-420c-4469-a116-0e9a6d8d2654');
// Up to 5D FTN address RegExp
const ENIGMA_FTN_ADDRESS_REGEXP = /^([0-9]+):([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-\.]+)?$/i;
const ENIGMA_FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-\.]+)?$/i;
// See list here: https://github.com/Mithgol/node-fidonet-jam
// :TODO: proably move this elsewhere as a general method
function stringFromFTN(buf, encoding) {
var nullPos = buf.length;
for(var i = 0; i < buf.length; ++i) {
if(0x00 === buf[i]) {
nullPos = i;
break;
}
}
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
}
function stringToNullPaddedBuffer(s, bufLen) {
let buffer = new Buffer(bufLen).fill(0x00);
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
@ -143,57 +132,6 @@ function createMessageUuid(ftnMsgId, ftnArea) {
return uuid.unparse(u); // to string
}
function parseAddress(address) {
const m = ENIGMA_FTN_ADDRESS_REGEXP.exec(address);
if(m) {
let addr = {
zone : parseInt(m[1]),
net : parseInt(m[2]),
};
//
// substr(1) on the following to remove the
// captured prefix
//
if(m[3]) {
addr.node = parseInt(m[3].substr(1));
}
if(m[4]) {
addr.point = parseInt(m[4].substr(1));
}
if(m[5]) {
addr.domain = m[5].substr(1);
}
return addr;
}
}
function formatAddress(address, dimensions) {
let addr = `${address.zone}:${address.net}`;
// allow for e.g. '4D' or 5
const dim = parseInt(dimensions.toString()[0]);
if(dim >= 3) {
addr += `/${address.node}`;
}
// missing & .0 are equiv for point
if(dim >= 4 && address.point) {
addr += `.${addresss.point}`;
}
if(5 === dim && address.domain) {
addr += `@${address.domain.toLowerCase()}`;
}
return addr;
}
function getMessageSerialNumber(message) {
return ('00000000' + ((Math.floor((Date.now() - Date.UTC(2016, 1, 1)) / 1000) +
message.messageId)).toString(16)).substr(-8);
@ -235,7 +173,8 @@ function getMessageSerialNumber(message) {
// ENiGMA½: <messageId>.<areaTag>@<5dFtnAddress> <serial>
//
function getMessageIdentifier(message, address) {
return `${message.messageId}.${message.areaTag.toLowerCase()}@${formatAddress(address, '5D')} ${getMessageSerialNumber(message)}`;
const addrStr = new Address(address).toString('5D');
return `${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message)}`;
}
//
@ -288,5 +227,113 @@ function getOrigin(address) {
Config.messageNetworks.originName :
Config.general.boardName;
return ` * Origin: ${origin} (${formatAddress(address, '5D')})`;
const addrStr = new Address(address).toString('5D');
return ` * Origin: ${origin} (${addrStr})`;
}
function getTearLine() {
return `--- ENiGMA 1/2 v{$packageJson.version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
}
function getAbbreviatedNetNodeList(netNodes) {
let abbrList = '';
let currNet;
netNodes.forEach(netNode => {
if(currNet !== netNode.net) {
abbrList += `${netNode.net}/`;
currNet = netNode.net;
}
abbrList += `${netNode.node} `;
});
return abbrList.trim(); // remove trailing space
}
function parseAbbreviatedNetNodeList(netNodes) {
//
// Make sure we have an array of objects.
// Allow for a single object or string(s)
//
if(!_.isArray(netNodes)) {
if(_.isString(netNodes)) {
netNodes = netNodes.split(' ');
} else {
netNodes = [ netNodes ];
}
}
//
// Convert any strings to parsed address objects
//
return netNodes.map(a => {
if(_.isObject(a)) {
return a;
} else {
return Address.fromString(a);
}
});
}
//
// Return a FTS-0004.001 SEEN-BY entry(s) that include
// all pre-existing SEEN-BY entries with the addition
// of |additions|.
//
// See http://ftsc.org/docs/fts-0004.001
// and notes at http://ftsc.org/docs/fsc-0043.002.
//
// For a great write up, see http://www.skepticfiles.org/aj/basics03.htm
//
// This method returns an sorted array of values, but
// not the "SEEN-BY" prefix itself
//
function getUpdatedSeenByEntries(existingEntries, additions) {
/*
From FTS-0004:
"There can be many seen-by lines at the end of Conference
Mail messages, and they are the real "meat" of the control
information. They are used to determine the systems to
receive the exported messages. The format of the line is:
SEEN-BY: 132/101 113 136/601 1014/1
The net/node numbers correspond to the net/node numbers of
the systems having already received the message. In this way
a message is never sent to a system twice. In a conference
with many participants the number of seen-by lines can be
very large. This line is added if it is not already a part
of the message, or added to if it already exists, each time
a message is exported to other systems. This is a REQUIRED
field, and Conference Mail will not function correctly if
this field is not put in place by other Echomail compatible
programs."
*/
existingEntries = existingEntries || [];
if(!_.isArray(existingEntries)) {
existingEntries = [ existingEntries ];
}
additions = parseAbbreviatedNetNodeList(additions).sort(Address.getComparator());
//
// For now, we'll just append a new SEEN-BY entry
//
// :TODO: we should at least try and update what is already there in a smart way
existingEntries.push(getAbbreviatedNetNodeList(additions));
return existingEntries;
}
function getUpdatedPathEntries(existingEntries, localAddress) {
// :TODO: append to PATH in a smart way! We shoudl try to fit at least the last existing line
existingEntries = existingEntries || [];
if(!_.isArray(existingEntries)) {
existingEntries = [ existingEntries ];
}
existingEntries.push(getAbbreviatedNetNodeList(
parseAbbreviatedNetNodeList(localAddress)));
return existingEntries;
}

View File

@ -109,8 +109,9 @@ Message.FtnPropertyNames = {
FtnDestNode : 'ftn_dest_node',
FtnOrigNetwork : 'ftn_orig_network',
FtnDestNetwork : 'ftn_dest_network',
FtnAttrFlags1 : 'ftn_attr_flags1',
FtnAttrFlags2 : 'ftn_attr_flags2',
FtnAttrFlags : 'ftn_attr_flags',
//FtnAttrFlags1 : 'ftn_attr_flags1',
//FtnAttrFlags2 : 'ftn_attr_flags2',
FtnCost : 'ftn_cost',
FtnOrigZone : 'ftn_orig_zone',
FtnDestZone : 'ftn_dest_zone',

View File

@ -2,8 +2,12 @@
'use strict';
// ENiGMA½
var MessageScanTossModule = require('../scan_toss_module.js').MessageScanTossModule;
var Config = require('../config.js').config;
let MessageScanTossModule = require('../scan_toss_module.js').MessageScanTossModule;
let Config = require('../config.js').config;
let ftnMailpacket = require('../ftn_mail_packet.js');
let ftnUtil = require('../ftn_util.js');
let moment = require('moment');
exports.moduleInfo = {
name : 'FTN',
@ -18,7 +22,60 @@ function FTNMessageScanTossModule() {
this.config = Config.scannerTossers.ftn_bso;
this.createMessagePacket = function(message, config) {
this.prepareMessage(message);
let packet = new ftnMailPacket.Packet();
let packetHeader = new ftnMailpacket.PacketHeader();
packetHeader.init(
config.network.localAddress,
config.remoteAddress);
packetHeader.setPassword(config.remoteNode.packetPassword || '');
};
this.prepareMessage = function(message, config) {
//
// Set various FTN kludges/etc.
//
message.meta.FtnProperty = message.meta.FtnProperty || {};
message.meta.FtnProperty.ftn_orig_node = config.network.localAddress.node;
message.meta.FtnProperty.ftn_dest_node = config.remoteAddress.node;
message.meta.FtnProperty.ftn_orig_network = config.network.localAddress.net;
message.meta.FtnProperty.ftn_dest_network = config.remoteAddress.net;
// :TODO: attr1 & 2
message.meta.FtnProperty.ftn_cost = 0;
message.meta.FtnProperty.ftn_tear_line = ftnUtil.getTearLine();
message.meta.FtnProperty.ftn_origin = ftnUtil.getOrigin(config.network.localAddress);
if(message.areaTag) {
message.meta.FtnProperty.ftn_area = message.areaTag;
} else {
// :TODO: add "Via" line -- FSP-1010
}
//
// When exporting messages, we should create/update SEEN-BY
// with remote address(s) we are exporting to.
//
message.meta.FtnProperty.ftn_seen_by =
ftnUtil.getUpdatedSeenByEntries(
message.meta.FtnProperty.ftn_seen_by,
Config.messageNetworks.ftn.areas[message.areaTag].uplinks
);
//
// And create/update PATH for ourself
//
message.meta.FtnKludge['PATH'] =
ftnUtil.getUpdatedPathEntries(
message.meta.FtnKludge['PATH'],
config.network.localAddress.node
);
};
}
require('util').inherits(FTNMessageScanTossModule, MessageScanTossModule);

View File

@ -1,14 +1,16 @@
/* jslint node: true */
'use strict';
var miscUtil = require('./misc_util.js');
let miscUtil = require('./misc_util.js');
let iconv = require('iconv-lite');
exports.stylizeString = stylizeString;
exports.pad = pad;
exports.replaceAt = replaceAt;
exports.isPrintable = isPrintable;
exports.debugEscapedString = debugEscapedString;
exports.stylizeString = stylizeString;
exports.pad = pad;
exports.replaceAt = replaceAt;
exports.isPrintable = isPrintable;
exports.debugEscapedString = debugEscapedString;
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
// :TODO: create Unicode verison of this
var VOWELS = [ 'a', 'e', 'i', 'o', 'u' ];
@ -176,6 +178,23 @@ function debugEscapedString(s) {
return JSON.stringify(s).slice(1, -1);
}
function stringFromNullTermBuffer(buf, encoding) {
/*var nullPos = buf.length;
for(var i = 0; i < buf.length; ++i) {
if(0x00 === buf[i]) {
nullPos = i;
break;
}
}
*/
let nullPos = buf.indexOf(new Buffer( [ 0x00 ] ));
if(-1 === nullPos) {
nullPos = buf.length;
}
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
}
//
// Extend String.format's object syntax with some modifiers
// e.g.: '{username!styleL33t}'.format( { username : 'Leet User' } ) -> "L33t U53r"