* 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:
parent
7b5ab029f9
commit
13d5c4d8f4
|
@ -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 || '');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 => {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
203
core/ftn_util.js
203
core/ftn_util.js
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue