blob: 801db7df062ff47ab8beda2c4e3d075d7fb6e4cb [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
68 m_mutex.lock();
69 shouldResume = m_shouldResume;
70 m_shouldResume = false;
71 m_mutex.unlock();
72
73 } while (shouldResume);
Yingdi Yud45777b2014-10-16 23:54:11 -070074
75 std::cerr << "Bye!" << std::endl;
76}
77
78// private methods:
79void
80ChatDialogBackend::initializeSync()
81{
Yingdi Yu4647f022015-02-01 00:26:38 -080082 BOOST_ASSERT(m_sock == nullptr);
Yingdi Yud45777b2014-10-16 23:54:11 -070083
Yingdi Yu45da92a2015-02-02 13:17:03 -080084 m_face = make_shared<ndn::Face>();
Yingdi Yu4647f022015-02-01 00:26:38 -080085 m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
Yingdi Yud45777b2014-10-16 23:54:11 -070086
Yingdi Yu45da92a2015-02-02 13:17:03 -080087 // initialize validator
88 shared_ptr<ndn::IdentityCertificate> anchor = loadTrustAnchor();
89
90 if (static_cast<bool>(anchor)) {
91 shared_ptr<ndn::ValidatorRegex> validator =
92 make_shared<ndn::ValidatorRegex>(m_face.get()); // TODO: Change to Face*
93 validator->addDataVerificationRule(
94 make_shared<ndn::SecRuleRelative>("^<>*<%F0.>(<>*)$",
95 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
96 ">", "\\1", "\\1\\2", true));
97 validator->addDataVerificationRule(
98 make_shared<ndn::SecRuleRelative>("(<>*)$",
99 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
100 ">", "\\1", "\\1\\2", true));
101 validator->addTrustAnchor(anchor);
102
103 m_validator = validator;
104 }
105 else
106 m_validator = shared_ptr<ndn::Validator>();
107
108
Yingdi Yud45777b2014-10-16 23:54:11 -0700109 // create a new SyncSocket
110 m_sock = make_shared<chronosync::Socket>(m_chatroomPrefix,
111 m_routableUserChatPrefix,
Yingdi Yu4647f022015-02-01 00:26:38 -0800112 ref(*m_face),
Yingdi Yu45da92a2015-02-02 13:17:03 -0800113 bind(&ChatDialogBackend::processSyncUpdate, this, _1),
114 m_signingId,
115 m_validator);
Yingdi Yud45777b2014-10-16 23:54:11 -0700116
117 // schedule a new join event
Yingdi Yu4647f022015-02-01 00:26:38 -0800118 m_scheduler->scheduleEvent(time::milliseconds(600),
119 bind(&ChatDialogBackend::sendJoin, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700120
121 // cancel existing hello event if it exists
Yingdi Yu4647f022015-02-01 00:26:38 -0800122 if (m_helloEventId != nullptr) {
123 m_scheduler->cancelEvent(m_helloEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700124 m_helloEventId.reset();
125 }
126}
127
Yingdi Yu45da92a2015-02-02 13:17:03 -0800128class IoDeviceSource
129{
130public:
131 typedef char char_type;
132 typedef boost::iostreams::source_tag category;
133
134 explicit
135 IoDeviceSource(QIODevice& source)
136 : m_source(source)
137 {
138 }
139
140 std::streamsize
141 read(char* buffer, std::streamsize n)
142 {
143 return m_source.read(buffer, n);
144 }
145private:
146 QIODevice& m_source;
147};
148
149shared_ptr<ndn::IdentityCertificate>
150ChatDialogBackend::loadTrustAnchor()
151{
152 QFile anchorFile(":/security/anchor.cert");
153
154 if (!anchorFile.open(QIODevice::ReadOnly)) {
155 return {};
156 }
157
158 boost::iostreams::stream<IoDeviceSource> anchorFileStream(anchorFile);
159 return ndn::io::load<ndn::IdentityCertificate>(anchorFileStream);
160}
161
Yingdi Yud45777b2014-10-16 23:54:11 -0700162void
Yingdi Yu4647f022015-02-01 00:26:38 -0800163ChatDialogBackend::close()
164{
165 if (m_joined)
166 sendLeave();
167
168 usleep(100000);
169
170 m_scheduler->cancelAllEvents();
171 m_helloEventId.reset();
172 m_roster.clear();
Yingdi Yu45da92a2015-02-02 13:17:03 -0800173 m_validator.reset();
Yingdi Yu4647f022015-02-01 00:26:38 -0800174 m_sock.reset();
175}
176
177void
Yingdi Yud45777b2014-10-16 23:54:11 -0700178ChatDialogBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
179{
180 _LOG_DEBUG("<<< processing Tree Update");
181
182 if (updates.empty()) {
183 return;
184 }
185
186 std::vector<NodeInfo> nodeInfos;
187
188
Yingdi Yu1cc45d92015-02-09 14:19:54 -0800189 for (size_t i = 0; i < updates.size(); i++) {
Yingdi Yud45777b2014-10-16 23:54:11 -0700190 // update roster
191 if (m_roster.find(updates[i].session) == m_roster.end()) {
192 m_roster[updates[i].session].sessionPrefix = updates[i].session;
193 m_roster[updates[i].session].hasNick = false;
194 }
195
196 // fetch missing chat data
197 if (updates[i].high - updates[i].low < 3) {
198 for (chronosync::SeqNo seq = updates[i].low; seq <= updates[i].high; ++seq) {
199 m_sock->fetchData(updates[i].session, seq,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800200 [this] (const shared_ptr<const ndn::Data>& data) {
201 this->processChatData(data, true, true);
202 },
203 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
204 this->processChatData(data, true, false);
205 },
206 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700207 2);
208 _LOG_DEBUG("<<< Fetching " << updates[i].session << "/" << seq);
209 }
210 }
211 else {
212 // There are too many msgs to fetch, let's just fetch the latest one
213 m_sock->fetchData(updates[i].session, updates[i].high,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800214 [this] (const shared_ptr<const ndn::Data>& data) {
215 this->processChatData(data, false, true);
216 },
217 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
218 this->processChatData(data, false, false);
219 },
220 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700221 2);
222 }
223
224 // prepare notification to frontend
225 NodeInfo nodeInfo;
226 nodeInfo.sessionPrefix = QString::fromStdString(updates[i].session.toUri());
227 nodeInfo.seqNo = updates[i].high;
228 nodeInfos.push_back(nodeInfo);
229 }
230
231 // reflect the changes on GUI
232 emit syncTreeUpdated(nodeInfos,
233 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
234}
235
236void
Yingdi Yu45da92a2015-02-02 13:17:03 -0800237ChatDialogBackend::processChatData(const ndn::shared_ptr<const ndn::Data>& data,
238 bool needDisplay,
239 bool isValidated)
Yingdi Yud45777b2014-10-16 23:54:11 -0700240{
241 SyncDemo::ChatMessage msg;
242
243 if (!msg.ParseFromArray(data->getContent().value(), data->getContent().value_size())) {
244 _LOG_DEBUG("Errrrr.. Can not parse msg with name: " <<
245 data->getName() << ". what is happening?");
246 // nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
247 msg.set_from("inconnu");
248 msg.set_type(SyncDemo::ChatMessage::OTHER);
249 return;
250 }
251
252 Name remoteSessionPrefix = data->getName().getPrefix(-1);
253
254 if (msg.type() == SyncDemo::ChatMessage::LEAVE) {
255 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
256
257 if (it != m_roster.end()) {
258 // cancel timeout event
259 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800260 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700261
262 // notify frontend to remove the remote session (node)
263 emit sessionRemoved(QString::fromStdString(remoteSessionPrefix.toUri()),
264 QString::fromStdString(msg.from()),
265 msg.timestamp());
266
267 // remove roster entry
268 m_roster.erase(remoteSessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800269
270 emit eraseInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
271 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700272 }
273 }
274 else {
275 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
276
277 if (it == m_roster.end()) {
278 // Should not happen
279 BOOST_ASSERT(false);
280 }
281
282 // If we haven't got any message from this session yet.
283 if (m_roster[remoteSessionPrefix].hasNick == false) {
284 m_roster[remoteSessionPrefix].userNick = msg.from();
285 m_roster[remoteSessionPrefix].hasNick = true;
286 emit sessionAdded(QString::fromStdString(remoteSessionPrefix.toUri()),
287 QString::fromStdString(msg.from()),
288 msg.timestamp());
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800289
290 emit addInRoster(remoteSessionPrefix.getPrefix(IDENTITY_OFFSET),
291 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700292 }
293
294 // If we get a new nick for an existing session, update it.
295 if (m_roster[remoteSessionPrefix].userNick != msg.from()) {
296 m_roster[remoteSessionPrefix].userNick = msg.from();
297 emit nickUpdated(QString::fromStdString(remoteSessionPrefix.toUri()),
298 QString::fromStdString(msg.from()));
299 }
300
301 // If a timeout event has been scheduled, cancel it.
302 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800303 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700304
305 // (Re)schedule another timeout event after 3 HELLO_INTERVAL;
306 it->second.timeoutEventId =
Yingdi Yu4647f022015-02-01 00:26:38 -0800307 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
308 bind(&ChatDialogBackend::remoteSessionTimeout,
309 this, remoteSessionPrefix));
Yingdi Yud45777b2014-10-16 23:54:11 -0700310
311 // If chat message, notify the frontend
Yingdi Yu45da92a2015-02-02 13:17:03 -0800312 if (msg.type() == SyncDemo::ChatMessage::CHAT) {
313 if (isValidated)
314 emit chatMessageReceived(QString::fromStdString(msg.from()),
315 QString::fromStdString(msg.data()),
316 msg.timestamp());
317 else
318 emit chatMessageReceived(QString::fromStdString(msg.from() + " (Unverified)"),
319 QString::fromStdString(msg.data()),
320 msg.timestamp());
321 }
Yingdi Yud45777b2014-10-16 23:54:11 -0700322
323 // Notify frontend to plot notification on DigestTree.
324 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()));
325 }
326}
327
328void
329ChatDialogBackend::remoteSessionTimeout(const Name& sessionPrefix)
330{
331 time_t timestamp =
332 static_cast<time_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
333
334 // notify frontend
335 emit sessionRemoved(QString::fromStdString(sessionPrefix.toUri()),
336 QString::fromStdString(m_roster[sessionPrefix].userNick),
337 timestamp);
338
339 // remove roster entry
340 m_roster.erase(sessionPrefix);
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800341
342 emit eraseInRoster(sessionPrefix.getPrefix(IDENTITY_OFFSET),
343 Name::Component(m_chatroomName));
Yingdi Yud45777b2014-10-16 23:54:11 -0700344}
345
346void
347ChatDialogBackend::sendMsg(SyncDemo::ChatMessage& msg)
348{
349 // send msg
350 ndn::OBufferStream os;
351 msg.SerializeToOstream(&os);
352
353 if (!msg.IsInitialized()) {
354 _LOG_DEBUG("Errrrr.. msg was not probally initialized " << __FILE__ <<
355 ":" << __LINE__ << ". what is happening?");
356 abort();
357 }
358
359 uint64_t nextSequence = m_sock->getLogic().getSeqNo() + 1;
360
361 m_sock->publishData(os.buf()->buf(), os.buf()->size(), FRESHNESS_PERIOD);
362
363 std::vector<NodeInfo> nodeInfos;
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800364 Name sessionName = m_sock->getLogic().getSessionName();
365 NodeInfo nodeInfo = {QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700366 nextSequence};
367 nodeInfos.push_back(nodeInfo);
368
369 emit syncTreeUpdated(nodeInfos,
370 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
371}
372
373void
374ChatDialogBackend::sendJoin()
375{
376 m_joined = true;
377
378 SyncDemo::ChatMessage msg;
379 prepareControlMessage(msg, SyncDemo::ChatMessage::JOIN);
380 sendMsg(msg);
381
Yingdi Yu4647f022015-02-01 00:26:38 -0800382 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
383 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700384
Qiuhan Ding43c8e162015-02-02 15:16:48 -0800385 Name sessionName = m_sock->getLogic().getSessionName();
386 emit sessionAdded(QString::fromStdString(sessionName.toUri()),
Yingdi Yud45777b2014-10-16 23:54:11 -0700387 QString::fromStdString(msg.from()),
388 msg.timestamp());
389}
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
Yingdi Yu4647f022015-02-01 00:26:38 -0800489 m_mutex.lock();
490 m_shouldResume = true;
491 m_mutex.unlock();
492
493 close();
494
Qiuhan Ding112ee482015-03-11 11:54:11 -0700495 updatePrefixes();
496
Yingdi Yu4647f022015-02-01 00:26:38 -0800497 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700498 }
499}
500
501void
502ChatDialogBackend::shutdown()
503{
Yingdi Yu4647f022015-02-01 00:26:38 -0800504 m_mutex.lock();
505 m_shouldResume = false;
506 m_mutex.unlock();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700507
Yingdi Yu4647f022015-02-01 00:26:38 -0800508 close();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700509
Yingdi Yu4647f022015-02-01 00:26:38 -0800510 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700511}
512
Yingdi Yueb692ac2015-02-10 18:46:18 -0800513} // namespace chronochat
Yingdi Yud45777b2014-10-16 23:54:11 -0700514
515#if WAF
516#include "chat-dialog-backend.moc"
517// #include "chat-dialog-backend.cpp.moc"
518#endif