hiveguard-backend
Version:
Backend for HiveGuard
1,575 lines (1,510 loc) • 45.4 kB
JavaScript
/*
* Copyright 2021-2022 Dimitrios-Georgios Akestoridis
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require('dotenv').config();
const _ = require('lodash');
const express = require('express');
const { Pool } = require('pg');
const axios = require('axios');
const {
isValidKey,
isValidWIDSSensorID,
isValidWIDSSensorAPI,
} = require('./validations');
const defaults = require('./defaults.json');
class AggregationServer {
constructor(config = {}) {
this.networkKeys = new Set();
this.linkKeys = new Set();
this.widsSensors = [];
this.lastPacketCounters = {};
this.lastByteCounters = {};
this.lastMACSeqnums = {};
this.lastBeaconSeqnums = {};
this.lastNWKSeqnums = {};
this.lastNWKAUXSeqnums = {};
this.lastBtryPercs = {};
this.lastEvents = {};
this.aggregationIPAddress = (
config.aggregationIPAddress || defaults.aggregationIPAddress
);
this.aggregationPortNumber = (
config.aggregationPortNumber || defaults.aggregationPortNumber
);
this.aggregationDelay = (
config.aggregationDelay || defaults.aggregationDelay
);
this.retentionIPAddress = (
config.retentionIPAddress || defaults.retentionIPAddress
);
this.retentionPortNumber = (
config.retentionPortNumber || defaults.retentionPortNumber
);
this.app = express();
this.app.use(express.json());
this.app.use(
(req, res, next) => {
res.setHeader(
'Access-Control-Allow-Origin',
config.originURL || defaults.originURL,
);
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type',
);
res.setHeader(
'Access-Control-Allow-Methods',
'GET, OPTIONS, POST, DELETE',
);
next();
},
);
this.pool = new Pool(
{
host: config.databaseIPAddress || defaults.databaseIPAddress,
port: config.databasePortNumber || defaults.databasePortNumber,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASS,
},
);
this.app.post(
'/api/registry',
async (req, res, next) => {
if (
!isValidWIDSSensorID(req.body.wids_sensor_id)
|| !isValidWIDSSensorAPI(req.body.wids_sensor_api)
) {
res.sendStatus(400);
return;
}
try {
const result = await this.pool.query(
'SELECT * FROM wids_sensors '
+ 'WHERE wids_sensor_id=$1 OR wids_sensor_api=$2',
[
req.body.wids_sensor_id,
req.body.wids_sensor_api,
],
);
if (result.rows.length !== 0) {
res.sendStatus(400);
return;
}
await this.pool.query(
'INSERT INTO wids_sensors (wids_sensor_id, wids_sensor_api) '
+ 'VALUES ($1, $2)',
[
req.body.wids_sensor_id,
req.body.wids_sensor_api,
],
);
res.sendStatus(200);
this.updateWIDSSensors();
} catch (err) {
next(err);
}
},
);
this.app.delete(
'/api/registry/:id',
async (req, res, next) => {
if (!isValidWIDSSensorID(req.params.id)) {
res.sendStatus(400);
return;
}
try {
const result = await this.pool.query(
'SELECT * FROM wids_sensors WHERE wids_sensor_id=$1',
[
req.params.id,
],
);
if (result.rows.length === 0) {
res.sendStatus(404);
return;
}
if (result.rows.length !== 1) {
res.sendStatus(500);
return;
}
await this.pool.query(
'DELETE FROM wids_sensors WHERE wids_sensor_id=$1',
[
req.params.id,
],
);
res.sendStatus(200);
this.updateWIDSSensors();
} catch (err) {
next(err);
}
},
);
this.monitorRetentionMetadata = async () => {
try {
const retentionMetadataURL = (
`http://${this.retentionIPAddress}:${this.retentionPortNumber}/api`
+ '/active-wids-sensors'
);
const response = await axios.get(retentionMetadataURL);
if (!_.isEqual(response.data, this.widsSensors)) {
await axios.put(retentionMetadataURL, this.widsSensors);
}
} catch (err) {
console.error(err);
} finally {
setTimeout(this.monitorRetentionMetadata, this.aggregationDelay);
}
};
this.checkLinkKeys = async (url) => {
try {
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(key) => {
if (isValidKey(key) && !this.linkKeys.has(key)) {
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO link_keys (key) VALUES (DECODE($1, $2))',
[
key,
'hex',
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
}
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
await this.updateLinkKeys();
}
const postKeys = [];
this.linkKeys.forEach(
(key) => {
if (isValidKey(key) && !response.data.includes(key)) {
postKeys.push(key);
}
},
);
if (postKeys.length > 0) {
await axios.post(url, postKeys);
}
} catch (err) {
console.error(err);
}
};
this.monitorLinkKeys = async () => {
this.widsSensors.forEach(
(row) => {
this.checkLinkKeys(`${row.wids_sensor_api}/link-keys`);
},
);
setTimeout(this.monitorLinkKeys, this.aggregationDelay);
};
this.checkNetworkKeys = async (url) => {
try {
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(key) => {
if (isValidKey(key) && !this.networkKeys.has(key)) {
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO network_keys (key) '
+ 'VALUES (DECODE($1, $2))',
[
key,
'hex',
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
}
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
await this.updateNetworkKeys();
}
const postKeys = [];
this.networkKeys.forEach(
(key) => {
if (isValidKey(key) && !response.data.includes(key)) {
postKeys.push(key);
}
},
);
if (postKeys.length > 0) {
await axios.post(url, postKeys);
}
} catch (err) {
console.error(err);
}
};
this.monitorNetworkKeys = async () => {
this.widsSensors.forEach(
(row) => {
this.checkNetworkKeys(`${row.wids_sensor_api}/network-keys`);
},
);
setTimeout(this.monitorNetworkKeys, this.aggregationDelay);
};
this.insertEvents = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastEvents) {
url += `?last=${this.lastEvents[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastEvents[id]
|| parseFloat(entry.epochTimestamp) > this.lastEvents[id]
) {
this.lastEvents[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_events (wids_sensor_id, '
+ 'epoch_timestamp, description, inspected) '
+ 'VALUES ($1, $2, $3, $4)',
[
id,
entry.epochTimestamp,
entry.description,
false,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateEventsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertEvents(
row.wids_sensor_id,
`${row.wids_sensor_api}/events`,
);
},
);
setTimeout(this.aggregateEventsData, this.aggregationDelay);
};
this.insertBtryPercs = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastBtryPercs) {
url += `?last=${this.lastBtryPercs[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastBtryPercs[id]
|| parseFloat(entry.epochTimestamp) > this.lastBtryPercs[id]
) {
this.lastBtryPercs[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_battery_percentages (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, percentage) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
id,
entry.epochTimestamp,
entry.srcpanid,
entry.srcshortaddr,
entry.batteryPercentage,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateBtryPercsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertBtryPercs(
row.wids_sensor_id,
`${row.wids_sensor_api}/battery-percentages`,
);
},
);
setTimeout(this.aggregateBtryPercsData, this.aggregationDelay);
};
this.insertNWKAUXSeqnums = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastNWKAUXSeqnums) {
url += `?last=${this.lastNWKAUXSeqnums[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastNWKAUXSeqnums[id]
|| parseFloat(entry.epochTimestamp) > this.lastNWKAUXSeqnums[id]
) {
this.lastNWKAUXSeqnums[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_nwkaux_seqnums (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, '
+ 'nwkaux_seqnum) VALUES ($1, $2, $3, $4, $5)',
[
id,
entry.epochTimestamp,
entry.srcpanid,
entry.srcshortaddr,
entry.nwkauxSeqnum,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateNWKAUXSeqnumsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertNWKAUXSeqnums(
row.wids_sensor_id,
`${row.wids_sensor_api}/nwkaux-seqnums`,
);
},
);
setTimeout(this.aggregateNWKAUXSeqnumsData, this.aggregationDelay);
};
this.insertNWKSeqnums = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastNWKSeqnums) {
url += `?last=${this.lastNWKSeqnums[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastNWKSeqnums[id]
|| parseFloat(entry.epochTimestamp) > this.lastNWKSeqnums[id]
) {
this.lastNWKSeqnums[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_nwk_seqnums (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, nwk_seqnum) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
id,
entry.epochTimestamp,
entry.srcpanid,
entry.srcshortaddr,
entry.nwkSeqnum,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateNWKSeqnumsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertNWKSeqnums(
row.wids_sensor_id,
`${row.wids_sensor_api}/nwk-seqnums`,
);
},
);
setTimeout(this.aggregateNWKSeqnumsData, this.aggregationDelay);
};
this.insertBeaconSeqnums = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastBeaconSeqnums) {
url += `?last=${this.lastBeaconSeqnums[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastBeaconSeqnums[id]
|| parseFloat(entry.epochTimestamp) > this.lastBeaconSeqnums[id]
) {
this.lastBeaconSeqnums[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_beacon_seqnums (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, '
+ 'beacon_seqnum) VALUES ($1, $2, $3, $4, $5)',
[
id,
entry.epochTimestamp,
entry.srcpanid,
entry.srcshortaddr,
entry.beaconSeqnum,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateBeaconSeqnumsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertBeaconSeqnums(
row.wids_sensor_id,
`${row.wids_sensor_api}/beacon-seqnums`,
);
},
);
setTimeout(this.aggregateBeaconSeqnumsData, this.aggregationDelay);
};
this.insertMACSeqnums = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastMACSeqnums) {
url += `?last=${this.lastMACSeqnums[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastMACSeqnums[id]
|| parseFloat(entry.epochTimestamp) > this.lastMACSeqnums[id]
) {
this.lastMACSeqnums[id] = parseFloat(entry.epochTimestamp);
}
insertPromises.push(
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_mac_seqnums (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, mac_seqnum) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
id,
entry.epochTimestamp,
entry.srcpanid,
entry.srcshortaddr,
entry.macSeqnum,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
),
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateMACSeqnumsData = () => {
this.widsSensors.forEach(
(row) => {
this.insertMACSeqnums(
row.wids_sensor_id,
`${row.wids_sensor_api}/mac-seqnums`,
);
},
);
setTimeout(this.aggregateMACSeqnumsData, this.aggregationDelay);
};
this.insertByteCounterPromise = (
widsSensorID,
epochTimestamp,
srcpanid,
srcshortaddr,
byteCounter,
) => (
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_byte_counters (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, byte_counter) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
widsSensorID,
epochTimestamp,
srcpanid,
srcshortaddr,
byteCounter,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
)
);
this.insertByteCounters = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastByteCounters) {
url += `?last=${this.lastByteCounters[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastByteCounters[id]
|| parseFloat(entry.epochTimestamp) > this.lastByteCounters[id]
) {
this.lastByteCounters[id] = parseFloat(entry.epochTimestamp);
}
entry.panByteCounters.forEach(
(network) => {
insertPromises.push(
this.insertByteCounterPromise(
id,
entry.epochTimestamp,
network.srcpanid,
null,
network.counter,
),
);
network.deviceByteCounters.forEach(
(device) => {
insertPromises.push(
this.insertByteCounterPromise(
id,
entry.epochTimestamp,
network.srcpanid,
device.srcshortaddr,
device.counter,
),
);
},
);
},
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregateByteCountersData = () => {
this.widsSensors.forEach(
(row) => {
this.insertByteCounters(
row.wids_sensor_id,
`${row.wids_sensor_api}/byte-counters`,
);
},
);
setTimeout(this.aggregateByteCountersData, this.aggregationDelay);
};
this.insertPacketCounterPromise = (
widsSensorID,
epochTimestamp,
srcpanid,
srcshortaddr,
packetCounter,
) => (
new Promise(
(resolve, reject) => {
this.pool.query(
'INSERT INTO wids_packet_counters (wids_sensor_id, '
+ 'epoch_timestamp, srcpanid, srcshortaddr, packet_counter) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
widsSensorID,
epochTimestamp,
srcpanid,
srcshortaddr,
packetCounter,
],
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
);
},
)
);
this.insertPacketCounters = async (id, baseURL) => {
try {
let url = baseURL;
if (id in this.lastPacketCounters) {
url += `?last=${this.lastPacketCounters[id]}`;
}
const response = await axios.get(url);
const insertPromises = [];
response.data.forEach(
(entry) => {
if (
!this.lastPacketCounters[id]
|| (
parseFloat(entry.epochTimestamp) > this.lastPacketCounters[id]
)
) {
this.lastPacketCounters[id] = parseFloat(entry.epochTimestamp);
}
entry.panPacketCounters.forEach(
(network) => {
insertPromises.push(
this.insertPacketCounterPromise(
id,
entry.epochTimestamp,
network.srcpanid,
null,
network.counter,
),
);
network.devicePacketCounters.forEach(
(device) => {
insertPromises.push(
this.insertPacketCounterPromise(
id,
entry.epochTimestamp,
network.srcpanid,
device.srcshortaddr,
device.counter,
),
);
},
);
},
);
},
);
if (insertPromises.length > 0) {
await Promise.all(insertPromises);
}
} catch (err) {
console.error(err);
}
};
this.aggregatePacketCountersData = () => {
this.widsSensors.forEach(
(row) => {
this.insertPacketCounters(
row.wids_sensor_id,
`${row.wids_sensor_api}/packet-counters`,
);
},
);
setTimeout(this.aggregatePacketCountersData, this.aggregationDelay);
};
this.insertPairsRow = async (widsSensorID, dataEntry) => {
try {
await this.pool.query(
'INSERT INTO wids_pairs (wids_sensor_id, panid, srcaddr, dstaddr, '
+ 'earliest, latest) VALUES ($1, $2, $3, $4, $5, $6)',
[
widsSensorID,
dataEntry.panid,
dataEntry.srcaddr,
dataEntry.dstaddr,
dataEntry.earliest,
dataEntry.latest,
],
);
} catch (err) {
console.error(err);
}
};
this.updatePairsData = async (widsSensorID, url) => {
try {
const response = await axios.get(url);
await this.pool.query(
'DELETE FROM wids_pairs WHERE wids_sensor_id=$1',
[
widsSensorID,
],
);
response.data.forEach(
(dataEntry) => {
this.insertPairsRow(widsSensorID, dataEntry);
},
);
} catch (err) {
console.error(err);
}
};
this.aggregatePairsData = () => {
this.widsSensors.forEach(
(row) => {
this.updatePairsData(
row.wids_sensor_id,
`${row.wids_sensor_api}/pairs`,
);
},
);
setTimeout(this.aggregatePairsData, this.aggregationDelay);
};
this.insertExtendedAddressesRow = async (widsSensorID, dataEntry) => {
try {
await this.pool.query(
'INSERT INTO wids_extended_addresses (wids_sensor_id, '
+ 'extendedaddr, altset, macset, nwkset, earliest, latest) VALUES '
+ '($1, $2, $3, $4, $5, $6, $7)',
[
widsSensorID,
dataEntry.extendedaddr,
dataEntry.altset,
dataEntry.macset,
dataEntry.nwkset,
dataEntry.earliest,
dataEntry.latest,
],
);
} catch (err) {
console.error(err);
}
};
this.updateExtendedAddressesData = async (widsSensorID, url) => {
try {
const response = await axios.get(url);
await this.pool.query(
'DELETE FROM wids_extended_addresses WHERE wids_sensor_id=$1',
[
widsSensorID,
],
);
response.data.forEach(
(dataEntry) => {
this.insertExtendedAddressesRow(widsSensorID, dataEntry);
},
);
} catch (err) {
console.error(err);
}
};
this.aggregateExtendedAddressesData = () => {
this.widsSensors.forEach(
(row) => {
this.updateExtendedAddressesData(
row.wids_sensor_id,
`${row.wids_sensor_api}/extended-addresses`,
);
},
);
setTimeout(this.aggregateExtendedAddressesData, this.aggregationDelay);
};
this.insertShortAddressesRow = async (widsSensorID, dataEntry) => {
try {
await this.pool.query(
'INSERT INTO wids_short_addresses (wids_sensor_id, panid, '
+ 'shortaddr, altset, macset, nwkset, earliest, latest) VALUES '
+ '($1, $2, $3, $4, $5, $6, $7, $8)',
[
widsSensorID,
dataEntry.panid,
dataEntry.shortaddr,
dataEntry.altset,
dataEntry.macset,
dataEntry.nwkset,
dataEntry.earliest,
dataEntry.latest,
],
);
} catch (err) {
console.error(err);
}
};
this.updateShortAddressesData = async (widsSensorID, url) => {
try {
const response = await axios.get(url);
await this.pool.query(
'DELETE FROM wids_short_addresses WHERE wids_sensor_id=$1',
[
widsSensorID,
],
);
response.data.forEach(
(dataEntry) => {
this.insertShortAddressesRow(widsSensorID, dataEntry);
},
);
} catch (err) {
console.error(err);
}
};
this.aggregateShortAddressesData = () => {
this.widsSensors.forEach(
(row) => {
this.updateShortAddressesData(
row.wids_sensor_id,
`${row.wids_sensor_api}/short-addresses`,
);
},
);
setTimeout(this.aggregateShortAddressesData, this.aggregationDelay);
};
this.insertNetworksRow = async (widsSensorID, dataEntry) => {
try {
await this.pool.query(
'INSERT INTO wids_networks (wids_sensor_id, panid, epidset, '
+ 'earliest, latest) VALUES ($1, $2, $3, $4, $5)',
[
widsSensorID,
dataEntry.panid,
dataEntry.epidset,
dataEntry.earliest,
dataEntry.latest,
],
);
} catch (err) {
console.error(err);
}
};
this.updateNetworksData = async (widsSensorID, url) => {
try {
const response = await axios.get(url);
await this.pool.query(
'DELETE FROM wids_networks WHERE wids_sensor_id=$1',
[
widsSensorID,
],
);
response.data.forEach(
(dataEntry) => {
this.insertNetworksRow(widsSensorID, dataEntry);
},
);
} catch (err) {
console.error(err);
}
};
this.aggregateNetworksData = () => {
this.widsSensors.forEach(
(row) => {
this.updateNetworksData(
row.wids_sensor_id,
`${row.wids_sensor_api}/networks`,
);
},
);
setTimeout(this.aggregateNetworksData, this.aggregationDelay);
};
this.insertUtilMeasurements = async (id, url) => {
try {
const response = await axios.get(url);
await this.pool.query(
'INSERT INTO wids_utilization (wids_sensor_id, '
+ 'epoch_timestamp, cpu_percent, memory_percent, disk_percent) '
+ 'VALUES ($1, $2, $3, $4, $5)',
[
id,
response.data.epochTimestamp,
response.data.cpuPercent,
response.data.memoryPercent,
response.data.diskPercent,
],
);
} catch (err) {
console.error(err);
}
};
this.aggregateUtilData = () => {
this.widsSensors.forEach(
(row) => {
this.insertUtilMeasurements(
row.wids_sensor_id,
`${row.wids_sensor_api}/utilization`,
);
},
);
setTimeout(this.aggregateUtilData, this.aggregationDelay);
};
this.initLastEvents = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_events '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastEvents[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastBtryPercs = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_battery_percentages '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastBtryPercs[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastNWKAUXSeqnums = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_nwkaux_seqnums '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastNWKAUXSeqnums[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastNWKSeqnums = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_nwk_seqnums '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastNWKSeqnums[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastBeaconSeqnums = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_beacon_seqnums '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastBeaconSeqnums[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastMACSeqnums = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_mac_seqnums '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastMACSeqnums[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastByteCounters = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_byte_counters '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastByteCounters[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastPacketCounters = async () => {
try {
const maxTimestamps = await Promise.all(
this.widsSensors.map(
(row) => new Promise(
(resolve, reject) => {
this.pool.query(
'SELECT MAX(epoch_timestamp) '
+ 'FROM wids_packet_counters '
+ 'WHERE wids_sensor_id=$1',
[
row.wids_sensor_id,
],
(err, extractResult) => {
if (err) {
reject(err);
} else {
resolve(
{
id: row.wids_sensor_id,
result: extractResult,
},
);
}
},
);
},
),
),
);
maxTimestamps.forEach(
(tmp) => {
if (tmp.result.rows.length === 1 && tmp.result.rows[0].max) {
this.lastPacketCounters[tmp.id] = parseFloat(
tmp.result.rows[0].max,
);
}
},
);
} catch (err) {
console.error(err);
}
};
this.initLastTimestamps = async () => {
await Promise.all(
[
this.initLastPacketCounters(),
this.initLastByteCounters(),
this.initLastMACSeqnums(),
this.initLastBeaconSeqnums(),
this.initLastNWKSeqnums(),
this.initLastNWKAUXSeqnums(),
this.initLastBtryPercs(),
this.initLastEvents(),
],
);
};
this.updateWIDSSensors = async () => {
try {
const result = await this.pool.query(
'SELECT wids_sensor_id, wids_sensor_api FROM wids_sensors',
);
this.widsSensors = result.rows;
} catch (err) {
console.error(err);
}
};
this.updateLinkKeys = async () => {
try {
const result = await this.pool.query(
'SELECT ENCODE(key, $1) FROM link_keys',
[
'hex',
],
);
this.linkKeys.clear();
result.rows.forEach(
(row) => {
this.linkKeys.add(row.encode);
},
);
} catch (err) {
console.error(err);
}
};
this.updateNetworkKeys = async () => {
try {
const result = await this.pool.query(
'SELECT ENCODE(key, $1) FROM network_keys',
[
'hex',
],
);
this.networkKeys.clear();
result.rows.forEach(
(row) => {
this.networkKeys.add(row.encode);
},
);
} catch (err) {
console.error(err);
}
};
this.updateCachedMetadata = async () => {
await Promise.all(
[
this.updateNetworkKeys(),
this.updateLinkKeys(),
this.updateWIDSSensors(),
],
);
};
this.startAggregationRoutine = async () => {
await this.updateCachedMetadata();
await this.initLastTimestamps();
this.aggregateUtilData();
this.aggregateNetworksData();
this.aggregateShortAddressesData();
this.aggregateExtendedAddressesData();
this.aggregatePairsData();
this.aggregatePacketCountersData();
this.aggregateByteCountersData();
this.aggregateMACSeqnumsData();
this.aggregateBeaconSeqnumsData();
this.aggregateNWKSeqnumsData();
this.aggregateNWKAUXSeqnumsData();
this.aggregateBtryPercsData();
this.aggregateEventsData();
this.monitorNetworkKeys();
this.monitorLinkKeys();
this.monitorRetentionMetadata();
};
}
start() {
this.startAggregationRoutine();
this.app.listen(
this.aggregationPortNumber,
this.aggregationIPAddress,
() => {
console.log(
`Started an aggregation server at ${this.aggregationIPAddress}`
+ `:${this.aggregationPortNumber}`,
);
},
);
}
}
module.exports = AggregationServer;