blob: e7ed3eadec3ddad31a56be4a7fbb2c3fafcad0f3 [file] [log] [blame]
Qiuhan Ding43c8e162015-02-02 15:16:48 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2013, Regents of the University of California
4 *
5 * BSD license, See the LICENSE file for more information
6 *
7 * Author: Qiuhan Ding <qiuhanding@cs.ucla.edu>
8 * Yingdi Yu <yingdi@cs.ucla.edu>
9 */
10
11#include "chatroom-discovery-backend.hpp"
12#include <QStringList>
13
14
15#ifndef Q_MOC_RUN
16
17#endif
18
19namespace chronochat {
20
21static const time::milliseconds FRESHNESS_PERIOD(60000);
22static const time::seconds REFRESH_INTERVAL(60);
23static const time::seconds HELLO_INTERVAL(60);
24static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E}; // %F0.
25// a count enforced when a manager himself find another one publish chatroom data
26static const int MAXIMUM_COUNT = 3;
27static const int IDENTITY_OFFSET = -1;
28
29ChatroomDiscoveryBackend::ChatroomDiscoveryBackend(const Name& routingPrefix,
30 const Name& identity,
31 QObject* parent)
32 : QThread(parent)
33 , m_routingPrefix(routingPrefix)
34 , m_identity(identity)
35 , m_randomGenerator(static_cast<unsigned int>(std::time(0)))
36 , m_rangeUniformRandom(m_randomGenerator, boost::uniform_int<>(500,2000))
37 , m_shouldResume(false)
38{
39 m_discoveryPrefix.append("ndn")
40 .append("broadcast")
41 .append("ChronoChat")
42 .append("Discovery");
43 m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
44 updatePrefixes();
45}
46
47ChatroomDiscoveryBackend::~ChatroomDiscoveryBackend()
48{
49}
50
51void
52ChatroomDiscoveryBackend::run()
53{
54 bool shouldResume = false;
55 do {
56 initializeSync();
57
58 if (m_face == nullptr)
59 break;
60
61 m_face->getIoService().run();
62
63 m_mutex.lock();
64 shouldResume = m_shouldResume;
65 m_shouldResume = false;
66 m_mutex.unlock();
67
68 } while (shouldResume);
69
70 std::cerr << "Bye!" << std::endl;
71}
72
73void
74ChatroomDiscoveryBackend::initializeSync()
75{
76 BOOST_ASSERT(m_sock == nullptr);
77
78 m_face = shared_ptr<ndn::Face>(new ndn::Face);
79 m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
80
81 m_sock = make_shared<chronosync::Socket>(m_discoveryPrefix,
82 Name(),
83 ref(*m_face),
84 bind(&ChatroomDiscoveryBackend::processSyncUpdate,
85 this, _1));
86
87 // add an timer to refresh front end
88 if (m_refreshPanelId != nullptr) {
89 m_scheduler->cancelEvent(m_refreshPanelId);
90 }
91 m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
92 [this] { sendChatroomList(); });
93}
94
95void
96ChatroomDiscoveryBackend::close()
97{
98 m_scheduler->cancelAllEvents();
99 m_refreshPanelId.reset();
100 m_chatroomList.clear();
101 m_sock.reset();
102}
103
104void
105ChatroomDiscoveryBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
106{
107 if (updates.empty()) {
108 return;
109 }
110 for (const auto& update : updates) {
111 m_sock->fetchData(update.session, update.high,
112 bind(&ChatroomDiscoveryBackend::processChatroomData, this, _1), 2);
113 }
114}
115
116void
117ChatroomDiscoveryBackend::processChatroomData(const ndn::shared_ptr<const ndn::Data>& data)
118{
119 // extract chatroom name by get(-3)
120 Name::Component chatroomName = data->getName().get(-3);
121 auto it = m_chatroomList.find(chatroomName);
122 if (it == m_chatroomList.end()) {
123 m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
124 m_chatroomList[chatroomName].count = 0;
125 m_chatroomList[chatroomName].isPrint = false;
126 m_chatroomList[chatroomName].isParticipant = false;
127 m_chatroomList[chatroomName].isManager = false;
128 it = m_chatroomList.find(chatroomName);
129 }
130 // If the user is the manager of this chatroom, he should not receive any data from this chatroom
131 if (it->second.isManager) {
132 if (it->second.count < MAXIMUM_COUNT) {
133 it->second.count++;
134 return;
135 }
136 else {
137 it->second.count = 0;
138 if (m_routableUserDiscoveryPrefix < data->getName()) {
139 // when two managers exist, the one with "smaller" name take the control
140 sendUpdate(chatroomName);
141 return;
142 }
143 else {
144 if (it->second.helloTimeoutEventId != nullptr) {
145 m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
146 }
147 it->second.helloTimeoutEventId = nullptr;
148 it->second.isManager = false;
149 }
150
151 }
152 }
153
154 else if (it->second.isParticipant) {
155 if (it->second.localChatroomTimeoutEventId != nullptr)
156 m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
157
158 // If a user start a random timer it means that he think his own chatroom is not alive
159 // But when he receive some packet, it means that this chatroom is alive, so he can
160 // cancel the timer
161 if (it->second.managerSelectionTimeoutEventId != nullptr)
162 m_scheduler->cancelEvent(it->second.managerSelectionTimeoutEventId);
163 it->second.managerSelectionTimeoutEventId = nullptr;
164
165 it->second.localChatroomTimeoutEventId =
166 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
167 bind(&ChatroomDiscoveryBackend::localSessionTimeout,
168 this, chatroomName));
169 }
170 else {
171 if (!data->getContent().empty()) {
172 ChatroomInfo chatroom;
173 chatroom.wireDecode(data->getContent().blockFromValue());
174 it->second.info = chatroom;
175 }
176
177 if (it->second.remoteChatroomTimeoutEventId != nullptr)
178 m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
179
180 it->second.remoteChatroomTimeoutEventId =
181 m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
182 bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
183 this, chatroomName));
184 }
185 // if this is a chatroom that haven't been print on the discovery panel, print it.
186 if(!it->second.isPrint) {
187 sendChatroomList();
188 it->second.isPrint = true;
189 }
190}
191
192void
193ChatroomDiscoveryBackend::localSessionTimeout(const Name::Component& chatroomName)
194{
195 auto it = m_chatroomList.find(chatroomName);
196 if (it == m_chatroomList.end() || it->second.isParticipant == false)
197 return;
198 it->second.managerSelectionTimeoutEventId =
199 m_scheduler->scheduleEvent(time::milliseconds(m_rangeUniformRandom()),
200 bind(&ChatroomDiscoveryBackend::randomSessionTimeout,
201 this, chatroomName));
202}
203
204void
205ChatroomDiscoveryBackend::remoteSessionTimeout(const Name::Component& chatroomName)
206{
207 m_chatroomList.erase(chatroomName);
208}
209
210void
211ChatroomDiscoveryBackend::randomSessionTimeout(const Name::Component& chatroomName)
212{
213 Name prefix = m_routableUserDiscoveryPrefix;
214 prefix.append(chatroomName);
215 m_sock->addSyncNode(prefix);
216
217 emit chatroomInfoRequest(chatroomName.toUri(), true);
218}
219
220void
221ChatroomDiscoveryBackend::sendUpdate(const Name::Component& chatroomName)
222{
223 auto it = m_chatroomList.find(chatroomName);
224 if (it != m_chatroomList.end() && it->second.isManager) {
225 ndn::Block buf = it->second.info.wireEncode();
226
227 if (it->second.helloTimeoutEventId != nullptr) {
228 m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
229 }
230
231 m_sock->publishData(buf.wire(), buf.size(), FRESHNESS_PERIOD, it->second.chatroomPrefix);
232
233 it->second.helloTimeoutEventId =
234 m_scheduler->scheduleEvent(HELLO_INTERVAL,
235 bind(&ChatroomDiscoveryBackend::sendUpdate, this, chatroomName));
236 // if this is a chatroom that haven't been print on the discovery panel, print it.
237 if(!it->second.isPrint) {
238 sendChatroomList();
239 it->second.isPrint = true;
240 }
241 }
242}
243
244void
245ChatroomDiscoveryBackend::updatePrefixes()
246{
247 Name temp;
248 if (m_routingPrefix.isPrefixOf(m_userDiscoveryPrefix))
249 temp = m_userDiscoveryPrefix;
250 else
251 temp.append(m_routingPrefix)
252 .append(ROUTING_HINT_SEPARATOR, 2)
253 .append(m_userDiscoveryPrefix);
254
255 Name routableIdentity = m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET);
256 for (auto& chatroom : m_chatroomList) {
257 if (chatroom.second.isParticipant) {
258 chatroom.second.info.removeParticipant(routableIdentity);
259 chatroom.second.info.addParticipant(temp.getPrefix(IDENTITY_OFFSET));
260 }
261 }
262 m_routableUserDiscoveryPrefix = temp;
263}
264
265void
266ChatroomDiscoveryBackend::updateRoutingPrefix(const QString& routingPrefix)
267{
268 Name newRoutingPrefix(routingPrefix.toStdString());
269 if (!newRoutingPrefix.empty() && newRoutingPrefix != m_routingPrefix) {
270 // Update localPrefix
271 m_routingPrefix = newRoutingPrefix;
272
273 updatePrefixes();
274
275 m_mutex.lock();
276 m_shouldResume = true;
277 m_mutex.unlock();
278
279 close();
280
281 m_face->getIoService().stop();
282 }
283}
284
285void
286ChatroomDiscoveryBackend::onEraseInRoster(ndn::Name sessionPrefix,
287 ndn::Name::Component chatroomName)
288{
289 auto it = m_chatroomList.find(chatroomName);
290 if (it != m_chatroomList.end()) {
291 it->second.info.removeParticipant(sessionPrefix);
292 if (it->second.info.getParticipants().size() == 0) {
293 // Before deleting the chatroom, cancel the hello event timer if exist
294 if (it->second.helloTimeoutEventId != nullptr)
295 m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
296
297 m_chatroomList.erase(chatroomName);
298 Name prefix = sessionPrefix;
299 prefix.append("CHRONOCHAT-DISCOVERYDATA").append(chatroomName);
300 m_sock->removeSyncNode(prefix);
301 sendChatroomList();
302 return;
303 }
304
305 if (sessionPrefix.isPrefixOf(m_routableUserDiscoveryPrefix)) {
306 it->second.isParticipant = false;
307 it->second.isManager = false;
308 it->second.isPrint = false;
309 it->second.count = 0;
310 if (it->second.helloTimeoutEventId != nullptr)
311 m_scheduler->cancelEvent(it->second.helloTimeoutEventId);
312 it->second.helloTimeoutEventId = nullptr;
313
314 if (it->second.localChatroomTimeoutEventId != nullptr)
315 m_scheduler->cancelEvent(it->second.localChatroomTimeoutEventId);
316 it->second.localChatroomTimeoutEventId = nullptr;
317
318 it->second.remoteChatroomTimeoutEventId =
319 m_scheduler->scheduleEvent(HELLO_INTERVAL * 5,
320 bind(&ChatroomDiscoveryBackend::remoteSessionTimeout,
321 this, chatroomName));
322 }
323
324 if (it->second.isManager) {
325 sendUpdate(chatroomName);
326 }
327 }
328}
329
330void
331ChatroomDiscoveryBackend::onAddInRoster(ndn::Name sessionPrefix,
332 ndn::Name::Component chatroomName)
333{
334 auto it = m_chatroomList.find(chatroomName);
335 if (it != m_chatroomList.end()) {
336 it->second.info.addParticipant(sessionPrefix);
337 if (it->second.isManager)
338 sendUpdate(chatroomName);
339 }
340 else {
341 m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
342 m_chatroomList[chatroomName].info.setName(chatroomName);
343 m_chatroomList[chatroomName].info.addParticipant(sessionPrefix);
344 }
345}
346
347void
348ChatroomDiscoveryBackend::onNewChatroomForDiscovery(Name::Component chatroomName)
349{
350 Name newPrefix = m_routableUserDiscoveryPrefix;
351 newPrefix.append(chatroomName);
352 auto it = m_chatroomList.find(chatroomName);
353 if (it == m_chatroomList.end()) {
354 m_chatroomList[chatroomName].chatroomPrefix = newPrefix;
355 m_chatroomList[chatroomName].isParticipant = true;
356 m_chatroomList[chatroomName].isManager = false;
357 m_chatroomList[chatroomName].count = 0;
358 m_chatroomList[chatroomName].isPrint = false;
359 m_scheduler->scheduleEvent(time::milliseconds(600),
360 bind(&ChatroomDiscoveryBackend::randomSessionTimeout, this,
361 chatroomName));
362 }
363 else {
364 // Entering an existing chatroom
365 it->second.isParticipant = true;
366 it->second.isManager = false;
367 it->second.chatroomPrefix = newPrefix;
368
369 if (it->second.remoteChatroomTimeoutEventId != nullptr)
370 m_scheduler->cancelEvent(it->second.remoteChatroomTimeoutEventId);
371 it->second.isPrint = false;
372 it->second.remoteChatroomTimeoutEventId = nullptr;
373
374 it->second.localChatroomTimeoutEventId =
375 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
376 bind(&ChatroomDiscoveryBackend::localSessionTimeout,
377 this, chatroomName));
378 emit chatroomInfoRequest(chatroomName.toUri(), false);
379 }
380}
381
382void
383ChatroomDiscoveryBackend::onRespondChatroomInfoRequest(ChatroomInfo chatroomInfo, bool isManager)
384{
385 if (isManager)
386 chatroomInfo.setManager(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET));
387 Name::Component chatroomName = chatroomInfo.getName();
388 m_chatroomList[chatroomName].chatroomName = chatroomName.toUri();
389 m_chatroomList[chatroomName].isManager = isManager;
390 m_chatroomList[chatroomName].count = 0;
391 m_chatroomList[chatroomName].info = chatroomInfo;
392 sendChatroomList();
393 onAddInRoster(m_routableUserDiscoveryPrefix.getPrefix(IDENTITY_OFFSET), chatroomName);
394}
395
396void
397ChatroomDiscoveryBackend::onIdentityUpdated(const QString& identity)
398{
399 m_chatroomList.clear();
400 m_identity = Name(identity.toStdString());
401 m_userDiscoveryPrefix.clear();
402 m_userDiscoveryPrefix.append(m_identity).append("CHRONOCHAT-DISCOVERYDATA");
403 updatePrefixes();
404
405 m_mutex.lock();
406 m_shouldResume = true;
407 m_mutex.unlock();
408
409 close();
410
411 m_face->getIoService().stop();
412}
413
414void
415ChatroomDiscoveryBackend::sendChatroomList()
416{
417 QStringList chatroomList;
418 for (const auto& chatroom : m_chatroomList) {
419 chatroomList << QString::fromStdString(chatroom.first.toUri());
420 }
421
422 emit chatroomListReady(chatroomList);
423 if (m_refreshPanelId != nullptr) {
424 m_scheduler->cancelEvent(m_refreshPanelId);
425 }
426 m_refreshPanelId = m_scheduler->scheduleEvent(REFRESH_INTERVAL,
427 [this] { sendChatroomList(); });
428}
429
430void
431ChatroomDiscoveryBackend::onWaitForChatroomInfo(const QString& chatroomName)
432{
433 auto chatroom = m_chatroomList.find(Name::Component(chatroomName.toStdString()));
434 if (chatroom != m_chatroomList.end())
435 emit chatroomInfoReady(chatroom->second.info);
436}
437
438void
439ChatroomDiscoveryBackend::shutdown()
440{
441 m_mutex.lock();
442 m_shouldResume = false;
443 m_mutex.unlock();
444
445 close();
446
447 m_face->getIoService().stop();
448}
449
450
451} // namespace chronochat
452
453#if WAF
454#include "chatroom-discovery-backend.moc"
455#endif