From f43dbc16a329b2ffedd2103dc9ddbcb1343ef668 Mon Sep 17 00:00:00 2001
From: Adhidarma Hadiwinoto <me@adhisimon.org>
Date: Wed, 31 Jul 2019 16:14:26 +0700
Subject: [PATCH] New modem selector

---
 lib/apiserver/index.js   | 10 ++++++
 lib/modemSelect.js       | 34 ++++++++++++++++++
 lib/modems.js            | 34 ------------------
 lib/modems2.js           | 91 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/partner-last-seen.js |  2 +-
 lib/transport.js         | 78 +++++++++++------------------------------
 test/modems.js           | 22 ++++++------
 7 files changed, 167 insertions(+), 104 deletions(-)
 create mode 100644 lib/modemSelect.js
 delete mode 100644 lib/modems.js
 create mode 100644 lib/modems2.js

diff --git a/lib/apiserver/index.js b/lib/apiserver/index.js
index 098da61..e18e4fb 100644
--- a/lib/apiserver/index.js
+++ b/lib/apiserver/index.js
@@ -16,6 +16,7 @@ const logger = require('komodo-sdk/logger');
 const transport = require('../transport');
 const partnerLastSeen = require('../partner-last-seen');
 const history = require('../history');
+const modems = require('../modems2');
 
 const app = express();
 messagingService.setTransport(transport);
@@ -52,6 +53,15 @@ function onIncomingSms(req, res) {
         message: req.query.msg,
     });
 
