Comparing version 0.5.1 to 0.6.0-rc.0
176
lib/smpp.js
@@ -14,4 +14,9 @@ var net = require('net'), | ||
var transport = net; | ||
var connectTimeout; | ||
this._extractPDUs = this._extractPDUs.bind(self); | ||
this.sequence = 0; | ||
this.paused = false; | ||
this.closed = false; | ||
this.remoteAddress = null; | ||
this._proxyProtocolChecked = false; | ||
this._busy = false; | ||
@@ -21,11 +26,30 @@ this._callbacks = {}; | ||
this._command_length = null; | ||
this._mode = null; | ||
this._id = Math.floor(Math.random() * (999999 - 100000)) + 100000; // random session id | ||
this._prevBytesRead = 0; | ||
if (options.socket) { | ||
// server mode | ||
this._mode = "server"; | ||
this.socket = options.socket; | ||
} else { | ||
// client mode | ||
this._mode = "client"; | ||
if (options.tls) { | ||
transport = tls; | ||
} | ||
connectTimeout = setTimeout(function() { | ||
if (self.socket) { | ||
var e = new Error("Timeout of " + options.connectTimeout + "ms while connecting to " + | ||
self.options.host + ":" + self.options.port); | ||
e.code = "ETIMEOUT"; | ||
e.timeout = options.connectTimeout; | ||
self.socket.destroy(e); | ||
} | ||
}, options.connectTimeout); | ||
this.socket = transport.connect(this.options); | ||
this.socket.on('connect', function() { | ||
self.emit('connect'); | ||
this.socket.on('connect', (function() { | ||
clearTimeout(connectTimeout); | ||
self.remoteAddress = self.socket.remoteAddress; | ||
self.debug("server.connected", "connected to server", {secure: options.tls}); | ||
self.emit('connect'); // @todo should emmit the session, but it would break BC | ||
if(self.options.auto_enquire_link_period) { | ||
@@ -36,9 +60,27 @@ self._interval = setInterval(function() { | ||
} | ||
}); | ||
this.socket.on('secureConnect', function() { | ||
self.emit('secureConnect'); | ||
}); | ||
}).bind(this)); | ||
this.socket.on('secureConnect', (function() { | ||
self.emit('secureConnect'); // @todo should emmit the session, but it would break BC | ||
}).bind(this)); | ||
} | ||
this.socket.on('readable', this._extractPDUs.bind(this)); | ||
this.remoteAddress = this.socket.remoteAddress; | ||
this.socket.on('open', function() { | ||
self.debug("socket.open"); | ||
}); | ||
this.socket.on('readable', function() { | ||
if ( (self.socket.bytesRead - self._prevBytesRead) > 0 ) { | ||
// on disconnections the readable event receives 0 bytes, we do not want to debug that | ||
self.debug("socket.data.in", null, {bytes: self.socket.bytesRead - self._prevBytesRead}); | ||
self._prevBytesRead = self.socket.bytesRead; | ||
} | ||
self._extractPDUs(); | ||
}); | ||
this.socket.on('close', function() { | ||
self.closed = true; | ||
clearTimeout(connectTimeout); | ||
if (self._mode === "server") { | ||
self.debug("client.disconnected", "client has disconnected"); | ||
} else { | ||
self.debug("server.disconnected", "disconnected from server"); | ||
} | ||
self.emit('close'); | ||
@@ -51,7 +93,9 @@ if(self._interval) { | ||
this.socket.on('error', function(e) { | ||
self.emit('error', e); | ||
if(self._interval) { | ||
clearTimeout(connectTimeout); | ||
self.debug("socket.error", e.message, e); | ||
if (self._interval) { | ||
clearInterval(self._interval); | ||
self._interval = 0; | ||
} | ||
self.emit('error', e); // Emitted errors will kill the program if they're not captured. | ||
}); | ||
@@ -62,2 +106,30 @@ } | ||
Session.prototype.debug = function(type, msg, payload) { | ||
if (type === undefined) type = null; | ||
if (msg === undefined) msg = null; | ||
if (this.options.debug) { | ||
var coloredTypes = { | ||
"reset": "\x1b[0m", | ||
"dim": "\x1b[2m", | ||
"client.connected": "\x1b[1m\x1b[34m", | ||
"client.disconnected": "\x1b[1m\x1b[31m", | ||
"server.connected": "\x1b[1m\x1b[34m", | ||
"server.disconnected": "\x1b[1m\x1b[31m", | ||
"pdu.command.in": "\x1b[36m", | ||
"pdu.command.out": "\x1b[32m", | ||
"socket.error": "\x1b[41m\x1b[30m" | ||
} | ||
var now = new Date(); | ||
var logBuffer = now.toISOString() + | ||
" - " + (this._mode === "server" ? "srv" : "cli") + | ||
" - " + this._id + | ||
" - " + (coloredTypes.hasOwnProperty(type) ? coloredTypes[type] + type + coloredTypes.reset : type) + | ||
" - " + (msg !== null ? msg : "" ) + | ||
" - " + coloredTypes.dim + (payload !== undefined ? JSON.stringify(payload) : "") + coloredTypes.reset; | ||
if (this.remoteAddress) logBuffer += " - [" + this.remoteAddress + "]" | ||
console.log( logBuffer ); | ||
} | ||
this.emit('debug', type, msg, payload); | ||
} | ||
Session.prototype.connect = function() { | ||
@@ -79,2 +151,6 @@ this.sequence = 0; | ||
try { | ||
if(this._mode === "server" && this.options.enable_proxy_protocol_detection && !this._proxyProtocolChecked) { | ||
this._handleProxyProtocolV1(); | ||
this._proxyProtocolChecked = true; | ||
} | ||
if(!this._command_length) { | ||
@@ -89,3 +165,5 @@ this._command_length = PDU.commandLength(this.socket); | ||
} | ||
this.debug("pdu.command.in", pdu.command, pdu); | ||
} catch (e) { | ||
this.debug("pdu.command.error", e.message, e); | ||
this.emit('error', e); | ||
@@ -105,2 +183,49 @@ return; | ||
Session.prototype._handleProxyProtocolV1 = function() { | ||
var proxyBuffer = this.socket.read(6); | ||
if (!proxyBuffer) { | ||
return; | ||
} | ||
// Header specs: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt | ||
if (proxyBuffer.toString() === 'PROXY ') { | ||
var proxyProtocol = proxyBuffer.toString(); | ||
var char = null; | ||
var b = null; | ||
var prevChar = null; | ||
while (b = this.socket.read(1)) { | ||
char = b.toString('ascii'); | ||
proxyProtocol += char; | ||
if (char === "\n" && prevChar === "\r") break; | ||
if (proxyProtocol.length > 108) { | ||
this.debug("proxy_protocol.error", "Proxy protocol header cannot exceed 108 bytes", proxyProtocol); | ||
var e = new Error('Proxy protocol header cannot exceed 108 bytes'); | ||
e.code = "PROXY_PROTOCOL_HEADER_TOO_LARGE"; | ||
throw e; | ||
} | ||
prevChar = char; | ||
} | ||
this.debug("proxy_protocol.header.decoded", "Decoded", proxyProtocol.split("\r").join("").split("\n").join("")); | ||
var proxyProtocolAddressFound = null; | ||
if (proxyProtocol.indexOf('PROXY TCP') === 0) { // 'PROXY TCP(4|6) .+\r\n' | ||
var proxyProtocolParts = proxyProtocol.substring(10).split(' '); | ||
if (proxyProtocolParts.length>1) proxyProtocolAddressFound = proxyProtocolParts[1]; | ||
} | ||
if (proxyProtocolAddressFound) { | ||
this.remoteAddress = proxyProtocolAddressFound.toLowerCase(); | ||
this.debug("proxy_protocol.address.ok", "Found "+this.remoteAddress, this.remoteAddress); | ||
} else { | ||
this.debug("proxy_protocol.address.ko", "Not found"); | ||
} | ||
} else { | ||
this.debug("proxy_protocol.header.error","Header mismatch"); | ||
this.socket.unshift(proxyBuffer); | ||
} | ||
} | ||
Session.prototype.send = function(pdu, responseCallback, sendCallback) { | ||
@@ -127,3 +252,6 @@ if (!this.socket.writable) { | ||
} | ||
this.socket.write(pdu.toBuffer(), function() { | ||
this.debug("pdu.command.out", pdu.command, pdu); | ||
var buffer = pdu.toBuffer(); | ||
this.socket.write(buffer, (function() { | ||
this.debug("socket.data.out", null, {bytes: buffer.length}); | ||
this.emit('send', pdu); | ||
@@ -133,3 +261,3 @@ if (sendCallback) { | ||
} | ||
}.bind(this)); | ||
}).bind(this)); | ||
return true; | ||
@@ -149,3 +277,7 @@ }; | ||
if (callback) { | ||
this.socket.once('close', callback); | ||
if (this.closed) { | ||
this.socket.once('close', callback); | ||
} else { | ||
callback(); | ||
} | ||
} | ||
@@ -157,3 +289,7 @@ this.socket.end(); | ||
if (callback) { | ||
this.socket.once('close', callback); | ||
if (this.closed) { | ||
this.socket.once('close', callback); | ||
} else { | ||
callback(); | ||
} | ||
} | ||
@@ -196,5 +332,7 @@ this.socket.destroy(); | ||
var transport = this.tls ? tls : net; | ||
this.options = options; | ||
transport.Server.call(this, options, function(socket) { | ||
var session = new Session({socket: socket}); | ||
var session = new Session({socket: socket, enable_proxy_protocol_detection: self.options.enable_proxy_protocol_detection, debug: self.options.debug}); | ||
session.debug("client.connected", "client has connected"); | ||
session.server = self; | ||
@@ -256,3 +394,3 @@ self.sessions.push(session); | ||
options.host = options.slashes ? options.hostname : url; | ||
options.tls = options.protocol == 'ssmpp:'; | ||
options.tls = options.protocol === 'ssmpp:'; | ||
} else if (typeof url == 'function') { | ||
@@ -266,10 +404,14 @@ listener = url; | ||
options.port = url.port; | ||
options.tls = url.protocol == 'ssmpp:'; | ||
options.tls = url.protocol === 'ssmpp:'; | ||
} | ||
} | ||
options.port = options.port || (options.tls ? 3550 : 2775); | ||
options.debug = options.debug || false; | ||
options.connectTimeout = options.connectTimeout || 30000; | ||
var session = new Session(options); | ||
if (listener) { | ||
session.on(options.tls ? 'secureConnect' : 'connect', listener); | ||
session.on(options.tls ? 'secureConnect' : 'connect', function() { | ||
listener(session); | ||
}); | ||
} | ||
@@ -276,0 +418,0 @@ |
{ | ||
"name": "smpp", | ||
"version": "0.5.1", | ||
"version": "0.6.0-rc.0", | ||
"description": "SMPP client and server implementation in node.js", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/farhadi/node-smpp", |
@@ -37,21 +37,23 @@ node-smpp | ||
url: 'smpp://example.com:2775', | ||
auto_enquire_link_period: 10000 | ||
auto_enquire_link_period: 10000, | ||
debug: true | ||
}, function() { | ||
session.bind_transceiver({ | ||
system_id: 'YOUR_SYSTEM_ID', | ||
password: 'YOUR_PASSWORD' | ||
}, function(pdu) { | ||
if (pdu.command_status === 0) { | ||
// Successfully bound | ||
session.submit_sm({ | ||
destination_addr: 'DESTINATION NUMBER', | ||
short_message: 'Hello!' | ||
}, function(pdu) { | ||
if (pdu.command_status === 0) { | ||
// Message successfully sent | ||
console.log(pdu.message_id); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
session.bind_transceiver({ | ||
system_id: 'YOUR_SYSTEM_ID', | ||
password: 'YOUR_PASSWORD' | ||
}, function(pdu) { | ||
if (pdu.command_status == 0) { | ||
// Successfully bound | ||
session.submit_sm({ | ||
destination_addr: 'DESTINATION NUMBER', | ||
short_message: 'Hello!' | ||
}, function(pdu) { | ||
if (pdu.command_status == 0) { | ||
// Message successfully sent | ||
console.log(pdu.message_id); | ||
} | ||
}); | ||
} | ||
}); | ||
``` | ||
@@ -63,3 +65,8 @@ | ||
var smpp = require('smpp'); | ||
var server = smpp.createServer(function(session) { | ||
var server = smpp.createServer({ | ||
debug: true | ||
}, function(session) { | ||
session.on('error', function (err) { | ||
// Something ocurred, not listening for this event will terminate the program | ||
}); | ||
session.on('bind_transceiver', function(pdu) { | ||
@@ -82,5 +89,49 @@ // we pause the session to prevent further incoming pdu events, | ||
}); | ||
server.listen(2775); | ||
``` | ||
It's very important to listen for session errors, not listening for error events will terminate the program. | ||
### Debug | ||
To enable a simple debug of ingoing/outgoing messages pass `debug: true` as server/client option. Debug is disabled by default. | ||
Alternatively, you can listen for the `debug` even and write your own implementation: | ||
``` javascript | ||
session.on('debug', function(type, msg, payload) { | ||
console.log({type: type, msg: msg, payload: payload}); | ||
}); | ||
``` | ||
### Handling client connection errors: | ||
In case of errors while trying to connect, an `error` event will be emitted by the session and the program will be terminated if it's not listened. This is how you should check for errors. | ||
``` javascript | ||
session.on('error', function(e) { | ||
// empty callback to catch emitted errors to prevent exit due unhandled errors | ||
if (e.code === "ETIMEOUT") { | ||
// TIMEOUT | ||
} else if (e.code === "ECONNREFUSED" { | ||
// CONNECTION REFUSED | ||
} else { | ||
// OTHER ERROR | ||
} | ||
}); | ||
``` | ||
### Connection timeout: | ||
By default the socket will be dropped after 30000 ms if it doesn't connect. A `connectTimeout` option can be sent when making connections with the server in order to change this setting. | ||
### Proxy protocol (v1) support : | ||
[Proxy Protocol header specs](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) | ||
Pass `enable_proxy_protocol_detection: true` as server option. | ||
- Only Proxy protocol v1 is supported | ||
- `session.remote_addr` will contain the proxied source ip. | ||
- `session.socket.remote_addr` will contain the proxy ip. | ||
- Even with proxy protocol detection enabled the server will understand non-proxied requests. | ||
- Tests are provided to make sure TCP4, TCP6 & UNKNOWN proxy headers are handled correctly. Tests work by injecting fake proxy protocol headers upon establishing connection. | ||
- Security: Proxy CIDRs validation is yet to be implemented. | ||
Encodings | ||
@@ -87,0 +138,0 @@ --------- |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
70571
2049
352