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 = {
|
module.exports = {
|
||||||
"env": {
|
env: {
|
||||||
"node": true,
|
node: true,
|
||||||
"commonjs": true,
|
commonjs: true,
|
||||||
"es2021": true
|
es2021: true,
|
||||||
},
|
"jest/globals": true,
|
||||||
"extends": "eslint:recommended",
|
},
|
||||||
"overrides": [
|
extends: "eslint:recommended",
|
||||||
{
|
overrides: [
|
||||||
"env": {
|
{
|
||||||
"node": true
|
env: {
|
||||||
},
|
node: true,
|
||||||
"files": [
|
},
|
||||||
".eslintrc.{js,cjs}"
|
files: [".eslintrc.{js,cjs}"],
|
||||||
],
|
parserOptions: {
|
||||||
"parserOptions": {
|
sourceType: "script",
|
||||||
"sourceType": "script"
|
},
|
||||||
}
|
},
|
||||||
}
|
],
|
||||||
],
|
parserOptions: {
|
||||||
"parserOptions": {
|
ecmaVersion: "latest",
|
||||||
"ecmaVersion": "latest"
|
},
|
||||||
},
|
rules: {},
|
||||||
"rules": {
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
out
|
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
|
# SMPP_CLI
|
||||||
|
|
||||||
### A small application used to create SMPP clients or centers. Serves as both the SMSC and ESME.
|
### A small application used to create SMPP clients or centers. Serves as both the SMSC and ESME.
|
||||||
|
|
||||||
(Taken from the help page)
|
(Taken from the help page)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
```
|
```
|
||||||
CLI SMPP (Client)
|
CLI SMPP (Client)
|
||||||
|
|
||||||
Options
|
Options
|
||||||
|
|
||||||
--help Display this usage guide.
|
--help Display this usage guide.
|
||||||
-h, --host string The host (IP) to connect to.
|
-h, --host string The host (IP) to connect to.
|
||||||
-p, --port number The port to connect to.
|
-p, --port number The port to connect to.
|
||||||
-s, --systemid string SMPP related login info.
|
-s, --systemid string SMPP related login info.
|
||||||
-w, --password string SMPP related login info.
|
-w, --password string SMPP related login info.
|
||||||
--sessions number Number of sessions to start, defaults to 1.
|
--sessions number Number of sessions to start, defaults to 1.
|
||||||
--messagecount number Number of messages to send; Optional, 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
|
--window number Defines the amount of messages that are allowed to be
|
||||||
'in flight'. The client no longer waits for a
|
'in flight'. The client no longer waits for a
|
||||||
response before sending the next message for up to
|
response before sending the next message for up to
|
||||||
<window> messages. Defaults to 100.
|
<window> messages. Defaults to 100.
|
||||||
--windowsleep number Defines the amount time (in ms) waited between
|
--windowsleep number Defines the amount time (in ms) waited between
|
||||||
retrying in the case of full window. Defaults to 100.
|
retrying in the case of full window. Defaults to 100.
|
||||||
--mps number Number of messages to send per second
|
--mps number Number of messages to send per second
|
||||||
--source string Source field of the sent messages.
|
--source string Source field of the sent messages.
|
||||||
--destination string Destination field of the sent messages.
|
--destination string Destination field of the sent messages.
|
||||||
--message string Text content of the sent messages.
|
--message string Text content of the sent messages.
|
||||||
--debug Display all traffic to and from the client; Debug
|
--debug Display all traffic to and from the client; Debug
|
||||||
mode.
|
mode.
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
CLI SMPP (Center)
|
CLI SMPP (Center)
|
||||||
|
|
||||||
Options
|
Options
|
||||||
|
|
||||||
--help Display this usage guide.
|
--help Display this usage guide.
|
||||||
-p, --port number The port to connect to.
|
-p, --port number The port to connect to.
|
||||||
-s, --systemid string SMPP related login info.
|
-s, --systemid string SMPP related login info.
|
||||||
-w, --password string SMPP related login info.
|
-w, --password string SMPP related login info.
|
||||||
--dr Whether or not to send Delivery Reports.
|
--dr Whether or not to send Delivery Reports.
|
||||||
--sessions number Maximum number of client sessions to accept, defaults
|
--sessions number Maximum number of client sessions to accept, defaults
|
||||||
to 8.
|
to 8.
|
||||||
--messagecount number Number of messages to send; Optional, defaults to 0.
|
--messagecount number Number of messages to send; Optional, defaults to 0.
|
||||||
--window number Defines the amount of messages that are allowed to be
|
--window number Defines the amount of messages that are allowed to be
|
||||||
'in flight'. The client no longer waits for a
|
'in flight'. The client no longer waits for a
|
||||||
response before sending the next message for up to
|
response before sending the next message for up to
|
||||||
<window> messages. Defaults to 100.
|
<window> messages. Defaults to 100.
|
||||||
--windowsleep number Defines the amount time (in ms) waited between
|
--windowsleep number Defines the amount time (in ms) waited between
|
||||||
retrying in the case of full window. Defaults to 100.
|
retrying in the case of full window. Defaults to 100.
|
||||||
--mps number Number of messages to send per second
|
--mps number Number of messages to send per second
|
||||||
--source string Source field of the sent messages.
|
--source string Source field of the sent messages.
|
||||||
--destination string Destination field of the sent messages.
|
--destination string Destination field of the sent messages.
|
||||||
--message string Text content of the sent messages.
|
--message string Text content of the sent messages.
|
||||||
--debug Display all traffic to and from the center; Debug
|
--debug Display all traffic to and from the center; Debug
|
||||||
mode.
|
mode.
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
#### Center example usage:
|
#### Center example usage:
|
||||||
```
|
```
|
||||||
./center-win.exe \
|
./center-win.exe \
|
||||||
--port 7001 \
|
--port 7001 \
|
||||||
--systemid test \
|
--systemid test \
|
||||||
--password test \
|
--password test \
|
||||||
--sessions 10
|
--sessions 10
|
||||||
```
|
```
|
||||||
Running this command will spawn an SMPP center (SMSC) which will:
|
Running this command will spawn an SMPP center (SMSC) which will:
|
||||||
- Start listening on 7001
|
- Start listening on 7001
|
||||||
- Accept clients with test:test credentials
|
- Accept clients with test:test credentials
|
||||||
- Allow up to a maximum of 10 sessions
|
- Allow up to a maximum of 10 sessions
|
||||||
#### Client example usage:
|
#### Client example usage:
|
||||||
```
|
```
|
||||||
./client-win.exe \
|
./client-win.exe \
|
||||||
--host localhost \
|
--host localhost \
|
||||||
--port 7001 \
|
--port 7001 \
|
||||||
--systemid test \
|
--systemid test \
|
||||||
--password test \
|
--password test \
|
||||||
--window 5 \
|
--window 5 \
|
||||||
--windowsleep 100 \
|
--windowsleep 100 \
|
||||||
--messagecount 10000 \
|
--messagecount 10000 \
|
||||||
--mps 10 \
|
--mps 10 \
|
||||||
--sessions 10
|
--sessions 10
|
||||||
```
|
```
|
||||||
Running this command will spawn an SMPP client (ESME) which will:
|
Running this command will spawn an SMPP client (ESME) which will:
|
||||||
- Try to connect 10 sessions to localhost:7001
|
- Try to connect 10 sessions to localhost:7001
|
||||||
- Once connected try to bind using test:test
|
- 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
|
- 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
|
- If the window is filled during sending the session will wait 100ms before attempting send again
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
#### Center example usage (sending):
|
#### Center example usage (sending):
|
||||||
```
|
```
|
||||||
./center-win.exe \
|
./center-win.exe \
|
||||||
--port 7001 \
|
--port 7001 \
|
||||||
--systemid test \
|
--systemid test \
|
||||||
--password test \
|
--password test \
|
||||||
--window 5 \
|
--window 5 \
|
||||||
--windowsleep 100 \
|
--windowsleep 100 \
|
||||||
--messagecount 10000 \
|
--messagecount 10000 \
|
||||||
--mps 10 \
|
--mps 10 \
|
||||||
--sessions 10
|
--sessions 10
|
||||||
```
|
```
|
||||||
Running this command will spawn an SMPP center (SMSC) which will:
|
Running this command will spawn an SMPP center (SMSC) which will:
|
||||||
- Start listening on 7001
|
- Start listening on 7001
|
||||||
- Accept clients with test:test credentials
|
- Accept clients with test:test credentials
|
||||||
- Allow up to a maximum of 10 sessions
|
- 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
|
- 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
|
- 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 smpp = require("smpp");
|
||||||
const commandLineArgs = require("command-line-args");
|
const commandLineArgs = require("command-line-args");
|
||||||
const commandLineUsage = require("command-line-usage");
|
const commandLineUsage = require("command-line-usage");
|
||||||
const NanoTimer = require("nanotimer");
|
const NanoTimer = require("nanotimer");
|
||||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||||
const { verifyDefaults, verifyExists } = require("./utils");
|
const { verifyDefaults, verifyExists, sendPdu } = require("./utils");
|
||||||
const { centerOptions } = require("./cliOptions");
|
const { centerOptions } = require("./cliOptions");
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const { MetricManager } = require("./metrics/metricManager");
|
const { MetricManager } = require("./metrics/metricManager");
|
||||||
|
|
||||||
const options = commandLineArgs(centerOptions);
|
const options = commandLineArgs(centerOptions);
|
||||||
const logger = createBaseLogger(options);
|
const logger = createBaseLogger(options);
|
||||||
|
|
||||||
if (options.help) {
|
if (options.help) {
|
||||||
const usage = commandLineUsage([
|
const usage = commandLineUsage([
|
||||||
{
|
{
|
||||||
header: "CLI SMPP (Center)",
|
header: "CLI SMPP (Center)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Options",
|
header: "Options",
|
||||||
optionList: centerOptions,
|
optionList: centerOptions,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
console.log(usage);
|
console.log(usage);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifyDefaults(options, centerOptions);
|
||||||
verifyDefaults(options, centerOptions);
|
verifyExists(options.port, "Port can not be undefined or empty! (--port)", 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.systemid, "SystemID can not be undefined or empty! (--systemid)", logger);
|
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||||
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
|
||||||
|
let inFlight = 0;
|
||||||
let inFlight = 0;
|
let sent = 0;
|
||||||
let sent = 0;
|
let success = 0;
|
||||||
let success = 0;
|
let failed = 0;
|
||||||
let failed = 0;
|
const sendTimer = new NanoTimer();
|
||||||
const sendTimer = new NanoTimer();
|
const metricManager = new MetricManager(options);
|
||||||
const metricManager = new MetricManager(options);
|
|
||||||
|
// TODO: Currently bars are broken
|
||||||
// TODO: Fix issue where a client disconnecting does not stop this timer
|
// A major rework will need to happen before bars are able to play nice with multiple sessions
|
||||||
// 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
|
||||||
// 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
|
||||||
// Instead just use the same timer but make a pool of connections; That way both problems will be solved
|
function startInterval(sessions, sessionLogger, rxMetrics) {
|
||||||
function startInterval(session, sessionLogger, rxMetrics) {
|
if (!options.messagecount > 0) {
|
||||||
if (!options.messagecount > 0) {
|
sessionLogger.info("No messages to send");
|
||||||
sessionLogger.info("No messages to send");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
let sessionPointer = 0;
|
||||||
sendTimer.setInterval(
|
sendTimer.setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
if (sent >= options.messagecount) {
|
if (sent >= options.messagecount) {
|
||||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||||
sendTimer.clearInterval();
|
sendTimer.clearInterval();
|
||||||
} else if (inFlight < options.window) {
|
} else if (inFlight < options.window) {
|
||||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||||
session.deliver_sm(
|
const pdu = new smpp.PDU("deliver_sm", {
|
||||||
{
|
source_addr: options.source,
|
||||||
source_addr: options.source,
|
destination_addr: options.destination,
|
||||||
destination_addr: options.destination,
|
short_message: options.message,
|
||||||
short_message: options.message,
|
});
|
||||||
},
|
|
||||||
function (pdu) {
|
if (sessionPointer >= sessions.length) {
|
||||||
inFlight--;
|
sessionPointer = 0;
|
||||||
if (pdu.command_status === 0) {
|
}
|
||||||
sessionLogger.info(`Received response with id ${pdu.message_id}`);
|
sendPdu(sessions[sessionPointer++], pdu, sessionLogger, options.longsms)
|
||||||
success++;
|
.then((resp) => {
|
||||||
} else {
|
inFlight--;
|
||||||
sessionLogger.warn(`Message failed with id ${pdu.message_id}`);
|
sessionLogger.info(`Received response with id ${resp.message_id}`);
|
||||||
failed++;
|
success++;
|
||||||
}
|
})
|
||||||
}
|
.catch((resp) => {
|
||||||
);
|
inFlight--;
|
||||||
rxMetrics.AddEvent();
|
sessionLogger.warn(`Message failed with id ${resp.message_id}`);
|
||||||
sent++;
|
failed++;
|
||||||
inFlight++;
|
});
|
||||||
} else {
|
sent++;
|
||||||
sessionLogger.warn(
|
inFlight++;
|
||||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
} else {
|
||||||
);
|
sessionLogger.warn(
|
||||||
sendTimer.clearInterval();
|
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||||
setTimeout(() => startInterval(session, sessionLogger), options.windowsleep);
|
);
|
||||||
}
|
sendTimer.clearInterval();
|
||||||
},
|
setTimeout(() => startInterval(sessions, sessionLogger), options.windowsleep);
|
||||||
"",
|
}
|
||||||
`${1 / options.mps} s`
|
},
|
||||||
);
|
"",
|
||||||
}
|
`${1 / options.mps} s`
|
||||||
|
);
|
||||||
logger.info(`Staring server on port ${options.port}...`);
|
}
|
||||||
let sessionid = 1;
|
|
||||||
let messageid = 0;
|
logger.info(`Staring server on port ${options.port}...`);
|
||||||
const server = smpp.createServer(
|
let sessionid = 1;
|
||||||
{
|
let messageid = 0;
|
||||||
debug: options.debug,
|
const sessions = [];
|
||||||
},
|
const server = smpp.createServer(
|
||||||
function (session) {
|
{
|
||||||
const id = sessionid++;
|
debug: options.debug,
|
||||||
const sessionLogger = createSessionLogger(options, id);
|
},
|
||||||
const rxMetrics = metricManager.AddMetrics(`Session-${id}-RX`);
|
function (session) {
|
||||||
const txMetrics = metricManager.AddMetrics(`Session-${id}-TX`);
|
const id = sessionid++;
|
||||||
|
const sessionLogger = createSessionLogger(options, id);
|
||||||
session.on("bind_transceiver", function (pdu) {
|
const rxMetrics = metricManager.AddMetrics(`Session-${id}-RX`);
|
||||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
const txMetrics = metricManager.AddMetrics(`Session-${id}-TX`);
|
||||||
sessionLogger.info("Client connected");
|
|
||||||
session.send(pdu.response());
|
session.on("bind_transceiver", function (pdu) {
|
||||||
startInterval(session, sessionLogger);
|
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||||
} else {
|
sessions.push(session);
|
||||||
sessionLogger.warn(
|
sessionLogger.info(`Client connected, currently: ${sessions.length}`);
|
||||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
session.send(pdu.response());
|
||||||
);
|
startInterval(sessions, sessionLogger);
|
||||||
pdu.response({
|
} else {
|
||||||
command_status: smpp.ESME_RBINDFAIL,
|
sessionLogger.warn(
|
||||||
});
|
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||||
session.close();
|
);
|
||||||
}
|
pdu.response({
|
||||||
});
|
command_status: smpp.ESME_RBINDFAIL,
|
||||||
session.on("bind_transmitter", function (pdu) {
|
});
|
||||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
session.close();
|
||||||
sessionLogger.info("Client connected");
|
}
|
||||||
session.send(pdu.response());
|
});
|
||||||
startInterval(session, sessionLogger);
|
session.on("bind_transmitter", function (pdu) {
|
||||||
} else {
|
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||||
sessionLogger.warn(
|
sessionLogger.info("Client connected");
|
||||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
session.send(pdu.response());
|
||||||
);
|
startInterval(session, sessionLogger, rxMetrics);
|
||||||
pdu.response({
|
} else {
|
||||||
command_status: smpp.ESME_RBINDFAIL,
|
sessionLogger.warn(
|
||||||
});
|
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||||
session.close();
|
);
|
||||||
}
|
pdu.response({
|
||||||
});
|
command_status: smpp.ESME_RBINDFAIL,
|
||||||
session.on("bind_receiver", function (pdu) {
|
});
|
||||||
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
session.close();
|
||||||
sessionLogger.info("Client connected");
|
}
|
||||||
session.send(pdu.response());
|
});
|
||||||
startInterval(session, sessionLogger);
|
session.on("bind_receiver", function (pdu) {
|
||||||
} else {
|
if (pdu.system_id === options.systemid && pdu.password === options.password) {
|
||||||
sessionLogger.warn(
|
sessionLogger.info("Client connected");
|
||||||
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
session.send(pdu.response());
|
||||||
);
|
startInterval(session, sessionLogger);
|
||||||
pdu.response({
|
} else {
|
||||||
command_status: smpp.ESME_RBINDFAIL,
|
sessionLogger.warn(
|
||||||
});
|
`Client tried to connect with incorrect login ('${pdu.system_id}' '${pdu.password}')`
|
||||||
session.close();
|
);
|
||||||
}
|
pdu.response({
|
||||||
});
|
command_status: smpp.ESME_RBINDFAIL,
|
||||||
session.on("enquire_link", function (pdu) {
|
});
|
||||||
session.send(pdu.response());
|
session.close();
|
||||||
});
|
}
|
||||||
session.on("submit_sm", async function (pdu) {
|
});
|
||||||
if (!options.dr) {
|
session.on("enquire_link", function (pdu) {
|
||||||
sessionLogger.info("Replying to incoming submit_sm");
|
session.send(pdu.response());
|
||||||
if (options.bars) {
|
});
|
||||||
rxMetrics.AddEvent();
|
session.on("submit_sm", async function (pdu) {
|
||||||
}
|
if (!options.dr) {
|
||||||
// setTimeout(() => {
|
sessionLogger.info("Replying to incoming submit_sm");
|
||||||
// session.send(pdu.response());
|
if (options.bars) {
|
||||||
// }, 200);
|
rxMetrics.AddEvent();
|
||||||
session.send(pdu.response());
|
}
|
||||||
return;
|
// setTimeout(() => {
|
||||||
}
|
// session.send(pdu.response());
|
||||||
|
// }, 200);
|
||||||
sessionLogger.info("Generating DR for incoming submit_sm");
|
session.send(pdu.response());
|
||||||
let response = pdu.response();
|
return;
|
||||||
|
}
|
||||||
let smppid = messageid++;
|
|
||||||
if (options.randid) {
|
sessionLogger.info("Generating DR for incoming submit_sm");
|
||||||
smppid = crypto.randomBytes(8).toString("hex");
|
let response = pdu.response();
|
||||||
}
|
|
||||||
|
let smppid = messageid++;
|
||||||
response.message_id = smppid.toString(16);
|
if (options.randid) {
|
||||||
session.send(response);
|
smppid = crypto.randomBytes(8).toString("hex");
|
||||||
|
}
|
||||||
let drMessage = "";
|
|
||||||
let date = new Date()
|
response.message_id = smppid.toString(16);
|
||||||
.toISOString()
|
session.send(response);
|
||||||
.replace(/T/, "")
|
|
||||||
.replace(/\..+/, "")
|
let drMessage = "";
|
||||||
.replace(/-/g, "")
|
let date = new Date()
|
||||||
.replace(/:/g, "")
|
.toISOString()
|
||||||
.substring(2, 12);
|
.replace(/T/, "")
|
||||||
|
.replace(/\..+/, "")
|
||||||
drMessage += "id:" + response.message_id + " ";
|
.replace(/-/g, "")
|
||||||
drMessage += "sub:001 ";
|
.replace(/:/g, "")
|
||||||
drMessage += "dlvrd:001 ";
|
.substring(2, 12);
|
||||||
drMessage += "submit date:" + date + " ";
|
|
||||||
drMessage += "done date:" + date + " ";
|
drMessage += "id:" + response.message_id + " ";
|
||||||
drMessage += "stat:DELIVRD ";
|
drMessage += "sub:001 ";
|
||||||
drMessage += "err:000 ";
|
drMessage += "dlvrd:001 ";
|
||||||
drMessage += "text:";
|
drMessage += "submit date:" + date + " ";
|
||||||
|
drMessage += "done date:" + date + " ";
|
||||||
const DRPdu = {
|
drMessage += "stat:DELIVRD ";
|
||||||
source_addr: pdu.destination_addr,
|
drMessage += "err:000 ";
|
||||||
destination_addr: pdu.source_addr,
|
drMessage += "text:";
|
||||||
short_message: drMessage,
|
|
||||||
esm_class: 4,
|
const DRPdu = {
|
||||||
};
|
source_addr: pdu.destination_addr,
|
||||||
sessionLogger.info(`Generated DR as ${drMessage}`);
|
destination_addr: pdu.source_addr,
|
||||||
session.deliver_sm(DRPdu);
|
short_message: drMessage,
|
||||||
if (txMetrics) {
|
esm_class: 4,
|
||||||
txMetrics.AddEvent();
|
};
|
||||||
}
|
sessionLogger.info(`Generated DR as ${drMessage}`);
|
||||||
});
|
session.deliver_sm(DRPdu);
|
||||||
|
if (txMetrics) {
|
||||||
session.on("close", function () {
|
txMetrics.AddEvent();
|
||||||
sessionLogger.warn(`Session closed`);
|
}
|
||||||
session.close();
|
});
|
||||||
});
|
|
||||||
session.on("error", function (err) {
|
session.on("close", function () {
|
||||||
sessionLogger.error(`Fatal error ${err}`);
|
sessions.splice(sessions.indexOf(session), 1);
|
||||||
session.close();
|
sessionLogger.warn(`Session closed, now ${sessions.length}`);
|
||||||
});
|
session.close();
|
||||||
}
|
if (sessions.length === 0) {
|
||||||
);
|
sessionLogger.info("No more sessions, stopping sending timer");
|
||||||
|
sendTimer.clearInterval();
|
||||||
server.on("error", function (err) {
|
}
|
||||||
logger.error(`Fatal server error ${err}`);
|
});
|
||||||
server.close();
|
session.on("error", function (err) {
|
||||||
process.exit(1);
|
sessionLogger.error(`Fatal error ${err}`);
|
||||||
});
|
session.close();
|
||||||
|
});
|
||||||
server.listen(options.port);
|
}
|
||||||
logger.info(`SMPP Server listening on ${options.port}`);
|
);
|
||||||
|
|
||||||
|
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 = [
|
const clientOptions = [
|
||||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||||
{ name: "host", alias: "h", type: String, description: "The host (IP) to connect to." },
|
{ 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: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||||
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||||
{ name: "password", alias: "w", 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: "sessions", type: Number, description: "Number of sessions to start, defaults to 1.", defaultOption: 1 },
|
||||||
{
|
{
|
||||||
name: "messagecount",
|
name: "messagecount",
|
||||||
type: Number,
|
type: Number,
|
||||||
description: "Number of messages to send; Optional, defaults to 1.",
|
description: "Number of messages to send; Optional, defaults to 1.",
|
||||||
defaultOption: 1,
|
defaultOption: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "window",
|
name: "window",
|
||||||
type: Number,
|
type: Number,
|
||||||
description:
|
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.",
|
"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,
|
defaultOption: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "windowsleep",
|
name: "windowsleep",
|
||||||
type: Number,
|
type: Number,
|
||||||
description:
|
description:
|
||||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||||
defaultOption: 100,
|
defaultOption: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "mps",
|
name: "mps",
|
||||||
type: Number,
|
type: Number,
|
||||||
description: "Number of messages to send per second",
|
description: "Number of messages to send per second",
|
||||||
defaultOption: 999999,
|
defaultOption: 999999,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "source",
|
name: "source",
|
||||||
type: String,
|
type: String,
|
||||||
description: "Source field of the sent messages.",
|
description: "Source field of the sent messages.",
|
||||||
defaultOption: "smppDebugClient",
|
defaultOption: "smppDebugClient",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "destination",
|
name: "destination",
|
||||||
type: String,
|
type: String,
|
||||||
description: "Destination field of the sent messages.",
|
description: "Destination field of the sent messages.",
|
||||||
defaultOption: "smpp",
|
defaultOption: "smpp",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "message",
|
name: "message",
|
||||||
type: String,
|
type: String,
|
||||||
description: "Text content of the sent messages.",
|
description: "Text content of the sent messages.",
|
||||||
defaultOption: "smpp debug message",
|
defaultOption: "smpp debug message",
|
||||||
},
|
},
|
||||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the client; Debug mode." },
|
{ 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: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
||||||
{
|
{
|
||||||
name: "bars",
|
name: "bars",
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "metricsinterval",
|
name: "metricsinterval",
|
||||||
type: Number,
|
type: Number,
|
||||||
defaultOption: 5,
|
defaultOption: 5,
|
||||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "defaultmaxrate",
|
name: "defaultmaxrate",
|
||||||
type: Number,
|
type: Number,
|
||||||
defaultOption: 1000,
|
defaultOption: 1000,
|
||||||
description: "Default max rate for metrics/bars."
|
description: "Default max rate for metrics/bars."
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
name: "longsms",
|
||||||
const centerOptions = [
|
type: Boolean,
|
||||||
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
description: "Split messages into multiple parts. Applies only if message is too big for one packet."
|
||||||
{ 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 },
|
const centerOptions = [
|
||||||
{
|
{ name: "help", type: Boolean, description: "Display this usage guide." },
|
||||||
name: "randid",
|
{ name: "port", alias: "p", type: Number, description: "The port to connect to." },
|
||||||
type: Boolean,
|
{ name: "systemid", alias: "s", type: String, description: "SMPP related login info." },
|
||||||
description: "SMPP ID generation is entirely random instead of sequential.",
|
{ name: "password", alias: "w", type: String, description: "SMPP related login info." },
|
||||||
defaultOption: false,
|
{ name: "dr", type: Boolean, description: "Whether or not to send Delivery Reports.", defaultOption: false },
|
||||||
},
|
{
|
||||||
{
|
name: "randid",
|
||||||
name: "sessions",
|
type: Boolean,
|
||||||
type: Number,
|
description: "SMPP ID generation is entirely random instead of sequential.",
|
||||||
description: "Maximum number of client sessions to accept, defaults to 8.",
|
defaultOption: false,
|
||||||
defaultOption: 8,
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "sessions",
|
||||||
name: "messagecount",
|
type: Number,
|
||||||
type: Number,
|
description: "Maximum number of client sessions to accept, defaults to 8.",
|
||||||
description: "Number of messages to send; Optional, defaults to 0.",
|
defaultOption: 8,
|
||||||
defaultOption: 0,
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "messagecount",
|
||||||
name: "window",
|
type: Number,
|
||||||
type: Number,
|
description: "Number of messages to send; Optional, defaults to 0.",
|
||||||
description:
|
defaultOption: 0,
|
||||||
"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: "window",
|
||||||
{
|
type: Number,
|
||||||
name: "windowsleep",
|
description:
|
||||||
type: 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.",
|
||||||
description:
|
defaultOption: 100,
|
||||||
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
},
|
||||||
defaultOption: 100,
|
{
|
||||||
},
|
name: "windowsleep",
|
||||||
{
|
type: Number,
|
||||||
name: "mps",
|
description:
|
||||||
type: Number,
|
"Defines the amount time (in ms) waited between retrying in the case of full window. Defaults to 100.",
|
||||||
description: "Number of messages to send per second",
|
defaultOption: 100,
|
||||||
defaultOption: 999999,
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "mps",
|
||||||
name: "source",
|
type: Number,
|
||||||
type: String,
|
description: "Number of messages to send per second",
|
||||||
description: "Source field of the sent messages.",
|
defaultOption: 999999,
|
||||||
defaultOption: "smppDebugClient",
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "source",
|
||||||
name: "destination",
|
type: String,
|
||||||
type: String,
|
description: "Source field of the sent messages.",
|
||||||
description: "Destination field of the sent messages.",
|
defaultOption: "smppDebugClient",
|
||||||
defaultOption: "smpp",
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "destination",
|
||||||
name: "message",
|
type: String,
|
||||||
type: String,
|
description: "Destination field of the sent messages.",
|
||||||
description: "Text content of the sent messages.",
|
defaultOption: "smpp",
|
||||||
defaultOption: "smpp debug message",
|
},
|
||||||
},
|
{
|
||||||
{ name: "debug", type: Boolean, description: "Display all traffic to and from the center; Debug mode." },
|
name: "message",
|
||||||
{ name: "logs", type: Boolean, description: "Write logs (to stdout), defaults to true." },
|
type: String,
|
||||||
{
|
description: "Text content of the sent messages.",
|
||||||
name: "bars",
|
defaultOption: "smpp debug message",
|
||||||
type: Boolean,
|
},
|
||||||
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
{ 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: "metricsinterval",
|
name: "bars",
|
||||||
type: Number,
|
type: Boolean,
|
||||||
defaultOption: 5,
|
description: "Display TX and RX bars. Can be used with logs (although it will make a mess)."
|
||||||
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
},
|
||||||
},
|
{
|
||||||
{
|
name: "metricsinterval",
|
||||||
name: "defaultmaxrate",
|
type: Number,
|
||||||
type: Number,
|
defaultOption: 5,
|
||||||
defaultOption: 1000,
|
description: "Interval for measuring metrics. A value of 5 considers the packets within the last 5 seconds. Defaults to 5."
|
||||||
description: "Default max rate for metrics/bars."
|
},
|
||||||
},
|
{
|
||||||
];
|
name: "defaultmaxrate",
|
||||||
|
type: Number,
|
||||||
module.exports = { clientOptions, centerOptions };
|
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 smpp = require("smpp");
|
||||||
const commandLineArgs = require("command-line-args");
|
const commandLineArgs = require("command-line-args");
|
||||||
const commandLineUsage = require("command-line-usage");
|
const commandLineUsage = require("command-line-usage");
|
||||||
const NanoTimer = require("nanotimer");
|
const NanoTimer = require("nanotimer");
|
||||||
const { createBaseLogger, createSessionLogger } = require("./logger");
|
const { createBaseLogger, createSessionLogger } = require("./logger");
|
||||||
const { verifyDefaults, verifyExists } = require("./utils");
|
const { verifyDefaults, verifyExists, sendPdu } = require("./utils");
|
||||||
const { clientOptions } = require("./cliOptions");
|
const { clientOptions } = require("./cliOptions");
|
||||||
const { MetricManager } = require("./metrics/metricManager");
|
const { MetricManager } = require("./metrics/metricManager");
|
||||||
|
|
||||||
const options = commandLineArgs(clientOptions);
|
const options = commandLineArgs(clientOptions);
|
||||||
const logger = createBaseLogger(options);
|
const logger = createBaseLogger(options);
|
||||||
|
|
||||||
if (options.help) {
|
if (options.help) {
|
||||||
const usage = commandLineUsage([
|
const usage = commandLineUsage([
|
||||||
{
|
{
|
||||||
header: "CLI SMPP (Client)",
|
header: "CLI SMPP (Client)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Options",
|
header: "Options",
|
||||||
optionList: clientOptions,
|
optionList: clientOptions,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
content: "Project home: {underline https://github.com/PhatDave/SMPP_CLI}",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
console.log(usage);
|
console.log(usage);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyDefaults(options, clientOptions);
|
verifyDefaults(options, clientOptions);
|
||||||
verifyExists(options.host, "Host can not be undefined or empty! (--host)", logger);
|
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.port, "Port can not be undefined or empty! (--port)", logger);
|
||||||
verifyExists(options.systemid, "SystemID can not be undefined or empty! (--systemid)", 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);
|
verifyExists(options.password, "Password can not be undefined or empty! (--password)", logger);
|
||||||
|
|
||||||
let inFlight = 0;
|
let inFlight = 0;
|
||||||
let sent = 0;
|
let sent = 0;
|
||||||
let success = 0;
|
let success = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
const sendTimer = new NanoTimer();
|
const sendTimer = new NanoTimer();
|
||||||
const metricManager = new MetricManager(options);
|
const metricManager = new MetricManager(options);
|
||||||
|
|
||||||
function startInterval(session, sessionLogger, metrics) {
|
function startInterval(session, sessionLogger, metrics) {
|
||||||
if (!metrics.progress && options.bars === true) {
|
if (!metrics.progress && options.bars === true) {
|
||||||
metrics.progress = metricManager.AddMetrics("Send progress", false);
|
metrics.progress = metricManager.AddMetrics("Send progress", false);
|
||||||
metrics.progress.bar.total = options.messagecount;
|
metrics.progress.bar.total = options.messagecount;
|
||||||
metrics.window = metricManager.AddMetrics("Send window", false);
|
metrics.window = metricManager.AddMetrics("Send window", false);
|
||||||
metrics.window.bar.total = options.window;
|
metrics.window.bar.total = options.window;
|
||||||
}
|
}
|
||||||
sendTimer.setInterval(
|
sendTimer.setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
if (sent >= options.messagecount) {
|
if (sent >= options.messagecount) {
|
||||||
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
sessionLogger.info(`Finished sending messages success:${success}, failed:${failed}, idling...`);
|
||||||
sendTimer.clearInterval();
|
sendTimer.clearInterval();
|
||||||
} else if (inFlight < options.window) {
|
} else if (inFlight < options.window) {
|
||||||
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
sessionLogger.info(`Sending message ${sent + 1}/${options.messagecount}`);
|
||||||
if (options.bars) {
|
if (options.bars) {
|
||||||
metrics.progress.bar.increment();
|
metrics.progress.bar.increment();
|
||||||
metrics.window.bar.increment();
|
metrics.window.bar.increment();
|
||||||
}
|
}
|
||||||
session.submit_sm(
|
const pdu = new smpp.PDU("submit_sm", {
|
||||||
{
|
source_addr: options.source,
|
||||||
source_addr: options.source,
|
destination_addr: options.destination,
|
||||||
destination_addr: options.destination,
|
short_message: options.message,
|
||||||
short_message: options.message,
|
});
|
||||||
},
|
|
||||||
function (pdu) {
|
sendPdu(session, pdu, sessionLogger, options.longsms)
|
||||||
if (metrics.window?.bar) {
|
.then((resp) => {
|
||||||
metrics.window.bar.update(metrics.window.bar.value - 1);
|
inFlight--;
|
||||||
}
|
sessionLogger.info(`Received response with id ${resp.message_id}`);
|
||||||
inFlight--;
|
success++;
|
||||||
if (pdu.command_status === 0) {
|
})
|
||||||
sessionLogger.info(`Received response with id ${pdu.message_id}`);
|
.catch((resp) => {
|
||||||
success++;
|
inFlight--;
|
||||||
} else {
|
sessionLogger.warn(`Message failed with id ${resp.message_id}`);
|
||||||
sessionLogger.warn(`Message failed with id ${pdu.message_id}`);
|
failed++;
|
||||||
failed++;
|
});
|
||||||
}
|
|
||||||
}
|
if (metrics.txMetrics) {
|
||||||
);
|
metrics.txMetrics.AddEvent();
|
||||||
if (metrics.txMetrics) {
|
}
|
||||||
metrics.txMetrics.AddEvent();
|
sent++;
|
||||||
}
|
inFlight++;
|
||||||
sent++;
|
} else {
|
||||||
inFlight++;
|
sessionLogger.warn(
|
||||||
} else {
|
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
||||||
sessionLogger.warn(
|
);
|
||||||
`${inFlight}/${options.window} messages pending, waiting for a reply before sending more`
|
sendTimer.clearInterval();
|
||||||
);
|
setTimeout(() => startInterval(session, sessionLogger, metrics), options.windowsleep);
|
||||||
sendTimer.clearInterval();
|
}
|
||||||
setTimeout(() => startInterval(session, sessionLogger, metrics), options.windowsleep);
|
},
|
||||||
}
|
"",
|
||||||
},
|
`${1 / options.mps} s`
|
||||||
"",
|
);
|
||||||
`${1 / options.mps} s`
|
}
|
||||||
);
|
|
||||||
}
|
for (let i = 0; i < options.sessions; i++) {
|
||||||
|
const sessionLogger = createSessionLogger(options, i);
|
||||||
for (let i = 0; i < options.sessions; i++) {
|
sessionLogger.info(`Connecting to ${options.host}:${options.port}...`);
|
||||||
const sessionLogger = createSessionLogger(options, i);
|
const session = smpp.connect(
|
||||||
sessionLogger.info(`Connecting to ${options.host}:${options.port}...`);
|
{
|
||||||
const session = smpp.connect(
|
url: `smpp://${options.host}:${options.port}`,
|
||||||
{
|
auto_enquire_link_period: 10000,
|
||||||
url: `smpp://${options.host}:${options.port}`,
|
debug: options.debug,
|
||||||
auto_enquire_link_period: 10000,
|
},
|
||||||
debug: options.debug,
|
function () {
|
||||||
},
|
sessionLogger.info(
|
||||||
function () {
|
`Connected, sending bind_transciever with systemId '${options.systemid}' and password '${options.password}'...`
|
||||||
sessionLogger.info(
|
);
|
||||||
`Connected, sending bind_transciever with systemId '${options.systemid}' and password '${options.password}'...`
|
session.on('close', function () {
|
||||||
);
|
sessionLogger.error(`Session closed`);
|
||||||
session.bind_transceiver(
|
process.exit(1);
|
||||||
{
|
});
|
||||||
system_id: options.systemid,
|
session.bind_transceiver(
|
||||||
password: options.password,
|
{
|
||||||
},
|
system_id: options.systemid,
|
||||||
function (pdu) {
|
password: options.password,
|
||||||
if (pdu.command_status === 0) {
|
},
|
||||||
sessionLogger.info(
|
function (pdu) {
|
||||||
`Successfully bound, sending ${options.messagecount} messages '${options.source}'->'${options.destination}' ('${options.message}')`
|
if (pdu.command_status === 0) {
|
||||||
);
|
sessionLogger.info(
|
||||||
const rxMetrics = metricManager.AddMetrics(`Session-${i}-RX`);
|
`Successfully bound, sending ${options.messagecount} messages '${options.source}'->'${options.destination}' ('${options.message}')`
|
||||||
const txMetrics = metricManager.AddMetrics(`Session-${i}-TX`);
|
);
|
||||||
startInterval(session, sessionLogger, {
|
const rxMetrics = metricManager.AddMetrics(`Session-${i}-RX`);
|
||||||
rxMetrics,
|
const txMetrics = metricManager.AddMetrics(`Session-${i}-TX`);
|
||||||
txMetrics,
|
startInterval(session, sessionLogger, {
|
||||||
});
|
rxMetrics,
|
||||||
// TODO: Add error message for invalid systemid and password
|
txMetrics,
|
||||||
|
});
|
||||||
session.on("deliver_sm", function (pdu) {
|
|
||||||
if (rxMetrics) {
|
session.on("deliver_sm", function (pdu) {
|
||||||
rxMetrics.AddEvent();
|
if (rxMetrics) {
|
||||||
}
|
rxMetrics.AddEvent();
|
||||||
sessionLogger.info("Got deliver_sm, replying...");
|
}
|
||||||
// setTimeout(() => {
|
sessionLogger.info("Got deliver_sm, replying...");
|
||||||
// session.send(pdu.response());
|
// setTimeout(() => {
|
||||||
// txMetrics.AddEvent();
|
// session.send(pdu.response());
|
||||||
// }, 200);
|
// txMetrics.AddEvent();
|
||||||
session.send(pdu.response());
|
// }, 200);
|
||||||
if (txMetrics) {
|
session.send(pdu.response());
|
||||||
txMetrics.AddEvent();
|
if (txMetrics) {
|
||||||
}
|
txMetrics.AddEvent();
|
||||||
});
|
}
|
||||||
session.on("enquire_link", function (pdu) {
|
});
|
||||||
session.send(pdu.response());
|
session.on("enquire_link", function (pdu) {
|
||||||
});
|
session.send(pdu.response());
|
||||||
session.on("close", function () {
|
});
|
||||||
sessionLogger.error(`Session closed`);
|
session.on("close", function () {
|
||||||
process.exit(1);
|
sessionLogger.error(`Session closed`);
|
||||||
});
|
process.exit(1);
|
||||||
session.on("error", function (err) {
|
});
|
||||||
sessionLogger.error(`Fatal error ${err}`);
|
session.on("error", function (err) {
|
||||||
process.exit(1);
|
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 { createLogger, format, transports } = require("winston");
|
||||||
const { combine, timestamp, label, printf } = format;
|
const { combine, timestamp, label, printf } = format;
|
||||||
|
|
||||||
const defaultFormat = printf(({ level, message, timestamp }) => {
|
const defaultFormat = printf(({ level, message, timestamp }) => {
|
||||||
return `${timestamp} ${level}: ${message}`;
|
return `${timestamp} ${level}: ${message}`;
|
||||||
});
|
});
|
||||||
const sessionFormat = printf(({ level, message, label, timestamp }) => {
|
const sessionFormat = printf(({ level, message, label, timestamp }) => {
|
||||||
return `${timestamp} [Session ${label}] ${level}: ${message}`;
|
return `${timestamp} [Session ${label}] ${level}: ${message}`;
|
||||||
});
|
});
|
||||||
function createBaseLogger(options) {
|
function createBaseLogger(options) {
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
format: combine(format.colorize({ all: true }), timestamp(), defaultFormat),
|
format: combine(format.colorize({ all: true }), timestamp(), defaultFormat),
|
||||||
transports: [new transports.Console()],
|
transports: [new transports.Console()],
|
||||||
});
|
});
|
||||||
|
|
||||||
const oldInfo = logger.info;
|
const oldInfo = logger.info;
|
||||||
const oldWarn = logger.info;
|
const oldWarn = logger.info;
|
||||||
const oldError = logger.error;
|
const oldError = logger.error;
|
||||||
logger.info = function (input) {
|
logger.info = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldInfo(input);
|
oldInfo(input);
|
||||||
};
|
};
|
||||||
logger.error = function (input) {
|
logger.error = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldError(input);
|
oldError(input);
|
||||||
};
|
};
|
||||||
logger.warn = function (input) {
|
logger.warn = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldWarn(input);
|
oldWarn(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
function createSessionLogger(options, ilabel) {
|
function createSessionLogger(options, ilabel) {
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
format: combine(label({ label: ilabel }), format.colorize({ all: true }), timestamp(), sessionFormat),
|
format: combine(label({ label: ilabel }), format.colorize({ all: true }), timestamp(), sessionFormat),
|
||||||
transports: [new transports.Console()],
|
transports: [new transports.Console()],
|
||||||
});
|
});
|
||||||
|
|
||||||
const oldInfo = logger.info;
|
const oldInfo = logger.info;
|
||||||
const oldWarn = logger.info;
|
const oldWarn = logger.info;
|
||||||
const oldError = logger.error;
|
const oldError = logger.error;
|
||||||
logger.info = function (input) {
|
logger.info = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldInfo(input);
|
oldInfo(input);
|
||||||
};
|
};
|
||||||
logger.error = function (input) {
|
logger.error = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldError(input);
|
oldError(input);
|
||||||
};
|
};
|
||||||
logger.warn = function (input) {
|
logger.warn = function (input) {
|
||||||
if (!shouldLog(options)) {
|
if (!shouldLog(options)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
oldWarn(input);
|
oldWarn(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldLog(options) {
|
function shouldLog(options) {
|
||||||
return options.logs || !options.bars;
|
return options.logs || !options.bars;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createBaseLogger, createSessionLogger };
|
module.exports = { createBaseLogger, createSessionLogger };
|
||||||
|
@@ -1,46 +1,46 @@
|
|||||||
class CircularBuffer {
|
class CircularBuffer {
|
||||||
constructor(size) {
|
constructor(size) {
|
||||||
this.buffer = new Array(size);
|
this.buffer = new Array(size);
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.head = 0;
|
this.head = 0;
|
||||||
this.tail = 0;
|
this.tail = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
push(item) {
|
push(item) {
|
||||||
this.buffer[this.head] = item;
|
this.buffer[this.head] = item;
|
||||||
this.head = (this.head + 1) % this.size;
|
this.head = (this.head + 1) % this.size;
|
||||||
if (this.head === this.tail) {
|
if (this.head === this.tail) {
|
||||||
this.tail = (this.tail + 1) % this.size;
|
this.tail = (this.tail + 1) % this.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toArray() {
|
toArray() {
|
||||||
const result = [];
|
const result = [];
|
||||||
let current = this.tail;
|
let current = this.tail;
|
||||||
for (let i = 0; i < this.size; i++) {
|
for (let i = 0; i < this.size; i++) {
|
||||||
if (this.buffer[current] !== undefined) {
|
if (this.buffer[current] !== undefined) {
|
||||||
result.push(this.buffer[current]);
|
result.push(this.buffer[current]);
|
||||||
}
|
}
|
||||||
current = (current + 1) % this.size;
|
current = (current + 1) % this.size;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
toArrayRecent(n = 10) {
|
toArrayRecent(n = 10) {
|
||||||
const result = [];
|
const result = [];
|
||||||
const threshold = Date.now() - n * 1000;
|
const threshold = Date.now() - n * 1000;
|
||||||
|
|
||||||
let current = (this.head - 1 + this.size) % this.size;
|
let current = (this.head - 1 + this.size) % this.size;
|
||||||
while (current !== this.tail) {
|
while (current !== this.tail) {
|
||||||
if (this.buffer[current] !== undefined && this.buffer[current].timestamp > threshold) {
|
if (this.buffer[current] !== undefined && this.buffer[current].timestamp > threshold) {
|
||||||
result.push(this.buffer[current]);
|
result.push(this.buffer[current]);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current = (current - 1 + this.size) % this.size;
|
current = (current - 1 + this.size) % this.size;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { CircularBuffer };
|
module.exports = { CircularBuffer };
|
||||||
|
@@ -1,30 +1,30 @@
|
|||||||
const cliProgress = require("cli-progress");
|
const cliProgress = require("cli-progress");
|
||||||
const { Metric } = require("./metrics");
|
const { Metric } = require("./metrics");
|
||||||
|
|
||||||
class MetricManager {
|
class MetricManager {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
if (options.bars) {
|
if (options.bars) {
|
||||||
this.metricBufferSize = 1000;
|
this.metricBufferSize = 1000;
|
||||||
this.multibar = new cliProgress.MultiBar(
|
this.multibar = new cliProgress.MultiBar(
|
||||||
{
|
{
|
||||||
clearOnComplete: false,
|
clearOnComplete: false,
|
||||||
barCompleteChar: "\u2588",
|
barCompleteChar: "\u2588",
|
||||||
barIncompleteChar: "\u2591",
|
barIncompleteChar: "\u2591",
|
||||||
format: " {bar} | {name} | {value}/{total}",
|
format: " {bar} | {name} | {value}/{total}",
|
||||||
},
|
},
|
||||||
cliProgress.Presets.shades_grey
|
cliProgress.Presets.shades_grey
|
||||||
);
|
);
|
||||||
setInterval(() => this.multibar.update(), 200);
|
setInterval(() => this.multibar.update(), 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddMetrics(name, refresh = true) {
|
AddMetrics(name, refresh = true) {
|
||||||
if (this.options.bars) {
|
if (this.options.bars) {
|
||||||
const metric = new Metric(name, this.multibar, this.metricBufferSize, this.options, refresh);
|
const metric = new Metric(name, this.multibar, this.metricBufferSize, this.options, refresh);
|
||||||
return metric;
|
return metric;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { MetricManager };
|
module.exports = { MetricManager };
|
||||||
|
@@ -1,39 +1,39 @@
|
|||||||
const { CircularBuffer } = require("./circularBuffer");
|
const { CircularBuffer } = require("./circularBuffer");
|
||||||
|
|
||||||
class Metric {
|
class Metric {
|
||||||
constructor(barName, multibar, bufferSize, options, refresh = true) {
|
constructor(barName, multibar, bufferSize, options, refresh = true) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.multibar = multibar;
|
this.multibar = multibar;
|
||||||
this.bar = multibar.create(0, 0);
|
this.bar = multibar.create(0, 0);
|
||||||
this.bar.update(0, { name: barName });
|
this.bar.update(0, { name: barName });
|
||||||
this.maxRate = this.options.defaultmaxrate;
|
this.maxRate = this.options.defaultmaxrate;
|
||||||
this.bar.total = this.maxRate;
|
this.bar.total = this.maxRate;
|
||||||
this.buffer = new CircularBuffer(bufferSize);
|
this.buffer = new CircularBuffer(bufferSize);
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
setInterval(this.UpdateBar.bind(this), 100);
|
setInterval(this.UpdateBar.bind(this), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddEvent() {
|
AddEvent() {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
this.buffer.push({ timestamp, count: 1 });
|
this.buffer.push({ timestamp, count: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
GetRate() {
|
GetRate() {
|
||||||
const entries = this.buffer.toArrayRecent(this.options.metricsinterval);
|
const entries = this.buffer.toArrayRecent(this.options.metricsinterval);
|
||||||
|
|
||||||
const totalRX = entries.reduce((sum, entry) => sum + entry.count, 0);
|
const totalRX = entries.reduce((sum, entry) => sum + entry.count, 0);
|
||||||
return Math.round((totalRX / this.options.metricsinterval) * 100) / 100;
|
return Math.round((totalRX / this.options.metricsinterval) * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateBar() {
|
UpdateBar() {
|
||||||
const eps = this.GetRate();
|
const eps = this.GetRate();
|
||||||
if (eps > this.maxRate) {
|
if (eps > this.maxRate) {
|
||||||
this.bar.total = eps;
|
this.bar.total = eps;
|
||||||
this.maxRate = eps;
|
this.maxRate = eps;
|
||||||
}
|
}
|
||||||
this.bar.update(eps);
|
this.bar.update(eps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Metric };
|
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-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-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-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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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) {
|
const smpp = require("smpp");
|
||||||
if (!value) {
|
|
||||||
logger.error(err);
|
function verifyExists(value, err, logger) {
|
||||||
process.exit(0);
|
if (!value) {
|
||||||
}
|
logger.error(err);
|
||||||
}
|
process.exit(0);
|
||||||
function verifyDefaults(options, definitions) {
|
}
|
||||||
for (const optionDefinition of definitions) {
|
}
|
||||||
if (optionDefinition.defaultOption) {
|
function verifyDefaults(options, definitions) {
|
||||||
if (!options[optionDefinition.name]) {
|
for (const optionDefinition of definitions) {
|
||||||
options[optionDefinition.name] = optionDefinition.defaultOption;
|
if (optionDefinition.defaultOption) {
|
||||||
}
|
if (!options[optionDefinition.name]) {
|
||||||
}
|
options[optionDefinition.name] = optionDefinition.defaultOption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
module.exports = { verifyDefaults, verifyExists };
|
}
|
||||||
|
|
||||||
|
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