+    modems.set({
+        name: req.query.modem,
+        imsi: req.query.modem_imsi,
+        msisdn: req.query.modem_msisdn,
+        reportIp: req.query.report_ip || req.ip,
+        reportPort: req.query.report_port,
+        reportApikey: req.query.report_apikey,
+    });
+
     logger.info('APISERVER: Incoming SMS', { modem: req.query.modem, from: req.query.number, from_with_suffix: numberWithSuffix, msg: req.query.msg });
     messagingService.onIncomingMessage({
         me: req.query.modem,
diff --git a/lib/modemSelect.js b/lib/modemSelect.js
new file mode 100644
index 0000000..4f441ad
--- /dev/null
+++ b/lib/modemSelect.js
@@ -0,0 +1,34 @@
+"use strict";
+
+function getModemConfig(modemName, modemsConfig) {
+    if (!modemsConfig) return;
+    if (!modemName) return;
+    if (typeof modemName === 'string' && !modemName.trim()) return;
+
+    return modemsConfig[modemName.trim()];    
+}
+
+function getModemUrl(modemName, modemsConfig) {
+    const modemConfig = getModemConfig(modemName, modemsConfig);
+    return modemConfig ? modemConfig.url : null;
+}
+
+function getModemApikey(modemName, modemsConfig) {
+    const modemConfig = getModemConfig(modemName, modemsConfig);
+    return modemConfig ? modemConfig.apikey : null;
+}
+
+function removeSuffixFromNumber(number, config) {
+    if (!config) {
+        config = {};
+    }
+
+    const suffix = config && config.number_suffix ? config.number_suffix : '@.*';
+    const re = new RegExp(suffix + '$');
+    return number.replace(re, '');
+}
+
+exports.getModemConfig = getModemConfig;
+exports.getModemUrl = getModemUrl;
+exports.getModemApikey = getModemApikey;
+exports.removeSuffixFromNumber = removeSuffixFromNumber;
\ No newline at end of file
diff --git a/lib/modems.js b/lib/modems.js
deleted file mode 100644
index 4f441ad..0000000
--- a/lib/modems.js
+++ /dev/null
@@ -1,34 +0,0 @@
-"use strict";
-
-function getModemConfig(modemName, modemsConfig) {
-    if (!modemsConfig) return;
-    if (!modemName) return;
-    if (typeof modemName === 'string' && !modemName.trim()) return;
-
-    return modemsConfig[modemName.trim()];    
-}
-
-function getModemUrl(modemName, modemsConfig) {
-    const modemConfig = getModemConfig(modemName, modemsConfig);
-    return modemConfig ? modemConfig.url : null;
-}
-
-function getModemApikey(modemName, modemsConfig) {
-    const modemConfig = getModemConfig(modemName, modemsConfig);
-    return modemConfig ? modemConfig.apikey : null;
-}
-
-function removeSuffixFromNumber(number, config) {
-    if (!config) {
-        config = {};
-    }
-
-    const suffix = config && config.number_suffix ? config.number_suffix : '@.*';
-    const re = new RegExp(suffix + '$');
-    return number.replace(re, '');
-}
-
-exports.getModemConfig = getModemConfig;
-exports.getModemUrl = getModemUrl;
-exports.getModemApikey = getModemApikey;
-exports.removeSuffixFromNumber = removeSuffixFromNumber;
\ No newline at end of file
diff --git a/lib/modems2.js b/lib/modems2.js
new file mode 100644
index 0000000..e863b2d
--- /dev/null
+++ b/lib/modems2.js
@@ -0,0 +1,91 @@
+'use strict';
+
+const modemList = {
+    by_name: {},
+    by_imsi: {},
+    by_msisdn: {},
+};
+
+/**
+ * Objek data sebuah modem.
+ * 
+ * @typedef   {Object} ModemData
+ * @property  {string} name - nama modem
+ * @property  {string} imsi - IMSI modem
+ * @property  {string} msisdn - MSISDN modem
+ * @property  {string} reportIp - IP modem
+ * @property  {number} reportPort - TCP port modem
+ * @property  {string} reportApikey - APIKEY modem
+ */
+
+/**
+ * Update data sebuah modem berdasarkan nama modem.
+ * 
+ * @param  {ModemData} val - objek data modem
+ */
+function touchByName(val) {
+    if (!val || !val.name) return;
+    if (typeof val.name !== 'string') return;
+    if (!val.name.trim()) return;
+
+    modemList.by_name[val.name] = val;
+}
+/**
+ * Update data sebuah modem berdasarkan IMSI.
+ * 
+ * @param  {ModemData} val - objek data modem
+ */
+function touchByIMSI(val) {
+    if (!val || !val.imsi) return;
+    if (typeof val.imsi !== 'string') return;
+    if (!val.imsi.trim()) return;
+
+    modemList.by_imsi[val.imsi] = val;
+}
+
+/**
+ * Update data sebuah modem berdasarkan MSISDN.
+ * 
+ * @param  {ModemData} val - objek data modem
+ */
+function touchByMSISDN(val) {
+    if (!val || !val.msisdn) return;
+    if (typeof val.msisdn !== 'string') return;
+    if (!val.msisdn.trim()) return;
+
+    modemList.by_msisdn[val.msisdn] = val;
+}
+
+/**
+ * Update data sebuah modem.
+ * 
+ * @param  {ModemData} val - objek data modem
+ * @see ModemData
+ */
+function touch(val) {
+    if (!val) return;
+
+    if (!val.reportIp) {
+        val.reportIp = '127.0.0.1';
+    }
+
+    touchByName(val);
+    touchByIMSI(val);
+    touchByMSISDN(val);
+}
+/**
+ * Ambil data sebuah modem.
+ * 
+ * @param  {string} selector - selector pencarian, valid jika bernilai salah satu dari: name, imsi, msisdn
+ * @param  {string} keyword - kata kunci modem yang ingin diambil
+ * @returns {ModemData} data modem terkait
+ */
+function get(selector, keyword) {
+    if (!selector || !keyword) return null;
+
+    return modemList[`by_${selector}`] ? modemList[`by_${selector}`][keyword] : null;
+}
+
+exports.touch = touch;
+exports.set = touch;
+exports.get = get;
\ No newline at end of file
diff --git a/lib/partner-last-seen.js b/lib/partner-last-seen.js
index 63b626b..1fe3347 100644
--- a/lib/partner-last-seen.js
+++ b/lib/partner-last-seen.js
@@ -1,6 +1,6 @@
 "use strict";
 
-const REDIS_TTL_SECS = 3600 * 24 * 7;
+const REDIS_TTL_SECS = 3600 * 24 * 31;
 
 const config = require('komodo-sdk/config');
 // const logger = require('komodo-sdk/logger');
diff --git a/lib/transport.js b/lib/transport.js
index f368247..a411f2f 100644
--- a/lib/transport.js
+++ b/lib/transport.js
@@ -1,5 +1,6 @@
 "use strict";
 
+const url = require('url');
 const request = require('request');
 const uuidv4 = require('uuid/v4');
 const moment = require('moment');
@@ -7,60 +8,13 @@ const moment = require('moment');
 const config = require('komodo-sdk/config');
 const logger = require('komodo-sdk/logger');
 
-const modems = require('./modems');
+const modemSelect = require('./modemSelect');
+const modems = require('./modems2');
 const partnerLastSeen = require('./partner-last-seen');
 const history = require('./history');
 
-async function _getApproriateHandlerByLastSeen(partnerNumber) {
-    logger.verbose('Looking for last seen on for partner number ' + partnerNumber);
-    const lastSeenFrom = await partnerLastSeen.get(partnerNumber);
-    return lastSeenFrom;
-}
-
-function _getApproriateHandlerByForced() {
-    if (!config.sending_handler || !config.sending_handler.length) return;
-
-    const sendingHandlerCount = config.sending_handler.length;
-    const idx = Math.floor(Math.random() * sendingHandlerCount);
-    return config.sending_handler[idx];
-}
-
-async function _getApproriateHandler(partnerNumber, origin) {
-    let handlerToUse;
-
-    if (config.handler_chooser_algorithm === 'FORCED') {
-        handlerToUse = _getApproriateHandlerByForced();
-        logger.verbose('Config file mentioned to using FORCED handler chooser algorithm', { handler_to_use: handlerToUse});
-    }
-    else {
-        handlerToUse = await _getApproriateHandlerByLastSeen(partnerNumber);
-        logger.verbose('Config file mentioned to using LAST-SEEN handler chooser algorithm', { handler_to_use: handlerToUse});
-    }
-
-    if (!modems.getModemConfig(handlerToUse, config.modems)) {
-        const handlerWithSameOrigin = modems.getModemConfig(origin, config.modems);
-        if (handlerWithSameOrigin) {
-            logger.verbose('Invalid approriate handler, using handler from the same ORIGIN request by CORE to send sms')
-            handlerToUse = origin;
-        }
-        else {
-            logger.verbose('Invalid approriate handler, using default handler to send sms')
-            handlerToUse = config.default_modem;
-        }
-    }
-
-    return handlerToUse;
-}
-
 function _send(destinationNumber, msg, handlerName) {
 
-    /*
-    if (msg.length > 160 && !config.do_not_trim_long_sms) {
-        logger.verbose('Message trim to 160 chars');
-        msg = msg.slice(0, 156) + ' ...';
-    }
-    */
-
     if (msg.length > 160) {
         const newMsg = msg.slice(0, 160);
         const remainingMsg = msg.slice(160);
@@ -73,14 +27,14 @@ function _send(destinationNumber, msg, handlerName) {
         return;
     }
 
-    const modem = modems.getModemConfig(handlerName, config.modems);
+    const modem = modems.get('name', handlerName);
     if (!modem) {
         logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, handler_name: handlerName });
         return;
     }
 
-    if (!modem.url || !modem.apikey) {
-        logger.warn('Invalid modem configuration', { config: modem, handler_name: handlerName });
+    if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) {
+        logger.warn('Invalid modem configuration', { modem });
         return;
     }
 
@@ -96,14 +50,17 @@ function _send(destinationNumber, msg, handlerName) {
         message: msg,
     });
 
-
     const requestOptions = {
-        url: modem.url,
+        url: url.format({
+            protocol: 'http',
+            hostname: modem.reportIp,
+            port: modem.reportPort,
+        }),
         qs: {
             msg: msg,
             number: destinationNumber,
             reqid: reqId,
-            apikey: modem.apikey
+            apikey: modem.reportApikey
         }
     }
 
@@ -134,10 +91,15 @@ async function send(partner, msg, origin) {
     msg = msg.trim();
     if (!msg) return;
 
-    const destinationNumber = modems.removeSuffixFromNumber(partner, config);
+    const destinationNumber = modemSelect.removeSuffixFromNumber(partner, config);
+
+    // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin });
+    let handlerName = await partnerLastSeen.get(destinationNumber) ;
 
-    logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin });
-    let handlerName = ( await _getApproriateHandler(destinationNumber) );
+    if (!handlerName) {
+        logger.warn(`Unknown handler for sending message to partner`, { partner, destinationNumber });
+        return;
+    }
 
     _send(destinationNumber, msg, handlerName);
 }
