blob: 216f4653919901bbb5cdf3d62bb990389373c0e9 [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
225 // prepare notification to frontend
226 NodeInfo nodeInfo;
227 nodeInfo.sessionPrefix = QString::fromStdString(updates[i].session.toUri());
228 nodeInfo.seqNo = updates[i].high;
229 nodeInfos.push_back(nodeInfo);
230 }
231
232 // reflect the changes on GUI
233 emit syncTreeUpdated(nodeInfos,
234 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
235}
236
237void
Yingdi Yu45da92a2015-02-02 13:17:03 -0800238ChatDialogBackend::processChatData(const ndn::shared_ptr<const ndn::Data>& data,
239 bool needDisplay,
240 bool isValidated)
Yingdi Yud45777b2014-10-16 23:54:11 -0700241{
242 SyncDemo::ChatMessage msg;
243
244 if (!msg.ParseFromArray(data->getContent().value(), data->getContent().value_size())) {
245 _LOG_DEBUG("Errrrr.. Can not parse msg with name: " <<
246 data->getName() << ". what is happening?");
247 // nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
248 msg.set_from("inconnu");
249 msg.set_type(SyncDemo::ChatMessage::OTHER);
250 return;
251 }
252
253 Name remoteSessionPrefix = data->getName().getPrefix(-1);
254
255 if (msg.type() == SyncDemo::ChatMessage::LEAVE) {
256 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
257
258 if (it != m_roster.end()) {
259 // cancel timeout event
260 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800261 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700262
263 // notify frontend to remove the remote session (node)
264 emit sessionRemoved(QString::fromStdString(remoteSessionPrefix.toUri()),
265 QString::fromStdString(msg.from()),
266 msg.timestamp());
267
268 // remove roster entry
269 m_roster.erase(remoteSessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800270
271 emit eraseInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
272 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700273 }
274 }
275 else {
276 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
277
278 if (it == m_roster.end()) {
279 // Should not happen
280 BOOST_ASSERT(false);
281 }
282
283 // If we haven't got any message from this session yet.
284 if (m_roster[remoteSessionPrefix].hasNick == false) {
285 m_roster[remoteSessionPrefix].userNick = msg.from();
286 m_roster[remoteSessionPrefix].hasNick = true;
287 emit sessionAdded(QString::fromStdString(remoteSessionPrefix.toUri()),
288 QString::fromStdString(msg.from()),
289 msg.timestamp());
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800290
291 emit addInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
292 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700293 }
294
295 // If we get a new nick for an existing session, update it.
296 if (m_roster[remoteSessionPrefix].userNick != msg.from()) {
297 m_roster[remoteSessionPrefix].userNick = msg.from();
298 emit nickUpdated(QString::fromStdString(remoteSessionPrefix.toUri()),
299 QString::fromStdString(msg.from()));
300 }
301
302 // If a timeout event has been scheduled, cancel it.
303 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800304 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700305
306 // (Re)schedule another timeout event after 3 HELLO_INTERVAL;
307 it->second.timeoutEventId =
Yingdi Yu4647f022015-02-01 00:26:38 -0800308 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
309 bind(&ChatDialogBackend::remoteSessionTimeout,
310 this, remoteSessionPrefix));
Yingdi Yud45777b2014-10-16 23:54:11 -0700311
312 // If chat message, notify the frontend
Yingdi Yu45da92a2015-02-02 13:17:03 -0800313 if (msg.type() == SyncDemo::ChatMessage::CHAT) {
314 if (isValidated)
315 emit chatMessageReceived(QString::fromStdString(msg.from()),
316 QString::fromStdString(msg.data()),
317 msg.timestamp());
318 else
319 emit chatMessageReceived(QString::fromStdString(msg.from() + " (Unverified)"),
320 QString::fromStdString(msg.data()),
321 msg.timestamp());
322 }
Yingdi Yud45777b2014-10-16 23:54:11 -0700323
324 // Notify frontend to plot notification on DigestTree.
325 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()));
326 }
327}
328
329void
330ChatDialogBackend::remoteSessionTimeout(const Name& sessionPrefix)
331{
332 time_t timestamp =
333 static_cast<time_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
334
335 // notify frontend
336 emit sessionRemoved(QString::fromStdString(sessionPrefix.toUri()),
337 QString::fromStdString(m_roster[sessionPrefix].userNick),
338 timestamp);
339
340 // remove roster entry
341 m_roster.erase(sessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800342
343 emit eraseInRoster(sessionPrefix.getPrefix(IDENTITY_OFFSET),
344 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700345}
346
347void
348ChatDialogBackend::sendMsg(SyncDemo::ChatMessage& msg)
349{
350 // send msg
351 ndn::OBufferStream os;
352 msg.SerializeToOstream(&os);
353
354 if (!msg.IsInitialized()) {
355 _LOG_DEBUG("Errrrr.. msg was not probally initialized " << __FILE__ <<
356 ":" << __LINE__ << ". what is happening?");
357 abort();
358 }
359
360 uint64_t nextSequence = m_sock->getLogic().getSeqNo() + 1;
361
362 m_sock->publishData(os.buf()->buf(), os.buf()->size(), FRESHNESS_PERIOD);
363
364 std::vector<NodeInfo> nodeInfos;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800365 Name sessionName = m_sock->getLogic().getSessionName();
366 NodeInfo nodeInfo = {QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700367 nextSequence};
368 nodeInfos.push_back(nodeInfo);
369
370 emit syncTreeUpdated(nodeInfos,
371 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
372}
373
374void
375ChatDialogBackend::sendJoin()
376{
377 m_joined = true;
378
379 SyncDemo::ChatMessage msg;
380 prepareControlMessage(msg, SyncDemo::ChatMessage::JOIN);
381 sendMsg(msg);
382
Yingdi Yu4647f022015-02-01 00:26:38 -0800383 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
384 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700385
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800386 Name sessionName = m_sock->getLogic().getSessionName();
387 emit sessionAdded(QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700388 QString::fromStdString(msg.from()),
389 msg.timestamp());
390}
391
392void
393ChatDialogBackend::sendHello()
394{
395 SyncDemo::ChatMessage msg;
396 prepareControlMessage(msg, SyncDemo::ChatMessage::HELLO);
397 sendMsg(msg);
398
Yingdi Yu4647f022015-02-01 00:26:38 -0800399 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
400 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700401}
402
403void
404ChatDialogBackend::sendLeave()
405{
406 SyncDemo::ChatMessage msg;
407 prepareControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
408 sendMsg(msg);
409
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800410 // get my own identity with routable prefix by getPrefix(-2)
411 emit eraseInRoster(m_routableUserChatPrefix.getPrefix(-2),
412 Name::Component(m_chatroomName));
413
Yingdi Yud45777b2014-10-16 23:54:11 -0700414 usleep(5000);
415 m_joined = false;
416}
417
418void
419ChatDialogBackend::prepareControlMessage(SyncDemo::ChatMessage& msg,
420 SyncDemo::ChatMessage::ChatMessageType type)
421{
422 msg.set_from(m_nick);
423 msg.set_to(m_chatroomName);
424 int32_t seconds =
425 static_cast<int32_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
426 msg.set_timestamp(seconds);
427 msg.set_type(type);
428}
429
430void
431ChatDialogBackend::prepareChatMessage(const QString& text,
432 time_t timestamp,
433 SyncDemo::ChatMessage &msg)
434{
435 msg.set_from(m_nick);
436 msg.set_to(m_chatroomName);
437 msg.set_data(text.toStdString());
438 msg.set_timestamp(timestamp);
439 msg.set_type(SyncDemo::ChatMessage::CHAT);
440}
441
442void
443ChatDialogBackend::updatePrefixes()
444{
445 m_routableUserChatPrefix.clear();
446
447 if (m_localRoutingPrefix.isPrefixOf(m_userChatPrefix))
448 m_routableUserChatPrefix = m_userChatPrefix;
449 else
450 m_routableUserChatPrefix.append(m_localRoutingPrefix)
Qiuhan Dingba3e57a2015-01-08 19:07:39 -0800451 .append(ROUTING_HINT_SEPARATOR)
Yingdi Yud45777b2014-10-16 23:54:11 -0700452 .append(m_userChatPrefix);
453
454 emit chatPrefixChanged(m_routableUserChatPrefix);
455}
456
457std::string
458ChatDialogBackend::getHexEncodedDigest(ndn::ConstBufferPtr digest)
459{
460 std::stringstream os;
461
462 CryptoPP::StringSource(digest->buf(), digest->size(), true,
463 new CryptoPP::HexEncoder(new CryptoPP::FileSink(os), false));
464 return os.str();
465}
466
467
468// public slots:
469void
470ChatDialogBackend::sendChatMessage(QString text, time_t timestamp)
471{
472 SyncDemo::ChatMessage msg;
473 prepareChatMessage(text, timestamp, msg);
474 sendMsg(msg);
475
476 emit chatMessageReceived(QString::fromStdString(msg.from()),
477 QString::fromStdString(msg.data()),
478 msg.timestamp());
479}
480
481void
482ChatDialogBackend::updateRoutingPrefix(const QString& localRoutingPrefix)
483{
484 Name newLocalRoutingPrefix(localRoutingPrefix.toStdString());
485
486 if (!newLocalRoutingPrefix.empty() && newLocalRoutingPrefix != m_localRoutingPrefix) {
487 // Update localPrefix
488 m_localRoutingPrefix = newLocalRoutingPrefix;
489
Qiuhan Dingf22c41b2015-03-11 13:19:01 -0700490 {
491 std::lock_guard<std::mutex>lock(m_mutex);
492 m_shouldResume = true;
493 }
Yingdi Yu4647f022015-02-01 00:26:38 -0800494
495 close();
496
Qiuhan Ding112ee482015-03-11 11:54:11 -0700497 updatePrefixes();
498
Yingdi Yu4647f022015-02-01 00:26:38 -0800499 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700500 }
501}
502
503void
504ChatDialogBackend::shutdown()
505{
Qiuhan Dingf22c41b2015-03-11 13:19:01 -0700506 {
507 std::lock_guard<std::mutex>lock(m_mutex);
508 m_shouldResume = false;
509 }
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700510
Yingdi Yu4647f022015-02-01 00:26:38 -0800511 close();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700512
Yingdi Yu4647f022015-02-01 00:26:38 -0800513 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700514}
515
Yingdi Yueb692ac2015-02-10 18:46:18 -0800516} // namespace chronochat
Yingdi Yud45777b2014-10-16 23:54:11 -0700517
518#if WAF
519#include "chat-dialog-backend.moc"
520// #include "chat-dialog-backend.cpp.moc"
521#endif