From 8b8f1c6b5995dabdaf61a5a0770dfed999765d5c Mon Sep 17 00:00:00 2001 From: lillem4n Date: Sun, 29 Mar 2015 11:37:23 +0200 Subject: [PATCH] Complete rewrite --- README.md | 60 +++++++- larvitsmpp.js | 363 +++++++++++++++++++++------------------------- larvitsmpp_old.js | 220 ++++++++++++++++++++++++++++ package.json | 5 +- 4 files changed, 446 insertions(+), 202 deletions(-) create mode 100644 larvitsmpp_old.js diff --git a/README.md b/README.md index 2e39f58..e0d3998 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,61 @@ # Larv IT SMPP -A wrapper for the smpp module, trying to make it more user friendly. +This is a simplified implementation of the SMPP protocol. It only supports transciever mode and all messages are sent via the "data_sm" command. + +## Server + +This will setup a password less server on localhost, port 2775 and console.log() incomming commands. + + var larvitsmpp = require('larvitsmpp'); + + larvitsmpp.server(function(err, serverSession) { + if (err) { + throw err; + } + + serverSession.on('data', function(data) { + console.log('command: ' + data.command); + }); + }); + +## Client + +This will setup a client that connects to localhost, port 2775 without username or password and send a message. + + var larvitsmpp = require('larvitsmpp'); + + larvitsmpp.client(function(err, clientSession) { + clientSession.send({ + 'sender': 46701113311, + 'receiver': 46709771337, + 'message': 'Hello world' + }, function(err, res) { + if (err) { + throw err; + } + + if (res) { + console.log('Woho! Message sent'); + } else { + console.log('Server did not accept :('); + } + }); + + clientSession.close(); + }); + +## Advanced server + +This example and its comments covers a lot of configuration options. + + var larvitsmpp = require('larvitsmpp'); + + larvitsmpp.server(function(err, serverSession) { + if (err) { + throw err; + } + + serverSession.on('data', function(data) { + console.log('command: ' + data.command); + }); + }); diff --git a/larvitsmpp.js b/larvitsmpp.js index 6e63c64..e97e1dc 100644 --- a/larvitsmpp.js +++ b/larvitsmpp.js @@ -1,220 +1,187 @@ -/** - * SMPP Wrapper - * - * Error codes: http://www.activexperts.com/activsms/sms/smpperrorcodes/ - */ 'use strict'; -var log = require('winston'), - merge = require('utils-merge'), - smpp = require('smpp'); +var log = require('winston'), + merge = require('utils-merge'), + net = require('net'); + EventEmitter = require('events').EventEmitter; -/** - * Set up SMPP server - * - * @param obj options - * * port - what port to bind to, defaults to 2775 - * * checkUserAndPass - to require username and password to bind - * to this server this should be a function, taking three - * parameters: username, password and callback(err) - * * timeout - number of ms before the link should be considered dead. Defaults to 30000 (30 sec) - * - */ -exports.server = function(options) { +function Session(options) { + var self = this; // Makes this available even in lower level function scopes + + // Also make this Session an eventEmitter + EventEmitter.call(this); + + // Set default options options = merge({ - 'port': 2775, - 'checkUserAndPass': false, - 'timeout': 30000 - }, options); + 'host': 'localhost', + 'port': 2775 + }, options || {}); - smpp.createServer(function(smppSession) { - var loggedIn = false, - killTimer; + // Initiate sequence at 0 + // This will increment on each command sent over this session + this.sequence = 0; - log.debug('larvitsmpp: server() - server session started'); - - function resetKillTimer() { - log.silly('larvitsmpp: server() - Resetting the kill timer'); - if (killTimer) { - clearTimeout(killTimer); - } - - killTimer = setTimeout(function() { - log.warn('larvitsmpp: server() - Closing session due to timeout'); - smppSession.close(); - }, options.timeout); - } - resetKillTimer(); - - /** - * Set logged in to true - */ - function login(pdu) { - loggedIn = true; - - smppSession.send(pdu.response({ - 'command_status': smpp.ESME_ROK - })); - - smppSession.resume(); - } - - /** - * General bind function for bind_receiver, bind_transmitter and bind_transceiver - * - * @param obj pdu - */ - function bindGeneral(pdu) { - if (options.checkUserAndPass instanceof Function) { - // We pause the smppSession to prevent further incoming pdu events, - // Untill we authorize the smppSession with some async operation. - smppSession.pause(); - log.debug('larvitsmpp: server() - Checking username and password'); - - options.checkUserAndPass(pdu.system_id, pdu.password, function(err) { - if (err) { - log.warn('larvitsmpp: server() - Wrong username or password. Username: "' + pdu.system_id + '"'); - - smppSession.send(pdu.response({ - 'command_status': smpp.ESME_RBINDFAIL - })); - smppSession.close(); - loggedIn = false; - return; - } - - log.debug('larvitsmpp: server() - Username and password is ok'); - login(pdu); - smppSession.resume(); - }); - } else { - login(pdu); - } - } - - smppSession.on('error', function() { - log.error('larvitsmpp: server() - smppSession error!', arguments); + if (options.socket) { + this.socket = options.socket; + } else { + this.port = options.port; + this.host = options.host; + this.socket = net.connect(this.port, this.host); + this.socket.on('connect', function() { + self.emit('connect'); }); + } + this.socket.on('data', function(chunk) { + self._buffer = Buffer.concat([self._buffer, chunk]); + self._extractPDUs(); + }); + this.socket.on('close', function() { + self.emit('close'); + }); + this.socket.on('error', function(e) { + self.emit('error', e); + }); +} - smppSession.on('pdu', function(pdu) { - log.silly('larvitsmpp: server() - Received command "' + pdu.command + '"'); +util.inherits(Session, EventEmitter); - resetKillTimer(); - - if (pdu.command === 'bind_receiver' || pdu.command === 'bind_transmitter' || pdu.command === 'bind_transceiver') { - bindGeneral(pdu); - } else if (pdu.command !== 'enquire_link') { - if (loggedIn) { - log.error('larvitsmpp: server() - Unkown command!', pdu); - - console.log('PDU!!!!'); - console.log(arguments); - } else { - log.warn('larvitsmpp: server() - Not logged in but trying to send non-bind command'); - - smppSession.send(pdu.response({ - 'command_status': smpp.ESME_RSERTYPUNAUTH - })); - } - } - }); - - smppSession.on('enquire_link', function(pdu) { - var sendRet; - - log.silly('larvitsmpp: server() - Enquire_link - client heart beat.'); - - sendRet = smppSession.send(pdu.response({ - 'command_status': smpp.ESME_ROK - })); - - if ( ! sendRet) { - // No writeable socket, do something! - } - - }); - }).listen(options.port); +Session.prototype.connect = function() { + this.sequence = 0; + this._callbacks = []; + this._buffer = new Buffer(0); + this.socket.connect(this.port, this.host); }; -/** - * Connect as a client to an SMPP server - * - * @param obj options - * * host (default 127.0.0.1) - * * port (default 2775) - * * username (default false) - * * password (default false) - * * mode (default 'transceiver', other options: 'receiver', 'transmitter') - * * heartbeat - set the heartbeat interval (default 10000) - * @param func callback(err, returnObj) - */ -exports.client = function(options, callback) { - var smppSession, - heartbeatTimer, - err, - returnObj = {'smppSession': smppSession}, - bindOptions = {}; - - function heartbeat() { - log.silly('larvitsmpp: client() - heartbeat() - called'); - - smppSession.enquire_link(); - heartbeatTimer = setTimeout(heartbeat, options.heartbeat); - // Here we need a timeout and then we need to reconnect +Session.prototype._extractPDUs = function() { + var pdu; + while (!this.paused && (pdu = PDU.fromBuffer(this._buffer))) { + this._buffer = this._buffer.slice(pdu.command_length); + this.emit('pdu', pdu); + this.emit(pdu.command, pdu); + if (pdu.isResponse() && this._callbacks[pdu.sequence_number]) { + this._callbacks[pdu.sequence_number](pdu); + delete this._callbacks[pdu.sequence_number]; + } } +}; - returnObj.close = function() { - clearTimeout(heartbeatTimer); - smppSession.close(); +Session.prototype.send = function(pdu, callback) { + if (!this.socket.writable) { + return false; + } + if (!pdu.isResponse()) { + // when server/session pair is used to proxy smpp + // traffic, the sequence_number will be provided by + // client otherwise we generate it automatically + if (!pdu.sequence_number) { + pdu.sequence_number = ++this.sequence; + } + + if (callback) { + this._callbacks[pdu.sequence_number] = callback; + } + } + this.socket.write(pdu.toBuffer(), function() { + this.emit('send', pdu); + }.bind(this)); + return true; +}; + +Session.prototype.pause = function() { + this.paused = true; + this.socket.pause(); +}; + +Session.prototype.resume = function() { + this.paused = false; + this.socket.resume(); + this._extractPDUs(); +}; + +Session.prototype.close = function() { + this.socket.end(); +}; + +var createShortcut = function(command) { + return function(options, callback) { + return this.send(new PDU(command, options), callback); }; +}; - options = merge({ - 'host': '127.0.0.1', - 'port': 2775, - 'username': undefined, - 'password': undefined, - 'mode': 'transceiver', - 'heartbeat': 10000 - }, options); +for (var command in defs.commands) { + Session.prototype[command] = createShortcut(command); +} - log.info('larvitsmpp: client() - Connecting to SMPP server at ' + options.host + ':' + options.port); - smppSession = smpp.connect(options.host, options.port); +function Server() { + var options, self = this; + this.sessions = []; - if (options.username !== undefined && options.password !== undefined) { - bindOptions.system_id = options.username; - bindOptions.password = options.password; - } - - if (options.mode === 'transceiver') { - log.error('bajs'); - } else if (options.mode === 'receiver') { - log.error('skabb'); - } else if (options.mode === 'transmitter') { - smppSession.bind_transmitter(bindOptions, function(pdu) { - var err; - - log.info('larvitsmpp: client() - bind_transmitter done.'); - - if (pdu.command_status === 0) { - log.info('larvitsmpp: client() - bind_transmitter returned 0, success!'); - - heartbeat(); - callback(null, returnObj); - } else { - err = new Error('models/smppsend.js: bind_transmitter returned "' + pdu.command_status + '", fail!'); - log.warn(err.message, pdu); - smppSession.close(); - callback(err); - } - }); + if (typeof arguments[0] == 'function') { + options = {}; + this.on('session', arguments[0]); } else { - err = new Error('larvitsmpp: client() - Invalid connection mode'); - log.error(err.message); - callback(err); + options = arguments[0] || {}; + if (typeof arguments[1] == 'function') { + this.on('session', arguments[1]); + } } - smppSession.on('pdu', function(pdu) { - log.silly('larvitsmpp: client() - Received command "' + pdu.command + '"'); + net.Server.call(this, options, function(socket) { + var session = new Session({socket: socket}); + session.server = self; + self.sessions.push(session); + socket.on('close', function() { + self.sessions.splice(self.sessions.indexOf(socket), 1); + }); + self.emit('session', session); }); +} -}; \ No newline at end of file +util.inherits(Server, net.Server); + +Server.prototype.listen = function() { + var args = [2775]; + if (typeof arguments[0] == 'function') { + args[1] = arguments[0]; + } else if (arguments.length > 0) { + args = arguments; + } + return net.Server.prototype.listen.apply(this, args); +}; + +exports.createServer = function() { + return new Server(arguments[0], arguments[1]); +}; + +exports.connect = exports.createSession = function(host, port) { + return new Session({ + host: host || 'localhost', + port: port || 2775 // Default SMPP port is 2775 + }); +}; + +exports.addCommand = function(command, options) { + options.command = command; + defs.commands[command] = options; + defs.commandsById[options.id] = options; + Session.prototype[command] = createShortcut(command); +}; + +exports.addTLV = function(tag, options) { + options.tag = tag; + defs.tlvs[tlv] = options; + defs.tlvsById[options.id] = options; +}; + +exports.Session = Session; +exports.Server = Server; +exports.PDU = PDU; +for (var key in defs) { + exports[key] = defs[key]; +} +for (var error in defs.errors) { + exports[error] = defs.errors[error]; +} +for (var key in defs.consts) { + exports[key] = defs.consts[key]; +} diff --git a/larvitsmpp_old.js b/larvitsmpp_old.js new file mode 100644 index 0000000..6e63c64 --- /dev/null +++ b/larvitsmpp_old.js @@ -0,0 +1,220 @@ +/** + * SMPP Wrapper + * + * Error codes: http://www.activexperts.com/activsms/sms/smpperrorcodes/ + */ +'use strict'; + +var log = require('winston'), + merge = require('utils-merge'), + smpp = require('smpp'); + +/** + * Set up SMPP server + * + * @param obj options + * * port - what port to bind to, defaults to 2775 + * * checkUserAndPass - to require username and password to bind + * to this server this should be a function, taking three + * parameters: username, password and callback(err) + * * timeout - number of ms before the link should be considered dead. Defaults to 30000 (30 sec) + * + */ +exports.server = function(options) { + options = merge({ + 'port': 2775, + 'checkUserAndPass': false, + 'timeout': 30000 + }, options); + + smpp.createServer(function(smppSession) { + var loggedIn = false, + killTimer; + + log.debug('larvitsmpp: server() - server session started'); + + function resetKillTimer() { + log.silly('larvitsmpp: server() - Resetting the kill timer'); + if (killTimer) { + clearTimeout(killTimer); + } + + killTimer = setTimeout(function() { + log.warn('larvitsmpp: server() - Closing session due to timeout'); + smppSession.close(); + }, options.timeout); + } + resetKillTimer(); + + /** + * Set logged in to true + */ + function login(pdu) { + loggedIn = true; + + smppSession.send(pdu.response({ + 'command_status': smpp.ESME_ROK + })); + + smppSession.resume(); + } + + /** + * General bind function for bind_receiver, bind_transmitter and bind_transceiver + * + * @param obj pdu + */ + function bindGeneral(pdu) { + if (options.checkUserAndPass instanceof Function) { + // We pause the smppSession to prevent further incoming pdu events, + // Untill we authorize the smppSession with some async operation. + smppSession.pause(); + log.debug('larvitsmpp: server() - Checking username and password'); + + options.checkUserAndPass(pdu.system_id, pdu.password, function(err) { + if (err) { + log.warn('larvitsmpp: server() - Wrong username or password. Username: "' + pdu.system_id + '"'); + + smppSession.send(pdu.response({ + 'command_status': smpp.ESME_RBINDFAIL + })); + smppSession.close(); + loggedIn = false; + return; + } + + log.debug('larvitsmpp: server() - Username and password is ok'); + login(pdu); + smppSession.resume(); + }); + } else { + login(pdu); + } + } + + smppSession.on('error', function() { + log.error('larvitsmpp: server() - smppSession error!', arguments); + }); + + smppSession.on('pdu', function(pdu) { + log.silly('larvitsmpp: server() - Received command "' + pdu.command + '"'); + + resetKillTimer(); + + if (pdu.command === 'bind_receiver' || pdu.command === 'bind_transmitter' || pdu.command === 'bind_transceiver') { + bindGeneral(pdu); + } else if (pdu.command !== 'enquire_link') { + if (loggedIn) { + log.error('larvitsmpp: server() - Unkown command!', pdu); + + console.log('PDU!!!!'); + console.log(arguments); + } else { + log.warn('larvitsmpp: server() - Not logged in but trying to send non-bind command'); + + smppSession.send(pdu.response({ + 'command_status': smpp.ESME_RSERTYPUNAUTH + })); + } + } + }); + + smppSession.on('enquire_link', function(pdu) { + var sendRet; + + log.silly('larvitsmpp: server() - Enquire_link - client heart beat.'); + + sendRet = smppSession.send(pdu.response({ + 'command_status': smpp.ESME_ROK + })); + + if ( ! sendRet) { + // No writeable socket, do something! + } + + }); + }).listen(options.port); +}; + +/** + * Connect as a client to an SMPP server + * + * @param obj options + * * host (default 127.0.0.1) + * * port (default 2775) + * * username (default false) + * * password (default false) + * * mode (default 'transceiver', other options: 'receiver', 'transmitter') + * * heartbeat - set the heartbeat interval (default 10000) + * @param func callback(err, returnObj) + */ +exports.client = function(options, callback) { + var smppSession, + heartbeatTimer, + err, + returnObj = {'smppSession': smppSession}, + bindOptions = {}; + + function heartbeat() { + log.silly('larvitsmpp: client() - heartbeat() - called'); + + smppSession.enquire_link(); + heartbeatTimer = setTimeout(heartbeat, options.heartbeat); + // Here we need a timeout and then we need to reconnect + } + + returnObj.close = function() { + clearTimeout(heartbeatTimer); + smppSession.close(); + }; + + options = merge({ + 'host': '127.0.0.1', + 'port': 2775, + 'username': undefined, + 'password': undefined, + 'mode': 'transceiver', + 'heartbeat': 10000 + }, options); + + log.info('larvitsmpp: client() - Connecting to SMPP server at ' + options.host + ':' + options.port); + smppSession = smpp.connect(options.host, options.port); + + if (options.username !== undefined && options.password !== undefined) { + bindOptions.system_id = options.username; + bindOptions.password = options.password; + } + + if (options.mode === 'transceiver') { + log.error('bajs'); + } else if (options.mode === 'receiver') { + log.error('skabb'); + } else if (options.mode === 'transmitter') { + smppSession.bind_transmitter(bindOptions, function(pdu) { + var err; + + log.info('larvitsmpp: client() - bind_transmitter done.'); + + if (pdu.command_status === 0) { + log.info('larvitsmpp: client() - bind_transmitter returned 0, success!'); + + heartbeat(); + callback(null, returnObj); + } else { + err = new Error('models/smppsend.js: bind_transmitter returned "' + pdu.command_status + '", fail!'); + log.warn(err.message, pdu); + smppSession.close(); + callback(err); + } + }); + } else { + err = new Error('larvitsmpp: client() - Invalid connection mode'); + log.error(err.message); + callback(err); + } + + smppSession.on('pdu', function(pdu) { + log.silly('larvitsmpp: client() - Received command "' + pdu.command + '"'); + }); + +}; \ No newline at end of file diff --git a/package.json b/package.json index 3e455d1..b58e47f 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,10 @@ "private": false, "contributors": [], "dependencies": { - "smpp": "x", "winston": "x", "utils-merge": "x" }, - "description": "Wrapper for SMPP", + "description": "Simplified SMPP implementation", "devDependencies": {}, "keywords": [ "smpp", @@ -24,7 +23,7 @@ "url": "https://github.com/larvit/larvitsmpp", "type": "git" }, - "version": "0.0.1beta", + "version": "0.0.2beta", "readmeFilename": "README.md", "readme": "larvitsmpp", "bugs": {