enigma-bbs/core/ftn_mail_packet.js

941 lines
26 KiB
JavaScript

/* jslint node: true */
'use strict';
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');
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.Packet = Packet;
/*
:TODO: things
* Test SAUCE ignore/extraction
* FSP-1010 for netmail (see SBBS)
* Syncronet apparently uses odd origin lines
* Origin lines starting with "#" instead of "*" ?
*/
const FTN_PACKET_HEADER_SIZE = 58; // fixed header size
const FTN_PACKET_HEADER_TYPE = 2;
const FTN_PACKET_MESSAGE_TYPE = 2;
const FTN_PACKET_BAUD_TYPE_2_2 = 2;
// SAUCE magic header + version ("00")
const FTN_MESSAGE_SAUCE_HEADER = new Buffer('SAUCE00');
const FTN_MESSAGE_KLUDGE_PREFIX = '\x01';
class PacketHeader {
constructor(origAddr, destAddr, created, version) {
const EMPTY_ADDRESS = {
node : 0,
net : 0,
zone : 0,
point : 0,
};
this.packetVersion = version || '2+';
this.origAddress = origAddr || EMPTY_ADDRESS;
this.destAddress = destAddr || EMPTY_ADDRESS;
this.created = 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);
}
get origAddress() {
let addr = new Address({
node : this.origNode,
zone : this.origZone,
});
if(this.origPoint) {
addr.point = this.origPoint;
addr.net = this.auxNet;
} else {
addr.net = this.origNet;
}
return addr;
}
set origAddress(address) {
if(_.isString(address)) {
address = Address.fromString(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;
}
get destAddress() {
let addr = new Address({
node : this.destNode,
net : this.destNet,
zone : this.destZone,
});
if(this.destPoint) {
addr.point = this.destPoint;
}
return addr;
}
set destAddress(address) {
if(_.isString(address)) {
address = Address.fromString(address);
}
this.destNode = address.node;
this.destNet = address.net;
this.destZone = address.zone;
this.destZone2 = address.zone;
this.destPoint = address.point || 0;
}
get created() {
return moment(this); // use year, month, etc. properties
}
set created(momentCreated) {
if(!moment.isMoment(momentCreated)) {
created = moment(momentCreated);
}
this.year = momentCreated.year();
this.month = momentCreated.month();
this.day = momentCreated.day();
this.hour = momentCreated.hour();
this.minute = momentCreated.minute();
this.second = momentCreated.second();
}
}
exports.PacketHeader = PacketHeader;
/*
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:
//
// * Type 2 FTS-0001 @ http://ftsc.org/docs/fts-0001.016 (Obsolete)
// * Type 2.2 FSC-0045 @ http://ftsc.org/docs/fsc-0045.001
// * Type 2+ FSC-0039 and FSC-0048 @ http://ftsc.org/docs/fsc-0039.004
// and http://ftsc.org/docs/fsc-0048.002
//
// Additional resources:
// * Writeup on differences between type 2, 2.2, and 2+:
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
//
function Packet() {
var self = this;
this.parsePacketHeader = function(packetBuffer, cb) {
assert(Buffer.isBuffer(packetBuffer));
if(packetBuffer.length < FTN_PACKET_HEADER_SIZE) {
cb(new Error('Buffer too small'));
return;
}
//
// Start out reading as if this is a FSC-0048 2+ packet
//
binary.parse(packetBuffer)
.word16lu('origNode')
.word16lu('destNode')
.word16lu('year')
.word16lu('month')
.word16lu('day')
.word16lu('hour')
.word16lu('minute')
.word16lu('second')
.word16lu('baud')
.word16lu('packetType')
.word16lu('origNet')
.word16lu('destNet')
.word8('prodCodeLo')
.word8('prodRevLo') // aka serialNo
.buffer('password', 8) // null padded C style string
.word16lu('origZone')
.word16lu('destZone')
//
// The following is "filler" in FTS-0001, specifics in
// FSC-0045 and FSC-0048
//
.word16lu('auxNet')
.word16lu('capWordValidate')
.word8('prodCodeHi')
.word8('prodRevHi')
.word16lu('capWord')
.word16lu('origZone2')
.word16lu('destZone2')
.word16lu('origPoint')
.word16lu('destPoint')
.word32lu('prodData')
.tap(packetHeader => {
// Convert password from NULL padded array to string
//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));
return;
}
//
// What kind of packet do we really have here?
//
// :TODO: adjust values based on version discovered
if(FTN_PACKET_BAUD_TYPE_2_2 === packetHeader.baud) {
packetHeader.packetVersion = '2.2';
// See FSC-0045
packetHeader.origPoint = packetHeader.year;
packetHeader.destPoint = packetHeader.month;
packetHeader.destDomain = packetHeader.origZone2;
packetHeader.origDomain = packetHeader.auxNet;
} else {
//
// See heuristics described in FSC-0048, "Receiving Type-2+ bundles"
//
const capWordValidateSwapped =
((packetHeader.capWordValidate & 0xff) << 8) |
((packetHeader.capWordValidate >> 8) & 0xff);
if(capWordValidateSwapped === packetHeader.capWord &&
0 != packetHeader.capWord &&
packetHeader.capWord & 0x0001)
{
packetHeader.packetVersion = '2+';
// See FSC-0048
if(-1 === packetHeader.origNet) {
packetHeader.origNet = packetHeader.auxNet;
}
} else {
packetHeader.packetVersion = '2';
// :TODO: should fill bytes be 0?
}
}
//
// Date/time components into something more reasonable
// Note: The names above match up with object members moment() allows
//
packetHeader.created = moment(packetHeader);
let ph = new PacketHeader();
_.assign(ph, packetHeader);
cb(null, ph);
});
};
this.writePacketHeader = function(packetHeader, ws) {
let buffer = new Buffer(FTN_PACKET_HEADER_SIZE);
buffer.writeUInt16LE(packetHeader.origNode, 0);
buffer.writeUInt16LE(packetHeader.destNode, 2);
buffer.writeUInt16LE(packetHeader.created.year(), 4);
buffer.writeUInt16LE(packetHeader.created.month(), 6);
buffer.writeUInt16LE(packetHeader.created.date(), 8);
buffer.writeUInt16LE(packetHeader.created.hour(), 10);
buffer.writeUInt16LE(packetHeader.created.minute(), 12);
buffer.writeUInt16LE(packetHeader.created.second(), 14);
buffer.writeUInt16LE(packetHeader.baud, 16);
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
buffer.writeUInt16LE(packetHeader.origNet, 20);
buffer.writeUInt16LE(packetHeader.destNet, 22);
buffer.writeUInt8(packetHeader.prodCodeLo, 24);
buffer.writeUInt8(packetHeader.prodRevHi, 25);
const pass = ftn.stringToNullPaddedBuffer(packetHeader.password, 8);
pass.copy(buffer, 26);
buffer.writeUInt16LE(packetHeader.origZone, 34);
buffer.writeUInt16LE(packetHeader.destZone, 36);
// :TODO: update header information appropriately for 2 and 2.2
switch(packetHeader.packetVersion) {
case '2' :
// filler...
packetHeader.auxNet = 0;
packetHeader.capWordValidate = 0;
packetHeader.prodCodeHi = 0;
packetHeader.prodRevLo = 0;
packetHeader.capWord = 0;
packetHeader.origZone2 = 0;
packetHeader.destZone2 = 0;
packetHeader.origPoint = 0;
packetHeader.destPoint = 0;
break;
case '2.2' :
packetHeader.day = 0;
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;
}
buffer.writeUInt16LE(packetHeader.auxNet, 38);
buffer.writeUInt16LE(packetHeader.capWordValidate, 40);
buffer.writeUInt8(packetHeader.prodCodeHi, 42);
buffer.writeUInt8(packetHeader.prodRevLo, 43);
buffer.writeUInt16LE(packetHeader.capWord, 44);
buffer.writeUInt16LE(packetHeader.origZone2, 46);
buffer.writeUInt16LE(packetHeader.destZone2, 48);
buffer.writeUInt16LE(packetHeader.origPoint, 50);
buffer.writeUInt16LE(packetHeader.destPoint, 52);
// Store in "ENiG" in prodData unless we already have something useful
if(0 === packetHeader.prodData) {
packetHeader.prodData = 0x47694e45;
}
buffer.writeUInt32LE(packetHeader.prodData, 54);
ws.write(buffer);
};
this.processMessageBody = function(messageBodyBuffer, cb) {
//
// From FTS-0001.16:
// "Message text is unbounded and null terminated (note exception below).
//
// A 'hard' carriage return, 0DH, marks the end of a paragraph, and must
// be preserved.
//
// So called 'soft' carriage returns, 8DH, may mark a previous
// processor's automatic line wrap, and should be ignored. Beware that
// they may be followed by linefeeds, or may not.
//
// All linefeeds, 0AH, should be ignored. Systems which display message
// text should wrap long lines to suit their application."
//
// This can be a bit tricky:
// * Decoding as CP437 converts 0x8d -> 0xec, so we'll need to correct for that
// * Many kludge lines specify an encoding. If we find one of such lines, we'll
// likely need to re-decode as the specified encoding
// * SAUCE is binary-ish data, so we need to inspect for it before any
// decoding occurs
//
let messageBodyData = {
message : [],
kludgeLines : {}, // KLUDGE:[value1, value2, ...] map
seenBy : [],
};
function addKludgeLine(line) {
const sepIndex = line.indexOf(':');
const key = line.substr(0, sepIndex).toUpperCase();
const value = line.substr(sepIndex + 1).trim();
//
// Allow mapped value to be either a key:value if there is only
// one entry, or key:[value1, value2,...] if there are more
//
if(messageBodyData.kludgeLines[key]) {
if(!_.isArray(messageBodyData.kludgeLines[key])) {
messageBodyData.kludgeLines[key] = [ messageBodyData.kludgeLines[key] ];
}
messageBodyData.kludgeLines[key].push(value);
} else {
messageBodyData.kludgeLines[key] = value;
}
}
async.series(
[
function extractSauce(callback) {
// :TODO: This is wrong: SAUCE may not have EOF marker for one, also if it's
// present, we need to extract it but keep the rest of hte message intact as it likely
// has SEEN-BY, PATH, and other kludge information *appended*
const sauceHeaderPosition = messageBodyBuffer.indexOf(FTN_MESSAGE_SAUCE_HEADER);
if(sauceHeaderPosition > -1) {
sauce.readSAUCE(messageBodyBuffer.slice(sauceHeaderPosition, sauceHeaderPosition + sauce.SAUCE_SIZE), (err, theSauce) => {
if(!err) {
// we read some SAUCE - don't re-process that portion into the body
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition) + messageBodyBuffer.slice(sauceHeaderPosition + sauce.SAUCE_SIZE);
// messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
messageBodyData.sauce = theSauce;
} else {
console.log(err)
}
callback(null); // failure to read SAUCE is OK
});
} else {
callback(null);
}
},
function extractMessageData(callback) {
const messageLines =
iconv.decode(messageBodyBuffer, 'CP437').replace(/[\xec\n]/g, '').split(/\r/g);
let preOrigin = true;
messageLines.forEach(line => {
if(0 === line.length) {
messageBodyData.message.push('');
return;
}
if(preOrigin) {
if(line.startsWith('AREA:')) {
messageBodyData.area = line.substring(line.indexOf(':') + 1).trim();
} else if(line.startsWith('--- ')) {
// Tear Lines are tracked allowing for specialized display/etc.
messageBodyData.tearLine = line;
} else if(/[ ]{1,2}(\* )?Origin\: /.test(line)) { // To spec is " * Origin: ..."
messageBodyData.originLine = line;
preOrigin = false;
} else if(FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) {
addKludgeLine(line.slice(1));
} else {
// regular ol' message line
messageBodyData.message.push(line);
}
} else {
if(line.startsWith('SEEN-BY:')) {
messageBodyData.seenBy.push(line.substring(line.indexOf(':') + 1).trim());
} else if(FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) {
addKludgeLine(line.slice(1));
}
}
});
callback(null);
}
],
function complete(err) {
messageBodyData.message = messageBodyData.message.join('\n');
cb(messageBodyData);
}
);
};
this.parsePacketMessages = function(messagesBuffer, iterator, cb) {
const NULL_TERM_BUFFER = new Buffer( [ 0 ] );
var count = 0;
binary.stream(messagesBuffer).loop(function looper(end, vars) {
//
// Some variable names used here match up directly with well known
// meta data names used with FTN messages.
//
this
.word16lu('messageType')
.word16lu('ftn_orig_node')
.word16lu('ftn_dest_node')
.word16lu('ftn_orig_network')
.word16lu('ftn_dest_network')
.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
.scan('fromUserName', NULL_TERM_BUFFER) // :TODO: 36 bytes max
.scan('subject', NULL_TERM_BUFFER) // :TODO: 72 bytes max
.scan('message', NULL_TERM_BUFFER)
.tap(function tapped(msgData) {
if(!msgData.ftn_orig_node) {
// end marker -- no more messages
end();
return;
}
if(FTN_PACKET_MESSAGE_TYPE != msgData.messageType) {
end();
// :TODO: This is probably a bug if we hit a bad message after at leats one iterate
cb(new Error('Unsupported message type: ' + msgData.messageType));
return;
}
++count;
//
// Convert null terminated arrays to strings
//
let convMsgData = {};
[ 'modDateTime', 'toUserName', 'fromUserName', 'subject' ].forEach(k => {
convMsgData[k] = iconv.decode(msgData[k], 'CP437');
});
//
// The message body itself is a special beast as it may
// contain special origin lines, kludges, SAUCE in the case
// of ANSI files, etc.
//
let msg = new Message( {
toUserName : convMsgData.toUserName,
fromUserName : convMsgData.fromUserName,
subject : convMsgData.subject,
modTimestamp : ftn.getDateFromFtnDateTime(convMsgData.modDateTime),
});
msg.meta.FtnProperty = {};
msg.meta.FtnProperty.ftn_orig_node = msgData.ftn_orig_node;
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_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) {
msg.message = messageBodyData.message;
msg.meta.FtnKludge = messageBodyData.kludgeLines;
if(messageBodyData.tearLine) {
msg.meta.FtnProperty.ftn_tear_line = messageBodyData.tearLine;
}
if(messageBodyData.seenBy.length > 0) {
msg.meta.FtnProperty.ftn_seen_by = messageBodyData.seenBy;
}
if(messageBodyData.area) {
msg.meta.FtnProperty.ftn_area = messageBodyData.area;
}
if(messageBodyData.originLine) {
msg.meta.FtnProperty.ftn_origin = messageBodyData.originLine;
}
//
// Update message UUID, if possible, based on MSGID and AREA
//
if(_.isString(msg.meta.FtnKludge.MSGID) &&
_.isString(msg.meta.FtnProperty.ftn_area) &&
msg.meta.FtnKludge.MSGID.length > 0 &&
msg.meta.FtnProperty.ftn_area.length > 0)
{
msg.uuid = ftn.createMessageUuid(
msg.meta.FtnKludge.MSGID,
msg.meta.FtnProperty.area);
}
iterator('message', msg);
--count;
if(0 === count) {
cb(null);
}
})
});
});
};
this.writeMessage = function(message, ws) {
let basicHeader = new Buffer(34);
basicHeader.writeUInt16LE(FTN_PACKET_MESSAGE_TYPE, 0);
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_orig_node, 2);
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.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');
dateTimeBuffer.copy(basicHeader, 14);
ws.write(basicHeader);
// toUserName & fromUserName: up to 36 bytes in length, NULL term'd
// :TODO: DRY...
let encBuf = iconv.encode(message.toUserName + '\0', 'CP437').slice(0, 36);
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
ws.write(encBuf);
encBuf = iconv.encode(message.fromUserName + '\0', 'CP437').slice(0, 36);
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
ws.write(encBuf);
// subject: up to 72 bytes in length, NULL term'd
encBuf = iconv.encode(message.subject + '\0', 'CP437').slice(0, 72);
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
ws.write(encBuf);
//
// message: unbound length, NULL term'd
//
// We need to build in various special lines - kludges, area,
// seen-by, etc.
//
// :TODO: Put this in it's own method
let msgBody = '';
function appendMeta(k, m) {
if(m) {
let a = m;
if(!_.isArray(a)) {
a = [ a ];
}
a.forEach(v => {
msgBody += `${k}: ${v}\r`;
});
}
}
//
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// AREA:CONFERENCE
// Should be first line in a message
//
if(message.meta.FtnProperty.ftn_area) {
msgBody += `AREA:${message.meta.FtnProperty.ftn_area}\r`; // note: no ^A (0x01)
}
Object.keys(message.meta.FtnKludge).forEach(k => {
// we want PATH to be last
if('PATH' !== k) {
appendMeta(`\x01${k}`, message.meta.FtnKludge[k]);
}
});
msgBody += message.message;
//
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// Tear line should be near the bottom of a message
//
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'));
};
this.parsePacketBuffer = function(packetBuffer, iterator, cb) {
async.series(
[
function processHeader(callback) {
self.parsePacketHeader(packetBuffer, (err, header) => {
if(!err) {
iterator('header', header);
}
callback(err);
});
},
function processMessages(callback) {
self.parsePacketMessages(
packetBuffer.slice(FTN_PACKET_HEADER_SIZE),
iterator,
callback);
}
],
cb // complete
);
};
}
//
// 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(
[
function getBufferIfPath(callback) {
if(_.isString(pathOrBuffer)) {
fs.readFile(pathOrBuffer, (err, data) => {
pathOrBuffer = data;
callback(err);
});
} else {
callback(null);
}
},
function parseBuffer(callback) {
self.parsePacketBuffer(pathOrBuffer, iterator, err => {
callback(err);
});
}
],
function complete(err) {
cb(err);
}
);
};
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);
}
messages.forEach(msg => {
this.writeMessage(msg, ws);
});
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 }
);
};
/*
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(LOCAL_ADDRESS, REMOTE_ADDRESS);
var packet = new Packet();
var theHeader;
var written = false;
let messagesToWrite = [];
packet.read(
process.argv[2],
function iterator(dataType, data) {
if('header' === dataType) {
theHeader = data;
console.log(theHeader);
} else if('message' === dataType) {
const msg = data;
console.log(msg);
messagesToWrite.push(msg);
let address = {
zone : 46,
net : 1,
node : 232,
domain : 'l33t.codes',
};
msg.areaTag = 'agn_bbs';
msg.messageId = 1234;
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(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'))
console.log('Via: ' + ftn.getVia(address))
console.log(Address.fromString('46:1/232.4@l33t.codes').isMatch('*:1/232.*'))
//console.log(Address.fromString('46:1/232.4@l33t.codes').getMatchScore('46:1/232'))
}
},
function completion(err) {
console.log('complete!')
console.log(err);
console.log(messagesToWrite.length)
packet.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messagesToWrite, err => {
});
}
);
*/