Sleds/K2Daemon/OfficeSubscriptions.cpp

440 lines
12 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
/*
Copyright (c) 2013 IOnU Security Inc. All rights reserved
Created October 2013 by Kendrick Webster
K2Daemon/OfficeSubscriptions.cpp - implementation for OfficeSubscriptions.h
*/
#if !defined(NDEBUG)
#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/algorithm/string.hpp>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <stdint.h>
#include <string.h>
#include "global.h"
#include "ClientMap.h"
#include "ClientCache.h"
#include "OfficeSubscriptions.h"
// Comment or un-comment the following line as needed (but leave it commented out for production):
//#define DEBUG_OFFICE_SUBSCRIPTIONS
#ifdef DEBUG_OFFICE_SUBSCRIPTIONS
#define logprintf_ logprintf
#else
#define logprintf_(...)
#endif
using boost::multi_index_container;
using namespace boost::multi_index;
/* --------------------------------------------------------------------------
Low level helper functions
*/
void client_urn_to_office_urn(const char* client_urn, std::string& office_urn)
{
const char* p = strrchr(client_urn, ':');
if (p)
{
while (p > client_urn)
{
if (':' == *--p)
{
break;
}
}
office_urn.assign(client_urn, p - client_urn);
office_urn += "::";
}
else
{
office_urn.clear();
}
}
/* --------------------------------------------------------------------------
Office URN <--> ID map
*/
namespace OfficeMap
{
typedef uint64_t id_t;
struct COfficeMap
{
id_t id;
std::string urn;
};
struct by_urn {};
struct by_id {};
typedef multi_index_container<
COfficeMap,
indexed_by<
hashed_unique<
tag<by_urn>,
BOOST_MULTI_INDEX_MEMBER(COfficeMap, std::string, urn)
>,
hashed_unique<
tag<by_id>,
BOOST_MULTI_INDEX_MEMBER(COfficeMap, id_t, id)
>
>
> office_map_t;
typedef office_map_t::index<by_urn>::type& omap_urn_index_t;
typedef office_map_t::index<by_id>::type& omap_id_index_t;
typedef office_map_t::index<by_urn>::type::iterator omap_urn_iterator_t;
typedef office_map_t::index<by_id>::type::iterator omap_id_iterator_t;
// constants and data
static const id_t NULL_ID = 0; // valid IDs are non-zero
office_map_t office_map;
// Functions
const std::string& UrnFromId (id_t id);
id_t IdFromUrn (const std::string& urn);
id_t Map (const std::string& urn);
void Unmap (id_t id);
unsigned int Count (void);
}
// Get URN from ID
// CAUTION: returned reference becomes invalid (must be discarded)
// if the office is later unmapped
const std::string& OfficeMap::UrnFromId(id_t id)
{
static std::string empty_str;
omap_id_index_t idx = office_map.get<by_id>();
omap_id_iterator_t it = idx.find(id);
if (it != idx.end())
{
return it->urn;
}
return empty_str;
}
// Get ID from URN if mapped
OfficeMap::id_t OfficeMap::IdFromUrn(const std::string& urn)
{
omap_urn_index_t idx = office_map.get<by_urn>();
omap_urn_iterator_t it = idx.find(urn);
if (it != idx.end())
{
return it->id;
}
return NULL_ID;
}
// Get ID from URN, add to map if not already mapped
OfficeMap::id_t OfficeMap::Map(const std::string& urn)
{
omap_urn_index_t idx = office_map.get<by_urn>();
omap_urn_iterator_t it = idx.find(urn);
if (it != idx.end())
{
return it->id;
}
else
{
static id_t next_id = 0;
COfficeMap om;
om.id = ++next_id;
om.urn = urn;
idx.insert(om);
return om.id;
}
}
// Remove mapping
void OfficeMap::Unmap(id_t id)
{
omap_id_index_t idx = office_map.get<by_id>();
omap_id_iterator_t it = idx.find(id);
if (it != idx.end())
{
idx.erase(it);
}
else
{
logprintf(Log::error, "OfficeMap::Unmap id(%d) not found", id);
}
}
// returns the number of offices mapped
unsigned int OfficeMap::Count(void)
{
return office_map.size();
}
/* --------------------------------------------------------------------------
Office status subscription maps (to keep track of which clients are
interested in which offices)
*/
typedef std::unordered_set<ClientMap::id_t> client_set_t;
typedef std::unordered_map<OfficeMap::id_t, client_set_t> offices_to_clients_t;
static offices_to_clients_t offices_to_clients;
typedef std::unordered_set<OfficeMap::id_t> office_set_t;
typedef std::unordered_map<ClientMap::id_t, office_set_t> clients_to_offices_t;
static clients_to_offices_t clients_to_offices;
static std::string soStr;
static int soCount;
static office_set_t oSet;
static unsigned int subscriptionCount;
/* --------------------------------------------------------------------------
Status of devices within each office (to notify clients when the entire
office goes offline) ... a client ID associated with an office indicates
that the client is online. An office is "offline" iff it is associated
with no (0) clients.
*/
static offices_to_clients_t office_client_status;
static void beginSubscribeOffice(const char* client_urn)
{
logprintf_(Log::debug3, "Setting office subscription list for client \"%s\", offices listed below:", client_urn);
soStr.clear();
soCount = 0;
oSet.clear();
}
static void nextSubscribeOffice(const char* client_urn, const std::string& office_urn)
{
++soCount;
if (0 < soStr.length())
{
soStr += ", ";
}
soStr += office_urn;
if (80 < soStr.length())
{
logprintf_(Log::debug3, " %s", soStr.c_str());
soStr.clear();
}
OfficeMap::id_t id = OfficeMap::Map(office_urn);
oSet.insert(id);
offices_to_clients[id].insert(ClientMap::IdFromUrn(client_urn));
++subscriptionCount;
}
static void endSubscribeOffice(const char* client_urn)
{
if (0 < soStr.length())
{
logprintf_(Log::debug3, " %s", soStr.c_str());
soStr.clear();
}
logprintf_(Log::debug3, "End of office subscription list, %d office(s) subscribed", soCount);
if (0 < soCount)
{
clients_to_offices[ClientMap::IdFromUrn(client_urn)] = oSet;
}
logprintf(Log::debug2, "%d client(s) in clients_to_offices map", clients_to_offices.size());
}
static void beginUnsubscribeOffice(const char* client_urn)
{
logprintf_(Log::debug3, "Removing office subscriptions for client \"%s\", offices listed below:", client_urn);
soStr.clear();
soCount = 0;
}
static void nextUnsubscribeOffice(const char* client_urn, const std::string& office_urn)
{
++soCount;
if (0 < soStr.length())
{
soStr += ", ";
}
soStr += office_urn;
if (80 < soStr.length())
{
logprintf_(Log::debug3, " %s", soStr.c_str());
soStr.clear();
}
OfficeMap::id_t id = OfficeMap::IdFromUrn(office_urn);
if (1 != offices_to_clients.count(id))
{
logprintf(Log::error, "nextUnsubscribeOffice: office %s not found in office to clients map", office_urn.c_str());
}
else
{
client_set_t& client_set = offices_to_clients.at(id);
if (1 == client_set.erase(ClientMap::IdFromUrn(client_urn)))
{
--subscriptionCount;
if (client_set.empty())
{
// too chatty, enable only as-needed for special debugging
// logprintf_(Log::debug3, " - removing %s from office map", office_urn.c_str());
if (1 != offices_to_clients.erase(id))
{
logprintf(Log::error, "offices_to_clients.erase(%d) failed", id);
}
OfficeMap::Unmap(id);
}
}
else
{
logprintf(Log::error, "nextUnsubscribeOffice: client %s not found in office %s client set", client_urn, office_urn.c_str());
}
}
}
static void endUnsubscribeOffice(void)
{
if (0 < soStr.length())
{
logprintf_(Log::debug3, " %s", soStr.c_str());
soStr.clear();
}
logprintf_(Log::debug3, "End of office unsubscribe list, %d office(s) unsubscribed", soCount);
}
static void unsubscribeOffices(const char* client_urn)
{
clients_to_offices_t::const_iterator it = clients_to_offices.find(ClientMap::IdFromUrn(client_urn));
beginUnsubscribeOffice(client_urn);
if (clients_to_offices.end() != it)
{
const office_set_t& offices = it->second;
for (const OfficeMap::id_t& office_id: offices)
{
nextUnsubscribeOffice(client_urn, OfficeMap::UrnFromId(office_id));
}
clients_to_offices.erase(it);
}
endUnsubscribeOffice();
}
static bool isOfficeOffline(OfficeMap::id_t office_id, const char* client_urn, OfficeSubscriptions::client_state_t client_state)
{
ClientMap::id_t client_id = ClientMap::IdFromUrn(client_urn);
if (OfficeSubscriptions::OFFLINE == client_state)
{
offices_to_clients_t::iterator it = office_client_status.find(office_id);
if (office_client_status.end() == it)
{
return true;
}
else
{
client_set_t& clients = it->second;
clients.erase(client_id);
if (clients.empty())
{
office_client_status.erase(it);
return true;
}
return false;
}
}
else
{
office_client_status[office_id].insert(client_id);
return false;
}
}
/* --------------------------------------------------------------------------
Module interface
*/
void OfficeSubscriptions::Subscribe(const char* client_urn, const char* office_urns)
{
using boost::algorithm::trim;
std::string str;
unsubscribeOffices(client_urn);
beginSubscribeOffice(client_urn);
const char *p = strchr(office_urns, K2IPC_OFFICE_DELIMITER);
while (p)
{
size_t len = p - office_urns;
str.assign(office_urns, len);
trim(str);
nextSubscribeOffice(client_urn, str);
office_urns += len + 1;
p = strchr(office_urns, K2IPC_OFFICE_DELIMITER);
}
if (0 != office_urns[0])
{
str = office_urns;
trim(str);
nextSubscribeOffice(client_urn, str);
}
endSubscribeOffice(client_urn);
}
void OfficeSubscriptions::NotifyStatus(const char* client_urn, client_state_t client_state)
{
std::string office_urn, message, message_2;
client_urn_to_office_urn(client_urn, office_urn);
OfficeMap::id_t office_id = OfficeMap::IdFromUrn(office_urn);
bool office_offline = isOfficeOffline(office_id, client_urn, client_state);
switch (client_state)
{
case OFFLINE:
message = K2IPC_STATUS_OFFLINE;
break;
case ONLINE:
message = K2IPC_STATUS_ONLINE;
break;
}
message += K2IPC_STATUS_DELIMITER;
message += client_urn;
if (office_offline)
{
message_2 = K2IPC_STATUS_OFFLINE;
message_2 += K2IPC_STATUS_DELIMITER;
message_2 += office_urn;
}
int n = 0;
offices_to_clients_t::const_iterator it = offices_to_clients.find(office_id);
if (offices_to_clients.end() != it)
{
const client_set_t& clients = it->second;
for (const ClientMap::id_t& client_id: clients)
{
++n;
ClientCache::SendDeviceStatus(client_id, message.c_str());
if (office_offline)
{
ClientCache::SendDeviceStatus(client_id, message_2.c_str());
}
}
}
logprintf(Log::debug2, "%s - notified %d client(s) subscribed to office %s", message.c_str(), n, office_urn.c_str());
}
unsigned int OfficeSubscriptions::OfficeCount(void)
{
return OfficeMap::Count();
}
unsigned int OfficeSubscriptions::SubscriptionCount(void)
{
return subscriptionCount;
}