Monstrous amount of changes. Bad commit disciplin.

This commit is contained in:
2015-05-10 23:42:11 +02:00
parent 2307800d22
commit 80c0153b57
4 changed files with 292 additions and 107 deletions
+5 -5
View File
@@ -11,7 +11,7 @@ This will setup a client that connects to localhost, port 2775 without username
var larvitsmpp = require('larvitsmpp'); var larvitsmpp = require('larvitsmpp');
larvitsmpp.client(function(err, clientSession) { larvitsmpp.client(function(err, clientSession) {
clientSession.send({ clientSession.sendSms({
'from': '46701113311', 'from': '46701113311',
'to': '46709771337', 'to': '46709771337',
'message': 'Hello world' 'message': 'Hello world'
@@ -45,7 +45,7 @@ This will setup a client that connects to given host, port with username and pas
throw err; throw err;
} }
console.log('SMS sent, smsId: ' + smsId); console.log('SMS sent, smsId(s): ' + smsId.join(', '));
console.log('Return PDU object:'); console.log('Return PDU object:');
console.log(retPduObj); console.log(retPduObj);
}); });
@@ -101,10 +101,10 @@ Example code below:
} }
// Incoming SMS! // Incoming SMS!
serverSession.on('sms', function(sms, callback) { serverSession.on('sms', function(sms) {
// It is important to run the callback since this is a part of the protocol // It is important to run the sms.resp() since this is a part of the protocol
callback({ sms.resp({
// Decimal value status code // Decimal value status code
// Default is 0 == no error // Default is 0 == no error
+271 -95
View File
@@ -2,8 +2,167 @@
var log = require('winston'), var log = require('winston'),
events = require('events'), events = require('events'),
moment = require('moment'),
utils = require('./utils'), utils = require('./utils'),
defs = require('./defs'); defs = require('./defs'),
async = require('async');
/*
* Send a response to an sms
* This must be called from an sms object context
*
* @param str status - see list at defs.errors - defaults to 'ESME_ROK' - no error (OPTIONAL)
* @param func callback(err, [retPdu, ...])
*/
function smsResp(status, callback) {
var sms = this,
err,
i,
tasks = [];
if (typeof status === 'function') {
callback = status;
status = true;
}
if (typeof callback !== 'function') {
callback = function() {};
}
if (sms.smsId === undefined) {
sms.smsId = '';
}
// Accept a generic positive status
if (status === 'true' || status === true || status === 0) {
status = 'ESME_ROK';
}
// Accept a generic negative status
if (status === 'false' || status === false || status === 1) {
status = 'ESME_RUNKNOWNERR'; // Set to unknown error in this case
}
if (sms.pduObjs === undefined && sms.pduObj === undefined) {
err = new Error('No pdu objects found to base return PDU upon');
log.warn('larvitsmpp: lib/session.js: smsResp() - ' + err.message);
callback(err);
return;
}
if (sms.pduObjs === undefined) {
sms.pduObjs = [sms.pduObjs];
}
// Build async tasks to run the responses in parallel
i = 0;
while (sms.pduObjs[i] !== undefined) {
tasks.push(function(callback) {
sms.session.sendReturn(
sms.pduObj,
status,
{'message_id': sms.smsId},
false,
callback
);
});
i ++;
}
sms.session.sendReturn(
sms.pduObj,
status,
{'message_id': sms.smsId},
false,
callback
);
}
/**
* Send a dlr to an sms
* This must be called from an sms object context
*
* @param str status - see list at defs.consts.MESSAGE_STATE - defaults to 'DELIVERED'
* @param func callback(err, retPdu)
*/
function smsDlr(status, callback) {
var shortMessage,
dlrPduObj,
err,
sms = this;
if (typeof status === 'function') {
callback = status;
status = undefined;
}
if (status === undefined || status === true || status === 2 || status === 'true') {
status = 2;
} else if (defs.consts.MESSAGE_STATE[status] !== undefined) {
status = defs.consts.MESSAGE_STATE[status];
} else if (defs.constsById.MESSAGE_STATE[status]) {
status = parseInt(status);
} else {
status = 5; // UNDELIVERABLE
}
if (typeof callback !== 'function') {
callback = function() {};
}
if (sms.smsId === undefined) {
err = new Error('Trying to send DLR with no smsId.');
log.warn('larvitsmpp: lib/session.js: smsDlr() - ' + err.message);
callback(err);
return;
}
shortMessage = 'id:' + sms.smsId + ' sub:001 ';
if (status === 2) {
shortMessage += 'dlvrd:1 ';
} else {
shortMessage += 'dlvrd:0 ';
}
shortMessage += 'submit date:' + utils.smppDate(sms.submitTime);
shortMessage += ' done date:' + utils.smppDate(new Date());
if (status === 2) {
shortMessage += ' stat:DELIVRD err:0 text:xxx';
} else {
shortMessage += ' stat:UNDELIVERABLE err:1 text:xxx';
}
dlrPduObj = {
'cmdName': 'deliver_sm',
'params': {
'source_addr': sms.from,
'destination_addr': sms.to,
'esm_class': 4,
'short_message': shortMessage
},
'tlvs': {
'receipted_message_id': {
'tagId': 0x001E,
'tagName': 'receipted_message_id',
'tagValue': sms.smsId
},
'message_state': {
'tagId': 0x0427,
'tagName': 'message_state',
'tagValue': status
}
}
};
sms.session.send(dlrPduObj, false, callback);
}
/** /**
* Generic session function * Generic session function
@@ -136,14 +295,14 @@ function session(sock) {
log.debug('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPduObj) - cmdName: ' + incPduObj.cmdName + ' seqNr: ' + incPduObj.seqNr + ' cmdStatus: ' + incPduObj.cmdStatus); log.debug('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPduObj) - cmdName: ' + incPduObj.cmdName + ' seqNr: ' + incPduObj.seqNr + ' cmdStatus: ' + incPduObj.cmdStatus);
// Make sure this is the actual response to the sent PDU // Make sure this is the actual response to the sent PDU
if (incPduObj.isResponse() && incPduObj.seqNr === pduObj.seqNr) { if (incPduObj.isResp() && incPduObj.seqNr === pduObj.seqNr) {
callback(null, incPduObj); callback(null, incPduObj);
if (closeAfterSend) { if (closeAfterSend) {
returnObj.closeSocket(); returnObj.closeSocket();
} }
} else { } else {
err = new Error('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPduObj) - Event triggered but incoming PDU is not a response or seqNr does not match. isResponse: ' + incPduObj.isResponse().toString() + ' incSeqNr: ' + incPduObj.seqNr + ' expected seqNr: ' + pduObj.seqNr); err = new Error('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPduObj) - Event triggered but incoming PDU is not a response or seqNr does not match. isResp: ' + incPduObj.isResp().toString() + ' incSeqNr: ' + incPduObj.seqNr + ' expected seqNr: ' + pduObj.seqNr);
log.warn(err.message); log.warn(err.message);
callback(err); callback(err);
} }
@@ -189,6 +348,8 @@ function session(sock) {
return; return;
} }
log.silly('larvitsmpp: lib/session.js: session() - returnObj.sendReturn() - Sending return PDU: ' + retPdu.toString('hex'));
returnObj.sockWrite(retPdu, closeAfterSend); returnObj.sockWrite(retPdu, closeAfterSend);
if (typeof callback === 'function') { if (typeof callback === 'function') {
@@ -208,7 +369,8 @@ function session(sock) {
* @param func callback(err, smsId, retPduObj) * @param func callback(err, smsId, retPduObj)
*/ */
returnObj.sendLongSms = function(smsOptions, callback) { returnObj.sendLongSms = function(smsOptions, callback) {
var msgs = utils.splitMsg(smsOptions.message), var smsIds = [],
msgs = utils.splitMsg(smsOptions.message),
encoding = defs.encodings.detect(smsOptions.message); // Set encoding once for all message parts encoding = defs.encodings.detect(smsOptions.message); // Set encoding once for all message parts
function sendPart(i) { function sendPart(i) {
@@ -233,8 +395,10 @@ function session(sock) {
log.debug('larvitsmpp: lib/session.js: returnObj.sendLongSms() - pduObj: ' + JSON.stringify(pduObj)); log.debug('larvitsmpp: lib/session.js: returnObj.sendLongSms() - pduObj: ' + JSON.stringify(pduObj));
returnObj.send(pduObj, function(err, retPduObj) { returnObj.send(pduObj, function(err, retPduObj) {
smsIds.push(retPduObj.params.message_id);
if (typeof callback === 'function' && msgs[i + 1] === undefined) { if (typeof callback === 'function' && msgs[i + 1] === undefined) {
callback(err, retPduObj.params.message_id, retPduObj); callback(err, smsIds, retPduObj);
} else if (msgs[i + 1] !== undefined) { } else if (msgs[i + 1] !== undefined) {
sendPart(i + 1); sendPart(i + 1);
} }
@@ -281,97 +445,106 @@ function session(sock) {
returnObj.send(pduObj, function(err, retPduObj) { returnObj.send(pduObj, function(err, retPduObj) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(err, retPduObj.params.message_id, retPduObj); callback(err, [retPduObj.params.message_id], retPduObj);
} }
}); });
}; };
/** // Temporary storage for long sms parts
* Send a DLR // These should be cleared if they lingre to long to avoid memory leaks
* returnObj.longSmses = {};
* @param obj sms - sms object
* @param bol status - defaults to true
* @param func callback(err)
*/
returnObj.sendDlr = function(sms, status, callback) {
var shortMessage = 'id:' + sms.smsId + ' sub:001 ',
dlrPduObj;
if (typeof status === 'function') { // Store long smses in the temporary storage
callback = status; returnObj.longSms = function(pduObj) {
status = true; var smsGroupId = pduObj.params.short_message[3],
} smsParts = pduObj.params.short_message[4],
longSmsId = pduObj.params.source_addr + '_' + pduObj.params.destination_addr + '_' + smsGroupId;
if (status === undefined) { if (returnObj.longSmses[longSmsId] === undefined) {
status = true; returnObj.longSmses[longSmsId] = {
} 'created': new Date(),
'partsCount': parseInt(smsParts),
if (status) { 'pduObjs': [{
shortMessage += 'dlvrd:1 '; 'partNr': pduObj.params.short_message[5], // We save this here to easier sort the array later on
'pduObj': pduObj
}]
};
} else { } else {
shortMessage += 'dlvrd:0 '; returnObj.longSmses[longSmsId].pduObjs.push(pduObj);
}
shortMessage += 'submit date:' + utils.smppDate(sms.submitTime);
shortMessage += ' done date:' + utils.smppDate(new Date());
if (status) {
shortMessage += ' stat:DELIVRD err:0 text:xxx';
} else {
shortMessage += ' stat:UNDELIVERABLE err:1 text:xxx';
}
dlrPduObj = {
'cmdName': 'deliver_sm',
'params': {
'source_addr': sms.from,
'destination_addr': sms.to,
'esm_class': 4,
'short_message': shortMessage
},
'tlvs': {
'receipted_message_id': {
'tagId': 0x001E,
'tagName': 'receipted_message_id',
'tagValue': sms.smsId
},
'message_state': {
'tagId': 0x0427,
'tagName': 'message_state',
'tagValue': 2
}
} }
}; };
if ( ! status) { // Walk through the long sms storage to investigate if we can send complete messages along
dlrPduObj.tlvs.message_state.tagValue = 5; // or should remove old ones
returnObj.checkLongSmses = function() {
var smsGroupId,
smsGroup,
smsObj,
i,
curPduObj;
// Sort function to sort group parts
function sortLongSmsPdus(a, b) {
if (a.partNr < b.partNr) {
return - 1;
} }
dlrPduObj.seqNr = 323; if (a.partNr > b.partNr) {
return 1;
utils.objToPdu(dlrPduObj, function(err, pdu) {
if (err) {
throw err;
} }
utils.pduToObj(pdu, function(err, newObj) { return 0;
if (err) {
throw err;
} }
console.log('newObj:'); // Callback function for emitted sms event
console.log(newObj); function smsReceived(smsData) {
}); delete returnObj.longSmses[smsGroupId];
}); }
returnObj.send(dlrPduObj, function(err, retPduObj) { for (smsGroupId in returnObj.longSmses) {
console.log('WHATTA WHATTA'); smsGroup = returnObj.longSmses[smsGroupId];
console.log(retPduObj);
// All parts are accounted for! Emit sms event and clear from tmp storage
if (typeof callback === 'function') { if (smsGroup.partsCount === smsGroup.pduObjs.length) {
callback(err, retPduObj.params.message_id, retPduObj); log.debug('larvitsmpp: lib/session.js: session() - returnObj.checkLongSmses() - All parts accounted for in smsGroupId "' + smsGroupId + '", emitting sms event.');
smsObj = {
// These are needed for references here and there in functions
'session': returnObj,
'pduObjs': smsGroup.pduObjs,
'from': smsGroup.pduObjs[0].params.source_addr,
'to': smsGroup.pduObjs[0].params.destination_addr,
'submitTime': new Date(),
'message': '',
'dlr': Boolean(smsGroup.pduObjs[0].params.registered_delivery),
'sendResp': smsResp,
'sendDlr': smsDlr
};
// Concatenate all the parts messages to one and set references to the session
// First we need to sort the parts, since they can come in random order
smsObj.pduObjs.sort(sortLongSmsPdus);
i = 0;
while (smsObj.pduObjs[i] !== undefined) {
curPduObj = smsObj.pduObjs[i];
curPduObj.session = returnObj;
smsObj.message += utils.decode(curPduObj.params.short_message, curPduObj.params.data_coding, curPduObj.params.short_message[0]);
i ++;
}
returnObj.emit('sms', smsObj);
} else if (moment(new Date()).diff(smsGroup.created, 'hours') > 24) {
log.info('larvitsmpp: lib/session.js: session() - returnObj.checkLongSmses() - smsGroupId "' + smsGroupId + '" is removed from returnObj.longSmses due to being older than 24 hours.');
delete returnObj.longSmses[smsGroupId];
}
} }
});
}; };
// Handle incomming commands. // Handle incomming commands.
@@ -384,7 +557,7 @@ function session(sock) {
// TLV message_state must exists // TLV message_state must exists
if (pduObj.tlvs.message_state === undefined) { if (pduObj.tlvs.message_state === undefined) {
log.info('larvitsmpp: lib/session.js: session() - returnObj.deliverSm() - TLV message_state is missing. SeqNr: ' + pduObj.seqNr); log.info('larvitsmpp: lib/session.js: session() - returnObj.handleCmd.deliver_sm() - TLV message_state is missing. SeqNr: ' + pduObj.seqNr);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM'); returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return; return;
@@ -392,7 +565,7 @@ function session(sock) {
// TLV message_state needs to be valid // TLV message_state needs to be valid
if (defs.constsById.MESSAGE_STATE[pduObj.tlvs.message_state.tagValue] === undefined) { if (defs.constsById.MESSAGE_STATE[pduObj.tlvs.message_state.tagValue] === undefined) {
log.info('larvitsmpp: lib/session.js: session() - returnObj.deliverSm() - Invalid TLV message_state: "' + pduObj.tlvs.message_state.tagValue + '". SeqNr: ' + pduObj.seqNr); log.info('larvitsmpp: lib/session.js: session() - returnObj.handleCmd.deliver_sm() - Invalid TLV message_state: "' + pduObj.tlvs.message_state.tagValue + '". SeqNr: ' + pduObj.seqNr);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM'); returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return; return;
@@ -400,7 +573,7 @@ function session(sock) {
// TLV receipted_message_id must exist // TLV receipted_message_id must exist
if (pduObj.tlvs.receipted_message_id === undefined) { if (pduObj.tlvs.receipted_message_id === undefined) {
log.info('larvitsmpp: lib/session.js: session() - returnObj.deliverSm() - TLV receipted_message_id is missing. SeqNr: ' + pduObj.seqNr); log.info('larvitsmpp: lib/session.js: session() - returnObj.handleCmd.deliver_sm() - TLV receipted_message_id is missing. SeqNr: ' + pduObj.seqNr);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM'); returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return; return;
@@ -427,25 +600,28 @@ function session(sock) {
returnObj.handleCmd.submit_sm = function(pduObj) { returnObj.handleCmd.submit_sm = function(pduObj) {
var smsObj = {}; var smsObj = {};
// If esm_class is 0x40 it means this is just a part of a larger message
if (pduObj.params.esm_class === 0x40) {
returnObj.longSms(pduObj);
return; // Long messages should not get handled here at all, so cancel execution here
}
smsObj = { smsObj = {
// These are needed for references here and there in functions
'session': returnObj,
'pduObj': pduObj,
'from': pduObj.params.source_addr, 'from': pduObj.params.source_addr,
'to': pduObj.params.destination_addr, 'to': pduObj.params.destination_addr,
'submitTime': new Date(), 'submitTime': new Date(),
'message': pduObj.params.short_message, 'message': pduObj.params.short_message,
'dlr': Boolean(pduObj.params.registered_delivery) 'dlr': Boolean(pduObj.params.registered_delivery),
'sendResp': smsResp,
'sendDlr': smsDlr
}; };
function smsReceived(smsData) { returnObj.emit('sms', smsObj);
if (smsData === undefined) {
smsData = {};
}
smsObj.smsId = smsData.smsId;
returnObj.sendReturn(pduObj, 'ESME_ROK', {'message_id': smsObj.smsId});
}
returnObj.emit('sms', smsObj, smsReceived);
}; };
// Handle incoming unbind // Handle incoming unbind
@@ -534,7 +710,7 @@ function session(sock) {
} else { } else {
log.verbose('larvitsmpp: lib/session.js: session() - returnObj.on(incomingPdu) - Incoming PDU parsed. Seqnr: ' + pduObj.seqNr + ' cmd: ' + pduObj.cmdName + ' cmdStatus: ' + pduObj.cmdStatus + ' hex: ' + pdu.toString('hex')); log.verbose('larvitsmpp: lib/session.js: session() - returnObj.on(incomingPdu) - Incoming PDU parsed. Seqnr: ' + pduObj.seqNr + ' cmd: ' + pduObj.cmdName + ' cmdStatus: ' + pduObj.cmdStatus + ' hex: ' + pdu.toString('hex'));
if (pduObj.isResponse()) { if (pduObj.isResp()) {
// We do this so we can remove the dynamic event listeners to not have a memory leak // We do this so we can remove the dynamic event listeners to not have a memory leak
returnObj.emit('incomingPduObj' + pduObj.seqNr, pduObj); returnObj.emit('incomingPduObj' + pduObj.seqNr, pduObj);
+8 -3
View File
@@ -159,11 +159,16 @@ function writeBuffer(obj, cmdLength, callback) {
* *
* @param buf buffer * @param buf buffer
* @param str encoding 'ASCII', 'LATIN1' or 'UCS2' or hex values * @param str encoding 'ASCII', 'LATIN1' or 'UCS2' or hex values
* @param int offset - defaults to 0
* @return str in utf8 format * @return str in utf8 format
*/ */
function decodeMsg(buffer, encoding) { function decodeMsg(buffer, encoding, offset) {
var checkEnc; var checkEnc;
if (offset === undefined) {
offset = 0;
}
for (checkEnc in defs.consts.ENCODING) { for (checkEnc in defs.consts.ENCODING) {
if (parseInt(encoding) === defs.consts.ENCODING[checkEnc] || encoding === checkEnc) { if (parseInt(encoding) === defs.consts.ENCODING[checkEnc] || encoding === checkEnc) {
encoding = checkEnc; encoding = checkEnc;
@@ -175,7 +180,7 @@ function decodeMsg(buffer, encoding) {
encoding = 'ASCII'; encoding = 'ASCII';
} }
return defs.encodings[encoding].decode(buffer); return defs.encodings[encoding].decode(buffer.slice(offset, buffer.length));
} }
function encodeMsg(str) { function encodeMsg(str) {
@@ -208,7 +213,7 @@ function pduToObj(pdu, stupidNullByte, callback) {
} }
// Returns true if this PDU is a response to another PDU // Returns true if this PDU is a response to another PDU
retObj.isResponse = function() { retObj.isResp = function() {
return ! ! (this.cmdId & 0x80000000); return ! ! (this.cmdId & 0x80000000);
}; };
+7 -3
View File
@@ -9,7 +9,9 @@
"dependencies": { "dependencies": {
"winston": "latest", "winston": "latest",
"utils-merge": "latest", "utils-merge": "latest",
"iconv-lite": "latest" "iconv-lite": "latest",
"moment": "latest",
"async": "latest"
}, },
"description": "Simplified SMPP implementation", "description": "Simplified SMPP implementation",
"devDependencies": { "devDependencies": {
@@ -27,12 +29,14 @@
"url": "https://github.com/larvit/larvitsmpp", "url": "https://github.com/larvit/larvitsmpp",
"type": "git" "type": "git"
}, },
"version": "0.0.2beta", "version": "0.0.3",
"readmeFilename": "README.md", "readmeFilename": "README.md",
"readme": "larvitsmpp", "readme": "larvitsmpp",
"bugs": { "bugs": {
"url": "https://github.com/larvit/larvitsmpp/issues" "url": "https://github.com/larvit/larvitsmpp/issues"
}, },
"homepage": "https://github.com/larvit/larvitsmpp", "homepage": "https://github.com/larvit/larvitsmpp",
"scripts": {} "scripts": {
"test": "mocha"
}
} }