@mdf.js/openc2-core
Version:
MMS - API Core - OpenC2
185 lines • 6.8 kB
JavaScript
"use strict";
/**
* Copyright 2024 Mytra Control S.L. All rights reserved.
*
* Use of this source code is governed by an MIT-style license that can be found in the LICENSE file
* or at https://opensource.org/licenses/MIT.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsumerMap = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@mdf.js/core");
const events_1 = tslib_1.__importDefault(require("events"));
const lodash_1 = require("lodash");
const uuid_1 = require("uuid");
const helpers_1 = require("../../helpers");
class ConsumerMap extends events_1.default {
/**
* Create a new instance of the consumer map
* @param name - name of the producer
* @param agingInterval - agingInterval to update the consumer map
* @param maxAge - Max allowed age in in milliseconds for a table entry
*/
constructor(name, agingInterval, maxAge) {
super();
this.name = name;
this.agingInterval = agingInterval;
this.maxAge = maxAge;
/** Component identification */
this.componentId = (0, uuid_1.v4)();
/** Perform the aging of the consumer map */
this.aging = () => {
var _a;
const emitAsAged = [];
const checkTime = new Date().getTime();
for (const [consumerId, consumer] of this.map.entries()) {
const age = checkTime - new Date((_a = consumer.time) !== null && _a !== void 0 ? _a : 0).getTime();
if (age > this.maxAge) {
emitAsAged.push(consumerId);
this.map.delete(consumerId);
}
}
if (emitAsAged.length > 0) {
this.emit('aged', emitAsAged);
this.emit('updated', emitAsAged);
}
if (this.map.size === 0) {
clearInterval(this.agingTimer);
this.agingTimer = undefined;
}
};
this.map = new Map();
}
/** Get the actual nodes */
get nodes() {
return Array.from(this.map.values())
.map(node => node.observedValue)
.filter(node => node !== undefined);
}
/**
* Returns the grouped features of all the consumer in the map
* @returns
*/
getGroupedFeatures() {
const nodes = this.nodes;
const pairs = {};
const arrayOfPairs = nodes.map(node => { var _a; return ((_a = node.results) === null || _a === void 0 ? void 0 : _a.pairs) || {}; });
for (const nodePairs of arrayOfPairs) {
for (const [action, targets] of Object.entries(nodePairs)) {
if (pairs[action]) {
pairs[action] = (0, lodash_1.uniq)(pairs[action].concat(targets));
}
else {
pairs[action] = targets;
}
}
}
const profiles = (0, lodash_1.uniq)(nodes.map(node => { var _a, _b; return (_b = (_a = node.results) === null || _a === void 0 ? void 0 : _a.profiles) !== null && _b !== void 0 ? _b : []; }).flat());
return { pairs, profiles };
}
/**
* Return the consumers identifiers that has the indicated action/target pair
* @param action - action to search
* @param target - target requested
*/
getConsumersWithPair(action, target) {
const consumers = [];
for (const [consumerId, consumer] of this.map.entries()) {
const targets = (0, lodash_1.get)(consumer, `observedValue.results.pairs.${action}`, []);
if (targets.includes(target)) {
consumers.push(consumerId);
}
}
return consumers;
}
/**
* Perform the update of the health map
* @param responses - responses to update the consumer map
*/
update(responses) {
const emitAsNewEntry = [];
const emitAsChanged = [];
for (const response of responses) {
const actualRegistry = this.map.get(response.from);
if (!actualRegistry) {
emitAsNewEntry.push(response.from);
}
else if (!(0, lodash_1.isEqual)(actualRegistry.observedValue, response.content)) {
emitAsChanged.push(response.from);
}
this.updateEntry(response);
}
if (emitAsNewEntry.length > 0) {
this.emit('new', emitAsNewEntry);
}
if (emitAsChanged.length > 0) {
this.emit('update', emitAsChanged);
}
if (emitAsChanged.length > 0 || emitAsNewEntry.length > 0) {
this.emit('updated', emitAsChanged.concat(emitAsNewEntry));
}
this.emitStatus();
if (!this.agingTimer && this.map.size > 0) {
this.agingTimer = setInterval(this.aging, this.agingInterval);
}
}
/**
* Get one node from the map
* @param consumerId - consumer id to get the status
* @returns
*/
getNode(consumerId) {
return this.map.get(consumerId);
}
/** Return the state of all the underlying consumers */
get checks() {
return { [`${this.name}:consumers`]: Array.from(this.map.values()) };
}
/**
* Update the consumer map with a new response
* @param response - response to update the consumer map
*/
updateEntry(response) {
this.map.set(response.from, {
componentId: response.from,
componentType: 'OpenC2 Consumer',
time: new Date(response.created).toISOString(),
status: helpers_1.Accessors.getStatusFromResponseMessage(response),
observedValue: response.content,
observedUnit: 'features',
});
}
/** Overall component status */
get status() {
return core_1.Health.overallStatus(this.checks);
}
/** Emit the status if it's different from the last emitted status */
emitStatus() {
if (this.lastStatusEmitted !== this.status) {
this.lastStatusEmitted = this.status;
this.emit('status', this.status);
}
}
/** Clean the nodes map */
clear() {
this.map.clear();
if (this.agingTimer) {
clearInterval(this.agingTimer);
this.agingTimer = undefined;
}
}
/** Fake start method used to implement the Resource interface */
async start() {
return Promise.resolve();
}
/** Fake stop method used to implement the Resource interface */
async stop() {
return Promise.resolve();
}
/** Fake close method used to implement the Resource interface */
async close() {
return Promise.resolve();
}
}
exports.ConsumerMap = ConsumerMap;
//# sourceMappingURL=ConsumerMap.js.map