Files
smpp-js/lib/session.js
T
2015-05-01 19:37:08 +02:00

456 lines
13 KiB
JavaScript

'use strict';
var log = require('winston'),
events = require('events'),
smppUtils = require('./utils'),
defs = require('./defs');
/**
* Generic session function
*
* @param obj sock - socket object
* @return obj (returnObj)
*/
function session(sock) {
var returnObj = new events.EventEmitter();
log.silly('larvitsmpp: lib/session.js: session() - New session started from ' + sock.remoteAddress + ':' + sock.remotePort);
returnObj.loggedIn = false;
// Sequence number used for commands initiated from us
returnObj.ourSeqNr = 1;
// Make the socket transparent via the returned emitter
returnObj.sock = sock;
/**
* Increase our sequence number
*/
returnObj.incOurSeqNr = function() {
returnObj.ourSeqNr = returnObj.ourSeqNr + 1;
// If we pass the maximum, start over at 1
if (returnObj.ourSeqNr > 2147483646) {
returnObj.ourSeqNr = 1;
}
};
/**
* Close the socket
* Always use this function to close the socket so we get it on log
*/
returnObj.closeSocket = function() {
log.verbose('larvitsmpp: lib/session.js: session() - closeSocket() - Closing socket for ' + sock.remoteAddress + ':' + sock.remotePort);
if (returnObj.enqLinkTimer) {
log.debug('larvitsmpp: lib/session.js: session() - closeSocket() - enqLinkTimer found, clearing.');
clearTimeout(returnObj.enqLinkTimer);
}
sock.destroy();
};
/**
* Write PDU to socket
*
* @param buf or obj pdu - can also take PDU object
* @param bol closeAfterSend - if true will close the socket after sending
*/
returnObj.sockWrite = function(pdu, closeAfterSend) {
if ( ! Buffer.isBuffer(pdu)) {
smppUtils.objToPdu(pdu, function(err, buffer) {
if (err) {
log.warn('larvitsmpp: lib/session.js: session() - sockWrite() - Could not convert PDU to buffer');
returnObj.closeSocket();
return;
}
returnObj.sockWrite(buffer);
});
return;
}
try {
log.verbose('larvitsmpp: lib/session.js: session() - sockWrite() - sending PDU. SeqNr: ' + pdu.readUInt32BE(12) + ' cmd: ' + defs.cmdsById[pdu.readUInt32BE(4)].command + ' cmdStatus: ' + defs.errorsById[parseInt(pdu.readUInt32BE(8))] + ' hex: ' + pdu.toString('hex'));
} catch (e) {
log.error('larvitsmpp: lib/session.js: session() - sockWrite() - PDU buffer is invalid. Buffer hex: "' + pdu.toString('hex') + '"');
return;
}
sock.write(pdu);
if (closeAfterSend) {
returnObj.closeSocket();
}
};
/**
* Send a PDU to the remote
*
* @param buf or obj pdu
* @param bol closeAfterSend - Will close after return is fetched. Defaults to false (OPTIONAL)
* @param func callback(err, retPdu) (OPTIONAL)
*/
returnObj.send = function(pdu, closeAfterSend, callback) {
var pduObj = pdu,
err = null;
// Make sure the pdu is an object
if (Buffer.isBuffer(pdu)) {
smppUtils.pduToObj(pdu, function(err, pduObj) {
if (err) {
callback(err);
return;
}
returnObj.send(pduObj, closeAfterSend, callback);
});
return;
}
// Make sure the sequence number is set and is correct
pduObj.seqNr = returnObj.ourSeqNr;
log.debug('larvitsmpp: lib/session.js: session() - returnObj.send() - Sending PDU to remote. cmdName: ' + pduObj.cmdName + ' seqNr: ' + pduObj.seqNr);
// If closeAndSend is omitted, put callback in its place
if (typeof closeAfterSend === 'function') {
callback = closeAfterSend;
closeAfterSend = undefined;
}
// Make sure the callack is a function
if (typeof callback !== 'function') {
callback = function(){};
}
// Response PDUs are not allowed with the send() command, they should use the sendReturn()
if (pduObj.cmdName.substring(pduObj.cmdName - 5) === '_resp') {
err = new Error('larvitsmpp: lib/session.js: session() - returnObj.send() - Given pduObj is a response, use sendReturn() instead. cmdName: ' + pduObj.cmdName);
callback(err);
return;
}
// When the return is fetched, call the callback
returnObj.on('incomingPdu' + pduObj.seqNr, function(incPduObj) {
log.debug('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPdu) - cmdName: ' + incPduObj.cmdName + ' seqNr: ' + incPduObj.seqNr + ' cmdStatus: ' + incPduObj.cmdStatus);
// Clean up by removing this listener or else it will lurk along forever
returnObj.removeAllListeners('incomingPdu' + pduObj.seqNr);
// Make sure this is the actual response to the sent PDU
if (incPduObj.isResponse() && incPduObj.seqNr === pduObj.seqNr) {
callback(null, incPduObj);
if (closeAfterSend) {
returnObj.closeSocket();
}
} else {
err = new Error('larvitsmpp: lib/session.js: session() - returnObj.send() - returnObj.on(incomingPdu) - 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);
log.warn(err.message);
callback(err);
}
});
// Increase our internal sequence number
returnObj.incOurSeqNr();
// Write the PDU to socket
returnObj.sockWrite(pduObj);
};
/**
* Send a return to given PDU
*
* @param obj or buf pdu
* @param str status - see list at defs.errors - defaults to 'ESME_ROK' - no error (OPTIONAL)
* @param obj params (OPTIONAL)
* @param bol closeAfterSend - if true will close the socket after sending (OPTIONAL)
* @param func callback(err, retPdu) (OPTIONAL)
*/
returnObj.sendReturn = function(pdu, status, params, closeAfterSend, callback) {
if (typeof params === 'function') {
callback = params;
params = undefined;
closeAfterSend = undefined;
}
if (typeof closeAfterSend === 'function') {
callback = closeAfterSend;
closeAfterSend = undefined;
}
smppUtils.pduReturn(pdu, status, params, function(err, retPdu) {
if (err) {
log.error('larvitsmpp: lib/session.js: session() - returnObj.sendReturn() - Could not create return PDU: ' + err.message);
returnObj.closeSocket();
if (typeof callback === 'function') {
callback(err);
}
return;
}
returnObj.sockWrite(retPdu, closeAfterSend);
if (typeof callback === 'function') {
callback(null, retPdu);
}
});
};
/**
* Send an SMS
*
* @param obj smsOptions
* from - alphanum or international format
* to - international format
* message - string
* dlrRequested - boolean defaults to false
* @param func callback(err, smsId, retPduObj)
*/
returnObj.sendSms = function(smsOptions, callback) {
var pduObj = {};
pduObj.cmdName = 'submit_sm';
pduObj.params = {
'source_addr': smsOptions.from,
'destination_addr': smsOptions.to,
'short_message': smsOptions.message
};
// Request DLRs!
if (smsOptions.dlrRequested) {
pduObj.params.registered_delivery = 0x01;
}
returnObj.send(pduObj, function(err, retPduObj) {
if (typeof callback === 'function') {
callback(err, retPduObj.params.message_id, retPduObj);
}
});
};
/**
* Send a DLR
*
* @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 ',
err,
dlrPduObj;
if (typeof status === 'function') {
callback = status;
status = true;
}
if (status === undefined) {
status = true;
}
if (status) {
shortMessage += 'dlvrd:1 ';
} else {
shortMessage += 'dlvrd:0 ';
}
shortMessage += 'submit date:' + smppUtils.smppDate(sms.submitTime);
shortMessage += ' done date:' + smppUtils.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) {
dlrPduObj.tlvs.message_state.tagValue = 5;
}
dlrPduObj.seqNr = 323;
console.log('before:');
console.log(dlrPduObj);
smppUtils.objToPdu(dlrPduObj, function(err, pdu) {
if (err) {
throw err;
}
smppUtils.pduToObj(pdu, function(err, newObj) {
if (err) {
throw err;
}
console.log('newObj:');
console.log(newObj);
});
});
return;
returnObj.send(dlrPduObj, function(err, retPduObj) {
console.log('WHATTA WHATTA');
console.log(retPduObj);
/*if (typeof callback === 'function') {
callback(err, retPduObj.params.message_id, retPduObj);
}*/
});
};
// Handle incoming deliver_sm
returnObj.deliverSm = function(pduObj) {
var dlrObj;
// TLV message_state must exists
if (pduObj.tlvs.message_state === undefined) {
log.info('larvitsmpp: lib/session.js: session() - returnObj.deliverSm() - TLV message_state is missing. SeqNr: ' + pduObj.seqNr);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return;
}
// TLV message_state needs to be valid
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);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return;
}
// TLV receipted_message_id must exist
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);
returnObj.sendReturn(pduObj, 'ESME_RINVTLVSTREAM');
return;
}
dlrObj = {
'statusMsg': defs.constsById.MESSAGE_STATE[pduObj.tlvs.message_state.tagValue],
'statusId': pduObj.tlvs.message_state.tagValue,
'smsId': pduObj.tlvs.receipted_message_id.tagValue
};
returnObj.emit('dlr', dlrObj, pduObj);
returnObj.sendReturn(pduObj);
};
// Handle incoming submit_sm
returnObj.submitSm = function(pduObj) {
var smsObj = {};
smsObj = {
'from': pduObj.params.source_addr,
'to': pduObj.params.destination_addr,
'submitTime': new Date(),
'message': pduObj.params.short_message,
'dlrRequested': Boolean(pduObj.params.registered_delivery)
};
function smsReceived(smsData) {
if (smsData === undefined) {
smsData = {};
}
smsObj.smsId = smsData.smsId;
returnObj.sendReturn(pduObj, 'ESME_ROK', {'message_id': smsObj.smsId});
}
returnObj.emit('sms', smsObj, smsReceived);
};
// Dummy, should be extended by serverSession or clientSession
returnObj.login = function() {
log.info('larvitsmpp: lib/session.js: session() - login() - Dummy login function ran, this might be a mistake');
returnObj.loggedIn = true;
};
// Dummy method - should be used by serverSession or clientSession
returnObj.resetEnqLinkTimer = function() {
log.silly('larvitsmpp: lib/session.js: session() - resetEnqLinkTimer() - Resetting the kill timer');
};
returnObj.enquireLink = function(pduObj) {
log.silly('larvitsmpp: lib/session.js: session() - enquireLink() - Enquiring link');
returnObj.resetEnqLinkTimer();
returnObj.sendReturn(pduObj);
};
returnObj.unbind = function() {
returnObj.send({
'cmdName': 'unbind'
}, true);
};
// Add a 'data' event handler to this instance of socket
sock.on('data', function(pduBuf) {
// Pass the data along to the returnObj
returnObj.emit('data', pduBuf);
// Reset the enquire link timer
returnObj.resetEnqLinkTimer();
log.silly('larvitsmpp: lib/session.js: session() - sock.on(data) - Incoming PDU: ' + pduBuf.toString('hex'));
smppUtils.pduToObj(pduBuf, function(err, pduObj) {
if (err) {
log.warn('larvitsmpp: lib/session.js: session() - Invalid PDU. ' + err.message);
returnObj.closeSocket();
} else {
log.verbose('larvitsmpp: lib/session.js: session() - sock.on(data) - Incoming PDU. Seqnr: ' + pduObj.seqNr + ' cmd: ' + pduObj.cmdName + ' cmdStatus: ' + pduObj.cmdStatus + ' hex: ' + pduBuf.toString('hex'));
if (pduObj.isResponse()) {
// We do this so we can remove the dynamic event listeners to not have a memory leak
returnObj.emit('incomingPdu' + pduObj.seqNr, pduObj);
} else {
returnObj.emit('incomingPdu', pduObj);
}
}
});
});
// Add a 'close' event handler to this instance of socket
sock.on('close', function() {
returnObj.emit('close');
if (returnObj.enqLinkTimer) {
log.debug('larvitsmpp: lib/session.js: session() - sock.on(close) - enqLinkTimer found, clearing.');
clearTimeout(returnObj.enqLinkTimer);
}
log.debug('larvitsmpp: lib/session.js: session() - sock.on(close) - socket closed');
});
return returnObj;
}
// Expose some functions
exports = module.exports = session;