diff --git a/test/modems.js b/test/modems.js
index 1e3a5d2..d116897 100644
--- a/test/modems.js
+++ b/test/modems.js
@@ -4,9 +4,9 @@
 
 const should = require('should');
 
-const modems = require('../lib/modems');
+const modemSelect = require('../lib/modemSelect');
 
-describe('#modems', function() {
+describe('#modemSelect', function() {
     
     describe('#getModemUrl', function() {
 
@@ -17,13 +17,13 @@ describe('#modems', function() {
         }
     
         it('should return correct url', function() {
-            modems.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/');
+            modemSelect.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/');
         })
 
         it ('should handle missing modems', function() {
-            should.not.exists(modems.getModemUrl('SMS0', null));
-            should.not.exists(modems.getModemUrl('SMS0', {}));
-            should.not.exists(modems.getModemUrl('SMS1', modemsConfig));
+            should.not.exists(modemSelect.getModemUrl('SMS0', null));
+            should.not.exists(modemSelect.getModemUrl('SMS0', {}));
+            should.not.exists(modemSelect.getModemUrl('SMS1', modemsConfig));
         })
     })
 
@@ -33,17 +33,17 @@ describe('#modems', function() {
         }
 
         it('should return correct number', function() {
-            modems.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234');
+            modemSelect.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234');
         })
 
         it ('should return correct number without suffix in the number', function() {
-            modems.removeSuffixFromNumber('08181234', config).should.equal('08181234');
+            modemSelect.removeSuffixFromNumber('08181234', config).should.equal('08181234');
         })
 
         it ('should return correct number without suffix in config', function() {
-            modems.removeSuffixFromNumber('08181234', null).should.equal('08181234');
-            modems.removeSuffixFromNumber('08181234', {}).should.equal('08181234');
-            modems.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234');
+            modemSelect.removeSuffixFromNumber('08181234', null).should.equal('08181234');
+            modemSelect.removeSuffixFromNumber('08181234', {}).should.equal('08181234');
+            modemSelect.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234');
         })
     })
 
-- 
1.9.0