blob: 1ea25ac9892a39f3d67aff5c43e30abb04e11c4d [file] [log] [blame]
Yingdi Yud45777b2014-10-16 23:54:11 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2013, Regents of the University of California
4 * Yingdi Yu
5 *
6 * BSD license, See the LICENSE file for more information
7 *
8 * Author: Yingdi Yu <yingdi@cs.ucla.edu>
9 */
10
11#include "chat-dialog-backend.hpp"
12
Yingdi Yu45da92a2015-02-02 13:17:03 -080013#include <QFile>
14
Yingdi Yud45777b2014-10-16 23:54:11 -070015#ifndef Q_MOC_RUN
16#include <ndn-cxx/util/io.hpp>
Yingdi Yu45da92a2015-02-02 13:17:03 -080017#include <ndn-cxx/security/validator-regex.hpp>
Yingdi Yud45777b2014-10-16 23:54:11 -070018#include "logging.h"
19#endif
20
21
22INIT_LOGGER("ChatDialogBackend");
23
Yingdi Yueb692ac2015-02-10 18:46:18 -080024namespace chronochat {
Yingdi Yud45777b2014-10-16 23:54:11 -070025
26static const time::milliseconds FRESHNESS_PERIOD(60000);
27static const time::seconds HELLO_INTERVAL(60);
28static const uint8_t ROUTING_HINT_SEPARATOR[2] = {0xF0, 0x2E}; // %F0.
Qiuhan Ding43c8e162015-02-02 15:16:48 -080029static const int IDENTITY_OFFSET = -3;
Yingdi Yud45777b2014-10-16 23:54:11 -070030
31ChatDialogBackend::ChatDialogBackend(const Name& chatroomPrefix,
32 const Name& userChatPrefix,
33 const Name& routingPrefix,
34 const std::string& chatroomName,
35 const std::string& nick,
Yingdi Yu45da92a2015-02-02 13:17:03 -080036 const Name& signingId,
Yingdi Yud45777b2014-10-16 23:54:11 -070037 QObject* parent)
38 : QThread(parent)
39 , m_localRoutingPrefix(routingPrefix)
40 , m_chatroomPrefix(chatroomPrefix)
41 , m_userChatPrefix(userChatPrefix)
42 , m_chatroomName(chatroomName)
43 , m_nick(nick)
Yingdi Yu45da92a2015-02-02 13:17:03 -080044 , m_signingId(signingId)
Yingdi Yud45777b2014-10-16 23:54:11 -070045{
46 updatePrefixes();
47}
48
49
50ChatDialogBackend::~ChatDialogBackend()
51{
52}
53
54// protected methods:
55void
56ChatDialogBackend::run()
57{
Yingdi Yu4647f022015-02-01 00:26:38 -080058 bool shouldResume = false;
59 do {
60 initializeSync();
Yingdi Yud45777b2014-10-16 23:54:11 -070061
Yingdi Yu4647f022015-02-01 00:26:38 -080062 if (m_face == nullptr)
63 break;
64
65 m_face->getIoService().run();
66
67 m_mutex.lock();
68 shouldResume = m_shouldResume;
69 m_shouldResume = false;
70 m_mutex.unlock();
71
72 } while (shouldResume);
Yingdi Yud45777b2014-10-16 23:54:11 -070073
74 std::cerr << "Bye!" << std::endl;
75}
76
77// private methods:
78void
79ChatDialogBackend::initializeSync()
80{
Yingdi Yu4647f022015-02-01 00:26:38 -080081 BOOST_ASSERT(m_sock == nullptr);
Yingdi Yud45777b2014-10-16 23:54:11 -070082
Yingdi Yu45da92a2015-02-02 13:17:03 -080083 m_face = make_shared<ndn::Face>();
Yingdi Yu4647f022015-02-01 00:26:38 -080084 m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
Yingdi Yud45777b2014-10-16 23:54:11 -070085
Yingdi Yu45da92a2015-02-02 13:17:03 -080086 // initialize validator
87 shared_ptr<ndn::IdentityCertificate> anchor = loadTrustAnchor();
88
89 if (static_cast<bool>(anchor)) {
90 shared_ptr<ndn::ValidatorRegex> validator =
91 make_shared<ndn::ValidatorRegex>(m_face.get()); // TODO: Change to Face*
92 validator->addDataVerificationRule(
93 make_shared<ndn::SecRuleRelative>("^<>*<%F0.>(<>*)$",
94 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
95 ">", "\\1", "\\1\\2", true));
96 validator->addDataVerificationRule(
97 make_shared<ndn::SecRuleRelative>("(<>*)$",
98 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
99 ">", "\\1", "\\1\\2", true));
100 validator->addTrustAnchor(anchor);
101
102 m_validator = validator;
103 }
104 else
105 m_validator = shared_ptr<ndn::Validator>();
106
107
Yingdi Yud45777b2014-10-16 23:54:11 -0700108 // create a new SyncSocket
109 m_sock = make_shared<chronosync::Socket>(m_chatroomPrefix,
110 m_routableUserChatPrefix,
Yingdi Yu4647f022015-02-01 00:26:38 -0800111 ref(*m_face),
Yingdi Yu45da92a2015-02-02 13:17:03 -0800112 bind(&ChatDialogBackend::processSyncUpdate, this, _1),
113 m_signingId,
114 m_validator);
Yingdi Yud45777b2014-10-16 23:54:11 -0700115
116 // schedule a new join event
Yingdi Yu4647f022015-02-01 00:26:38 -0800117 m_scheduler->scheduleEvent(time::milliseconds(600),
118 bind(&ChatDialogBackend::sendJoin, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700119
120 // cancel existing hello event if it exists
Yingdi Yu4647f022015-02-01 00:26:38 -0800121 if (m_helloEventId != nullptr) {
122 m_scheduler->cancelEvent(m_helloEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700123 m_helloEventId.reset();
124 }
125}
126
Yingdi Yu45da92a2015-02-02 13:17:03 -0800127class IoDeviceSource
128{
129public:
130 typedef char char_type;
131 typedef boost::iostreams::source_tag category;
132
133 explicit
134 IoDeviceSource(QIODevice& source)
135 : m_source(source)
136 {
137 }
138
139 std::streamsize
140 read(char* buffer, std::streamsize n)
141 {
142 return m_source.read(buffer, n);
143 }
144private:
145 QIODevice& m_source;
146};
147
148shared_ptr<ndn::IdentityCertificate>
149ChatDialogBackend::loadTrustAnchor()
150{
151 QFile anchorFile(":/security/anchor.cert");
152
153 if (!anchorFile.open(QIODevice::ReadOnly)) {
154 return {};
155 }
156
157 boost::iostreams::stream<IoDeviceSource> anchorFileStream(anchorFile);
158 return ndn::io::load<ndn::IdentityCertificate>(anchorFileStream);
159}
160
Yingdi Yud45777b2014-10-16 23:54:11 -0700161void
Yingdi Yu4647f022015-02-01 00:26:38 -0800162ChatDialogBackend::close()
163{
164 if (m_joined)
165 sendLeave();
166
167 usleep(100000);
168
169 m_scheduler->cancelAllEvents();
170 m_helloEventId.reset();
171 m_roster.clear();
Yingdi Yu45da92a2015-02-02 13:17:03 -0800172 m_validator.reset();
Yingdi Yu4647f022015-02-01 00:26:38 -0800173 m_sock.reset();
174}
175
176void
Yingdi Yud45777b2014-10-16 23:54:11 -0700177ChatDialogBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
178{
179 _LOG_DEBUG("<<< processing Tree Update");
180
181 if (updates.empty()) {
182 return;
183 }
184
185 std::vector<NodeInfo> nodeInfos;
186
187
Yingdi Yu1cc45d92015-02-09 14:19:54 -0800188 for (size_t i = 0; i < updates.size(); i++) {
Yingdi Yud45777b2014-10-16 23:54:11 -0700189 // update roster
190 if (m_roster.find(updates[i].session) == m_roster.end()) {
191 m_roster[updates[i].session].sessionPrefix = updates[i].session;
192 m_roster[updates[i].session].hasNick = false;
193 }
194
195 // fetch missing chat data
196 if (updates[i].high - updates[i].low < 3) {
197 for (chronosync::SeqNo seq = updates[i].low; seq <= updates[i].high; ++seq) {
198 m_sock->fetchData(updates[i].session, seq,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800199 [this] (const shared_ptr<const ndn::Data>& data) {
200 this->processChatData(data, true, true);
201 },
202 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
203 this->processChatData(data, true, false);
204 },
205 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700206 2);
207 _LOG_DEBUG("<<< Fetching " << updates[i].session << "/" << seq);
208 }
209 }
210 else {
211 // There are too many msgs to fetch, let's just fetch the latest one
212 m_sock->fetchData(updates[i].session, updates[i].high,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800213 [this] (const shared_ptr<const ndn::Data>& data) {
214 this->processChatData(data, false, true);
215 },
216 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
217 this->processChatData(data, false, false);
218 },
219 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700220 2);
221 }
222
223 // prepare notification to frontend
224 NodeInfo nodeInfo;
225 nodeInfo.sessionPrefix = QString::fromStdString(updates[i].session.toUri());
226 nodeInfo.seqNo = updates[i].high;
227 nodeInfos.push_back(nodeInfo);
228 }
229
230 // reflect the changes on GUI
231 emit syncTreeUpdated(nodeInfos,
232 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
233}
234
235void
Yingdi Yu45da92a2015-02-02 13:17:03 -0800236ChatDialogBackend::processChatData(const ndn::shared_ptr<const ndn::Data>& data,
237 bool needDisplay,
238 bool isValidated)
Yingdi Yud45777b2014-10-16 23:54:11 -0700239{
240 SyncDemo::ChatMessage msg;
241
242 if (!msg.ParseFromArray(data->getContent().value(), data->getContent().value_size())) {
243 _LOG_DEBUG("Errrrr.. Can not parse msg with name: " <<
244 data->getName() << ". what is happening?");
245 // nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
246 msg.set_from("inconnu");
247 msg.set_type(SyncDemo::ChatMessage::OTHER);
248 return;
249 }
250
251 Name remoteSessionPrefix = data->getName().getPrefix(-1);
252
253 if (msg.type() == SyncDemo::ChatMessage::LEAVE) {
254 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
255
256 if (it != m_roster.end()) {
257 // cancel timeout event
258 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800259 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700260
261 // notify frontend to remove the remote session (node)
262 emit sessionRemoved(QString::fromStdString(remoteSessionPrefix.toUri()),
263 QString::fromStdString(msg.from()),
264 msg.timestamp());
265
266 // remove roster entry
267 m_roster.erase(remoteSessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800268
269 emit eraseInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
270 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700271 }
272 }
273 else {
274 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
275
276 if (it == m_roster.end()) {
277 // Should not happen
278 BOOST_ASSERT(false);
279 }
280
281 // If we haven't got any message from this session yet.
282 if (m_roster[remoteSessionPrefix].hasNick == false) {
283 m_roster[remoteSessionPrefix].userNick = msg.from();
284 m_roster[remoteSessionPrefix].hasNick = true;
285 emit sessionAdded(QString::fromStdString(remoteSessionPrefix.toUri()),
286 QString::fromStdString(msg.from()),
287 msg.timestamp());
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800288
289 emit addInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
290 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700291 }
292
293 // If we get a new nick for an existing session, update it.
294 if (m_roster[remoteSessionPrefix].userNick != msg.from()) {
295 m_roster[remoteSessionPrefix].userNick = msg.from();
296 emit nickUpdated(QString::fromStdString(remoteSessionPrefix.toUri()),
297 QString::fromStdString(msg.from()));
298 }
299
300 // If a timeout event has been scheduled, cancel it.
301 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800302 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700303
304 // (Re)schedule another timeout event after 3 HELLO_INTERVAL;
305 it->second.timeoutEventId =
Yingdi Yu4647f022015-02-01 00:26:38 -0800306 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
307 bind(&ChatDialogBackend::remoteSessionTimeout,
308 this, remoteSessionPrefix));
Yingdi Yud45777b2014-10-16 23:54:11 -0700309
310 // If chat message, notify the frontend
Yingdi Yu45da92a2015-02-02 13:17:03 -0800311 if (msg.type() == SyncDemo::ChatMessage::CHAT) {
312 if (isValidated)
313 emit chatMessageReceived(QString::fromStdString(msg.from()),
314 QString::fromStdString(msg.data()),
315 msg.timestamp());
316 else
317 emit chatMessageReceived(QString::fromStdString(msg.from() + " (Unverified)"),
318 QString::fromStdString(msg.data()),
319 msg.timestamp());
320 }
Yingdi Yud45777b2014-10-16 23:54:11 -0700321
322 // Notify frontend to plot notification on DigestTree.
323 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()));
324 }
325}
326
327void
328ChatDialogBackend::remoteSessionTimeout(const Name& sessionPrefix)
329{
330 time_t timestamp =
331 static_cast<time_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
332
333 // notify frontend
334 emit sessionRemoved(QString::fromStdString(sessionPrefix.toUri()),
335 QString::fromStdString(m_roster[sessionPrefix].userNick),
336 timestamp);
337
338 // remove roster entry
339 m_roster.erase(sessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800340
341 emit eraseInRoster(sessionPrefix.getPrefix(IDENTITY_OFFSET),
342 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700343}
344
345void
346ChatDialogBackend::sendMsg(SyncDemo::ChatMessage& msg)
347{
348 // send msg
349 ndn::OBufferStream os;
350 msg.SerializeToOstream(&os);
351
352 if (!msg.IsInitialized()) {
353 _LOG_DEBUG("Errrrr.. msg was not probally initialized " << __FILE__ <<
354 ":" << __LINE__ << ". what is happening?");
355 abort();
356 }
357
358 uint64_t nextSequence = m_sock->getLogic().getSeqNo() + 1;
359
360 m_sock->publishData(os.buf()->buf(), os.buf()->size(), FRESHNESS_PERIOD);
361
362 std::vector<NodeInfo> nodeInfos;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800363 Name sessionName = m_sock->getLogic().getSessionName();
364 NodeInfo nodeInfo = {QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700365 nextSequence};
366 nodeInfos.push_back(nodeInfo);
367
368 emit syncTreeUpdated(nodeInfos,
369 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
370}
371
372void
373ChatDialogBackend::sendJoin()
374{
375 m_joined = true;
376
377 SyncDemo::ChatMessage msg;
378 prepareControlMessage(msg, SyncDemo::ChatMessage::JOIN);
379 sendMsg(msg);
380
Yingdi Yu4647f022015-02-01 00:26:38 -0800381 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
382 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700383
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800384 Name sessionName = m_sock->getLogic().getSessionName();
385 emit sessionAdded(QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700386 QString::fromStdString(msg.from()),
387 msg.timestamp());
388}
389
390void
391ChatDialogBackend::sendHello()
392{
393 SyncDemo::ChatMessage msg;
394 prepareControlMessage(msg, SyncDemo::ChatMessage::HELLO);
395 sendMsg(msg);
396
Yingdi Yu4647f022015-02-01 00:26:38 -0800397 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
398 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700399}
400
401void
402ChatDialogBackend::sendLeave()
403{
404 SyncDemo::ChatMessage msg;
405 prepareControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
406 sendMsg(msg);
407
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800408 // get my own identity with routable prefix by getPrefix(-2)
409 emit eraseInRoster(m_routableUserChatPrefix.getPrefix(-2),
410 Name::Component(m_chatroomName));
411
Yingdi Yud45777b2014-10-16 23:54:11 -0700412 usleep(5000);
413 m_joined = false;
414}
415
416void
417ChatDialogBackend::prepareControlMessage(SyncDemo::ChatMessage& msg,
418 SyncDemo::ChatMessage::ChatMessageType type)
419{
420 msg.set_from(m_nick);
421 msg.set_to(m_chatroomName);
422 int32_t seconds =
423 static_cast<int32_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
424 msg.set_timestamp(seconds);
425 msg.set_type(type);
426}
427
428void
429ChatDialogBackend::prepareChatMessage(const QString& text,
430 time_t timestamp,
431 SyncDemo::ChatMessage &msg)
432{
433 msg.set_from(m_nick);
434 msg.set_to(m_chatroomName);
435 msg.set_data(text.toStdString());
436 msg.set_timestamp(timestamp);
437 msg.set_type(SyncDemo::ChatMessage::CHAT);
438}
439
440void
441ChatDialogBackend::updatePrefixes()
442{
443 m_routableUserChatPrefix.clear();
444
445 if (m_localRoutingPrefix.isPrefixOf(m_userChatPrefix))
446 m_routableUserChatPrefix = m_userChatPrefix;
447 else
448 m_routableUserChatPrefix.append(m_localRoutingPrefix)
449 .append(ROUTING_HINT_SEPARATOR, 2)
450 .append(m_userChatPrefix);
451
452 emit chatPrefixChanged(m_routableUserChatPrefix);
453}
454
455std::string
456ChatDialogBackend::getHexEncodedDigest(ndn::ConstBufferPtr digest)
457{
458 std::stringstream os;
459
460 CryptoPP::StringSource(digest->buf(), digest->size(), true,
461 new CryptoPP::HexEncoder(new CryptoPP::FileSink(os), false));
462 return os.str();
463}
464
465
466// public slots:
467void
468ChatDialogBackend::sendChatMessage(QString text, time_t timestamp)
469{
470 SyncDemo::ChatMessage msg;
471 prepareChatMessage(text, timestamp, msg);
472 sendMsg(msg);
473
474 emit chatMessageReceived(QString::fromStdString(msg.from()),
475 QString::fromStdString(msg.data()),
476 msg.timestamp());
477}
478
479void
480ChatDialogBackend::updateRoutingPrefix(const QString& localRoutingPrefix)
481{
482 Name newLocalRoutingPrefix(localRoutingPrefix.toStdString());
483
484 if (!newLocalRoutingPrefix.empty() && newLocalRoutingPrefix != m_localRoutingPrefix) {
485 // Update localPrefix
486 m_localRoutingPrefix = newLocalRoutingPrefix;
487
488 updatePrefixes();
489
Yingdi Yu4647f022015-02-01 00:26:38 -0800490 m_mutex.lock();
491 m_shouldResume = true;
492 m_mutex.unlock();
493
494 close();
495
496 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700497 }
498}
499
500void
501ChatDialogBackend::shutdown()
502{
Yingdi Yu4647f022015-02-01 00:26:38 -0800503 m_mutex.lock();
504 m_shouldResume = false;
505 m_mutex.unlock();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700506
Yingdi Yu4647f022015-02-01 00:26:38 -0800507 close();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700508
Yingdi Yu4647f022015-02-01 00:26:38 -0800509 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700510}
511
Yingdi Yueb692ac2015-02-10 18:46:18 -0800512} // namespace chronochat
Yingdi Yud45777b2014-10-16 23:54:11 -0700513
514#if WAF
515#include "chat-dialog-backend.moc"
516// #include "chat-dialog-backend.cpp.moc"
517#endif