/* 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 #include #include #include #include #include #include #include #include #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, BOOST_MULTI_INDEX_MEMBER(COfficeMap, std::string, urn) >, hashed_unique< tag, BOOST_MULTI_INDEX_MEMBER(COfficeMap, id_t, id) > > > office_map_t; typedef office_map_t::index::type& omap_urn_index_t; typedef office_map_t::index::type& omap_id_index_t; typedef office_map_t::index::type::iterator omap_urn_iterator_t; typedef office_map_t::index::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(); 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(); 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(); 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(); 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 client_set_t; typedef std::unordered_map offices_to_clients_t; static offices_to_clients_t offices_to_clients; typedef std::unordered_set office_set_t; typedef std::unordered_map 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; }