440 lines
12 KiB
C++
440 lines
12 KiB
C++
|
|
/*
|
||
|
|
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;
|
||
|
|
}
|