Compare commits
10 Commits
9326923b72
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0539fa6621 | ||
a9979def99 | |||
19ae430779 | |||
c873628abc | |||
ba0d9bf430 | |||
f8a8365f90 | |||
06c7f42bf4 | |||
927ac2edc9 | |||
f7976d3738 | |||
0799dc6a4d |
48
.eslintrc.js
48
.eslintrc.js
@@ -1,26 +1,24 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"overrides": [
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"files": [
|
||||
".eslintrc.{js,cjs}"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
}
|
||||
env: {
|
||||
node: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
"jest/globals": true,
|
||||
},
|
||||
extends: "eslint:recommended",
|
||||
overrides: [
|
||||
{
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
files: [".eslintrc.{js,cjs}"],
|
||||
parserOptions: {
|
||||
sourceType: "script",
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
rules: {},
|
||||
};
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
node_modules
|
||||
out
|
||||
.idea
|
||||
.vscode
|
||||
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"driver": "SQLite",
|
||||
"name": "1",
|
||||
"database": "E:\\tmp\\output.db"
|
||||
}
|
||||
]
|
||||
}
|
224
README.md
224
README.md
@@ -1,112 +1,112 @@
|
||||
# SMPP_CLI
|
||||
|
||||
### A small application used to create SMPP clients or centers. Serves as both the SMSC and ESME.
|
||||
|
||||
(Taken from the help page)
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
CLI SMPP (Client)
|
||||
|
||||
Options
|
||||
|
||||
--help Display this usage guide.
|
||||
-h, --host string The host (IP) to connect to.
|
||||
-p, --port number The port to connect to.
|
||||
-s, --systemid string SMPP related login info.
|
||||
-w, --password string SMPP related login info.
|
||||
--sessions number Number of sessions to start, defaults to 1.
|
||||
--messagecount number Number of messages to send; Optional, defaults to 1.
|
||||
--window number Defines the amount of messages that are allowed to be
|
||||
'in flight'. The client no longer waits for a
|
||||
response before sending the next message for up to
|
||||
<window> messages. Defaults to 100.
|
||||
--windowsleep number Defines the amount time (in ms) waited between
|
||||
retrying in the case of full window. Defaults to 100.
|
||||
--mps number Number of messages to send per second
|
||||
--source string Source field of the sent messages.
|
||||
--destination string Destination field of the sent messages.
|
||||
--message string Text content of the sent messages.
|
||||
--debug Display all traffic to and from the client; Debug
|
||||
mode.
|
||||
```
|
||||
---
|
||||
```
|
||||
CLI SMPP (Center)
|
||||
|
||||
Options
|
||||
|
||||
--help Display this usage guide.
|
||||
-p, --port number The port to connect to.
|
||||
-s, --systemid string SMPP related login info.
|
||||
-w, --password string SMPP related login info.
|
||||
--dr Whether or not to send Delivery Reports.
|
||||
--sessions number Maximum number of client sessions to accept, defaults
|
||||
to 8.
|
||||
--messagecount number Number of messages to send; Optional, defaults to 0.
|
||||
--window number Defines the amount of messages that are allowed to be
|
||||
'in flight'. The client no longer waits for a
|
||||
response before sending the next message for up to
|
||||
<window> messages. Defaults to 100.
|
||||
--windowsleep number Defines the amount time (in ms) waited between
|
||||
retrying in the case of full window. Defaults to 100.
|
||||
--mps number Number of messages to send per second
|
||||
--source string Source field of the sent messages.
|
||||
--destination string Destination field of the sent messages.
|
||||
--message string Text content of the sent messages.
|
||||
--debug Display all traffic to and from the center; Debug
|
||||
mode.
|
||||
```
|
||||
---
|
||||
#### Center example usage:
|
||||
```
|
||||
./center-win.exe \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP center (SMSC) which will:
|
||||
- Start listening on 7001
|
||||
- Accept clients with test:test credentials
|
||||
- Allow up to a maximum of 10 sessions
|
||||
#### Client example usage:
|
||||
```
|
||||
./client-win.exe \
|
||||
--host localhost \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--window 5 \
|
||||
--windowsleep 100 \
|
||||
--messagecount 10000 \
|
||||
--mps 10 \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP client (ESME) which will:
|
||||
- Try to connect 10 sessions to localhost:7001
|
||||
- Once connected try to bind using test:test
|
||||
- Once bound send 10000 messages at a rate of 10 per second and a window size of 5
|
||||
- If the window is filled during sending the session will wait 100ms before attempting send again
|
||||
---
|
||||
---
|
||||
#### Center example usage (sending):
|
||||
```
|
||||
./center-win.exe \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--window 5 \
|
||||
--windowsleep 100 \
|
||||
--messagecount 10000 \
|
||||
--mps 10 \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP center (SMSC) which will:
|
||||
- Start listening on 7001
|
||||
- Accept clients with test:test credentials
|
||||
- Allow up to a maximum of 10 sessions
|
||||
- Once a client is connected start sending 10000 messages at a rate of 10 per second with a window size of 5
|
||||
- If the window is filled during sending the session will wait 100ms before attempting send again
|
||||
---
|
||||
# SMPP_CLI
|
||||
|
||||
### A small application used to create SMPP clients or centers. Serves as both the SMSC and ESME.
|
||||
|
||||
(Taken from the help page)
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
CLI SMPP (Client)
|
||||
|
||||
Options
|
||||
|
||||
--help Display this usage guide.
|
||||
-h, --host string The host (IP) to connect to.
|
||||
-p, --port number The port to connect to.
|
||||
-s, --systemid string SMPP related login info.
|
||||
-w, --password string SMPP related login info.
|
||||
--sessions number Number of sessions to start, defaults to 1.
|
||||
--messagecount number Number of messages to send; Optional, defaults to 1.
|
||||
--window number Defines the amount of messages that are allowed to be
|
||||
'in flight'. The client no longer waits for a
|
||||
response before sending the next message for up to
|
||||
<window> messages. Defaults to 100.
|
||||
--windowsleep number Defines the amount time (in ms) waited between
|
||||
retrying in the case of full window. Defaults to 100.
|
||||
--mps number Number of messages to send per second
|
||||
--source string Source field of the sent messages.
|
||||
--destination string Destination field of the sent messages.
|
||||
--message string Text content of the sent messages.
|
||||
--debug Display all traffic to and from the client; Debug
|
||||
mode.
|
||||
```
|
||||
---
|
||||
```
|
||||
CLI SMPP (Center)
|
||||
|
||||
Options
|
||||
|
||||
--help Display this usage guide.
|
||||
-p, --port number The port to connect to.
|
||||
-s, --systemid string SMPP related login info.
|
||||
-w, --password string SMPP related login info.
|
||||
--dr Whether or not to send Delivery Reports.
|
||||
--sessions number Maximum number of client sessions to accept, defaults
|
||||
to 8.
|
||||
--messagecount number Number of messages to send; Optional, defaults to 0.
|
||||
--window number Defines the amount of messages that are allowed to be
|
||||
'in flight'. The client no longer waits for a
|
||||
response before sending the next message for up to
|
||||
<window> messages. Defaults to 100.
|
||||
--windowsleep number Defines the amount time (in ms) waited between
|
||||
retrying in the case of full window. Defaults to 100.
|
||||
--mps number Number of messages to send per second
|
||||
--source string Source field of the sent messages.
|
||||
--destination string Destination field of the sent messages.
|
||||
--message string Text content of the sent messages.
|
||||
--debug Display all traffic to and from the center; Debug
|
||||
mode.
|
||||
```
|
||||
---
|
||||
#### Center example usage:
|
||||
```
|
||||
./center-win.exe \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP center (SMSC) which will:
|
||||
- Start listening on 7001
|
||||
- Accept clients with test:test credentials
|
||||
- Allow up to a maximum of 10 sessions
|
||||
#### Client example usage:
|
||||
```
|
||||
./client-win.exe \
|
||||
--host localhost \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--window 5 \
|
||||
--windowsleep 100 \
|
||||
--messagecount 10000 \
|
||||
--mps 10 \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP client (ESME) which will:
|
||||
- Try to connect 10 sessions to localhost:7001
|
||||
- Once connected try to bind using test:test
|
||||
- Once bound send 10000 messages at a rate of 10 per second and a window size of 5
|
||||
- If the window is filled during sending the session will wait 100ms before attempting send again
|
||||
---
|
||||
---
|
||||
#### Center example usage (sending):
|
||||
```
|
||||
./center-win.exe \
|
||||
--port 7001 \
|
||||
--systemid test \
|
||||
--password test \
|
||||
--window 5 \
|
||||
--windowsleep 100 \
|
||||
--messagecount 10000 \
|
||||
--mps 10 \
|
||||
--sessions 10
|
||||
```
|
||||
Running this command will spawn an SMPP center (SMSC) which will:
|
||||
- Start listening on 7001
|
||||
- Accept clients with test:test credentials
|
||||
- Allow up to a maximum of 10 sessions
|
||||
- Once a client is connected start sending 10000 messages at a rate of 10 per second with a window size of 5
|
||||
- If the window is filled during sending the session will wait 100ms before attempting send again
|
||||
---
|
||||
|
463
center.js
463
center.js
@@ -1,227 +1,236 @@
|
||||
const smpp = require("smpp");
|
||||
const commandLineArgs = require("command-line-args");
|
||||
const commandLineUsage = require("command-line-usage");
|
||||
const NanoTimer = require("nanotimer");
|
||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||
const { verifyDefaults, verifyExists } = require("./utils");
|
||||
const { centerOptions } = require("./cliOptions");
|
||||
const crypto = require("crypto");
|
||||
const { MetricManager } = require("./metrics/metricManager");
|
||||
|
||||
const options = commandLineArgs(centerOptions);
|
||||
const logger = createBaseLogger(options);
|
||||
|
||||
if (options.help) {
|
||||
const usage = commandLineUsage([
|
||||
{
|
||||
header: "CLI SMPP (Center)",
|
||||
},
|
||||
{
|
||||
header: "Options",
|
||||
optionList: centerOptions,
|
||||
},
|
||||
{
|
||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||
},
|
||||
]);
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
verifyDefaults(options, centerOptions);
|
||||
verifyExists(options.port, "Port can not be undefined or empty! (--port)", logger);
|
||||
verifyExists(options.systemid, "SystemID can not be undefined or empty! (--systemid)", logger);
|
||||
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||
|
||||
let inFlight = 0;
|
||||
let sent = 0;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const sendTimer = new NanoTimer();
|
||||
const metricManager = new MetricManager(options);
|
||||
|
||||
// TODO: Fix issue where a client disconnecting does not stop this timer
|
||||
// TODO: Fix issue where only one session is being utilized because they all share the same timer
|
||||
// TODO: Maybe add only receiver and only transmitter modes instead of transciever
|
||||
// Instead just use the same timer but make a pool of connections; That way both problems will be solved
|
||||
function startInterval(session, sessionLogger, rxMetrics) {
|
||||
if (!options.messagecount > 0) {
|
||||
sessionLogger.info("No messages to send");
|
||||
return;
|
||||
}
|
||||
sendTimer.setInterval(
|
||||
async () => {
|
||||
if (sent >= options.messagecount) {
|
||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||
sendTimer.clearInterval();
|
||||
} else if (inFlight < options.window) {
|
||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||
session.deliver_sm(
|
||||
{
|
||||
source_addr: options.source,
|
||||
destination_addr: options.destination,
|
||||
short_message: options.message,
|
||||
},
|
||||
function (pdu) {
|
||||
inFlight--;
|
||||
if (pdu.command_status === 0) {
|
||||
sessionLogger.info(`Received response with id ${pdu.message_id}`);
|
||||
success++;
|
||||
} else {
|
||||
sessionLogger.warn(`Message failed with id ${pdu.message_id}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
);
|
||||
rxMetrics.AddEvent();
|
||||
sent++;
|
||||
inFlight++;
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||
);
|
||||
sendTimer.clearInterval();
|
||||
setTimeout(() => startInterval(session, sessionLogger), options.windowsleep);
|
||||
}
|
||||
},
|
||||
"",
|
||||
`${1 / options.mps} s`
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Staring server on port ${options.port}...`);
|
||||
let sessionid = 1;
|
||||
let messageid = 0;
|
||||
const server = smpp.createServer(
|
||||
{
|
||||
debug: options.debug,
|
||||
},
|
||||
function (session) {
|
||||
const id = sessionid++;
|
||||
const sessionLogger = createSessionLogger(options, id);
|
||||
const rxMetrics = metricManager.AddMetrics(`Session-${id}-RX`);
|
||||
const txMetrics = metricManager.AddMetrics(`Session-${id}-TX`);
|
||||
|
||||
session.on("bind_transceiver", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessionLogger.info("Client connected");
|
||||
session.send(pdu.response());
|
||||
startInterval(session, sessionLogger);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("bind_transmitter", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessionLogger.info("Client connected");
|
||||
session.send(pdu.response());
|
||||
startInterval(session, sessionLogger);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("bind_receiver", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessionLogger.info("Client connected");
|
||||
session.send(pdu.response());
|
||||
startInterval(session, sessionLogger);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("enquire_link", function (pdu) {
|
||||
session.send(pdu.response());
|
||||
});
|
||||
session.on("submit_sm", async function (pdu) {
|
||||
if (!options.dr) {
|
||||
sessionLogger.info("Replying to incoming submit_sm");
|
||||
if (options.bars) {
|
||||
rxMetrics.AddEvent();
|
||||
}
|
||||
// setTimeout(() => {
|
||||
// session.send(pdu.response());
|
||||
// }, 200);
|
||||
session.send(pdu.response());
|
||||
return;
|
||||
}
|
||||
|
||||
sessionLogger.info("Generating DR for incoming submit_sm");
|
||||
let response = pdu.response();
|
||||
|
||||
let smppid = messageid++;
|
||||
if (options.randid) {
|
||||
smppid = crypto.randomBytes(8).toString("hex");
|
||||
}
|
||||
|
||||
response.message_id = smppid.toString(16);
|
||||
session.send(response);
|
||||
|
||||
let drMessage = "";
|
||||
let date = new Date()
|
||||
.toISOString()
|
||||
.replace(/T/, "")
|
||||
.replace(/\..+/, "")
|
||||
.replace(/-/g, "")
|
||||
.replace(/:/g, "")
|
||||
.substring(2, 12);
|
||||
|
||||
drMessage += "id:" + response.message_id + " ";
|
||||
drMessage += "sub:001 ";
|
||||
drMessage += "dlvrd:001 ";
|
||||
drMessage += "submit date:" + date + " ";
|
||||
drMessage += "done date:" + date + " ";
|
||||
drMessage += "stat:DELIVRD ";
|
||||
drMessage += "err:000 ";
|
||||
drMessage += "text:";
|
||||
|
||||
const DRPdu = {
|
||||
source_addr: pdu.destination_addr,
|
||||
destination_addr: pdu.source_addr,
|
||||
short_message: drMessage,
|
||||
esm_class: 4,
|
||||
};
|
||||
sessionLogger.info(`Generated DR as ${drMessage}`);
|
||||
session.deliver_sm(DRPdu);
|
||||
if (txMetrics) {
|
||||
txMetrics.AddEvent();
|
||||
}
|
||||
});
|
||||
|
||||
session.on("close", function () {
|
||||
sessionLogger.warn(`Session closed`);
|
||||
session.close();
|
||||
});
|
||||
session.on("error", function (err) {
|
||||
sessionLogger.error(`Fatal error ${err}`);
|
||||
session.close();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
server.on("error", function (err) {
|
||||
logger.error(`Fatal server error ${err}`);
|
||||
server.close();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
server.listen(options.port);
|
||||
logger.info(`SMPP Server listening on ${options.port}`);
|
||||
const smpp = require("smpp");
|
||||
const commandLineArgs = require("command-line-args");
|
||||
const commandLineUsage = require("command-line-usage");
|
||||
const NanoTimer = require("nanotimer");
|
||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||
const { verifyDefaults, verifyExists, sendPdu } = require("./utils");
|
||||
const { centerOptions } = require("./cliOptions");
|
||||
const crypto = require("crypto");
|
||||
const { MetricManager } = require("./metrics/metricManager");
|
||||
|
||||
const options = commandLineArgs(centerOptions);
|
||||
const logger = createBaseLogger(options);
|
||||
|
||||
if (options.help) {
|
||||
const usage = commandLineUsage([
|
||||
{
|
||||
header: "CLI SMPP (Center)",
|
||||
},
|
||||
{
|
||||
header: "Options",
|
||||
optionList: centerOptions,
|
||||
},
|
||||
{
|
||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||
},
|
||||
]);
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
verifyDefaults(options, centerOptions);
|
||||
verifyExists(options.port, "Port can not be undefined or empty! (--port)", logger);
|
||||
verifyExists(options.systemid, "SystemID can not be undefined or empty! (--systemid)", logger);
|
||||
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||
|
||||
let inFlight = 0;
|
||||
let sent = 0;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const sendTimer = new NanoTimer();
|
||||
const metricManager = new MetricManager(options);
|
||||
|
||||
// TODO: Currently bars are broken
|
||||
// A major rework will need to happen before bars are able to play nice with multiple sessions
|
||||
// TODO: Maybe add only receiver and only transmitter modes instead of transciever
|
||||
// Instead just use the same timer but make a pool of connections; That way both problems will be solved
|
||||
function startInterval(sessions, sessionLogger, rxMetrics) {
|
||||
if (!options.messagecount > 0) {
|
||||
sessionLogger.info("No messages to send");
|
||||
return;
|
||||
}
|
||||
let sessionPointer = 0;
|
||||
sendTimer.setInterval(
|
||||
async () => {
|
||||
if (sent >= options.messagecount) {
|
||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||
sendTimer.clearInterval();
|
||||
} else if (inFlight < options.window) {
|
||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
source_addr: options.source,
|
||||
destination_addr: options.destination,
|
||||
short_message: options.message,
|
||||
});
|
||||
|
||||
if (sessionPointer >= sessions.length) {
|
||||
sessionPointer = 0;
|
||||
}
|
||||
sendPdu(sessions[sessionPointer++], pdu, sessionLogger, options.longsms)
|
||||
.then((resp) => {
|
||||
inFlight--;
|
||||
sessionLogger.info(`Received response with id ${resp.message_id}`);
|
||||
success++;
|
||||
})
|
||||
.catch((resp) => {
|
||||
inFlight--;
|
||||
sessionLogger.warn(`Message failed with id ${resp.message_id}`);
|
||||
failed++;
|
||||
});
|
||||
sent++;
|
||||
inFlight++;
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||
);
|
||||
sendTimer.clearInterval();
|
||||
setTimeout(() => startInterval(sessions, sessionLogger), options.windowsleep);
|
||||
}
|
||||
},
|
||||
"",
|
||||
`${1 / options.mps} s`
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Staring server on port ${options.port}...`);
|
||||
let sessionid = 1;
|
||||
let messageid = 0;
|
||||
const sessions = [];
|
||||
const server = smpp.createServer(
|
||||
{
|
||||
debug: options.debug,
|
||||
},
|
||||
function (session) {
|
||||
const id = sessionid++;
|
||||
const sessionLogger = createSessionLogger(options, id);
|
||||
const rxMetrics = metricManager.AddMetrics(`Session-${id}-RX`);
|
||||
const txMetrics = metricManager.AddMetrics(`Session-${id}-TX`);
|
||||
|
||||
session.on("bind_transceiver", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessions.push(session);
|
||||
sessionLogger.info(`Client connected, currently: ${sessions.length}`);
|
||||
session.send(pdu.response());
|
||||
startInterval(sessions, sessionLogger);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("bind_transmitter", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessionLogger.info("Client connected");
|
||||
session.send(pdu.response());
|
||||
startInterval(session, sessionLogger, rxMetrics);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("bind_receiver", function (pdu) {
|
||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||
sessionLogger.info("Client connected");
|
||||
session.send(pdu.response());
|
||||
startInterval(session, sessionLogger);
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||
);
|
||||
pdu.response({
|
||||
command_status: smpp.ESME_RBINDFAIL,
|
||||
});
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
session.on("enquire_link", function (pdu) {
|
||||
session.send(pdu.response());
|
||||
});
|
||||
session.on("submit_sm", async function (pdu) {
|
||||
if (!options.dr) {
|
||||
sessionLogger.info("Replying to incoming submit_sm");
|
||||
if (options.bars) {
|
||||
rxMetrics.AddEvent();
|
||||
}
|
||||
// setTimeout(() => {
|
||||
// session.send(pdu.response());
|
||||
// }, 200);
|
||||
session.send(pdu.response());
|
||||
return;
|
||||
}
|
||||
|
||||
sessionLogger.info("Generating DR for incoming submit_sm");
|
||||
let response = pdu.response();
|
||||
|
||||
let smppid = messageid++;
|
||||
if (options.randid) {
|
||||
smppid = crypto.randomBytes(8).toString("hex");
|
||||
}
|
||||
|
||||
response.message_id = smppid.toString(16);
|
||||
session.send(response);
|
||||
|
||||
let drMessage = "";
|
||||
let date = new Date()
|
||||
.toISOString()
|
||||
.replace(/T/, "")
|
||||
.replace(/\..+/, "")
|
||||
.replace(/-/g, "")
|
||||
.replace(/:/g, "")
|
||||
.substring(2, 12);
|
||||
|
||||
drMessage += "id:" + response.message_id + " ";
|
||||
drMessage += "sub:001 ";
|
||||
drMessage += "dlvrd:001 ";
|
||||
drMessage += "submit date:" + date + " ";
|
||||
drMessage += "done date:" + date + " ";
|
||||
drMessage += "stat:DELIVRD ";
|
||||
drMessage += "err:000 ";
|
||||
drMessage += "text:";
|
||||
|
||||
const DRPdu = {
|
||||
source_addr: pdu.destination_addr,
|
||||
destination_addr: pdu.source_addr,
|
||||
short_message: drMessage,
|
||||
esm_class: 4,
|
||||
};
|
||||
sessionLogger.info(`Generated DR as ${drMessage}`);
|
||||
session.deliver_sm(DRPdu);
|
||||
if (txMetrics) {
|
||||
txMetrics.AddEvent();
|
||||
}
|
||||
});
|
||||
|
||||
session.on("close", function () {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
sessionLogger.warn(`Session closed, now ${sessions.length}`);
|
||||
session.close();
|
||||
if (sessions.length === 0) {
|
||||
sessionLogger.info("No more sessions, stopping sending timer");
|
||||
sendTimer.clearInterval();
|
||||
}
|
||||
});
|
||||
session.on("error", function (err) {
|
||||
sessionLogger.error(`Fatal error ${err}`);
|
||||
session.close();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
server.on("error", function (err) {
|
||||
logger.error(`Fatal server error ${err}`);
|
||||
server.close();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
server.listen(options.port);
|
||||
logger.info(`SMPP Server listening on ${options.port}`);
|
||||
|
322
cliOptions.js
322
cliOptions.js
@@ -1,156 +1,166 @@
|
||||
const clientOptions = [
|
||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||
{ name: "host", alias: "h", type: String, description: "The host (IP) to connect to." },
|
||||
{ name: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||
{ name: "password", alias: "w", type: String, description: "SMPP related login info." },
|
||||
{ name: "sessions", type: Number, description: "Number of sessions to start, defaults to 1.", defaultOption: 1 },
|
||||
{
|
||||
name: "messagecount",
|
||||
type: Number,
|
||||
description: "Number of messages to send; Optional, defaults to 1.",
|
||||
defaultOption: 1,
|
||||
},
|
||||
{
|
||||
name: "window",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount of messages that are allowed to be 'in flight'. The client no longer waits for a response before sending the next message for up to <window> messages. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "windowsleep",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "mps",
|
||||
type: Number,
|
||||
description: "Number of messages to send per second",
|
||||
defaultOption: 999999,
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
type: String,
|
||||
description: "Source field of the sent messages.",
|
||||
defaultOption: "smppDebugClient",
|
||||
},
|
||||
{
|
||||
name: "destination",
|
||||
type: String,
|
||||
description: "Destination field of the sent messages.",
|
||||
defaultOption: "smpp",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: String,
|
||||
description: "Text content of the sent messages.",
|
||||
defaultOption: "smpp debug message",
|
||||
},
|
||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the client; Debug mode." },
|
||||
{ name: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
||||
{
|
||||
name: "bars",
|
||||
type: Boolean,
|
||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||
},
|
||||
{
|
||||
name: "metricsinterval",
|
||||
type: Number,
|
||||
defaultOption: 5,
|
||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||
},
|
||||
{
|
||||
name: "defaultmaxrate",
|
||||
type: Number,
|
||||
defaultOption: 1000,
|
||||
description: "Default max rate for metrics/bars."
|
||||
},
|
||||
];
|
||||
|
||||
const centerOptions = [
|
||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||
{ name: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||
{ name: "password", alias: "w", type: String, description: "SMPP related login info." },
|
||||
{ name: "dr", type: Boolean, description: "Whether or not to send Delivery Reports.", defaultOption: false },
|
||||
{
|
||||
name: "randid",
|
||||
type: Boolean,
|
||||
description: "SMPP ID generation is entirely random instead of sequential.",
|
||||
defaultOption: false,
|
||||
},
|
||||
{
|
||||
name: "sessions",
|
||||
type: Number,
|
||||
description: "Maximum number of client sessions to accept, defaults to 8.",
|
||||
defaultOption: 8,
|
||||
},
|
||||
{
|
||||
name: "messagecount",
|
||||
type: Number,
|
||||
description: "Number of messages to send; Optional, defaults to 0.",
|
||||
defaultOption: 0,
|
||||
},
|
||||
{
|
||||
name: "window",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount of messages that are allowed to be 'in flight'. The client no longer waits for a response before sending the next message for up to <window> messages. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "windowsleep",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "mps",
|
||||
type: Number,
|
||||
description: "Number of messages to send per second",
|
||||
defaultOption: 999999,
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
type: String,
|
||||
description: "Source field of the sent messages.",
|
||||
defaultOption: "smppDebugClient",
|
||||
},
|
||||
{
|
||||
name: "destination",
|
||||
type: String,
|
||||
description: "Destination field of the sent messages.",
|
||||
defaultOption: "smpp",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: String,
|
||||
description: "Text content of the sent messages.",
|
||||
defaultOption: "smpp debug message",
|
||||
},
|
||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the center; Debug mode." },
|
||||
{ name: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
||||
{
|
||||
name: "bars",
|
||||
type: Boolean,
|
||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||
},
|
||||
{
|
||||
name: "metricsinterval",
|
||||
type: Number,
|
||||
defaultOption: 5,
|
||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||
},
|
||||
{
|
||||
name: "defaultmaxrate",
|
||||
type: Number,
|
||||
defaultOption: 1000,
|
||||
description: "Default max rate for metrics/bars."
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = { clientOptions, centerOptions };
|
||||
const clientOptions = [
|
||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||
{ name: "host", alias: "h", type: String, description: "The host (IP) to connect to." },
|
||||
{ name: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||
{ name: "password", alias: "w", type: String, description: "SMPP related login info." },
|
||||
{ name: "sessions", type: Number, description: "Number of sessions to start, defaults to 1.", defaultOption: 1 },
|
||||
{
|
||||
name: "messagecount",
|
||||
type: Number,
|
||||
description: "Number of messages to send; Optional, defaults to 1.",
|
||||
defaultOption: 1,
|
||||
},
|
||||
{
|
||||
name: "window",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount of messages that are allowed to be 'in flight'. The client no longer waits for a response before sending the next message for up to <window> messages. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "windowsleep",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "mps",
|
||||
type: Number,
|
||||
description: "Number of messages to send per second",
|
||||
defaultOption: 999999,
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
type: String,
|
||||
description: "Source field of the sent messages.",
|
||||
defaultOption: "smppDebugClient",
|
||||
},
|
||||
{
|
||||
name: "destination",
|
||||
type: String,
|
||||
description: "Destination field of the sent messages.",
|
||||
defaultOption: "smpp",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: String,
|
||||
description: "Text content of the sent messages.",
|
||||
defaultOption: "smpp debug message",
|
||||
},
|
||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the client; Debug mode." },
|
||||
{ name: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
||||
{
|
||||
name: "bars",
|
||||
type: Boolean,
|
||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||
},
|
||||
{
|
||||
name: "metricsinterval",
|
||||
type: Number,
|
||||
defaultOption: 5,
|
||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||
},
|
||||
{
|
||||
name: "defaultmaxrate",
|
||||
type: Number,
|
||||
defaultOption: 1000,
|
||||
description: "Default max rate for metrics/bars."
|
||||
},
|
||||
{
|
||||
name: "longsms",
|
||||
type: Boolean,
|
||||
description: "Split messages into multiple parts. Applies only if message is too big for one packet."
|
||||
},
|
||||
];
|
||||
|
||||
const centerOptions = [
|
||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||
{ name: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||
{ name: "password", alias: "w", type: String, description: "SMPP related login info." },
|
||||
{ name: "dr", type: Boolean, description: "Whether or not to send Delivery Reports.", defaultOption: false },
|
||||
{
|
||||
name: "randid",
|
||||
type: Boolean,
|
||||
description: "SMPP ID generation is entirely random instead of sequential.",
|
||||
defaultOption: false,
|
||||
},
|
||||
{
|
||||
name: "sessions",
|
||||
type: Number,
|
||||
description: "Maximum number of client sessions to accept, defaults to 8.",
|
||||
defaultOption: 8,
|
||||
},
|
||||
{
|
||||
name: "messagecount",
|
||||
type: Number,
|
||||
description: "Number of messages to send; Optional, defaults to 0.",
|
||||
defaultOption: 0,
|
||||
},
|
||||
{
|
||||
name: "window",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount of messages that are allowed to be 'in flight'. The client no longer waits for a response before sending the next message for up to <window> messages. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "windowsleep",
|
||||
type: Number,
|
||||
description:
|
||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||
defaultOption: 100,
|
||||
},
|
||||
{
|
||||
name: "mps",
|
||||
type: Number,
|
||||
description: "Number of messages to send per second",
|
||||
defaultOption: 999999,
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
type: String,
|
||||
description: "Source field of the sent messages.",
|
||||
defaultOption: "smppDebugClient",
|
||||
},
|
||||
{
|
||||
name: "destination",
|
||||
type: String,
|
||||
description: "Destination field of the sent messages.",
|
||||
defaultOption: "smpp",
|
||||
},
|
||||
{
|
||||
name: "message",
|
||||
type: String,
|
||||
description: "Text content of the sent messages.",
|
||||
defaultOption: "smpp debug message",
|
||||
},
|
||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the center; Debug mode." },
|
||||
{ name: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
||||
{
|
||||
name: "bars",
|
||||
type: Boolean,
|
||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||
},
|
||||
{
|
||||
name: "metricsinterval",
|
||||
type: Number,
|
||||
defaultOption: 5,
|
||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||
},
|
||||
{
|
||||
name: "defaultmaxrate",
|
||||
type: Number,
|
||||
defaultOption: 1000,
|
||||
description: "Default max rate for metrics/bars."
|
||||
},
|
||||
{
|
||||
name: "longsms",
|
||||
type: Boolean,
|
||||
description: "Split messages into multiple parts. Applies only if message is too big for one packet."
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = { clientOptions, centerOptions };
|
||||
|
324
client.js
324
client.js
@@ -1,160 +1,164 @@
|
||||
const smpp = require("smpp");
|
||||
const commandLineArgs = require("command-line-args");
|
||||
const commandLineUsage = require("command-line-usage");
|
||||
const NanoTimer = require("nanotimer");
|
||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||
const { verifyDefaults, verifyExists } = require("./utils");
|
||||
const { clientOptions } = require("./cliOptions");
|
||||
const { MetricManager } = require("./metrics/metricManager");
|
||||
|
||||
const options = commandLineArgs(clientOptions);
|
||||
const logger = createBaseLogger(options);
|
||||
|
||||
if (options.help) {
|
||||
const usage = commandLineUsage([
|
||||
{
|
||||
header: "CLI SMPP (Client)",
|
||||
},
|
||||
{
|
||||
header: "Options",
|
||||
optionList: clientOptions,
|
||||
},
|
||||
{
|
||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||
},
|
||||
]);
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
verifyDefaults(options, clientOptions);
|
||||
verifyExists(options.host, "Host can not be undefined or empty! (--host)", logger);
|
||||
verifyExists(options.port, "Port can not be undefined or empty! (--port)", logger);
|
||||
verifyExists(options.systemid, "SystemID can not be undefined or empty! (--systemid)", logger);
|
||||
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||
|
||||
let inFlight = 0;
|
||||
let sent = 0;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const sendTimer = new NanoTimer();
|
||||
const metricManager = new MetricManager(options);
|
||||
|
||||
function startInterval(session, sessionLogger, metrics) {
|
||||
if (!metrics.progress && options.bars === true) {
|
||||
metrics.progress = metricManager.AddMetrics("Send progress", false);
|
||||
metrics.progress.bar.total = options.messagecount;
|
||||
metrics.window = metricManager.AddMetrics("Send window", false);
|
||||
metrics.window.bar.total = options.window;
|
||||
}
|
||||
sendTimer.setInterval(
|
||||
async () => {
|
||||
if (sent >= options.messagecount) {
|
||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||
sendTimer.clearInterval();
|
||||
} else if (inFlight < options.window) {
|
||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||
if (options.bars) {
|
||||
metrics.progress.bar.increment();
|
||||
metrics.window.bar.increment();
|
||||
}
|
||||
session.submit_sm(
|
||||
{
|
||||
source_addr: options.source,
|
||||
destination_addr: options.destination,
|
||||
short_message: options.message,
|
||||
},
|
||||
function (pdu) {
|
||||
if (metrics.window?.bar) {
|
||||
metrics.window.bar.update(metrics.window.bar.value - 1);
|
||||
}
|
||||
inFlight--;
|
||||
if (pdu.command_status === 0) {
|
||||
sessionLogger.info(`Received response with id ${pdu.message_id}`);
|
||||
success++;
|
||||
} else {
|
||||
sessionLogger.warn(`Message failed with id ${pdu.message_id}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
);
|
||||
if (metrics.txMetrics) {
|
||||
metrics.txMetrics.AddEvent();
|
||||
}
|
||||
sent++;
|
||||
inFlight++;
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||
);
|
||||
sendTimer.clearInterval();
|
||||
setTimeout(() => startInterval(session, sessionLogger, metrics), options.windowsleep);
|
||||
}
|
||||
},
|
||||
"",
|
||||
`${1 / options.mps} s`
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.sessions; i++) {
|
||||
const sessionLogger = createSessionLogger(options, i);
|
||||
sessionLogger.info(`Connecting to ${options.host}:${options.port}...`);
|
||||
const session = smpp.connect(
|
||||
{
|
||||
url: `smpp://${options.host}:${options.port}`,
|
||||
auto_enquire_link_period: 10000,
|
||||
debug: options.debug,
|
||||
},
|
||||
function () {
|
||||
sessionLogger.info(
|
||||
`Connected, sending bind_transciever with systemId '${options.systemid}' and password '${options.password}'...`
|
||||
);
|
||||
session.bind_transceiver(
|
||||
{
|
||||
system_id: options.systemid,
|
||||
password: options.password,
|
||||
},
|
||||
function (pdu) {
|
||||
if (pdu.command_status === 0) {
|
||||
sessionLogger.info(
|
||||
`Successfully bound, sending ${options.messagecount} messages '${options.source}'->'${options.destination}' ('${options.message}')`
|
||||
);
|
||||
const rxMetrics = metricManager.AddMetrics(`Session-${i}-RX`);
|
||||
const txMetrics = metricManager.AddMetrics(`Session-${i}-TX`);
|
||||
startInterval(session, sessionLogger, {
|
||||
rxMetrics,
|
||||
txMetrics,
|
||||
});
|
||||
// TODO: Add error message for invalid systemid and password
|
||||
|
||||
session.on("deliver_sm", function (pdu) {
|
||||
if (rxMetrics) {
|
||||
rxMetrics.AddEvent();
|
||||
}
|
||||
sessionLogger.info("Got deliver_sm, replying...");
|
||||
// setTimeout(() => {
|
||||
// session.send(pdu.response());
|
||||
// txMetrics.AddEvent();
|
||||
// }, 200);
|
||||
session.send(pdu.response());
|
||||
if (txMetrics) {
|
||||
txMetrics.AddEvent();
|
||||
}
|
||||
});
|
||||
session.on("enquire_link", function (pdu) {
|
||||
session.send(pdu.response());
|
||||
});
|
||||
session.on("close", function () {
|
||||
sessionLogger.error(`Session closed`);
|
||||
process.exit(1);
|
||||
});
|
||||
session.on("error", function (err) {
|
||||
sessionLogger.error(`Fatal error ${err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
const smpp = require("smpp");
|
||||
const commandLineArgs = require("command-line-args");
|
||||
const commandLineUsage = require("command-line-usage");
|
||||
const NanoTimer = require("nanotimer");
|
||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||
const { verifyDefaults, verifyExists, sendPdu } = require("./utils");
|
||||
const { clientOptions } = require("./cliOptions");
|
||||
const { MetricManager } = require("./metrics/metricManager");
|
||||
|
||||
const options = commandLineArgs(clientOptions);
|
||||
const logger = createBaseLogger(options);
|
||||
|
||||
if (options.help) {
|
||||
const usage = commandLineUsage([
|
||||
{
|
||||
header: "CLI SMPP (Client)",
|
||||
},
|
||||
{
|
||||
header: "Options",
|
||||
optionList: clientOptions,
|
||||
},
|
||||
{
|
||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||
},
|
||||
]);
|
||||
console.log(usage);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
verifyDefaults(options, clientOptions);
|
||||
verifyExists(options.host, "Host can not be undefined or empty! (--host)", logger);
|
||||
verifyExists(options.port, "Port can not be undefined or empty! (--port)", logger);
|
||||
verifyExists(options.systemid, "SystemID can not be undefined or empty! (--systemid)", logger);
|
||||
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||
|
||||
let inFlight = 0;
|
||||
let sent = 0;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const sendTimer = new NanoTimer();
|
||||
const metricManager = new MetricManager(options);
|
||||
|
||||
function startInterval(session, sessionLogger, metrics) {
|
||||
if (!metrics.progress && options.bars === true) {
|
||||
metrics.progress = metricManager.AddMetrics("Send progress", false);
|
||||
metrics.progress.bar.total = options.messagecount;
|
||||
metrics.window = metricManager.AddMetrics("Send window", false);
|
||||
metrics.window.bar.total = options.window;
|
||||
}
|
||||
sendTimer.setInterval(
|
||||
async () => {
|
||||
if (sent >= options.messagecount) {
|
||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||
sendTimer.clearInterval();
|
||||
} else if (inFlight < options.window) {
|
||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||
if (options.bars) {
|
||||
metrics.progress.bar.increment();
|
||||
metrics.window.bar.increment();
|
||||
}
|
||||
const pdu = new smpp.PDU("submit_sm", {
|
||||
source_addr: options.source,
|
||||
destination_addr: options.destination,
|
||||
short_message: options.message,
|
||||
});
|
||||
|
||||
sendPdu(session, pdu, sessionLogger, options.longsms)
|
||||
.then((resp) => {
|
||||
inFlight--;
|
||||
sessionLogger.info(`Received response with id ${resp.message_id}`);
|
||||
success++;
|
||||
})
|
||||
.catch((resp) => {
|
||||
inFlight--;
|
||||
sessionLogger.warn(`Message failed with id ${resp.message_id}`);
|
||||
failed++;
|
||||
});
|
||||
|
||||
if (metrics.txMetrics) {
|
||||
metrics.txMetrics.AddEvent();
|
||||
}
|
||||
sent++;
|
||||
inFlight++;
|
||||
} else {
|
||||
sessionLogger.warn(
|
||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||
);
|
||||
sendTimer.clearInterval();
|
||||
setTimeout(() => startInterval(session, sessionLogger, metrics), options.windowsleep);
|
||||
}
|
||||
},
|
||||
"",
|
||||
`${1 / options.mps} s`
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.sessions; i++) {
|
||||
const sessionLogger = createSessionLogger(options, i);
|
||||
sessionLogger.info(`Connecting to ${options.host}:${options.port}...`);
|
||||
const session = smpp.connect(
|
||||
{
|
||||
url: `smpp://${options.host}:${options.port}`,
|
||||
auto_enquire_link_period: 10000,
|
||||
debug: options.debug,
|
||||
},
|
||||
function () {
|
||||
sessionLogger.info(
|
||||
`Connected, sending bind_transciever with systemId '${options.systemid}' and password '${options.password}'...`
|
||||
);
|
||||
session.on('close', function () {
|
||||
sessionLogger.error(`Session closed`);
|
||||
process.exit(1);
|
||||
});
|
||||
session.bind_transceiver(
|
||||
{
|
||||
system_id: options.systemid,
|
||||
password: options.password,
|
||||
},
|
||||
function (pdu) {
|
||||
if (pdu.command_status === 0) {
|
||||
sessionLogger.info(
|
||||
`Successfully bound, sending ${options.messagecount} messages '${options.source}'->'${options.destination}' ('${options.message}')`
|
||||
);
|
||||
const rxMetrics = metricManager.AddMetrics(`Session-${i}-RX`);
|
||||
const txMetrics = metricManager.AddMetrics(`Session-${i}-TX`);
|
||||
startInterval(session, sessionLogger, {
|
||||
rxMetrics,
|
||||
txMetrics,
|
||||
});
|
||||
|
||||
session.on("deliver_sm", function (pdu) {
|
||||
if (rxMetrics) {
|
||||
rxMetrics.AddEvent();
|
||||
}
|
||||
sessionLogger.info("Got deliver_sm, replying...");
|
||||
// setTimeout(() => {
|
||||
// session.send(pdu.response());
|
||||
// txMetrics.AddEvent();
|
||||
// }, 200);
|
||||
session.send(pdu.response());
|
||||
if (txMetrics) {
|
||||
txMetrics.AddEvent();
|
||||
}
|
||||
});
|
||||
session.on("enquire_link", function (pdu) {
|
||||
session.send(pdu.response());
|
||||
});
|
||||
session.on("close", function () {
|
||||
sessionLogger.error(`Session closed`);
|
||||
process.exit(1);
|
||||
});
|
||||
session.on("error", function (err) {
|
||||
sessionLogger.error(`Fatal error ${err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
sessionLogger.error(`Failed to bind, status ${pdu.command_status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
150
logger.js
150
logger.js
@@ -1,75 +1,75 @@
|
||||
const { createLogger, format, transports } = require("winston");
|
||||
const { combine, timestamp, label, printf } = format;
|
||||
|
||||
const defaultFormat = printf(({ level, message, timestamp }) => {
|
||||
return `${timestamp} ${level}: ${message}`;
|
||||
});
|
||||
const sessionFormat = printf(({ level, message, label, timestamp }) => {
|
||||
return `${timestamp} [Session ${label}] ${level}: ${message}`;
|
||||
});
|
||||
function createBaseLogger(options) {
|
||||
const logger = createLogger({
|
||||
format: combine(format.colorize({ all: true }), timestamp(), defaultFormat),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const oldInfo = logger.info;
|
||||
const oldWarn = logger.info;
|
||||
const oldError = logger.error;
|
||||
logger.info = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldInfo(input);
|
||||
};
|
||||
logger.error = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldError(input);
|
||||
};
|
||||
logger.warn = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldWarn(input);
|
||||
};
|
||||
|
||||
return logger;
|
||||
}
|
||||
function createSessionLogger(options, ilabel) {
|
||||
const logger = createLogger({
|
||||
format: combine(label({ label: ilabel }), format.colorize({ all: true }), timestamp(), sessionFormat),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const oldInfo = logger.info;
|
||||
const oldWarn = logger.info;
|
||||
const oldError = logger.error;
|
||||
logger.info = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldInfo(input);
|
||||
};
|
||||
logger.error = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldError(input);
|
||||
};
|
||||
logger.warn = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldWarn(input);
|
||||
};
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
function shouldLog(options) {
|
||||
return options.logs || !options.bars;
|
||||
}
|
||||
|
||||
module.exports = { createBaseLogger, createSessionLogger };
|
||||
const { createLogger, format, transports } = require("winston");
|
||||
const { combine, timestamp, label, printf } = format;
|
||||
|
||||
const defaultFormat = printf(({ level, message, timestamp }) => {
|
||||
return `${timestamp} ${level}: ${message}`;
|
||||
});
|
||||
const sessionFormat = printf(({ level, message, label, timestamp }) => {
|
||||
return `${timestamp} [Session ${label}] ${level}: ${message}`;
|
||||
});
|
||||
function createBaseLogger(options) {
|
||||
const logger = createLogger({
|
||||
format: combine(format.colorize({ all: true }), timestamp(), defaultFormat),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const oldInfo = logger.info;
|
||||
const oldWarn = logger.info;
|
||||
const oldError = logger.error;
|
||||
logger.info = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldInfo(input);
|
||||
};
|
||||
logger.error = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldError(input);
|
||||
};
|
||||
logger.warn = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldWarn(input);
|
||||
};
|
||||
|
||||
return logger;
|
||||
}
|
||||
function createSessionLogger(options, ilabel) {
|
||||
const logger = createLogger({
|
||||
format: combine(label({ label: ilabel }), format.colorize({ all: true }), timestamp(), sessionFormat),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const oldInfo = logger.info;
|
||||
const oldWarn = logger.info;
|
||||
const oldError = logger.error;
|
||||
logger.info = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldInfo(input);
|
||||
};
|
||||
logger.error = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldError(input);
|
||||
};
|
||||
logger.warn = function (input) {
|
||||
if (!shouldLog(options)) {
|
||||
return;
|
||||
}
|
||||
oldWarn(input);
|
||||
};
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
function shouldLog(options) {
|
||||
return options.logs || !options.bars;
|
||||
}
|
||||
|
||||
module.exports = { createBaseLogger, createSessionLogger };
|
||||
|
@@ -1,46 +1,46 @@
|
||||
class CircularBuffer {
|
||||
constructor(size) {
|
||||
this.buffer = new Array(size);
|
||||
this.size = size;
|
||||
this.head = 0;
|
||||
this.tail = 0;
|
||||
}
|
||||
|
||||
push(item) {
|
||||
this.buffer[this.head] = item;
|
||||
this.head = (this.head + 1) % this.size;
|
||||
if (this.head === this.tail) {
|
||||
this.tail = (this.tail + 1) % this.size;
|
||||
}
|
||||
}
|
||||
|
||||
toArray() {
|
||||
const result = [];
|
||||
let current = this.tail;
|
||||
for (let i = 0; i < this.size; i++) {
|
||||
if (this.buffer[current] !== undefined) {
|
||||
result.push(this.buffer[current]);
|
||||
}
|
||||
current = (current + 1) % this.size;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
toArrayRecent(n = 10) {
|
||||
const result = [];
|
||||
const threshold = Date.now() - n * 1000;
|
||||
|
||||
let current = (this.head - 1 + this.size) % this.size;
|
||||
while (current !== this.tail) {
|
||||
if (this.buffer[current] !== undefined && this.buffer[current].timestamp > threshold) {
|
||||
result.push(this.buffer[current]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
current = (current - 1 + this.size) % this.size;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { CircularBuffer };
|
||||
class CircularBuffer {
|
||||
constructor(size) {
|
||||
this.buffer = new Array(size);
|
||||
this.size = size;
|
||||
this.head = 0;
|
||||
this.tail = 0;
|
||||
}
|
||||
|
||||
push(item) {
|
||||
this.buffer[this.head] = item;
|
||||
this.head = (this.head + 1) % this.size;
|
||||
if (this.head === this.tail) {
|
||||
this.tail = (this.tail + 1) % this.size;
|
||||
}
|
||||
}
|
||||
|
||||
toArray() {
|
||||
const result = [];
|
||||
let current = this.tail;
|
||||
for (let i = 0; i < this.size; i++) {
|
||||
if (this.buffer[current] !== undefined) {
|
||||
result.push(this.buffer[current]);
|
||||
}
|
||||
current = (current + 1) % this.size;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
toArrayRecent(n = 10) {
|
||||
const result = [];
|
||||
const threshold = Date.now() - n * 1000;
|
||||
|
||||
let current = (this.head - 1 + this.size) % this.size;
|
||||
while (current !== this.tail) {
|
||||
if (this.buffer[current] !== undefined && this.buffer[current].timestamp > threshold) {
|
||||
result.push(this.buffer[current]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
current = (current - 1 + this.size) % this.size;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { CircularBuffer };
|
||||
|
@@ -1,30 +1,30 @@
|
||||
const cliProgress = require("cli-progress");
|
||||
const { Metric } = require("./metrics");
|
||||
|
||||
class MetricManager {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
if (options.bars) {
|
||||
this.metricBufferSize = 1000;
|
||||
this.multibar = new cliProgress.MultiBar(
|
||||
{
|
||||
clearOnComplete: false,
|
||||
barCompleteChar: "\u2588",
|
||||
barIncompleteChar: "\u2591",
|
||||
format: " {bar} | {name} | {value}/{total}",
|
||||
},
|
||||
cliProgress.Presets.shades_grey
|
||||
);
|
||||
setInterval(() => this.multibar.update(), 200);
|
||||
}
|
||||
}
|
||||
|
||||
AddMetrics(name, refresh = true) {
|
||||
if (this.options.bars) {
|
||||
const metric = new Metric(name, this.multibar, this.metricBufferSize, this.options, refresh);
|
||||
return metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { MetricManager };
|
||||
const cliProgress = require("cli-progress");
|
||||
const { Metric } = require("./metrics");
|
||||
|
||||
class MetricManager {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
if (options.bars) {
|
||||
this.metricBufferSize = 1000;
|
||||
this.multibar = new cliProgress.MultiBar(
|
||||
{
|
||||
clearOnComplete: false,
|
||||
barCompleteChar: "\u2588",
|
||||
barIncompleteChar: "\u2591",
|
||||
format: " {bar} | {name} | {value}/{total}",
|
||||
},
|
||||
cliProgress.Presets.shades_grey
|
||||
);
|
||||
setInterval(() => this.multibar.update(), 200);
|
||||
}
|
||||
}
|
||||
|
||||
AddMetrics(name, refresh = true) {
|
||||
if (this.options.bars) {
|
||||
const metric = new Metric(name, this.multibar, this.metricBufferSize, this.options, refresh);
|
||||
return metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { MetricManager };
|
||||
|
@@ -1,39 +1,39 @@
|
||||
const { CircularBuffer } = require("./circularBuffer");
|
||||
|
||||
class Metric {
|
||||
constructor(barName, multibar, bufferSize, options, refresh = true) {
|
||||
this.options = options;
|
||||
this.multibar = multibar;
|
||||
this.bar = multibar.create(0, 0);
|
||||
this.bar.update(0, { name: barName });
|
||||
this.maxRate = this.options.defaultmaxrate;
|
||||
this.bar.total = this.maxRate;
|
||||
this.buffer = new CircularBuffer(bufferSize);
|
||||
if (refresh) {
|
||||
setInterval(this.UpdateBar.bind(this), 100);
|
||||
}
|
||||
}
|
||||
|
||||
AddEvent() {
|
||||
const timestamp = Date.now();
|
||||
this.buffer.push({ timestamp, count: 1 });
|
||||
}
|
||||
|
||||
GetRate() {
|
||||
const entries = this.buffer.toArrayRecent(this.options.metricsinterval);
|
||||
|
||||
const totalRX = entries.reduce((sum, entry) => sum + entry.count, 0);
|
||||
return Math.round((totalRX / this.options.metricsinterval) * 100) / 100;
|
||||
}
|
||||
|
||||
UpdateBar() {
|
||||
const eps = this.GetRate();
|
||||
if (eps > this.maxRate) {
|
||||
this.bar.total = eps;
|
||||
this.maxRate = eps;
|
||||
}
|
||||
this.bar.update(eps);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Metric };
|
||||
const { CircularBuffer } = require("./circularBuffer");
|
||||
|
||||
class Metric {
|
||||
constructor(barName, multibar, bufferSize, options, refresh = true) {
|
||||
this.options = options;
|
||||
this.multibar = multibar;
|
||||
this.bar = multibar.create(0, 0);
|
||||
this.bar.update(0, { name: barName });
|
||||
this.maxRate = this.options.defaultmaxrate;
|
||||
this.bar.total = this.maxRate;
|
||||
this.buffer = new CircularBuffer(bufferSize);
|
||||
if (refresh) {
|
||||
setInterval(this.UpdateBar.bind(this), 100);
|
||||
}
|
||||
}
|
||||
|
||||
AddEvent() {
|
||||
const timestamp = Date.now();
|
||||
this.buffer.push({ timestamp, count: 1 });
|
||||
}
|
||||
|
||||
GetRate() {
|
||||
const entries = this.buffer.toArrayRecent(this.options.metricsinterval);
|
||||
|
||||
const totalRX = entries.reduce((sum, entry) => sum + entry.count, 0);
|
||||
return Math.round((totalRX / this.options.metricsinterval) * 100) / 100;
|
||||
}
|
||||
|
||||
UpdateBar() {
|
||||
const eps = this.GetRate();
|
||||
if (eps > this.maxRate) {
|
||||
this.bar.total = eps;
|
||||
this.maxRate = eps;
|
||||
}
|
||||
this.bar.update(eps);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Metric };
|
||||
|
@@ -8,7 +8,8 @@
|
||||
"build-windows-client": "nexe -i client.js -o out/client-windows -t windows-x86-18.18.2",
|
||||
"build-linux-server": "nexe -i server.js -o out/server-linux -t linux-x64-18.18.2",
|
||||
"build-windows-server": "nexe -i server.js -o out/server-windows -t windows-x86-18.18.2",
|
||||
"build": "sh build.sh"
|
||||
"build": "sh build.sh",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -22,6 +23,8 @@
|
||||
"winston": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.53.0"
|
||||
"@types/jest": "^29.5.10",
|
||||
"eslint": "^8.55.0",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
|
2040
pnpm-lock.yaml
generated
2040
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
94
tests/utils.test.js
Normal file
94
tests/utils.test.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const smpp = require("smpp");
|
||||
const { splitToParts, verifyExists, getCharacterSizeForEncoding } = require("../utils");
|
||||
|
||||
describe("splitToParts", () => {
|
||||
// A pdu is expected to be one part if it has less than 160 characters and is encoded using GSM7 (data_coding = null or 0)
|
||||
// Given a pdu with short_message length less than 160 chars, it should return an array with a single pdu.
|
||||
it("should return an array with a single pdu when short_message length is less than or equal to maxMessageSizeBits", () => {
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
short_message: "test message",
|
||||
});
|
||||
const result = splitToParts(pdu);
|
||||
expect(result.length).toBe(1);
|
||||
});
|
||||
|
||||
// Given a pdu with short_message length greater than 160 chars, it should return an array with 2 pdus.
|
||||
it("should return an array with two pdus when short_message length is greater than maxMessageSizeBits and less than or equal to maxMessageSizeBits * 2", () => {
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
short_message: "c".repeat(200),
|
||||
});
|
||||
const result = splitToParts(pdu);
|
||||
expect(result.length).toBe(2);
|
||||
});
|
||||
|
||||
// Given a pdu with short_message length greater than 320 chars, it should return an array with 2 pdus.
|
||||
it("should return an array with three pdus when short_message length is greater than maxMessageSizeBits * 2 and less than or equal to maxMessageSizeBits * 3", () => {
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
short_message: "c".repeat(400),
|
||||
});
|
||||
const result = splitToParts(pdu);
|
||||
expect(result.length).toBe(3);
|
||||
});
|
||||
|
||||
// Given a pdu with short_message length equal to 0, it should return an empty array.
|
||||
it("should return an empty array when short_message length is equal to 0", () => {
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
short_message: "",
|
||||
});
|
||||
const result = splitToParts(pdu);
|
||||
expect(result.length).toBe(0);
|
||||
});
|
||||
|
||||
// Given a pdu with short_message length equal to 320, it should return an array with two pdus.
|
||||
it("should return an array with two pdus when short_message length is equal to maxMessageSizeBits", () => {
|
||||
const pdu = new smpp.PDU("deliver_sm", {
|
||||
short_message: "c".repeat(320),
|
||||
});
|
||||
const result = splitToParts(pdu);
|
||||
expect(result.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCharacterSizeForEncoding", () => {
|
||||
// Returns 7 when data_coding is 0
|
||||
it("should return 7 when data_coding is 0", () => {
|
||||
const pdu = { data_coding: 0 };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(7);
|
||||
});
|
||||
|
||||
// Returns 8 when data_coding is 1
|
||||
it("should return 8 when data_coding is 1", () => {
|
||||
const pdu = { data_coding: 1 };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(8);
|
||||
});
|
||||
|
||||
// Returns 16 when data_coding is 8
|
||||
it("should return 16 when data_coding is 8", () => {
|
||||
const pdu = { data_coding: 8 };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(16);
|
||||
});
|
||||
|
||||
// Returns 7 when data_coding is null
|
||||
it("should return 0 when data_coding is null", () => {
|
||||
const pdu = { data_coding: null };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(7);
|
||||
});
|
||||
|
||||
// Returns 0 when data_coding is not a number
|
||||
it("should return 0 when data_coding is not a number", () => {
|
||||
const pdu = { data_coding: "abc" };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
// Returns 0 when data_coding is negative
|
||||
it("should return 0 when data_coding is negative", () => {
|
||||
const pdu = { data_coding: -1 };
|
||||
const result = getCharacterSizeForEncoding(pdu);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
121
utils.js
121
utils.js
@@ -1,17 +1,104 @@
|
||||
function verifyExists(value, err, logger) {
|
||||
if (!value) {
|
||||
logger.error(err);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
function verifyDefaults(options, definitions) {
|
||||
for (const optionDefinition of definitions) {
|
||||
if (optionDefinition.defaultOption) {
|
||||
if (!options[optionDefinition.name]) {
|
||||
options[optionDefinition.name] = optionDefinition.defaultOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { verifyDefaults, verifyExists };
|
||||
const smpp = require("smpp");
|
||||
|
||||
function verifyExists(value, err, logger) {
|
||||
if (!value) {
|
||||
logger.error(err);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
function verifyDefaults(options, definitions) {
|
||||
for (const optionDefinition of definitions) {
|
||||
if (optionDefinition.defaultOption) {
|
||||
if (!options[optionDefinition.name]) {
|
||||
options[optionDefinition.name] = optionDefinition.defaultOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCharacterSizeForEncoding(pdu) {
|
||||
let encoding = pdu.data_coding;
|
||||
if (!encoding) {
|
||||
encoding = 0;
|
||||
}
|
||||
let characterSizeBits = 0;
|
||||
switch (encoding) {
|
||||
case 0:
|
||||
characterSizeBits = 7;
|
||||
break;
|
||||
case 1:
|
||||
characterSizeBits = 8;
|
||||
break;
|
||||
case 8:
|
||||
characterSizeBits = 16;
|
||||
break;
|
||||
}
|
||||
return characterSizeBits;
|
||||
}
|
||||
|
||||
const maxMessageSizeBits = 1120;
|
||||
function splitToParts(pdu) {
|
||||
const charSize = getCharacterSizeForEncoding(pdu);
|
||||
const maxMessageLength = maxMessageSizeBits / charSize;
|
||||
|
||||
const splitMessage = [];
|
||||
const message = pdu.short_message;
|
||||
const messageLength = message.length;
|
||||
const messageCount = (messageLength / maxMessageLength) | 0;
|
||||
for (let i = 0; i < messageCount; i++) {
|
||||
splitMessage.push(message.slice(i * maxMessageLength, i * maxMessageLength + maxMessageLength));
|
||||
}
|
||||
|
||||
const pdus = splitMessage.map((messagePart, index) => {
|
||||
let udh = Buffer.from([0x05, 0x00, 0x03, this.iterator++, messageCount, index + 1]);
|
||||
|
||||
let partPdu = new smpp.PDU(pdu.command, { ...pdu });
|
||||
partPdu.short_message = {
|
||||
udh: udh,
|
||||
message: messagePart,
|
||||
};
|
||||
return partPdu;
|
||||
});
|
||||
return pdus;
|
||||
}
|
||||
|
||||
// TODO: Add "uselongsms" switch to options;
|
||||
async function sendPdu(session, pdu, logger, uselongsms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (uselongsms) {
|
||||
const pdus = splitToParts(pdu);
|
||||
logger.info(`Sending long sms of ${pdus.length} parts`);
|
||||
const total = pdus.length;
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const promises = pdus.map((pdu) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
session.send(pdu, (respPdu) => {
|
||||
if (respPdu.command_status === 0) {
|
||||
resolve(respPdu);
|
||||
} else {
|
||||
reject(respPdu);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Promise.all(promises)
|
||||
.then((responses) => {
|
||||
resolve(responses[0]);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
session.send(pdu, (respPdu) => {
|
||||
if (respPdu.command_status === 0) {
|
||||
resolve(respPdu);
|
||||
} else {
|
||||
reject(respPdu);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { verifyDefaults, verifyExists, sendPdu, splitToParts, getCharacterSizeForEncoding };
|
||||
|
Reference in New Issue
Block a user