Latest additions
This commit is contained in:
@@ -2,22 +2,6 @@
|
|||||||
|
|
||||||
This is a simplified implementation of the SMPP protocol. It only supports transciever mode and all messages are sent via the "data_sm" command.
|
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
|
## Client
|
||||||
|
|
||||||
This will setup a client that connects to localhost, port 2775 without username or password and send a message.
|
This will setup a client that connects to localhost, port 2775 without username or password and send a message.
|
||||||
@@ -44,6 +28,40 @@ This will setup a client that connects to localhost, port 2775 without username
|
|||||||
clientSession.close();
|
clientSession.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
## 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
#### connect
|
||||||
|
|
||||||
|
Triggered when the socket is connected to a client
|
||||||
|
|
||||||
|
#### data
|
||||||
|
|
||||||
|
Triggered when data is comming in on the socket
|
||||||
|
|
||||||
|
#### close
|
||||||
|
|
||||||
|
Triggered when the socket is closed
|
||||||
|
|
||||||
|
#### error
|
||||||
|
|
||||||
|
Generic error event
|
||||||
|
|
||||||
## Advanced server
|
## Advanced server
|
||||||
|
|
||||||
This example and its comments covers a lot of configuration options.
|
This example and its comments covers a lot of configuration options.
|
||||||
|
|||||||
+461
-171
@@ -1,15 +1,444 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var log = require('winston'),
|
var log = require('winston'),
|
||||||
merge = require('utils-merge'),
|
merge = require('utils-merge'),
|
||||||
net = require('net');
|
net = require('net'),
|
||||||
EventEmitter = require('events').EventEmitter;
|
events = require('events'),
|
||||||
|
defs = require('./defs');
|
||||||
|
|
||||||
function Session(options) {
|
/**
|
||||||
var self = this; // Makes this available even in lower level function scopes
|
* Decode a short_message
|
||||||
|
*
|
||||||
|
* @param buf buffer
|
||||||
|
* @param str encoding 'ASCII', 'LATIN1' or 'UCS2' or hex values
|
||||||
|
* @param func callback(err, str) - str will be in utf8 format
|
||||||
|
*/
|
||||||
|
function decodeMsg(buffer, encoding, callback) {
|
||||||
|
var err = null,
|
||||||
|
checkEnc;
|
||||||
|
|
||||||
// Also make this Session an eventEmitter
|
for (checkEnc in defs.consts.ENCODING) {
|
||||||
EventEmitter.call(this);
|
if (parseInt(encoding) === defs.consts.ENCODING[checkEnc] || encoding === checkEnc) {
|
||||||
|
encoding = checkEnc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defs.encodings[encoding] === undefined) {
|
||||||
|
log.info('larvitsmpp: decodeMsg() - Invalid encoding "' + encoding + '" given. Falling back to ASCII (0x01).');
|
||||||
|
encoding = 'ASCII';
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(err, defs.encodings[encoding].decode(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeMsg(str, callback) {
|
||||||
|
var encoding = defs.encodings.detect(str),
|
||||||
|
err = null,
|
||||||
|
buff;
|
||||||
|
|
||||||
|
buff = defs.encodings[encoding].encode(str);
|
||||||
|
|
||||||
|
callback(err, buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a PDU to an object
|
||||||
|
*
|
||||||
|
* @param buf pdu
|
||||||
|
* @param func callback(err, obj)
|
||||||
|
*/
|
||||||
|
function pduToObj(pdu, callback) {
|
||||||
|
var retObj = {'params': {}, 'tlvs': {}},
|
||||||
|
err = null,
|
||||||
|
offset,
|
||||||
|
command,
|
||||||
|
param,
|
||||||
|
tlvCmdId,
|
||||||
|
paramSize;
|
||||||
|
|
||||||
|
log.silly('larvitsmpp: pduToObj() - Decoding PDU to Obj. PDU buff in hex: ' + pdu.toString('hex'));
|
||||||
|
|
||||||
|
if (pdu.length < 16) {
|
||||||
|
err = new Error('larvitsmpp: pduToObj() - PDU is to small, minimum size is 16, given size is ' + pdu.length);
|
||||||
|
log.warn(err.message);
|
||||||
|
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the PDU Header
|
||||||
|
retObj.cmdLength = parseInt(pdu.readUInt32BE(0));
|
||||||
|
retObj.cmdId = parseInt(pdu.readUInt32BE(4));
|
||||||
|
retObj.cmdStatus = defs.errorsById[parseInt(pdu.readUInt32BE(8))];
|
||||||
|
retObj.seqNr = parseInt(pdu.readUInt32BE(12));
|
||||||
|
|
||||||
|
// Lookup the command id in the definitions
|
||||||
|
if (defs.cmdsById[retObj.cmdId] === undefined) {
|
||||||
|
err = new Error('larvitsmpp: pduToObj() - Unknown PDU command id: ' + retObj.cmdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(retObj.seqNr)) {
|
||||||
|
err = new Error('larvitsmpp: pduToObj() - Invalid seqNr, is not an interger: "' + retObj.seqNr + '"');
|
||||||
|
} else if (retObj.seqNr > 2147483646) {
|
||||||
|
err = new Error('larvitsmpp: pduToObj() - Invalid seqNr, maximum size of 2147483646 (0x7fffffff) exceeded.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If error is found, do not proceed with execution
|
||||||
|
if (err !== null) {
|
||||||
|
log.warn(err.message);
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command = defs.cmdsById[retObj.cmdId];
|
||||||
|
retObj.cmdName = command.command;
|
||||||
|
|
||||||
|
// Get all parameters from the body that should exists with this command
|
||||||
|
offset = 16; // 0-15 is the header, so the body starts at 16
|
||||||
|
for (param in command.params) {
|
||||||
|
|
||||||
|
// Get the parameter value by using the definition type read() function
|
||||||
|
try {
|
||||||
|
retObj.params[param] = command.params[param].type.read(pdu, offset);
|
||||||
|
|
||||||
|
// Short message seems to sometimes be terminated with a NULL and sometimes not. Very ugly and needs special care.
|
||||||
|
if (param === 'short_message') {
|
||||||
|
paramSize = command.params[param].type.size(pdu, offset);
|
||||||
|
} else {
|
||||||
|
paramSize = command.params[param].type.size(retObj.params[param]);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.silly('larvitsmpp: pduToObj() - Reading param "' + param + '" at offset ' + offset + ' with calculated size: ' + paramSize + ' content in hex: ' + pdu.slice(offset, offset + paramSize).toString('hex'));
|
||||||
|
|
||||||
|
// Increase the offset by the current params length
|
||||||
|
offset += paramSize;
|
||||||
|
} catch (e) {
|
||||||
|
err = new Error('larvitsmpp: pduToObj() - Failed to read param "' + param + '": ' + e.message);
|
||||||
|
callback(err);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the length is greater than the current offset, there must be TLVs - resolve them!
|
||||||
|
if (offset < retObj.cmdLength) {
|
||||||
|
console.log('TLVs found yo! :D Implement us');
|
||||||
|
console.log(pdu.length);
|
||||||
|
console.log(retObj.cmdLength);
|
||||||
|
console.log(offset);
|
||||||
|
console.log(retObj);
|
||||||
|
process.exit();
|
||||||
|
|
||||||
|
tlvCmdId = pdu.readUInt16BE(offset);
|
||||||
|
|
||||||
|
console.log('tlvCmdId:');
|
||||||
|
console.log(tlvCmdId);
|
||||||
|
process.exit();
|
||||||
|
|
||||||
|
offset ++; //erh... not really, do something more nice! :)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the short message if it is set
|
||||||
|
if (retObj.params.short_message !== undefined) {
|
||||||
|
|
||||||
|
decodeMsg(retObj.params.short_message, retObj.params.data_coding, function(err, decodedMsg) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
retObj.params.short_message = decodedMsg;
|
||||||
|
|
||||||
|
callback(null, retObj);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We need to do the standard callback in an else statement since
|
||||||
|
// this always would be called before the above callback if we didn't
|
||||||
|
callback(null, retObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform an object to a PDU
|
||||||
|
*
|
||||||
|
* @param obj obj - example {'cmdName': 'bind_transceiver_resp', 'cmdStatus': 'ESME_ROK', 'seqNr': 2} - to add parameters add a key 'params' as object
|
||||||
|
* @param func callback(err, pdu)
|
||||||
|
*/
|
||||||
|
function objToPdu(obj, callback) {
|
||||||
|
var cmdLength = 16, // All commands are at least 16 octets long
|
||||||
|
err = null,
|
||||||
|
param,
|
||||||
|
paramType,
|
||||||
|
buff,
|
||||||
|
seqNr,
|
||||||
|
i;
|
||||||
|
|
||||||
|
// Used to write buffer once the command length is known
|
||||||
|
function writeBuffer() {
|
||||||
|
var offset = 16; // Start the offset on the body
|
||||||
|
|
||||||
|
buff = new Buffer(cmdLength);
|
||||||
|
|
||||||
|
// Write PDU header
|
||||||
|
buff.writeUInt32BE(cmdLength, 0); // Command length for the first 4 octets
|
||||||
|
buff.writeUInt32BE(defs.cmds[obj.cmdName].id, 4); // Command id for the second 4 octets
|
||||||
|
buff.writeUInt32BE(defs.errors[obj.cmdStatus], 8); // Command status for the third 4 octets
|
||||||
|
buff.writeUInt32BE(seqNr, 12); // Sequence number as the fourth 4 octets
|
||||||
|
|
||||||
|
// Cycle through the defs list to make sure the params are in the right order
|
||||||
|
for (param in defs.cmds[obj.cmdName].params) {
|
||||||
|
paramType = defs.cmds[obj.cmdName].params[param].type;
|
||||||
|
|
||||||
|
// Write parameter value to buffer using the types method write()
|
||||||
|
paramType.write(obj.params[param], buff, offset);
|
||||||
|
|
||||||
|
// Increase the offset for the next param
|
||||||
|
offset += paramType.size(obj.params[param]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check so the command is ok
|
||||||
|
if (defs.cmds[obj.cmdName] === undefined) {
|
||||||
|
err = new Error('larvitsmpp: objToPdu() - Invalid cmdName: "' + obj.cmdName + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check so the command status is ok
|
||||||
|
if (obj.cmdStatus === undefined) {
|
||||||
|
// Default to OK
|
||||||
|
obj.cmdStatus = 'ESME_ROK';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defs.errors[obj.cmdStatus] === undefined) {
|
||||||
|
err = new Error('larvitsmpp: objToPdu() - Invalid cmdStatus: "' + obj.cmdStatus + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check so seqNr is ok
|
||||||
|
seqNr = parseInt(obj.seqNr);
|
||||||
|
if (isNaN(seqNr)) {
|
||||||
|
err = new Error('larvitsmpp: objToPdu() - Invalid seqNr, is not an interger: "' + obj.seqNr + '"');
|
||||||
|
} else if (seqNr > 2147483646) {
|
||||||
|
err = new Error('larvitsmpp: objToPdu() - Invalid seqNr, maximum size of 2147483646 (0x7fffffff) exceeded.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If error is found, do not proceed with execution
|
||||||
|
if (err !== null) {
|
||||||
|
log.warn(err.message);
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle params - All command params should always exists, even if they do not contain data.
|
||||||
|
for (param in defs.cmds[obj.cmdName].params) {
|
||||||
|
|
||||||
|
// Get the parameter type, int, string, cstring etc.
|
||||||
|
// This is needed so we can calculate length etc
|
||||||
|
paramType = defs.cmds[obj.cmdName].params[param].type;
|
||||||
|
|
||||||
|
// All params are mandatory. Set them if they are not set
|
||||||
|
if (obj.params === undefined) {
|
||||||
|
obj.params = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.params[param] === undefined) {
|
||||||
|
obj.params[param] = paramType.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdLength += paramType.size(obj.params[param]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.params !== undefined) {
|
||||||
|
i = 0;
|
||||||
|
while (obj.params[i] !== undefined) {
|
||||||
|
|
||||||
|
|
||||||
|
i ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLV params - optional parameters
|
||||||
|
|
||||||
|
writeBuffer();
|
||||||
|
callback(null, buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a PDU as a return to another PDU
|
||||||
|
*
|
||||||
|
* @param obj or buf pdu
|
||||||
|
* @param str status - see list at defs.errors - defaults to 'ESME_ROK' - no error
|
||||||
|
* @param func callback(err, pduBuffer)
|
||||||
|
*/
|
||||||
|
function pduReturn(pdu, status, callback) {
|
||||||
|
var err = null,
|
||||||
|
retPdu = {},
|
||||||
|
param;
|
||||||
|
|
||||||
|
if (Buffer.isBuffer(pdu)) {
|
||||||
|
pduToObj(pdu, function(err, pduObj) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pduReturn(pduObj, status, callback);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If status is a function, it is the callback.
|
||||||
|
// Default to OK status and sett callback correctly
|
||||||
|
if (typeof status === 'function') {
|
||||||
|
callback = status;
|
||||||
|
status = 'ESME_ROK';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === undefined) {
|
||||||
|
status = 'ESME_ROK';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defs.errors[status] === undefined) {
|
||||||
|
err = new Error('larvitsmpp: pduReturn() - Invalid status');
|
||||||
|
log.warn(err.message);
|
||||||
|
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defs.cmds[pdu.cmdName + '_resp'] === undefined) {
|
||||||
|
err = new Error('larvitsmpp: pduReturn() - This command does not have a response listed. Given command: "' + pdu.cmdName + '"');
|
||||||
|
log.warn(err.message);
|
||||||
|
|
||||||
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
retPdu.cmdName = pdu.cmdName + '_resp';
|
||||||
|
retPdu.cmdStatus = status;
|
||||||
|
retPdu.seqNr = pdu.seqNr;
|
||||||
|
retPdu.params = {};
|
||||||
|
|
||||||
|
// Populate parameters that should exist in the response
|
||||||
|
for (param in defs.cmds[pdu.cmdName + '_resp'].params) {
|
||||||
|
retPdu.params[param] = pdu.params[param];
|
||||||
|
}
|
||||||
|
|
||||||
|
objToPdu(retPdu, function(err, retPdu) {
|
||||||
|
callback(err, retPdu);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session function for the server
|
||||||
|
*
|
||||||
|
* @param obj sock - socket object
|
||||||
|
* @param obj options - as derived from server()
|
||||||
|
* @param func callback(err, sessionEmitter)
|
||||||
|
*/
|
||||||
|
function session(sock, options, callback) {
|
||||||
|
var sessionEmitter = new events.EventEmitter(),
|
||||||
|
loggedIn = false,
|
||||||
|
enquireLinkTimer; // A timer used for keep alive of the session
|
||||||
|
|
||||||
|
log.verbose('larvitsmpp: session() - New session started from ' + sock.remoteAddress + ':' + sock.remotePort);
|
||||||
|
|
||||||
|
// Make the socket transparent via the returned emitter
|
||||||
|
sessionEmitter.sock = sock;
|
||||||
|
|
||||||
|
// Add a 'data' event handler to this instance of socket
|
||||||
|
sock.on('data', function(pdu) {
|
||||||
|
var data,
|
||||||
|
respPdu;
|
||||||
|
|
||||||
|
// Try to parse the incoming PDU
|
||||||
|
try {
|
||||||
|
data = pduToObj(pdu);
|
||||||
|
} catch(e) {
|
||||||
|
log.warn('larvitsmpp: session() - Invalid PDU. ' + e.message);
|
||||||
|
|
||||||
|
pduReturn(data, 'ESME_RUNKNOWNERR', function(err, retPdu) {
|
||||||
|
if (err) {
|
||||||
|
log.error('larvitsmpp: session() - Could not create return PDU: ' + err.message);
|
||||||
|
sock.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock.write(retPdu);
|
||||||
|
sock.destroy();
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loggedIn === false) {
|
||||||
|
// Only bind_* is accepted when the client is not logged in
|
||||||
|
|
||||||
|
if (data.cmdName !== 'bind_transceiver' && data.cmdName !== 'bind_receiver' && data.cmdName !== 'bind_transmitter') {
|
||||||
|
pduReturn(data, 'ESME_RINVBNDSTS', function(err, retPdu) {
|
||||||
|
|
||||||
|
})
|
||||||
|
sock.write(pduReturn(data, 'ESME_RINVBNDSTS'))
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Pass the data along to the sessionEmitter
|
||||||
|
//sessionEmitter.emit('data', data);
|
||||||
|
|
||||||
|
console.log('Converted to obj:');
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log('DATA ' + sock.remoteAddress + ': ' + data);
|
||||||
|
// Write the data back to the socket, the client will receive it as data from the server
|
||||||
|
sock.write('You said "' + data + '"');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a 'close' event handler to this instance of socket
|
||||||
|
sock.on('close', function() {
|
||||||
|
sessionEmitter.emit('close');
|
||||||
|
console.log('CLOSED: ' + sock.remoteAddress +' '+ sock.remotePort);
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(null, sessionEmitter);
|
||||||
|
}/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a server
|
||||||
|
*
|
||||||
|
* @param obj options - host, port, checkuserpass() etc
|
||||||
|
* @param func callback(session)
|
||||||
|
*/
|
||||||
|
function server(options, callback) {
|
||||||
|
// Set default options
|
||||||
|
options = merge({
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 2775
|
||||||
|
}, options || {});
|
||||||
|
|
||||||
|
// Create a server instance, and chain the listen function to it
|
||||||
|
// The function passed to net.createServer() becomes the event handler for the 'connection' event
|
||||||
|
// The sock object the callback function receives UNIQUE for each connection
|
||||||
|
net.createServer(function(sock) {
|
||||||
|
// We have a connection - a socket object is assigned to the connection automatically
|
||||||
|
log.verbose('larvitsmpp: server() - Incomming connection! remoteAddress: ' + sock.remoteAddress + ' remotePort: ' + sock.remotePort);
|
||||||
|
|
||||||
|
session(sock, options, callback);
|
||||||
|
}).listen(options.port, options.host);
|
||||||
|
|
||||||
|
log.info('larvitsmpp: server() - Up and running at ' + options.host + ':' + options.port);
|
||||||
|
}/**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup a client
|
||||||
|
*
|
||||||
|
* @param obj options - host, port, username, password
|
||||||
|
* @param func callback(err, connection)
|
||||||
|
*/
|
||||||
|
function client(options, callback) {
|
||||||
|
var client = new net.Socket();
|
||||||
|
|
||||||
// Set default options
|
// Set default options
|
||||||
options = merge({
|
options = merge({
|
||||||
@@ -17,171 +446,32 @@ function Session(options) {
|
|||||||
'port': 2775
|
'port': 2775
|
||||||
}, options || {});
|
}, options || {});
|
||||||
|
|
||||||
// Initiate sequence at 0
|
client.connect(options.port, options.host, function() {
|
||||||
// This will increment on each command sent over this session
|
|
||||||
this.sequence = 0;
|
|
||||||
|
|
||||||
if (options.socket) {
|
console.log('CONNECTED TO: ' + options.host + ':' + options.port);
|
||||||
this.socket = options.socket;
|
// Write a message to the socket as soon as the client is connected, the server will receive it as message from the client
|
||||||
} else {
|
client.write('I am Chuck Norris!');
|
||||||
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');
|
// Add a 'data' event handler for the client socket
|
||||||
|
// data is what the server sent to this socket
|
||||||
|
client.on('data', function(data) {
|
||||||
|
|
||||||
|
console.log('DATA: ' + data);
|
||||||
|
// Close the client socket completely
|
||||||
|
client.destroy();
|
||||||
|
|
||||||
});
|
});
|
||||||
this.socket.on('error', function(e) {
|
|
||||||
self.emit('error', e);
|
// Add a 'close' event handler for the client socket
|
||||||
|
client.on('close', function() {
|
||||||
|
console.log('Connection closed');
|
||||||
});
|
});
|
||||||
}
|
}/**/
|
||||||
|
|
||||||
util.inherits(Session, EventEmitter);
|
// Expose some functions
|
||||||
|
exports.server = server;
|
||||||
Session.prototype.connect = function() {
|
exports.client = client;
|
||||||
this.sequence = 0;
|
exports.pduToObj = pduToObj;
|
||||||
this._callbacks = [];
|
exports.objToPdu = objToPdu;
|
||||||
this._buffer = new Buffer(0);
|
exports.pduReturn = pduReturn;
|
||||||
this.socket.connect(this.port, this.host);
|
|
||||||
};
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var command in defs.commands) {
|
|
||||||
Session.prototype[command] = createShortcut(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Server() {
|
|
||||||
var options, self = this;
|
|
||||||
this.sessions = [];
|
|
||||||
|
|
||||||
if (typeof arguments[0] == 'function') {
|
|
||||||
options = {};
|
|
||||||
this.on('session', arguments[0]);
|
|
||||||
} else {
|
|
||||||
options = arguments[0] || {};
|
|
||||||
if (typeof arguments[1] == 'function') {
|
|
||||||
this.on('session', arguments[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
+2
-1
@@ -8,7 +8,8 @@
|
|||||||
"contributors": [],
|
"contributors": [],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"winston": "x",
|
"winston": "x",
|
||||||
"utils-merge": "x"
|
"utils-merge": "x",
|
||||||
|
"iconv-lite": "x"
|
||||||
},
|
},
|
||||||
"description": "Simplified SMPP implementation",
|
"description": "Simplified SMPP implementation",
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var larvitsmpp = require('../larvitsmpp'),
|
||||||
|
assert = require('assert');
|
||||||
|
|
||||||
|
describe('PDU convertion', function() {
|
||||||
|
|
||||||
|
describe('No TLVs', function() {
|
||||||
|
it('should build a PDU buffer for bind_transceiver_resp with error correctly', function(done) {
|
||||||
|
larvitsmpp.objToPdu({
|
||||||
|
'cmdName': 'bind_transceiver_resp',
|
||||||
|
'cmdStatus': 'ESME_RALYBND',
|
||||||
|
'seqNr': 1
|
||||||
|
}, function(err, pdu) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(pdu.readUInt32BE(0) === 17, 'Command length should be 17, but is: "' + pdu.readUInt32BE(0) + '"');
|
||||||
|
assert(pdu.readUInt32BE(4).toString(16) === '80000009', 'Command ID should be 0x80000009');
|
||||||
|
assert(pdu.readUInt32BE(8).toString(16) === '5', 'Command status should be 0x00000005');
|
||||||
|
assert(pdu.readUInt32BE(12).toString(10) === '1', 'Sequence number should be 1');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to do the above test and put it back to an object', function(done) {
|
||||||
|
larvitsmpp.objToPdu({
|
||||||
|
'cmdName': 'bind_transceiver_resp',
|
||||||
|
'cmdStatus': 'ESME_RALYBND',
|
||||||
|
'seqNr': 1
|
||||||
|
}, function(err, pdu) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
larvitsmpp.pduToObj(pdu, function(err, obj) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(obj.cmdId.toString(16) === '80000009', 'Command ID should be 0x80000009');
|
||||||
|
assert(obj.cmdStatus === 'ESME_RALYBND', 'Command status should be "ESME_RALYBND"');
|
||||||
|
assert(obj.seqNr === 1, 'Sequence number should be 1');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse a PDU to obj correctly', function(done) {
|
||||||
|
var pdu = new Buffer('0000002F000000020000000000000001534D50503354455354007365637265743038005355424D4954310000010100', 'hex');
|
||||||
|
|
||||||
|
larvitsmpp.pduToObj(pdu, function(err, obj) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(obj.cmdId === 2, 'Command ID should be 2');
|
||||||
|
assert(obj.cmdStatus === 'ESME_ROK', 'Command status should be "ESME_ROK"');
|
||||||
|
assert(obj.cmdName === 'bind_transmitter', 'Command name should be "bind_transmitter"');
|
||||||
|
assert(obj.params.system_id === 'SMPP3TEST', 'system_id should be "SMPP3TEST"');
|
||||||
|
assert(obj.params.interface_version === 0, 'Interface version should be 0');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a return PDU without error', function(done) {
|
||||||
|
var pdu = new Buffer('0000002f000000020000000000000001534d50503354455354007365637265743038005355424d4954310000010100', 'hex');
|
||||||
|
|
||||||
|
larvitsmpp.pduReturn(pdu, function(err, retPdu) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
larvitsmpp.pduToObj(retPdu, function(err, retObj) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(retObj.cmdId === 2147483650, 'Command ID should be 2147483650');
|
||||||
|
assert(retObj.cmdStatus === 'ESME_ROK', 'Command status should be "ESME_ROK"');
|
||||||
|
assert(retObj.cmdName === 'bind_transmitter_resp', 'Command name should be "bind_transmitter_resp"');
|
||||||
|
assert(retObj.params.system_id === 'SMPP3TEST', 'system_id should be "SMPP3TEST"');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read a submit_sm with an ending NULL octet to the short_message', function(done) {
|
||||||
|
var pdu = new Buffer('0000003c0000000400000000000000020001003436373031313333313131000101343637303937373133333700000000000000000000047465737400', 'hex');
|
||||||
|
|
||||||
|
larvitsmpp.pduToObj(pdu, function(err, obj) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(obj.params.short_message === 'test', 'Param short_message should read "test"');
|
||||||
|
assert(obj.cmdLength === 60, 'Command length should be 60, due to the trailing NULL octet');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read a submit_sm without an ending NULL octet to the short_message', function(done) {
|
||||||
|
var pdu = new Buffer('0000003b00000004000000000000000200010034363730313133333131310001013436373039373731333337000000000000000000000474657374', 'hex');
|
||||||
|
|
||||||
|
larvitsmpp.pduToObj(pdu, function(err, obj) {
|
||||||
|
assert( ! err, 'Error should be negative');
|
||||||
|
|
||||||
|
assert(obj.params.short_message === 'test', 'Param short_message should read "test"');
|
||||||
|
assert(obj.cmdLength === 59, 'Command length should be 59');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user