blob: 371a119c1a802acd21b99c30806b152eb602054a [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
/*
* Copyright (c) 2013, Regents of the University of California
*
* BSD license, See the LICENSE file for more information
*
* Author: Qiuhan Ding <qiuhanding@cs.ucla.edu>
* Yingdi Yu <yingdi@cs.ucla.edu>
*/
#include "chatroom-discovery-backend.hpp"
#include <QStringList>
#ifndef Q_MOC_RUN
#endif
namespace chronochat {
static const time::milliseconds FRESHNESS_PERIOD(60000);
static const time::seconds REFRESH_INTERVAL(60);
static const time::seconds HELLO_INTERVAL(60);
static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
ndn::name::Component::fromEscapedString("%F0%2E");
// a count enforced when a manager himself find another one publish chatroom data
static const int MAXIMUM_COUNT = 3;
static const int IDENTITY_OFFSET = -1;
static const int CONNECTION_RETRY_TIMER = 3;
ChatroomDiscoveryBackend::ChatroomDiscoveryBackend(const Name& routingPrefix,
const Name& identity,
QObject* parent)
: QThread(parent)
, m_shouldResume(false)
, m_routingPrefix(routingPrefix)
, m_identity(identity)
, m_randomGenerator(static_cast<unsigned int>(std::time(0)))
, m_rangeUniformRandom(m_randomGenerator, boost::uniform_int<>(500,2000))
{
m_discoveryPrefix.append("ndn")
.append("broadcast")
.append("ChronoChat")
.append("Discovery");
m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
updatePrefixes();
}
ChatroomDiscoveryBackend::~ChatroomDiscoveryBackend()
{
}
void
ChatroomDiscoveryBackend::run()
{
bool shouldResume = false;
do {
initializeSync();
if (m_face == nullptr)
break;
try {
m_face->getIoService().run();
}
catch (std::runtime_error& e) {
{
std::lock_guard<std::mutex>lock(m_nfdConnectionMutex);
m_isNfdConnected = false;
}
emit nfdError();
{
std::lock_guard<std::mutex>lock(m_resumeMutex);
m_shouldResume = true;
}
#ifdef BOOST_THREAD_USES_CHRONO
time::seconds reconnectTimer = time::seconds(CONNECTION_RETRY_TIMER);
#else
boost::posix_time::time_duration reconnectTimer = boost::posix_time::seconds(CONNECTION_RETRY_TIMER);
#endif
while (!m_isNfdConnected) {
#ifdef BOOST_THREAD_USES_CHRONO
boost::this_thread::sleep_for(reconnectTimer);
#else
boost::this_thread::sleep(reconnectTimer);
#endif
}
}
{
std::lock_guard<std::mutex>lock(m_resumeMutex);
shouldResume = m_shouldResume;
m_shouldResume = false;
}
close();
} while (shouldResume);
std::cerr << "DiscoveryBackend: Bye!" << std::endl;
}
void
ChatroomDiscoveryBackend::initializeSync()
{
BOOST_ASSERT(m_sock == nullptr);
m_face = shared_ptr<ndn::Face>(new ndn::Face);
m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
m_sock = make_shared<chronosync::Socket>(m_discoveryPrefix,
Name(),
ref(*m_face),
bind(&ChatroomDiscoveryBackend::processSyncUpdate,
this, _1));
// add an timer to refresh front end
if (m_refreshPanelId != nullptr) {
m_scheduler->cancelEvent(m_refreshPanelId);
}
m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
[this] { sendChatroomList(); });
}
void
ChatroomDiscoveryBackend::close()
{
m_scheduler->cancelAllEvents();
m_refreshPanelId.reset();
m_chatroomList.clear();
m_sock.reset();
}
void
ChatroomDiscoveryBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
{
if (updates.empty()) {
return;
}
for (const auto& update : updates) {
m_sock->fetchData(update.session, update.high,
bind(&ChatroomDiscoveryBackend::processChatroomData, this, _1), 2);
}
}
void
ChatroomDiscoveryBackend::processChatroomData(const ndn::shared_ptr<const ndn::Data>& data)
{
// extract chatroom name by get(-3)
Name::Component chatroomName = data->getName().get(-3);
auto it = m_chatroomList.find(chatroomName);
if (it == m_chatroomList.end()) {
m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
m_chatroomList[chatroomName].count = 0;
m_chatroomList[chatroomName].isPrint = false;
m_chatroomList[chatroomName].isParticipant = false;
m_chatroomList[chatroomName].isManager = false;
it = m_chatroomList.find(chatroomName);
}
// If the user is the manager of this chatroom, he should not receive any data from this chatroom
if (it->second.isManager) {
if (it->second.count < MAXIMUM_COUNT) {
it->second.count++;
return;
}
else {
it->second.count = 0;
if (m_routableUserDiscoveryPrefix < data->getName()) {
// when two managers exist, the one with "smaller" name take the control
sendUpdate(chatroomName);
return;
}
else {
if (it->second.helloTimeoutEventId != nullptr) {
m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
}
it->second.helloTimeoutEventId = nullptr;
it->second.isManager = false;
}
}
}
else if (it->second.isParticipant) {
if (it->second.localChatroomTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
// If a user start a random timer it means that he think his own chatroom is not alive
// But when he receive some packet, it means that this chatroom is alive, so he can
// cancel the timer
if (it->second.managerSelectionTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.managerSelectionTimeoutEventId);
it->second.managerSelectionTimeoutEventId = nullptr;
it->second.localChatroomTimeoutEventId =
m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
bind(&ChatroomDiscoveryBackend::localSessionTimeout,
this, chatroomName));
}
else {
if (!data->getContent().empty()) {
ChatroomInfo chatroom;
chatroom.wireDecode(data->getContent().blockFromValue());
it->second.info = chatroom;
}
if (it->second.remoteChatroomTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
it->second.remoteChatroomTimeoutEventId =
m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
this, chatroomName));
}
// if this is a chatroom that haven't been print on the discovery panel, print it.
if(!it->second.isPrint) {
sendChatroomList();
it->second.isPrint = true;
}
}
void
ChatroomDiscoveryBackend::localSessionTimeout(const Name::Component& chatroomName)
{
auto it = m_chatroomList.find(chatroomName);
if (it == m_chatroomList.end() || it->second.isParticipant == false)
return;
it->second.managerSelectionTimeoutEventId =
m_scheduler->scheduleEvent(time::milliseconds(m_rangeUniformRandom()),
bind(&ChatroomDiscoveryBackend::randomSessionTimeout,
this, chatroomName));
}
void
ChatroomDiscoveryBackend::remoteSessionTimeout(const Name::Component& chatroomName)
{
m_chatroomList.erase(chatroomName);
}
void
ChatroomDiscoveryBackend::randomSessionTimeout(const Name::Component& chatroomName)
{
Name prefix = m_routableUserDiscoveryPrefix;
prefix.append(chatroomName);
m_sock->addSyncNode(prefix);
emit chatroomInfoRequest(chatroomName.toUri(), true);
}
void
ChatroomDiscoveryBackend::sendUpdate(const Name::Component& chatroomName)
{
auto it = m_chatroomList.find(chatroomName);
if (it != m_chatroomList.end() && it->second.isManager) {
ndn::Block buf = it->second.info.wireEncode();
if (it->second.helloTimeoutEventId != nullptr) {
m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
}
m_sock->publishData(buf.wire(), buf.size(), FRESHNESS_PERIOD, it->second.chatroomPrefix);
it->second.helloTimeoutEventId =
m_scheduler->scheduleEvent(HELLO_INTERVAL,
bind(&ChatroomDiscoveryBackend::sendUpdate, this, chatroomName));
// if this is a chatroom that haven't been print on the discovery panel, print it.
if(!it->second.isPrint) {
sendChatroomList();
it->second.isPrint = true;
}
}
}
void
ChatroomDiscoveryBackend::updatePrefixes()
{
Name temp;
if (m_routingPrefix.isPrefixOf(m_userDiscoveryPrefix))
temp = m_userDiscoveryPrefix;
else
temp.append(m_routingPrefix)
.append(ROUTING_HINT_SEPARATOR)
.append(m_userDiscoveryPrefix);
Name routableIdentity = m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET);
for (auto& chatroom : m_chatroomList) {
if (chatroom.second.isParticipant) {
chatroom.second.info.removeParticipant(routableIdentity);
chatroom.second.info.addParticipant(temp.getPrefix(IDENTITY_OFFSET));
}
}
m_routableUserDiscoveryPrefix = temp;
}
void
ChatroomDiscoveryBackend::updateRoutingPrefix(const QString& routingPrefix)
{
Name newRoutingPrefix(routingPrefix.toStdString());
if (!newRoutingPrefix.empty() && newRoutingPrefix != m_routingPrefix) {
// Update localPrefix
m_routingPrefix = newRoutingPrefix;
updatePrefixes();
{
std::lock_guard<std::mutex>lock(m_resumeMutex);
m_shouldResume = true;
}
m_face->getIoService().stop();
}
}
void
ChatroomDiscoveryBackend::onEraseInRoster(ndn::Name sessionPrefix,
ndn::Name::Component chatroomName)
{
auto it = m_chatroomList.find(chatroomName);
if (it != m_chatroomList.end()) {
it->second.info.removeParticipant(sessionPrefix);
if (it->second.info.getParticipants().size() == 0) {
// Before deleting the chatroom, cancel the hello event timer if exist
if (it->second.helloTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
m_chatroomList.erase(chatroomName);
Name prefix = sessionPrefix;
prefix.append("CHRONOCHAT-DISCOVERYDATA").append(chatroomName);
m_sock->removeSyncNode(prefix);
sendChatroomList();
return;
}
if (sessionPrefix.isPrefixOf(m_routableUserDiscoveryPrefix)) {
it->second.isParticipant = false;
it->second.isManager = false;
it->second.isPrint = false;
it->second.count = 0;
if (it->second.helloTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
it->second.helloTimeoutEventId = nullptr;
if (it->second.localChatroomTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
it->second.localChatroomTimeoutEventId = nullptr;
it->second.remoteChatroomTimeoutEventId =
m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
this, chatroomName));
}
if (it->second.isManager) {
sendUpdate(chatroomName);
}
}
}
void
ChatroomDiscoveryBackend::onAddInRoster(ndn::Name sessionPrefix,
ndn::Name::Component chatroomName)
{
auto it = m_chatroomList.find(chatroomName);
if (it != m_chatroomList.end()) {
it->second.info.addParticipant(sessionPrefix);
if (it->second.isManager)
sendUpdate(chatroomName);
}
else {
onNewChatroomForDiscovery(chatroomName);
}
}
void
ChatroomDiscoveryBackend::onNewChatroomForDiscovery(ndn::Name::Component chatroomName)
{
Name newPrefix = m_routableUserDiscoveryPrefix;
newPrefix.append(chatroomName);
auto it = m_chatroomList.find(chatroomName);
if (it == m_chatroomList.end()) {
m_chatroomList[chatroomName].chatroomPrefix = newPrefix;
m_chatroomList[chatroomName].isParticipant = true;
m_chatroomList[chatroomName].isManager = false;
m_chatroomList[chatroomName].count = 0;
m_chatroomList[chatroomName].isPrint = false;
m_scheduler->scheduleEvent(time::milliseconds(600),
bind(&ChatroomDiscoveryBackend::randomSessionTimeout, this,
chatroomName));
}
else {
// Entering an existing chatroom
it->second.isParticipant = true;
it->second.isManager = false;
it->second.chatroomPrefix = newPrefix;
if (it->second.remoteChatroomTimeoutEventId != nullptr)
m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
it->second.isPrint = false;
it->second.remoteChatroomTimeoutEventId = nullptr;
it->second.localChatroomTimeoutEventId =
m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
bind(&ChatroomDiscoveryBackend::localSessionTimeout,
this, chatroomName));
emit chatroomInfoRequest(chatroomName.toUri(), false);
}
}
void
ChatroomDiscoveryBackend::onRespondChatroomInfoRequest(ChatroomInfo chatroomInfo, bool isManager)
{
if (isManager)
chatroomInfo.setManager(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET));
Name::Component chatroomName = chatroomInfo.getName();
m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
m_chatroomList[chatroomName].isManager = isManager;
m_chatroomList[chatroomName].count = 0;
m_chatroomList[chatroomName].info = chatroomInfo;
sendChatroomList();
onAddInRoster(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET), chatroomName);
}
void
ChatroomDiscoveryBackend::onIdentityUpdated(const QString& identity)
{
m_chatroomList.clear();
m_identity = Name(identity.toStdString());
m_userDiscoveryPrefix.clear();
m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
updatePrefixes();
{
std::lock_guard<std::mutex>lock(m_resumeMutex);
m_shouldResume = true;
}
m_face->getIoService().stop();
}
void
ChatroomDiscoveryBackend::sendChatroomList()
{
QStringList chatroomList;
for (const auto& chatroom : m_chatroomList) {
chatroomList << QString::fromStdString(chatroom.first.toUri());
}
emit chatroomListReady(chatroomList);
if (m_refreshPanelId != nullptr) {
m_scheduler->cancelEvent(m_refreshPanelId);
}
m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
[this] { sendChatroomList(); });
}
void
ChatroomDiscoveryBackend::onWaitForChatroomInfo(const QString& chatroomName)
{
auto chatroom = m_chatroomList.find(Name::Component(chatroomName.toStdString()));
if (chatroom != m_chatroomList.end())
emit chatroomInfoReady(chatroom->second.info, chatroom->second.isParticipant);
}
void
ChatroomDiscoveryBackend::shutdown()
{
{
std::lock_guard<std::mutex>lock(m_resumeMutex);
m_shouldResume = false;
}
{
// In this case, we just stop checking the nfd connection and exit
std::lock_guard<std::mutex>lock(m_nfdConnectionMutex);
m_isNfdConnected = true;
}
m_face->getIoService().stop();
}
void
ChatroomDiscoveryBackend::onNfdReconnect()
{
std::lock_guard<std::mutex>lock(m_nfdConnectionMutex);
m_isNfdConnected = true;
}
} // namespace chronochat
#if WAF
#include "chatroom-discovery-backend.moc"
#endif