blob: f175615f90e7ca12f8f2d734819c05e0f176a392 [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.
29
30ChatDialogBackend::ChatDialogBackend(const Name& chatroomPrefix,
31 const Name& userChatPrefix,
32 const Name& routingPrefix,
33 const std::string& chatroomName,
34 const std::string& nick,
Yingdi Yu45da92a2015-02-02 13:17:03 -080035 const Name& signingId,
Yingdi Yud45777b2014-10-16 23:54:11 -070036 QObject* parent)
37 : QThread(parent)
38 , m_localRoutingPrefix(routingPrefix)
39 , m_chatroomPrefix(chatroomPrefix)
40 , m_userChatPrefix(userChatPrefix)
41 , m_chatroomName(chatroomName)
42 , m_nick(nick)
Yingdi Yu45da92a2015-02-02 13:17:03 -080043 , m_signingId(signingId)
Yingdi Yud45777b2014-10-16 23:54:11 -070044{
45 updatePrefixes();
46}
47
48
49ChatDialogBackend::~ChatDialogBackend()
50{
51}
52
53// protected methods:
54void
55ChatDialogBackend::run()
56{
Yingdi Yu4647f022015-02-01 00:26:38 -080057 bool shouldResume = false;
58 do {
59 initializeSync();
Yingdi Yud45777b2014-10-16 23:54:11 -070060
Yingdi Yu4647f022015-02-01 00:26:38 -080061 if (m_face == nullptr)
62 break;
63
64 m_face->getIoService().run();
65
66 m_mutex.lock();
67 shouldResume = m_shouldResume;
68 m_shouldResume = false;
69 m_mutex.unlock();
70
71 } while (shouldResume);
Yingdi Yud45777b2014-10-16 23:54:11 -070072
73 std::cerr << "Bye!" << std::endl;
74}
75
76// private methods:
77void
78ChatDialogBackend::initializeSync()
79{
Yingdi Yu4647f022015-02-01 00:26:38 -080080 BOOST_ASSERT(m_sock == nullptr);
Yingdi Yud45777b2014-10-16 23:54:11 -070081
Yingdi Yu45da92a2015-02-02 13:17:03 -080082 m_face = make_shared<ndn::Face>();
Yingdi Yu4647f022015-02-01 00:26:38 -080083 m_scheduler = unique_ptr<ndn::Scheduler>(new ndn::Scheduler(m_face->getIoService()));
Yingdi Yud45777b2014-10-16 23:54:11 -070084
Yingdi Yu45da92a2015-02-02 13:17:03 -080085 // initialize validator
86 shared_ptr<ndn::IdentityCertificate> anchor = loadTrustAnchor();
87
88 if (static_cast<bool>(anchor)) {
89 shared_ptr<ndn::ValidatorRegex> validator =
90 make_shared<ndn::ValidatorRegex>(m_face.get()); // TODO: Change to Face*
91 validator->addDataVerificationRule(
92 make_shared<ndn::SecRuleRelative>("^<>*<%F0.>(<>*)$",
93 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
94 ">", "\\1", "\\1\\2", true));
95 validator->addDataVerificationRule(
96 make_shared<ndn::SecRuleRelative>("(<>*)$",
97 "^([^<KEY>]*)<KEY>(<>*)<ksk-.*><ID-CERT>$",
98 ">", "\\1", "\\1\\2", true));
99 validator->addTrustAnchor(anchor);
100
101 m_validator = validator;
102 }
103 else
104 m_validator = shared_ptr<ndn::Validator>();
105
106
Yingdi Yud45777b2014-10-16 23:54:11 -0700107 // create a new SyncSocket
108 m_sock = make_shared<chronosync::Socket>(m_chatroomPrefix,
109 m_routableUserChatPrefix,
Yingdi Yu4647f022015-02-01 00:26:38 -0800110 ref(*m_face),
Yingdi Yu45da92a2015-02-02 13:17:03 -0800111 bind(&ChatDialogBackend::processSyncUpdate, this, _1),
112 m_signingId,
113 m_validator);
Yingdi Yud45777b2014-10-16 23:54:11 -0700114
115 // schedule a new join event
Yingdi Yu4647f022015-02-01 00:26:38 -0800116 m_scheduler->scheduleEvent(time::milliseconds(600),
117 bind(&ChatDialogBackend::sendJoin, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700118
119 // cancel existing hello event if it exists
Yingdi Yu4647f022015-02-01 00:26:38 -0800120 if (m_helloEventId != nullptr) {
121 m_scheduler->cancelEvent(m_helloEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700122 m_helloEventId.reset();
123 }
124}
125
Yingdi Yu45da92a2015-02-02 13:17:03 -0800126class IoDeviceSource
127{
128public:
129 typedef char char_type;
130 typedef boost::iostreams::source_tag category;
131
132 explicit
133 IoDeviceSource(QIODevice& source)
134 : m_source(source)
135 {
136 }
137
138 std::streamsize
139 read(char* buffer, std::streamsize n)
140 {
141 return m_source.read(buffer, n);
142 }
143private:
144 QIODevice& m_source;
145};
146
147shared_ptr<ndn::IdentityCertificate>
148ChatDialogBackend::loadTrustAnchor()
149{
150 QFile anchorFile(":/security/anchor.cert");
151
152 if (!anchorFile.open(QIODevice::ReadOnly)) {
153 return {};
154 }
155
156 boost::iostreams::stream<IoDeviceSource> anchorFileStream(anchorFile);
157 return ndn::io::load<ndn::IdentityCertificate>(anchorFileStream);
158}
159
Yingdi Yud45777b2014-10-16 23:54:11 -0700160void
Yingdi Yu4647f022015-02-01 00:26:38 -0800161ChatDialogBackend::close()
162{
163 if (m_joined)
164 sendLeave();
165
166 usleep(100000);
167
168 m_scheduler->cancelAllEvents();
169 m_helloEventId.reset();
170 m_roster.clear();
Yingdi Yu45da92a2015-02-02 13:17:03 -0800171 m_validator.reset();
Yingdi Yu4647f022015-02-01 00:26:38 -0800172 m_sock.reset();
173}
174
175void
Yingdi Yud45777b2014-10-16 23:54:11 -0700176ChatDialogBackend::processSyncUpdate(const std::vector<chronosync::MissingDataInfo>& updates)
177{
178 _LOG_DEBUG("<<< processing Tree Update");
179
180 if (updates.empty()) {
181 return;
182 }
183
184 std::vector<NodeInfo> nodeInfos;
185
186
Yingdi Yu1cc45d92015-02-09 14:19:54 -0800187 for (size_t i = 0; i < updates.size(); i++) {
Yingdi Yud45777b2014-10-16 23:54:11 -0700188 // update roster
189 if (m_roster.find(updates[i].session) == m_roster.end()) {
190 m_roster[updates[i].session].sessionPrefix = updates[i].session;
191 m_roster[updates[i].session].hasNick = false;
192 }
193
194 // fetch missing chat data
195 if (updates[i].high - updates[i].low < 3) {
196 for (chronosync::SeqNo seq = updates[i].low; seq <= updates[i].high; ++seq) {
197 m_sock->fetchData(updates[i].session, seq,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800198 [this] (const shared_ptr<const ndn::Data>& data) {
199 this->processChatData(data, true, true);
200 },
201 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
202 this->processChatData(data, true, false);
203 },
204 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700205 2);
206 _LOG_DEBUG("<<< Fetching " << updates[i].session << "/" << seq);
207 }
208 }
209 else {
210 // There are too many msgs to fetch, let's just fetch the latest one
211 m_sock->fetchData(updates[i].session, updates[i].high,
Yingdi Yu45da92a2015-02-02 13:17:03 -0800212 [this] (const shared_ptr<const ndn::Data>& data) {
213 this->processChatData(data, false, true);
214 },
215 [this] (const shared_ptr<const ndn::Data>& data, const std::string& msg) {
216 this->processChatData(data, false, false);
217 },
218 ndn::OnTimeout(),
Yingdi Yud45777b2014-10-16 23:54:11 -0700219 2);
220 }
221
222 // prepare notification to frontend
223 NodeInfo nodeInfo;
224 nodeInfo.sessionPrefix = QString::fromStdString(updates[i].session.toUri());
225 nodeInfo.seqNo = updates[i].high;
226 nodeInfos.push_back(nodeInfo);
227 }
228
229 // reflect the changes on GUI
230 emit syncTreeUpdated(nodeInfos,
231 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
232}
233
234void
Yingdi Yu45da92a2015-02-02 13:17:03 -0800235ChatDialogBackend::processChatData(const ndn::shared_ptr<const ndn::Data>& data,
236 bool needDisplay,
237 bool isValidated)
Yingdi Yud45777b2014-10-16 23:54:11 -0700238{
239 SyncDemo::ChatMessage msg;
240
241 if (!msg.ParseFromArray(data->getContent().value(), data->getContent().value_size())) {
242 _LOG_DEBUG("Errrrr.. Can not parse msg with name: " <<
243 data->getName() << ". what is happening?");
244 // nasty stuff: as a remedy, we'll form some standard msg for inparsable msgs
245 msg.set_from("inconnu");
246 msg.set_type(SyncDemo::ChatMessage::OTHER);
247 return;
248 }
249
250 Name remoteSessionPrefix = data->getName().getPrefix(-1);
251
252 if (msg.type() == SyncDemo::ChatMessage::LEAVE) {
253 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
254
255 if (it != m_roster.end()) {
256 // cancel timeout event
257 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800258 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700259
260 // notify frontend to remove the remote session (node)
261 emit sessionRemoved(QString::fromStdString(remoteSessionPrefix.toUri()),
262 QString::fromStdString(msg.from()),
263 msg.timestamp());
264
265 // remove roster entry
266 m_roster.erase(remoteSessionPrefix);
267 }
268 }
269 else {
270 BackendRoster::iterator it = m_roster.find(remoteSessionPrefix);
271
272 if (it == m_roster.end()) {
273 // Should not happen
274 BOOST_ASSERT(false);
275 }
276
277 // If we haven't got any message from this session yet.
278 if (m_roster[remoteSessionPrefix].hasNick == false) {
279 m_roster[remoteSessionPrefix].userNick = msg.from();
280 m_roster[remoteSessionPrefix].hasNick = true;
281 emit sessionAdded(QString::fromStdString(remoteSessionPrefix.toUri()),
282 QString::fromStdString(msg.from()),
283 msg.timestamp());
284 }
285
286 // If we get a new nick for an existing session, update it.
287 if (m_roster[remoteSessionPrefix].userNick != msg.from()) {
288 m_roster[remoteSessionPrefix].userNick = msg.from();
289 emit nickUpdated(QString::fromStdString(remoteSessionPrefix.toUri()),
290 QString::fromStdString(msg.from()));
291 }
292
293 // If a timeout event has been scheduled, cancel it.
294 if (static_cast<bool>(it->second.timeoutEventId))
Yingdi Yu4647f022015-02-01 00:26:38 -0800295 m_scheduler->cancelEvent(it->second.timeoutEventId);
Yingdi Yud45777b2014-10-16 23:54:11 -0700296
297 // (Re)schedule another timeout event after 3 HELLO_INTERVAL;
298 it->second.timeoutEventId =
Yingdi Yu4647f022015-02-01 00:26:38 -0800299 m_scheduler->scheduleEvent(HELLO_INTERVAL * 3,
300 bind(&ChatDialogBackend::remoteSessionTimeout,
301 this, remoteSessionPrefix));
Yingdi Yud45777b2014-10-16 23:54:11 -0700302
303 // If chat message, notify the frontend
Yingdi Yu45da92a2015-02-02 13:17:03 -0800304 if (msg.type() == SyncDemo::ChatMessage::CHAT) {
305 if (isValidated)
306 emit chatMessageReceived(QString::fromStdString(msg.from()),
307 QString::fromStdString(msg.data()),
308 msg.timestamp());
309 else
310 emit chatMessageReceived(QString::fromStdString(msg.from() + " (Unverified)"),
311 QString::fromStdString(msg.data()),
312 msg.timestamp());
313 }
Yingdi Yud45777b2014-10-16 23:54:11 -0700314
315 // Notify frontend to plot notification on DigestTree.
316 emit messageReceived(QString::fromStdString(remoteSessionPrefix.toUri()));
317 }
318}
319
320void
321ChatDialogBackend::remoteSessionTimeout(const Name& sessionPrefix)
322{
323 time_t timestamp =
324 static_cast<time_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
325
326 // notify frontend
327 emit sessionRemoved(QString::fromStdString(sessionPrefix.toUri()),
328 QString::fromStdString(m_roster[sessionPrefix].userNick),
329 timestamp);
330
331 // remove roster entry
332 m_roster.erase(sessionPrefix);
333}
334
335void
336ChatDialogBackend::sendMsg(SyncDemo::ChatMessage& msg)
337{
338 // send msg
339 ndn::OBufferStream os;
340 msg.SerializeToOstream(&os);
341
342 if (!msg.IsInitialized()) {
343 _LOG_DEBUG("Errrrr.. msg was not probally initialized " << __FILE__ <<
344 ":" << __LINE__ << ". what is happening?");
345 abort();
346 }
347
348 uint64_t nextSequence = m_sock->getLogic().getSeqNo() + 1;
349
350 m_sock->publishData(os.buf()->buf(), os.buf()->size(), FRESHNESS_PERIOD);
351
352 std::vector<NodeInfo> nodeInfos;
353 NodeInfo nodeInfo = {QString::fromStdString(m_routableUserChatPrefix.toUri()),
354 nextSequence};
355 nodeInfos.push_back(nodeInfo);
356
357 emit syncTreeUpdated(nodeInfos,
358 QString::fromStdString(getHexEncodedDigest(m_sock->getRootDigest())));
359}
360
361void
362ChatDialogBackend::sendJoin()
363{
364 m_joined = true;
365
366 SyncDemo::ChatMessage msg;
367 prepareControlMessage(msg, SyncDemo::ChatMessage::JOIN);
368 sendMsg(msg);
369
Yingdi Yu4647f022015-02-01 00:26:38 -0800370 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
371 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700372
373 emit sessionAdded(QString::fromStdString(m_routableUserChatPrefix.toUri()),
374 QString::fromStdString(msg.from()),
375 msg.timestamp());
376}
377
378void
379ChatDialogBackend::sendHello()
380{
381 SyncDemo::ChatMessage msg;
382 prepareControlMessage(msg, SyncDemo::ChatMessage::HELLO);
383 sendMsg(msg);
384
Yingdi Yu4647f022015-02-01 00:26:38 -0800385 m_helloEventId = m_scheduler->scheduleEvent(HELLO_INTERVAL,
386 bind(&ChatDialogBackend::sendHello, this));
Yingdi Yud45777b2014-10-16 23:54:11 -0700387}
388
389void
390ChatDialogBackend::sendLeave()
391{
392 SyncDemo::ChatMessage msg;
393 prepareControlMessage(msg, SyncDemo::ChatMessage::LEAVE);
394 sendMsg(msg);
395
396 usleep(5000);
397 m_joined = false;
398}
399
400void
401ChatDialogBackend::prepareControlMessage(SyncDemo::ChatMessage& msg,
402 SyncDemo::ChatMessage::ChatMessageType type)
403{
404 msg.set_from(m_nick);
405 msg.set_to(m_chatroomName);
406 int32_t seconds =
407 static_cast<int32_t>(time::toUnixTimestamp(time::system_clock::now()).count() / 1000);
408 msg.set_timestamp(seconds);
409 msg.set_type(type);
410}
411
412void
413ChatDialogBackend::prepareChatMessage(const QString& text,
414 time_t timestamp,
415 SyncDemo::ChatMessage &msg)
416{
417 msg.set_from(m_nick);
418 msg.set_to(m_chatroomName);
419 msg.set_data(text.toStdString());
420 msg.set_timestamp(timestamp);
421 msg.set_type(SyncDemo::ChatMessage::CHAT);
422}
423
424void
425ChatDialogBackend::updatePrefixes()
426{
427 m_routableUserChatPrefix.clear();
428
429 if (m_localRoutingPrefix.isPrefixOf(m_userChatPrefix))
430 m_routableUserChatPrefix = m_userChatPrefix;
431 else
432 m_routableUserChatPrefix.append(m_localRoutingPrefix)
433 .append(ROUTING_HINT_SEPARATOR, 2)
434 .append(m_userChatPrefix);
435
436 emit chatPrefixChanged(m_routableUserChatPrefix);
437}
438
439std::string
440ChatDialogBackend::getHexEncodedDigest(ndn::ConstBufferPtr digest)
441{
442 std::stringstream os;
443
444 CryptoPP::StringSource(digest->buf(), digest->size(), true,
445 new CryptoPP::HexEncoder(new CryptoPP::FileSink(os), false));
446 return os.str();
447}
448
449
450// public slots:
451void
452ChatDialogBackend::sendChatMessage(QString text, time_t timestamp)
453{
454 SyncDemo::ChatMessage msg;
455 prepareChatMessage(text, timestamp, msg);
456 sendMsg(msg);
457
458 emit chatMessageReceived(QString::fromStdString(msg.from()),
459 QString::fromStdString(msg.data()),
460 msg.timestamp());
461}
462
463void
464ChatDialogBackend::updateRoutingPrefix(const QString& localRoutingPrefix)
465{
466 Name newLocalRoutingPrefix(localRoutingPrefix.toStdString());
467
468 if (!newLocalRoutingPrefix.empty() && newLocalRoutingPrefix != m_localRoutingPrefix) {
469 // Update localPrefix
470 m_localRoutingPrefix = newLocalRoutingPrefix;
471
472 updatePrefixes();
473
Yingdi Yu4647f022015-02-01 00:26:38 -0800474 m_mutex.lock();
475 m_shouldResume = true;
476 m_mutex.unlock();
477
478 close();
479
480 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700481 }
482}
483
484void
485ChatDialogBackend::shutdown()
486{
Yingdi Yu4647f022015-02-01 00:26:38 -0800487 m_mutex.lock();
488 m_shouldResume = false;
489 m_mutex.unlock();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700490
Yingdi Yu4647f022015-02-01 00:26:38 -0800491 close();
Yingdi Yu2c9e7712014-10-20 11:55:05 -0700492
Yingdi Yu4647f022015-02-01 00:26:38 -0800493 m_face->getIoService().stop();
Yingdi Yud45777b2014-10-16 23:54:11 -0700494}
495
Yingdi Yueb692ac2015-02-10 18:46:18 -0800496} // namespace chronochat
Yingdi Yud45777b2014-10-16 23:54:11 -0700497
498#if WAF
499#include "chat-dialog-backend.moc"
500// #include "chat-dialog-backend.cpp.moc"
501#endif