blob: 6cd24ad10732dcbe5e29dc33bc5073c635d1b969 [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);
Qiuhan Dingba3e57a2015-01-08 19:07:39 -080028static const ndn::Name::Component ROUTING_HINT_SEPARATOR =
29 ndn::name::Component::fromEscapedString("%F0%2E");
Qiuhan Ding43c8e162015-02-02 15:16:48 -080030static const int IDENTITY_OFFSET = -3;
Yingdi Yud45777b2014-10-16 23:54:11 -070031
32ChatDialogBackend::ChatDialogBackend(const Name& chatroomPrefix,
33 const Name& userChatPrefix,
34 const Name& routingPrefix,
35 const std::string& chatroomName,
36 const std::string& nick,
Yingdi Yu45da92a2015-02-02 13:17:03 -080037 const Name& signingId,
Yingdi Yud45777b2014-10-16 23:54:11 -070038 QObject* parent)
39 : QThread(parent)
40 , m_localRoutingPrefix(routingPrefix)
41 , m_chatroomPrefix(chatroomPrefix)
42 , m_userChatPrefix(userChatPrefix)
43 , m_chatroomName(chatroomName)
44 , m_nick(nick)
Yingdi Yu45da92a2015-02-02 13:17:03 -080045 , m_signingId(signingId)
Yingdi Yud45777b2014-10-16 23:54:11 -070046{
47 updatePrefixes();
48}
49
50
51ChatDialogBackend::~ChatDialogBackend()
52{
53}
54
55// protected methods:
56void
57ChatDialogBackend::run()
58{
Yingdi Yu4647f022015-02-01 00:26:38 -080059 bool shouldResume = false;
60 do {
61 initializeSync();
Yingdi Yud45777b2014-10-16 23:54:11 -070062
Yingdi Yu4647f022015-02-01 00:26:38 -080063 if (m_face == nullptr)
64 break;
65
66 m_face->getIoService().run();
67
Qiuhan Dingf22c41b2015-03-11 13:19:01 -070068 {
69 std::lock_guard<std::mutex>lock(m_mutex);
70 shouldResume = m_shouldResume;
71 m_shouldResume = false;
72 }
Yingdi Yu4647f022015-02-01 00:26:38 -080073
74 } while (shouldResume);
Yingdi Yud45777b2014-10-16 23:54:11 -070075
76 std::cerr << "Bye!" << std::endl;
77}
78
79// private methods:
80void
81ChatDialogBackend::initializeSync()
82{
Yingdi Yu4647f022015-02-01 00:26:38 -080083 BOOST_ASSERT(m_sock == nullptr);
Yingdi Yud45777b2014-10-16 23:54:11 -070084
Yingdi Yu45da92a2015-02-02 13:17:03 -080085 m_face = make_shared<ndn::Face>();
Yingdi Yu4647f022015-02-01 00:26:38 -080086 m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
Yingdi Yud45777b2014-10-16 23:54:11 -070087
Yingdi Yu45da92a2015-02-02 13:17:03 -080088 // initialize validator
89 shared_ptr<ndn::IdentityCertificate> anchor = loadTrustAnchor();
90
91 if (static_cast<bool>(anchor)) {
92 shared_ptr<ndn::ValidatorRegex> validator =
93 make_shared<ndn::ValidatorRegex>(m_face.get()); // TODO: Change to Face*
94 validator->addDataVerificationRule(
95 make_shared<ndn::SecRuleRelative>("^<>*<%F0.>(<>*)$",
96 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
97 ">", "\\1", "\\1\\2", true));
98 validator->addDataVerificationRule(
99 make_shared<ndn::SecRuleRelative>("(<>*)$",
100 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
101 ">", "\\1", "\\1\\2", true));
102 validator->addTrustAnchor(anchor);
103
104 m_validator = validator;
105 }
106 else
107 m_validator = shared_ptr<ndn::Validator>();
108
109
Yingdi Yud45777b2014-10-16 23:54:11 -0700110 // create a new SyncSocket
111 m_sock = make_shared<chronosync::Socket>(m_chatroomPrefix,
112 m_routableUserChatPrefix,
Yingdi Yu4647f022015-02-01 00:26:38 -0800113 ref(*m_face),
Yingdi Yu45da92a2015-02-02 13:17:03 -0800114 bind(&ChatDialogBackend::processSyncUpdate, this, _1),
115 m_signingId,
116 m_validator);
Yingdi Yud45777b2014-10-16 23:54:11 -0700117
118 // schedule a new join event
Yingdi Yu4647f022015-02-01 00:26:38 -0800119 m_scheduler->scheduleEvent(time::milliseconds(600),
120 bind(&ChatDialogBackend::sendJoin, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700121
122 // cancel existing hello event if it exists
Yingdi Yu4647f022015-02-01 00:26:38 -0800123 if (m_helloEventId != nullptr) {
124 m_scheduler->cancelEvent(m_helloEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700125 m_helloEventId.reset();
126 }
127}
128
Yingdi Yu45da92a2015-02-02 13:17:03 -0800129class IoDeviceSource
130{
131public:
132 typedef char char_type;
133 typedef boost::iostreams::source_tag category;
134
135 explicit
136 IoDeviceSource(QIODevice& source)
137 : m_source(source)
138 {
139 }
140
141 std::streamsize
142 read(char* buffer, std::streamsize n)
143 {
144 return m_source.read(buffer, n);
145 }
146private:
147 QIODevice& m_source;
148};
149
150shared_ptr<ndn::IdentityCertificate>
151ChatDialogBackend::loadTrustAnchor()
152{
153 QFile anchorFile(":/security/anchor.cert");
154
155 if (!anchorFile.open(QIODevice::ReadOnly)) {
156 return {};
157 }
158
159 boost::iostreams::stream<IoDeviceSource> anchorFileStream(anchorFile);
160 return ndn::io::load<ndn::IdentityCertificate>(anchorFileStream);
161}
162
Yingdi Yud45777b2014-10-16 23:54:11 -0700163void
Yingdi Yu4647f022015-02-01 00:26:38 -0800164ChatDialogBackend::close()
165{
166 if (m_joined)
167 sendLeave();
168
169 usleep(100000);
170
171 m_scheduler->cancelAllEvents();
172 m_helloEventId.reset();
173 m_roster.clear();
Yingdi Yu45da92a2015-02-02 13:17:03 -0800174 m_validator.reset();
Yingdi Yu4647f022015-02-01 00:26:38 -0800175 m_sock.reset();
176}
177
178void
Yingdi Yud45777b2014-10-16 23:54:11 -0700179ChatDialogBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
180{
181 _LOG_DEBUG("<<< processing Tree Update");
182
183 if (updates.empty()) {
184 return;
185 }
186
187 std::vector<NodeInfo> nodeInfos;
188
189
Yingdi Yu1cc45d92015-02-09 14:19:54 -0800190 for (size_t i = 0; i < updates.size(); i++) {
Yingdi Yud45777b2014-10-16 23:54:11 -0700191 // update roster
192 if (m_roster.find(updates[i].session) == m_roster.end()) {
193 m_roster[updates[i].session].sessionPrefix = updates[i].session;
194 m_roster[updates[i].session].hasNick = false;
195 }
196
197 // fetch missing chat data
198 if (updates[i].high - updates[i].low < 3) {
199 for (chronosync::SeqNo seq = updates[i].low; seq <= updates[i].high; ++seq) {
200 m_sock->fetchData(updates[i].session, seq,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800201 [this] (const shared_ptr<const ndn::Data>& data) {
202 this->processChatData(data, true, true);
203 },
204 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
205 this->processChatData(data, true, false);
206 },
207 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700208 2);
209 _LOG_DEBUG("<<< Fetching " << updates[i].session << "/" << seq);
210 }
211 }
212 else {
213 // There are too many msgs to fetch, let's just fetch the latest one
214 m_sock->fetchData(updates[i].session, updates[i].high,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800215 [this] (const shared_ptr<const ndn::Data>& data) {
216 this->processChatData(data, false, true);
217 },
218 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
219 this->processChatData(data, false, false);
220 },
221 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700222 2);
223 }
224
Yingdi Yud45777b2014-10-16 23:54:11 -0700225 }
226
227 // reflect the changes on GUI
228 emit syncTreeUpdated(nodeInfos,
229 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
230}
231
232void
Yingdi Yu45da92a2015-02-02 13:17:03 -0800233ChatDialogBackend::processChatData(const ndn::shared_ptr<const ndn::Data>& data,
234 bool needDisplay,
235 bool isValidated)
Yingdi Yud45777b2014-10-16 23:54:11 -0700236{
237 SyncDemo::ChatMessage msg;
238
239 if (!msg.ParseFromArray(data->getContent().value(), data->getContent().value_size())) {
240 _LOG_DEBUG("Errrrr.. Can not parse msg with name: " <<
241 data->getName() << ". what is happening?");
242 // nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
243 msg.set_from("inconnu");
244 msg.set_type(SyncDemo::ChatMessage::OTHER);
245 return;
246 }
247
248 Name remoteSessionPrefix = data->getName().getPrefix(-1);
249
250 if (msg.type() == SyncDemo::ChatMessage::LEAVE) {
251 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
252
253 if (it != m_roster.end()) {
254 // cancel timeout event
255 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800256 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700257
258 // notify frontend to remove the remote session (node)
259 emit sessionRemoved(QString::fromStdString(remoteSessionPrefix.toUri()),
260 QString::fromStdString(msg.from()),
261 msg.timestamp());
262
263 // remove roster entry
264 m_roster.erase(remoteSessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800265
266 emit eraseInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
267 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700268 }
269 }
270 else {
271 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
272
273 if (it == m_roster.end()) {
274 // Should not happen
275 BOOST_ASSERT(false);
276 }
277
Qiuhan Ding7a4e7ef2015-02-03 20:25:50 -0800278 uint64_t seqNo = data->getName().get(-1).toNumber();
Yingdi Yud45777b2014-10-16 23:54:11 -0700279
280 // If a timeout event has been scheduled, cancel it.
281 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800282 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700283
284 // (Re)schedule another timeout event after 3 HELLO_INTERVAL;
285 it->second.timeoutEventId =
Yingdi Yu4647f022015-02-01 00:26:38 -0800286 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
287 bind(&ChatDialogBackend::remoteSessionTimeout,
288 this, remoteSessionPrefix));
Yingdi Yud45777b2014-10-16 23:54:11 -0700289
290 // If chat message, notify the frontend
Yingdi Yu45da92a2015-02-02 13:17:03 -0800291 if (msg.type() == SyncDemo::ChatMessage::CHAT) {
292 if (isValidated)
293 emit chatMessageReceived(QString::fromStdString(msg.from()),
294 QString::fromStdString(msg.data()),
295 msg.timestamp());
296 else
297 emit chatMessageReceived(QString::fromStdString(msg.from() + " (Unverified)"),
298 QString::fromStdString(msg.data()),
299 msg.timestamp());
300 }
Yingdi Yud45777b2014-10-16 23:54:11 -0700301
302 // Notify frontend to plot notification on DigestTree.
Qiuhan Ding7a4e7ef2015-02-03 20:25:50 -0800303
304 // If we haven't got any message from this session yet.
305 if (m_roster[remoteSessionPrefix].hasNick == false) {
306 m_roster[remoteSessionPrefix].userNick = msg.from();
307 m_roster[remoteSessionPrefix].hasNick = true;
308
309 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()),
310 QString::fromStdString(msg.from()),
311 seqNo,
312 msg.timestamp(),
313 true);
314
315 emit addInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
316 Name::Component(m_chatroomName));
317 }
318 else
319 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()),
320 QString::fromStdString(msg.from()),
321 seqNo,
322 msg.timestamp(),
323 false);
Yingdi Yud45777b2014-10-16 23:54:11 -0700324 }
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())));
Qiuhan Ding7a4e7ef2015-02-03 20:25:50 -0800370
371 emit messageReceived(QString::fromStdString(sessionName.toUri()),
372 QString::fromStdString(msg.from()),
373 nextSequence,
374 msg.timestamp(),
375 msg.type() == SyncDemo::ChatMessage::JOIN);
Yingdi Yud45777b2014-10-16 23:54:11 -0700376}
377
378void
379ChatDialogBackend::sendJoin()
380{
381 m_joined = true;
382
383 SyncDemo::ChatMessage msg;
384 prepareControlMessage(msg, SyncDemo::ChatMessage::JOIN);
385 sendMsg(msg);
386
Yingdi Yu4647f022015-02-01 00:26:38 -0800387 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
388 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700389}
390
391void
392ChatDialogBackend::sendHello()
393{
394 SyncDemo::ChatMessage msg;
395 prepareControlMessage(msg, SyncDemo::ChatMessage::HELLO);
396 sendMsg(msg);
397
Yingdi Yu4647f022015-02-01 00:26:38 -0800398 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
399 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700400}
401
402void
403ChatDialogBackend::sendLeave()
404{
405 SyncDemo::ChatMessage msg;
406 prepareControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
407 sendMsg(msg);
408
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800409 // get my own identity with routable prefix by getPrefix(-2)
410 emit eraseInRoster(m_routableUserChatPrefix.getPrefix(-2),
411 Name::Component(m_chatroomName));
412
Yingdi Yud45777b2014-10-16 23:54:11 -0700413 usleep(5000);
414 m_joined = false;
415}
416
417void
418ChatDialogBackend::prepareControlMessage(SyncDemo::ChatMessage& msg,
419 SyncDemo::ChatMessage::ChatMessageType type)
420{
421 msg.set_from(m_nick);
422 msg.set_to(m_chatroomName);
423 int32_t seconds =
424 static_cast<int32_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
425 msg.set_timestamp(seconds);
426 msg.set_type(type);
427}
428
429void
430ChatDialogBackend::prepareChatMessage(const QString& text,
431 time_t timestamp,
432 SyncDemo::ChatMessage &msg)
433{
434 msg.set_from(m_nick);
435 msg.set_to(m_chatroomName);
436 msg.set_data(text.toStdString());
437 msg.set_timestamp(timestamp);
438 msg.set_type(SyncDemo::ChatMessage::CHAT);
439}
440
441void
442ChatDialogBackend::updatePrefixes()
443{
444 m_routableUserChatPrefix.clear();
445
446 if (m_localRoutingPrefix.isPrefixOf(m_userChatPrefix))
447 m_routableUserChatPrefix = m_userChatPrefix;
448 else
449 m_routableUserChatPrefix.append(m_localRoutingPrefix)
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800450 .append(ROUTING_HINT_SEPARATOR)
Yingdi Yud45777b2014-10-16 23:54:11 -0700451 .append(m_userChatPrefix);
452
453 emit chatPrefixChanged(m_routableUserChatPrefix);
454}
455
456std::string
457ChatDialogBackend::getHexEncodedDigest(ndn::ConstBufferPtr digest)
458{
459 std::stringstream os;
460
461 CryptoPP::StringSource(digest->buf(), digest->size(), true,
462 new CryptoPP::HexEncoder(new CryptoPP::FileSink(os), false));
463 return os.str();
464}
465
466
467// public slots:
468void
469ChatDialogBackend::sendChatMessage(QString text, time_t timestamp)
470{
471 SyncDemo::ChatMessage msg;
472 prepareChatMessage(text, timestamp, msg);
473 sendMsg(msg);
474
475 emit chatMessageReceived(QString::fromStdString(msg.from()),
476 QString::fromStdString(msg.data()),
477 msg.timestamp());
478}
479
480void
481ChatDialogBackend::updateRoutingPrefix(const QString& localRoutingPrefix)
482{
483 Name newLocalRoutingPrefix(localRoutingPrefix.toStdString());
484
485 if (!newLocalRoutingPrefix.empty() && newLocalRoutingPrefix != m_localRoutingPrefix) {
486 // Update localPrefix
487 m_localRoutingPrefix = newLocalRoutingPrefix;
488
Qiuhan Dingf22c41b2015-03-11 13:19:01 -0700489 {
490 std::lock_guard<std::mutex>lock(m_mutex);
491 m_shouldResume = true;
492 }
Yingdi Yu4647f022015-02-01 00:26:38 -0800493
494 close();
495
Qiuhan Ding112ee482015-03-11 11:54:11 -0700496 updatePrefixes();
497
Yingdi Yu4647f022015-02-01 00:26:38 -0800498 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700499 }
500}
501
502void
503ChatDialogBackend::shutdown()
504{
Qiuhan Dingf22c41b2015-03-11 13:19:01 -0700505 {
506 std::lock_guard<std::mutex>lock(m_mutex);
507 m_shouldResume = false;
508 }
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700509
Yingdi Yu4647f022015-02-01 00:26:38 -0800510 close();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700511
Yingdi Yu4647f022015-02-01 00:26:38 -0800512 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700513}
514
Yingdi Yueb692ac2015-02-10 18:46:18 -0800515} // namespace chronochat
Yingdi Yud45777b2014-10-16 23:54:11 -0700516
517#if WAF
518#include "chat-dialog-backend.moc"
519// #include "chat-dialog-backend.cpp.moc"
520#endif