1 /*
2 Copyright © 2013 by Maxim Biro <nurupo.contributions@gmail.com>
3 Copyright © 2014-2019 by The qTox Project Contributors
4
5 This file is part of qTox, a Qt-based graphical interface for Tox.
6
7 qTox is libre software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 qTox is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with qTox. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "core.h"
22 #include "corefile.h"
23 #include "src/core/coreav.h"
24 #include "src/core/dhtserver.h"
25 #include "src/core/icoresettings.h"
26 #include "src/core/toxlogger.h"
27 #include "src/core/toxoptions.h"
28 #include "src/core/toxstring.h"
29 #include "src/model/groupinvite.h"
30 #include "src/model/status.h"
31 #include "src/net/bootstrapnodeupdater.h"
32 #include "src/nexus.h"
33 #include "src/persistence/profile.h"
34 #include "src/util/strongtype.h"
35 #include "src/util/compatiblerecursivemutex.h"
36
37 #include <QCoreApplication>
38 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
39 #include <QRandomGenerator>
40 #endif
41 #include <QRegularExpression>
42 #include <QString>
43 #include <QStringBuilder>
44 #include <QTimer>
45
46 #include <cassert>
47 #include <memory>
48
49 const QString Core::TOX_EXT = ".tox";
50
51 #define ASSERT_CORE_THREAD assert(QThread::currentThread() == coreThread.get())
52
53 namespace {
LogConferenceTitleError(TOX_ERR_CONFERENCE_TITLE error)54 bool LogConferenceTitleError(TOX_ERR_CONFERENCE_TITLE error)
55 {
56 switch (error) {
57 case TOX_ERR_CONFERENCE_TITLE_OK:
58 break;
59 case TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND:
60 qWarning() << "Conference title not found";
61 break;
62 case TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH:
63 qWarning() << "Invalid conference title length";
64 break;
65 case TOX_ERR_CONFERENCE_TITLE_FAIL_SEND:
66 qWarning() << "Failed to send title packet";
67 }
68 return error;
69 }
70
parseToxErrBootstrap(Tox_Err_Bootstrap error)71 bool parseToxErrBootstrap(Tox_Err_Bootstrap error)
72 {
73 switch(error) {
74 case TOX_ERR_BOOTSTRAP_OK:
75 return true;
76
77 case TOX_ERR_BOOTSTRAP_NULL:
78 qCritical() << "null argument when not expected";
79 return false;
80
81 case TOX_ERR_BOOTSTRAP_BAD_HOST:
82 qCritical() << "Could not resolve hostname, or invalid IP address";
83 return false;
84
85 case TOX_ERR_BOOTSTRAP_BAD_PORT:
86 qCritical() << "out of range port";
87 return false;
88
89 default:
90 qCritical() << "Unknown Tox_Err_bootstrap error code:" << error;
91 return false;
92 }
93 }
94
parseFriendSendMessageError(Tox_Err_Friend_Send_Message error)95 bool parseFriendSendMessageError(Tox_Err_Friend_Send_Message error)
96 {
97 switch (error) {
98 case TOX_ERR_FRIEND_SEND_MESSAGE_OK:
99 return true;
100 case TOX_ERR_FRIEND_SEND_MESSAGE_NULL:
101 qCritical() << "Send friend message passed an unexpected null argument";
102 return false;
103 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND:
104 qCritical() << "Send friend message could not find friend";
105 return false;
106 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED:
107 qCritical() << "Send friend message: friend is offline";
108 return false;
109 case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ:
110 qCritical() << "Failed to allocate more message queue";
111 return false;
112 case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG:
113 qCritical() << "Attemped to send message that's too long";
114 return false;
115 case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY:
116 qCritical() << "Attempted to send an empty message";
117 return false;
118 default:
119 qCritical() << "Unknown friend send message error:" << static_cast<int>(error);
120 return false;
121 }
122 }
123
parseConferenceSendMessageError(Tox_Err_Conference_Send_Message error)124 bool parseConferenceSendMessageError(Tox_Err_Conference_Send_Message error)
125 {
126 switch (error) {
127 case TOX_ERR_CONFERENCE_SEND_MESSAGE_OK:
128 return true;
129
130 case TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND:
131 qCritical() << "Conference not found";
132 return false;
133
134 case TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND:
135 qCritical() << "Conference message failed to send";
136 return false;
137
138 case TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION:
139 qCritical() << "No connection";
140 return false;
141
142 case TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG:
143 qCritical() << "Message too long";
144 return false;
145 default:
146 qCritical() << "Unknown Tox_Err_Conference_Send_Message error:" << static_cast<int>(error);
147 return false;
148 }
149 }
150 } // namespace
151
Core(QThread * coreThread)152 Core::Core(QThread* coreThread)
153 : tox(nullptr)
154 , av(nullptr)
155 , toxTimer{new QTimer{this}}
156 , coreThread(coreThread)
157 {
158 assert(toxTimer);
159 toxTimer->setSingleShot(true);
160 connect(toxTimer, &QTimer::timeout, this, &Core::process);
161 connect(coreThread, &QThread::finished, toxTimer, &QTimer::stop);
162 }
163
~Core()164 Core::~Core()
165 {
166 /*
167 * First stop the thread to stop the timer and avoid Core emitting callbacks
168 * into an already destructed CoreAV.
169 */
170 coreThread->exit(0);
171 coreThread->wait();
172
173 av.reset();
174 tox.reset();
175 }
176
177 /**
178 * @brief Registers all toxcore callbacks
179 * @param tox Tox instance to register the callbacks on
180 */
registerCallbacks(Tox * tox)181 void Core::registerCallbacks(Tox* tox)
182 {
183 tox_callback_friend_request(tox, onFriendRequest);
184 tox_callback_friend_message(tox, onFriendMessage);
185 tox_callback_friend_name(tox, onFriendNameChange);
186 tox_callback_friend_typing(tox, onFriendTypingChange);
187 tox_callback_friend_status_message(tox, onStatusMessageChanged);
188 tox_callback_friend_status(tox, onUserStatusChanged);
189 tox_callback_friend_connection_status(tox, onConnectionStatusChanged);
190 tox_callback_friend_read_receipt(tox, onReadReceiptCallback);
191 tox_callback_conference_invite(tox, onGroupInvite);
192 tox_callback_conference_message(tox, onGroupMessage);
193 tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange);
194 tox_callback_conference_peer_name(tox, onGroupPeerNameChange);
195 tox_callback_conference_title(tox, onGroupTitleChange);
196 }
197
198 /**
199 * @brief Factory method for the Core object
200 * @param savedata empty if new profile or saved data else
201 * @param settings Settings specific to Core
202 * @return nullptr or a Core object ready to start
203 */
makeToxCore(const QByteArray & savedata,const ICoreSettings * const settings,ToxCoreErrors * err)204 ToxCorePtr Core::makeToxCore(const QByteArray& savedata, const ICoreSettings* const settings,
205 ToxCoreErrors* err)
206 {
207 QThread* thread = new QThread();
208 if (thread == nullptr) {
209 qCritical() << "could not allocate Core thread";
210 return {};
211 }
212 thread->setObjectName("qTox Core");
213
214 auto toxOptions = ToxOptions::makeToxOptions(savedata, settings);
215 if (toxOptions == nullptr) {
216 qCritical() << "could not allocate Tox Options data structure";
217 if (err) {
218 *err = ToxCoreErrors::ERROR_ALLOC;
219 }
220 return {};
221 }
222
223 ToxCorePtr core(new Core(thread));
224 if (core == nullptr) {
225 if (err) {
226 *err = ToxCoreErrors::ERROR_ALLOC;
227 }
228 return {};
229 }
230
231 Tox_Err_New tox_err;
232 core->tox = ToxPtr(tox_new(*toxOptions, &tox_err));
233
234 switch (tox_err) {
235 case TOX_ERR_NEW_OK:
236 break;
237
238 case TOX_ERR_NEW_LOAD_BAD_FORMAT:
239 qCritical() << "failed to parse Tox save data";
240 if (err) {
241 *err = ToxCoreErrors::BAD_PROXY;
242 }
243 return {};
244
245 case TOX_ERR_NEW_PORT_ALLOC:
246 if (toxOptions->getIPv6Enabled()) {
247 toxOptions->setIPv6Enabled(false);
248 core->tox = ToxPtr(tox_new(*toxOptions, &tox_err));
249 if (tox_err == TOX_ERR_NEW_OK) {
250 qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery "
251 "may not work properly.";
252 break;
253 }
254 }
255
256 qCritical() << "can't to bind the port";
257 if (err) {
258 *err = ToxCoreErrors::FAILED_TO_START;
259 }
260 return {};
261
262 case TOX_ERR_NEW_PROXY_BAD_HOST:
263 case TOX_ERR_NEW_PROXY_BAD_PORT:
264 case TOX_ERR_NEW_PROXY_BAD_TYPE:
265 qCritical() << "bad proxy, error code:" << tox_err;
266 if (err) {
267 *err = ToxCoreErrors::BAD_PROXY;
268 }
269 return {};
270
271 case TOX_ERR_NEW_PROXY_NOT_FOUND:
272 qCritical() << "proxy not found";
273 if (err) {
274 *err = ToxCoreErrors::BAD_PROXY;
275 }
276 return {};
277
278 case TOX_ERR_NEW_LOAD_ENCRYPTED:
279 qCritical() << "attempted to load encrypted Tox save data";
280 if (err) {
281 *err = ToxCoreErrors::INVALID_SAVE;
282 }
283 return {};
284
285 case TOX_ERR_NEW_MALLOC:
286 qCritical() << "memory allocation failed";
287 if (err) {
288 *err = ToxCoreErrors::ERROR_ALLOC;
289 }
290 return {};
291
292 case TOX_ERR_NEW_NULL:
293 qCritical() << "a parameter was null";
294 if (err) {
295 *err = ToxCoreErrors::FAILED_TO_START;
296 }
297 return {};
298
299 default:
300 qCritical() << "Tox core failed to start, unknown error code:" << tox_err;
301 if (err) {
302 *err = ToxCoreErrors::FAILED_TO_START;
303 }
304 return {};
305 }
306
307 // tox should be valid by now
308 assert(core->tox != nullptr);
309
310 // toxcore is successfully created, create toxav
311 // TODO(sudden6): don't create CoreAv here, Core should be usable without CoreAV
312 core->av = CoreAV::makeCoreAV(core->tox.get(), core->coreLoopLock);
313 if (!core->av) {
314 qCritical() << "Toxav failed to start";
315 if (err) {
316 *err = ToxCoreErrors::FAILED_TO_START;
317 }
318 return {};
319 }
320
321 // create CoreFile
322 core->file = CoreFile::makeCoreFile(core.get(), core->tox.get(), core->coreLoopLock);
323 if (!core->file) {
324 qCritical() << "CoreFile failed to start";
325 if (err) {
326 *err = ToxCoreErrors::FAILED_TO_START;
327 }
328 return {};
329 }
330
331 registerCallbacks(core->tox.get());
332
333 // connect the thread with the Core
334 connect(thread, &QThread::started, core.get(), &Core::onStarted);
335 core->moveToThread(thread);
336
337 // when leaving this function 'core' should be ready for it's start() action or
338 // a nullptr
339 return core;
340 }
341
onStarted()342 void Core::onStarted()
343 {
344 ASSERT_CORE_THREAD;
345
346 // One time initialization stuff
347 QString name = getUsername();
348 if (!name.isEmpty()) {
349 emit usernameSet(name);
350 }
351
352 QString msg = getStatusMessage();
353 if (!msg.isEmpty()) {
354 emit statusMessageSet(msg);
355 }
356
357 ToxId id = getSelfId();
358 // Id comes from toxcore, must be valid
359 assert(id.isValid());
360 emit idSet(id);
361
362 loadFriends();
363 loadGroups();
364
365 process(); // starts its own timer
366 av->start();
367 emit avReady();
368 }
369
370 /**
371 * @brief Starts toxcore and it's event loop, can be called from any thread
372 */
start()373 void Core::start()
374 {
375 coreThread->start();
376 }
377
378
379 /**
380 * @brief Returns the global widget's Core instance
381 */
getInstance()382 Core* Core::getInstance()
383 {
384 return Nexus::getCore();
385 }
386
getAv() const387 const CoreAV* Core::getAv() const
388 {
389 return av.get();
390 }
391
getAv()392 CoreAV* Core::getAv()
393 {
394 return av.get();
395 }
396
getCoreFile() const397 CoreFile* Core::getCoreFile() const
398 {
399 return file.get();
400 }
401
402 /* Using the now commented out statements in checkConnection(), I watched how
403 * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials,
404 * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
405 * So I set the tolerance here at 25, and initial DCs should be very rare now.
406 * This should be able to go to 50 or 100 without affecting legitimate disconnects'
407 * downtime, but lets be conservative for now. Edit: now ~~40~~ 30.
408 */
409 #define CORE_DISCONNECT_TOLERANCE 30
410
411 /**
412 * @brief Processes toxcore events and ensure we stay connected, called by its own timer
413 */
process()414 void Core::process()
415 {
416 QMutexLocker ml{&coreLoopLock};
417
418 ASSERT_CORE_THREAD;
419
420 static int tolerance = CORE_DISCONNECT_TOLERANCE;
421 tox_iterate(tox.get(), this);
422
423 #ifdef DEBUG
424 // we want to see the debug messages immediately
425 fflush(stdout);
426 #endif
427
428 // TODO(sudden6): recheck if this is still necessary
429 if (checkConnection()) {
430 tolerance = CORE_DISCONNECT_TOLERANCE;
431 } else if (!(--tolerance)) {
432 bootstrapDht();
433 tolerance = 3 * CORE_DISCONNECT_TOLERANCE;
434 }
435
436 unsigned sleeptime =
437 qMin(tox_iteration_interval(tox.get()), getCoreFile()->corefileIterationInterval());
438 toxTimer->start(sleeptime);
439 }
440
checkConnection()441 bool Core::checkConnection()
442 {
443 ASSERT_CORE_THREAD;
444 static bool isConnected = false;
445 bool toxConnected = tox_self_get_connection_status(tox.get()) != TOX_CONNECTION_NONE;
446 if (toxConnected && !isConnected) {
447 qDebug() << "Connected to the DHT";
448 emit connected();
449 } else if (!toxConnected && isConnected) {
450 qDebug() << "Disconnected from the DHT";
451 emit disconnected();
452 }
453
454 isConnected = toxConnected;
455 return toxConnected;
456 }
457
458 /**
459 * @brief Connects us to the Tox network
460 */
bootstrapDht()461 void Core::bootstrapDht()
462 {
463 ASSERT_CORE_THREAD;
464
465 QList<DhtServer> bootstrapNodes = BootstrapNodeUpdater::loadDefaultBootstrapNodes();
466
467 int listSize = bootstrapNodes.size();
468 if (!listSize) {
469 qWarning() << "no bootstrap list?!?";
470 return;
471 }
472
473 int i = 0;
474 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
475 static int j = QRandomGenerator::global()->generate() % listSize;
476 #else
477 static int j = qrand() % listSize;
478 #endif
479 // i think the more we bootstrap, the more we jitter because the more we overwrite nodes
480 while (i < 2) {
481 const DhtServer& dhtServer = bootstrapNodes[j % listSize];
482 QString dhtServerAddress = dhtServer.address.toLatin1();
483 QString port = QString::number(dhtServer.port);
484 QString name = dhtServer.name;
485 qDebug() << QString("Connecting to a bootstrap node...");
486 QByteArray address = dhtServer.address.toLatin1();
487 // TODO: constucting the pk via ToxId is a workaround
488 ToxPk pk = ToxId{dhtServer.userId}.getPublicKey();
489
490 const uint8_t* pkPtr = pk.getData();
491
492 Tox_Err_Bootstrap error;
493 tox_bootstrap(tox.get(), address.constData(), dhtServer.port, pkPtr, &error);
494 parseToxErrBootstrap(error);
495
496 tox_add_tcp_relay(tox.get(), address.constData(), dhtServer.port, pkPtr, &error);
497 parseToxErrBootstrap(error);
498
499 ++j;
500 ++i;
501 }
502 }
503
onFriendRequest(Tox *,const uint8_t * cFriendPk,const uint8_t * cMessage,size_t cMessageSize,void * core)504 void Core::onFriendRequest(Tox*, const uint8_t* cFriendPk, const uint8_t* cMessage,
505 size_t cMessageSize, void* core)
506 {
507 ToxPk friendPk(cFriendPk);
508 QString requestMessage = ToxString(cMessage, cMessageSize).getQString();
509 emit static_cast<Core*>(core)->friendRequestReceived(friendPk, requestMessage);
510 }
511
onFriendMessage(Tox *,uint32_t friendId,Tox_Message_Type type,const uint8_t * cMessage,size_t cMessageSize,void * core)512 void Core::onFriendMessage(Tox*, uint32_t friendId, Tox_Message_Type type, const uint8_t* cMessage,
513 size_t cMessageSize, void* core)
514 {
515 bool isAction = (type == TOX_MESSAGE_TYPE_ACTION);
516 QString msg = ToxString(cMessage, cMessageSize).getQString();
517 emit static_cast<Core*>(core)->friendMessageReceived(friendId, msg, isAction);
518 }
519
onFriendNameChange(Tox *,uint32_t friendId,const uint8_t * cName,size_t cNameSize,void * core)520 void Core::onFriendNameChange(Tox*, uint32_t friendId, const uint8_t* cName, size_t cNameSize, void* core)
521 {
522 QString newName = ToxString(cName, cNameSize).getQString();
523 // no saveRequest, this callback is called on every connection, not just on name change
524 emit static_cast<Core*>(core)->friendUsernameChanged(friendId, newName);
525 }
526
onFriendTypingChange(Tox *,uint32_t friendId,bool isTyping,void * core)527 void Core::onFriendTypingChange(Tox*, uint32_t friendId, bool isTyping, void* core)
528 {
529 emit static_cast<Core*>(core)->friendTypingChanged(friendId, isTyping);
530 }
531
onStatusMessageChanged(Tox *,uint32_t friendId,const uint8_t * cMessage,size_t cMessageSize,void * core)532 void Core::onStatusMessageChanged(Tox*, uint32_t friendId, const uint8_t* cMessage,
533 size_t cMessageSize, void* core)
534 {
535 QString message = ToxString(cMessage, cMessageSize).getQString();
536 // no saveRequest, this callback is called on every connection, not just on name change
537 emit static_cast<Core*>(core)->friendStatusMessageChanged(friendId, message);
538 }
539
onUserStatusChanged(Tox *,uint32_t friendId,Tox_User_Status userstatus,void * core)540 void Core::onUserStatusChanged(Tox*, uint32_t friendId, Tox_User_Status userstatus, void* core)
541 {
542 Status::Status status;
543 switch (userstatus) {
544 case TOX_USER_STATUS_AWAY:
545 status = Status::Status::Away;
546 break;
547
548 case TOX_USER_STATUS_BUSY:
549 status = Status::Status::Busy;
550 break;
551
552 default:
553 status = Status::Status::Online;
554 break;
555 }
556
557 // no saveRequest, this callback is called on every connection, not just on name change
558 emit static_cast<Core*>(core)->friendStatusChanged(friendId, status);
559 }
560
onConnectionStatusChanged(Tox *,uint32_t friendId,Tox_Connection status,void * vCore)561 void Core::onConnectionStatusChanged(Tox*, uint32_t friendId, Tox_Connection status, void* vCore)
562 {
563 Core* core = static_cast<Core*>(vCore);
564 Status::Status friendStatus =
565 status != TOX_CONNECTION_NONE ? Status::Status::Online : Status::Status::Offline;
566 // Ignore Online because it will be emited from onUserStatusChanged
567 bool isOffline = friendStatus == Status::Status::Offline;
568 if (isOffline) {
569 emit core->friendStatusChanged(friendId, friendStatus);
570 core->checkLastOnline(friendId);
571 }
572 }
573
onGroupInvite(Tox * tox,uint32_t friendId,Tox_Conference_Type type,const uint8_t * cookie,size_t length,void * vCore)574 void Core::onGroupInvite(Tox* tox, uint32_t friendId, Tox_Conference_Type type,
575 const uint8_t* cookie, size_t length, void* vCore)
576 {
577 Core* core = static_cast<Core*>(vCore);
578 const QByteArray data(reinterpret_cast<const char*>(cookie), length);
579 const GroupInvite inviteInfo(friendId, type, data);
580 switch (type) {
581 case TOX_CONFERENCE_TYPE_TEXT:
582 qDebug() << QString("Text group invite by %1").arg(friendId);
583 emit core->groupInviteReceived(inviteInfo);
584 break;
585
586 case TOX_CONFERENCE_TYPE_AV:
587 qDebug() << QString("AV group invite by %1").arg(friendId);
588 emit core->groupInviteReceived(inviteInfo);
589 break;
590
591 default:
592 qWarning() << "Group invite with unknown type " << type;
593 }
594 }
595
onGroupMessage(Tox *,uint32_t groupId,uint32_t peerId,Tox_Message_Type type,const uint8_t * cMessage,size_t length,void * vCore)596 void Core::onGroupMessage(Tox*, uint32_t groupId, uint32_t peerId, Tox_Message_Type type,
597 const uint8_t* cMessage, size_t length, void* vCore)
598 {
599 Core* core = static_cast<Core*>(vCore);
600 bool isAction = type == TOX_MESSAGE_TYPE_ACTION;
601 QString message = ToxString(cMessage, length).getQString();
602 emit core->groupMessageReceived(groupId, peerId, message, isAction);
603 }
604
onGroupPeerListChange(Tox *,uint32_t groupId,void * vCore)605 void Core::onGroupPeerListChange(Tox*, uint32_t groupId, void* vCore)
606 {
607 const auto core = static_cast<Core*>(vCore);
608 qDebug() << QString("Group %1 peerlist changed").arg(groupId);
609 // no saveRequest, this callback is called on every connection to group peer, not just on brand new peers
610 emit core->groupPeerlistChanged(groupId);
611 }
612
onGroupPeerNameChange(Tox *,uint32_t groupId,uint32_t peerId,const uint8_t * name,size_t length,void * vCore)613 void Core::onGroupPeerNameChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* name,
614 size_t length, void* vCore)
615 {
616 const auto newName = ToxString(name, length).getQString();
617 qDebug() << QString("Group %1, Peer %2, name changed to %3").arg(groupId).arg(peerId).arg(newName);
618 auto* core = static_cast<Core*>(vCore);
619 auto peerPk = core->getGroupPeerPk(groupId, peerId);
620 emit core->groupPeerNameChanged(groupId, peerPk, newName);
621 }
622
onGroupTitleChange(Tox *,uint32_t groupId,uint32_t peerId,const uint8_t * cTitle,size_t length,void * vCore)623 void Core::onGroupTitleChange(Tox*, uint32_t groupId, uint32_t peerId, const uint8_t* cTitle,
624 size_t length, void* vCore)
625 {
626 Core* core = static_cast<Core*>(vCore);
627 QString author = core->getGroupPeerName(groupId, peerId);
628 emit core->saveRequest();
629 emit core->groupTitleChanged(groupId, author, ToxString(cTitle, length).getQString());
630 }
631
onReadReceiptCallback(Tox *,uint32_t friendId,uint32_t receipt,void * core)632 void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core)
633 {
634 emit static_cast<Core*>(core)->receiptRecieved(friendId, ReceiptNum{receipt});
635 }
636
acceptFriendRequest(const ToxPk & friendPk)637 void Core::acceptFriendRequest(const ToxPk& friendPk)
638 {
639 QMutexLocker ml{&coreLoopLock};
640 // TODO: error handling
641 uint32_t friendId = tox_friend_add_norequest(tox.get(), friendPk.getData(), nullptr);
642 if (friendId == std::numeric_limits<uint32_t>::max()) {
643 emit failedToAddFriend(friendPk);
644 } else {
645 emit saveRequest();
646 emit friendAdded(friendId, friendPk);
647 }
648 }
649
650 /**
651 * @brief Checks that sending friendship request is correct and returns error message accordingly
652 * @param friendId Id of a friend which request is destined to
653 * @param message Friendship request message
654 * @return Returns empty string if sending request is correct, according error message otherwise
655 */
getFriendRequestErrorMessage(const ToxId & friendId,const QString & message) const656 QString Core::getFriendRequestErrorMessage(const ToxId& friendId, const QString& message) const
657 {
658 QMutexLocker ml{&coreLoopLock};
659
660 if (!friendId.isValid()) {
661 return tr("Invalid Tox ID", "Error while sending friendship request");
662 }
663
664 if (message.isEmpty()) {
665 return tr("You need to write a message with your request",
666 "Error while sending friendship request");
667 }
668
669 if (message.length() > static_cast<int>(tox_max_friend_request_length())) {
670 return tr("Your message is too long!", "Error while sending friendship request");
671 }
672
673 if (hasFriendWithPublicKey(friendId.getPublicKey())) {
674 return tr("Friend is already added", "Error while sending friendship request");
675 }
676
677 return QString{};
678 }
679
requestFriendship(const ToxId & friendId,const QString & message)680 void Core::requestFriendship(const ToxId& friendId, const QString& message)
681 {
682 QMutexLocker ml{&coreLoopLock};
683
684 ToxPk friendPk = friendId.getPublicKey();
685 QString errorMessage = getFriendRequestErrorMessage(friendId, message);
686 if (!errorMessage.isNull()) {
687 emit failedToAddFriend(friendPk, errorMessage);
688 emit saveRequest();
689 return;
690 }
691
692 ToxString cMessage(message);
693 uint32_t friendNumber =
694 tox_friend_add(tox.get(), friendId.getBytes(), cMessage.data(), cMessage.size(), nullptr);
695 if (friendNumber == std::numeric_limits<uint32_t>::max()) {
696 qDebug() << "Failed to request friendship";
697 emit failedToAddFriend(friendPk);
698 } else {
699 qDebug() << "Requested friendship of " << friendNumber;
700 emit friendAdded(friendNumber, friendPk);
701 emit requestSent(friendPk, message);
702 }
703
704 emit saveRequest();
705 }
706
sendMessageWithType(uint32_t friendId,const QString & message,Tox_Message_Type type,ReceiptNum & receipt)707 bool Core::sendMessageWithType(uint32_t friendId, const QString& message, Tox_Message_Type type,
708 ReceiptNum& receipt)
709 {
710 int size = message.toUtf8().size();
711 auto maxSize = tox_max_message_length();
712 if (size > maxSize) {
713 qCritical() << "Core::sendMessageWithType called with message of size:" << size
714 << "when max is:" << maxSize << ". Ignoring.";
715 return false;
716 }
717
718 ToxString cMessage(message);
719 Tox_Err_Friend_Send_Message error;
720 receipt = ReceiptNum{tox_friend_send_message(tox.get(), friendId, type, cMessage.data(),
721 cMessage.size(), &error)};
722 if (parseFriendSendMessageError(error)) {
723 return true;
724 }
725 return false;
726 }
727
sendMessage(uint32_t friendId,const QString & message,ReceiptNum & receipt)728 bool Core::sendMessage(uint32_t friendId, const QString& message, ReceiptNum& receipt)
729 {
730 QMutexLocker ml(&coreLoopLock);
731 return sendMessageWithType(friendId, message, TOX_MESSAGE_TYPE_NORMAL, receipt);
732 }
733
sendAction(uint32_t friendId,const QString & action,ReceiptNum & receipt)734 bool Core::sendAction(uint32_t friendId, const QString& action, ReceiptNum& receipt)
735 {
736 QMutexLocker ml(&coreLoopLock);
737 return sendMessageWithType(friendId, action, TOX_MESSAGE_TYPE_ACTION, receipt);
738 }
739
sendTyping(uint32_t friendId,bool typing)740 void Core::sendTyping(uint32_t friendId, bool typing)
741 {
742 QMutexLocker ml{&coreLoopLock};
743
744 if (!tox_self_set_typing(tox.get(), friendId, typing, nullptr)) {
745 emit failedToSetTyping(typing);
746 }
747 }
748
sendGroupMessageWithType(int groupId,const QString & message,Tox_Message_Type type)749 void Core::sendGroupMessageWithType(int groupId, const QString& message, Tox_Message_Type type)
750 {
751 QMutexLocker ml{&coreLoopLock};
752
753 int size = message.toUtf8().size();
754 auto maxSize = tox_max_message_length();
755 if (size > maxSize) {
756 qCritical() << "Core::sendMessageWithType called with message of size:" << size
757 << "when max is:" << maxSize << ". Ignoring.";
758 return;
759 }
760
761 ToxString cMsg(message);
762 Tox_Err_Conference_Send_Message error;
763 tox_conference_send_message(tox.get(), groupId, type, cMsg.data(), cMsg.size(), &error);
764 if (!parseConferenceSendMessageError(error)) {
765 emit groupSentFailed(groupId);
766 return;
767 }
768 }
769
sendGroupMessage(int groupId,const QString & message)770 void Core::sendGroupMessage(int groupId, const QString& message)
771 {
772 QMutexLocker ml{&coreLoopLock};
773
774 sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_NORMAL);
775 }
776
sendGroupAction(int groupId,const QString & message)777 void Core::sendGroupAction(int groupId, const QString& message)
778 {
779 QMutexLocker ml{&coreLoopLock};
780
781 sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_ACTION);
782 }
783
changeGroupTitle(int groupId,const QString & title)784 void Core::changeGroupTitle(int groupId, const QString& title)
785 {
786 QMutexLocker ml{&coreLoopLock};
787
788 ToxString cTitle(title);
789 Tox_Err_Conference_Title error;
790 bool success = tox_conference_set_title(tox.get(), groupId, cTitle.data(), cTitle.size(), &error);
791 if (success && error == TOX_ERR_CONFERENCE_TITLE_OK) {
792 emit saveRequest();
793 emit groupTitleChanged(groupId, getUsername(), title);
794 return;
795 }
796
797 qCritical() << "Fail of tox_conference_set_title";
798 switch (error) {
799 case TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND:
800 qCritical() << "Conference not found";
801 break;
802
803 case TOX_ERR_CONFERENCE_TITLE_FAIL_SEND:
804 qCritical() << "Conference title failed to send";
805 break;
806
807 case TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH:
808 qCritical() << "Invalid length";
809 break;
810
811 default:
812 break;
813 }
814 }
815
removeFriend(uint32_t friendId)816 void Core::removeFriend(uint32_t friendId)
817 {
818 QMutexLocker ml{&coreLoopLock};
819
820 if (!tox_friend_delete(tox.get(), friendId, nullptr)) {
821 emit failedToRemoveFriend(friendId);
822 return;
823 }
824
825 emit saveRequest();
826 emit friendRemoved(friendId);
827 }
828
removeGroup(int groupId)829 void Core::removeGroup(int groupId)
830 {
831 QMutexLocker ml{&coreLoopLock};
832
833 Tox_Err_Conference_Delete error;
834 bool success = tox_conference_delete(tox.get(), groupId, &error);
835 if (success && error == TOX_ERR_CONFERENCE_DELETE_OK) {
836 emit saveRequest();
837 av->leaveGroupCall(groupId);
838 return;
839 }
840
841 qCritical() << "Fail of tox_conference_delete";
842 switch (error) {
843 case TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND:
844 qCritical() << "Conference not found";
845 break;
846
847 default:
848 break;
849 }
850 }
851
852 /**
853 * @brief Returns our username, or an empty string on failure
854 */
getUsername() const855 QString Core::getUsername() const
856 {
857 QMutexLocker ml{&coreLoopLock};
858
859 QString sname;
860 if (!tox) {
861 return sname;
862 }
863
864 int size = tox_self_get_name_size(tox.get());
865 uint8_t* name = new uint8_t[size];
866 tox_self_get_name(tox.get(), name);
867 sname = ToxString(name, size).getQString();
868 delete[] name;
869 return sname;
870 }
871
setUsername(const QString & username)872 void Core::setUsername(const QString& username)
873 {
874 QMutexLocker ml{&coreLoopLock};
875
876 if (username == getUsername()) {
877 return;
878 }
879
880 ToxString cUsername(username);
881 if (!tox_self_set_name(tox.get(), cUsername.data(), cUsername.size(), nullptr)) {
882 emit failedToSetUsername(username);
883 return;
884 }
885
886 emit usernameSet(username);
887 emit saveRequest();
888 }
889
890 /**
891 * @brief Returns our Tox ID
892 */
getSelfId() const893 ToxId Core::getSelfId() const
894 {
895 QMutexLocker ml{&coreLoopLock};
896
897 uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00};
898 tox_self_get_address(tox.get(), friendId);
899 return ToxId(friendId, TOX_ADDRESS_SIZE);
900 }
901
902 /**
903 * @brief Gets self public key
904 * @return Self PK
905 */
getSelfPublicKey() const906 ToxPk Core::getSelfPublicKey() const
907 {
908 QMutexLocker ml{&coreLoopLock};
909
910 uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00};
911 tox_self_get_address(tox.get(), friendId);
912 return ToxPk(friendId);
913 }
914
915 /**
916 * @brief Returns our public and private keys
917 */
getKeypair() const918 QPair<QByteArray, QByteArray> Core::getKeypair() const
919 {
920 QMutexLocker ml{&coreLoopLock};
921
922 QPair<QByteArray, QByteArray> keypair;
923 assert(tox != nullptr);
924
925 QByteArray pk(TOX_PUBLIC_KEY_SIZE, 0x00);
926 QByteArray sk(TOX_SECRET_KEY_SIZE, 0x00);
927 tox_self_get_public_key(tox.get(), reinterpret_cast<uint8_t*>(pk.data()));
928 tox_self_get_secret_key(tox.get(), reinterpret_cast<uint8_t*>(sk.data()));
929 keypair.first = pk;
930 keypair.second = sk;
931 return keypair;
932 }
933
934 /**
935 * @brief Returns our status message, or an empty string on failure
936 */
getStatusMessage() const937 QString Core::getStatusMessage() const
938 {
939 QMutexLocker ml{&coreLoopLock};
940
941 assert(tox != nullptr);
942
943 size_t size = tox_self_get_status_message_size(tox.get());
944 if (size == 0) {
945 return {};
946 }
947 uint8_t* name = new uint8_t[size];
948 tox_self_get_status_message(tox.get(), name);
949 QString sname = ToxString(name, size).getQString();
950 delete[] name;
951 return sname;
952 }
953
954 /**
955 * @brief Returns our user status
956 */
getStatus() const957 Status::Status Core::getStatus() const
958 {
959 QMutexLocker ml{&coreLoopLock};
960
961 return static_cast<Status::Status>(tox_self_get_status(tox.get()));
962 }
963
setStatusMessage(const QString & message)964 void Core::setStatusMessage(const QString& message)
965 {
966 QMutexLocker ml{&coreLoopLock};
967
968 if (message == getStatusMessage()) {
969 return;
970 }
971
972 ToxString cMessage(message);
973 if (!tox_self_set_status_message(tox.get(), cMessage.data(), cMessage.size(), nullptr)) {
974 emit failedToSetStatusMessage(message);
975 return;
976 }
977
978 emit saveRequest();
979 emit statusMessageSet(message);
980 }
981
setStatus(Status::Status status)982 void Core::setStatus(Status::Status status)
983 {
984 QMutexLocker ml{&coreLoopLock};
985
986 Tox_User_Status userstatus;
987 switch (status) {
988 case Status::Status::Online:
989 userstatus = TOX_USER_STATUS_NONE;
990 break;
991
992 case Status::Status::Away:
993 userstatus = TOX_USER_STATUS_AWAY;
994 break;
995
996 case Status::Status::Busy:
997 userstatus = TOX_USER_STATUS_BUSY;
998 break;
999
1000 default:
1001 return;
1002 break;
1003 }
1004
1005 tox_self_set_status(tox.get(), userstatus);
1006 emit saveRequest();
1007 emit statusSet(status);
1008 }
1009
1010 /**
1011 * @brief Returns the unencrypted tox save data
1012 */
getToxSaveData()1013 QByteArray Core::getToxSaveData()
1014 {
1015 QMutexLocker ml{&coreLoopLock};
1016
1017 uint32_t fileSize = tox_get_savedata_size(tox.get());
1018 QByteArray data;
1019 data.resize(fileSize);
1020 tox_get_savedata(tox.get(), (uint8_t*)data.data());
1021 return data;
1022 }
1023
1024 // Declared to avoid code duplication
1025 #define GET_FRIEND_PROPERTY(property, function, checkSize) \
1026 const size_t property##Size = function##_size(tox.get(), ids[i], nullptr); \
1027 if ((!checkSize || property##Size) && property##Size != SIZE_MAX) { \
1028 uint8_t* prop = new uint8_t[property##Size]; \
1029 if (function(tox.get(), ids[i], prop, nullptr)) { \
1030 QString propStr = ToxString(prop, property##Size).getQString(); \
1031 emit friend##property##Changed(ids[i], propStr); \
1032 } \
1033 \
1034 delete[] prop; \
1035 }
1036
loadFriends()1037 void Core::loadFriends()
1038 {
1039 QMutexLocker ml{&coreLoopLock};
1040
1041 const size_t friendCount = tox_self_get_friend_list_size(tox.get());
1042 if (friendCount == 0) {
1043 return;
1044 }
1045
1046 uint32_t* ids = new uint32_t[friendCount];
1047 tox_self_get_friend_list(tox.get(), ids);
1048 uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00};
1049 for (size_t i = 0; i < friendCount; ++i) {
1050 if (!tox_friend_get_public_key(tox.get(), ids[i], friendPk, nullptr)) {
1051 continue;
1052 }
1053
1054 emit friendAdded(ids[i], ToxPk(friendPk));
1055 GET_FRIEND_PROPERTY(Username, tox_friend_get_name, true);
1056 GET_FRIEND_PROPERTY(StatusMessage, tox_friend_get_status_message, false);
1057 checkLastOnline(ids[i]);
1058 }
1059 delete[] ids;
1060 }
1061
loadGroups()1062 void Core::loadGroups()
1063 {
1064 QMutexLocker ml{&coreLoopLock};
1065
1066 const size_t groupCount = tox_conference_get_chatlist_size(tox.get());
1067 if (groupCount == 0) {
1068 return;
1069 }
1070
1071 auto groupNumbers = new uint32_t[groupCount];
1072 tox_conference_get_chatlist(tox.get(), groupNumbers);
1073
1074 for (size_t i = 0; i < groupCount; ++i) {
1075 TOX_ERR_CONFERENCE_TITLE error;
1076 QString name;
1077 const auto groupNumber = groupNumbers[i];
1078 size_t titleSize = tox_conference_get_title_size(tox.get(), groupNumber, &error);
1079 const GroupId persistentId = getGroupPersistentId(groupNumber);
1080 const QString defaultName = tr("Groupchat %1").arg(persistentId.toString().left(8));
1081 if (LogConferenceTitleError(error)) {
1082 name = defaultName;
1083 } else {
1084 QByteArray nameByteArray = QByteArray(static_cast<int>(titleSize), Qt::Uninitialized);
1085 tox_conference_get_title(tox.get(), groupNumber,
1086 reinterpret_cast<uint8_t*>(nameByteArray.data()), &error);
1087 if (LogConferenceTitleError(error)) {
1088 name = defaultName;
1089 } else {
1090 name = ToxString(nameByteArray).getQString();
1091 }
1092 }
1093 if (getGroupAvEnabled(groupNumber)) {
1094 if (toxav_groupchat_enable_av(tox.get(), groupNumber, CoreAV::groupCallCallback, this)) {
1095 qCritical() << "Failed to enable audio on loaded group" << groupNumber;
1096 }
1097 }
1098 emit emptyGroupCreated(groupNumber, persistentId, name);
1099 }
1100
1101 delete[] groupNumbers;
1102 }
1103
checkLastOnline(uint32_t friendId)1104 void Core::checkLastOnline(uint32_t friendId)
1105 {
1106 QMutexLocker ml{&coreLoopLock};
1107
1108 const uint64_t lastOnline = tox_friend_get_last_online(tox.get(), friendId, nullptr);
1109 if (lastOnline != std::numeric_limits<uint64_t>::max()) {
1110 emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline));
1111 }
1112 }
1113
1114 /**
1115 * @brief Returns the list of friendIds in our friendlist, an empty list on error
1116 */
getFriendList() const1117 QVector<uint32_t> Core::getFriendList() const
1118 {
1119 QMutexLocker ml{&coreLoopLock};
1120
1121 QVector<uint32_t> friends;
1122 friends.resize(tox_self_get_friend_list_size(tox.get()));
1123 tox_self_get_friend_list(tox.get(), friends.data());
1124 return friends;
1125 }
1126
1127 /**
1128 * @brief Print in console text of error.
1129 * @param error Error to handle.
1130 * @return True if no error, false otherwise.
1131 */
parsePeerQueryError(Tox_Err_Conference_Peer_Query error) const1132 bool Core::parsePeerQueryError(Tox_Err_Conference_Peer_Query error) const
1133 {
1134 switch (error) {
1135 case TOX_ERR_CONFERENCE_PEER_QUERY_OK:
1136 return true;
1137
1138 case TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND:
1139 qCritical() << "Conference not found";
1140 return false;
1141
1142 case TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION:
1143 qCritical() << "No connection";
1144 return false;
1145
1146 case TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND:
1147 qCritical() << "Peer not found";
1148 return false;
1149
1150 default:
1151 qCritical() << "Unknow error code:" << error;
1152 return false;
1153 }
1154 }
1155
getGroupPersistentId(uint32_t groupNumber) const1156 GroupId Core::getGroupPersistentId(uint32_t groupNumber) const
1157 {
1158 QMutexLocker ml{&coreLoopLock};
1159
1160 size_t conferenceIdSize = TOX_CONFERENCE_UID_SIZE;
1161 QByteArray groupPersistentId(conferenceIdSize, Qt::Uninitialized);
1162 if (tox_conference_get_id(tox.get(), groupNumber,
1163 reinterpret_cast<uint8_t*>(groupPersistentId.data()))) {
1164 return GroupId{groupPersistentId};
1165 } else {
1166 qCritical() << "Failed to get conference ID of group" << groupNumber;
1167 return {};
1168 }
1169 }
1170
1171 /**
1172 * @brief Get number of peers in the conference.
1173 * @return The number of peers in the conference. UINT32_MAX on failure.
1174 */
getGroupNumberPeers(int groupId) const1175 uint32_t Core::getGroupNumberPeers(int groupId) const
1176 {
1177 QMutexLocker ml{&coreLoopLock};
1178
1179 Tox_Err_Conference_Peer_Query error;
1180 uint32_t count = tox_conference_peer_count(tox.get(), groupId, &error);
1181 if (!parsePeerQueryError(error)) {
1182 return std::numeric_limits<uint32_t>::max();
1183 }
1184
1185 return count;
1186 }
1187
1188 /**
1189 * @brief Get the name of a peer of a group
1190 */
getGroupPeerName(int groupId,int peerId) const1191 QString Core::getGroupPeerName(int groupId, int peerId) const
1192 {
1193 QMutexLocker ml{&coreLoopLock};
1194
1195 // from tox.h: "If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference)."
1196 if (peerId == std::numeric_limits<uint32_t>::max()) {
1197 return {};
1198 }
1199
1200 Tox_Err_Conference_Peer_Query error;
1201 size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, peerId, &error);
1202 if (!parsePeerQueryError(error)) {
1203 return QString{};
1204 }
1205
1206 QByteArray name(length, Qt::Uninitialized);
1207 uint8_t* namePtr = reinterpret_cast<uint8_t*>(name.data());
1208 bool success = tox_conference_peer_get_name(tox.get(), groupId, peerId, namePtr, &error);
1209 if (!parsePeerQueryError(error)) {
1210 return QString{};
1211 }
1212 assert(success);
1213
1214 return ToxString(name).getQString();
1215 }
1216
1217 /**
1218 * @brief Get the public key of a peer of a group
1219 */
getGroupPeerPk(int groupId,int peerId) const1220 ToxPk Core::getGroupPeerPk(int groupId, int peerId) const
1221 {
1222 QMutexLocker ml{&coreLoopLock};
1223
1224 uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00};
1225 Tox_Err_Conference_Peer_Query error;
1226 bool success = tox_conference_peer_get_public_key(tox.get(), groupId, peerId, friendPk, &error);
1227 if (!parsePeerQueryError(error)) {
1228 return ToxPk{};
1229 }
1230 assert(success);
1231
1232 return ToxPk(friendPk);
1233 }
1234
1235 /**
1236 * @brief Get the names of the peers of a group
1237 */
getGroupPeerNames(int groupId) const1238 QStringList Core::getGroupPeerNames(int groupId) const
1239 {
1240 QMutexLocker ml{&coreLoopLock};
1241
1242 assert(tox != nullptr);
1243
1244 uint32_t nPeers = getGroupNumberPeers(groupId);
1245 if (nPeers == std::numeric_limits<uint32_t>::max()) {
1246 qWarning() << "getGroupPeerNames: Unable to get number of peers";
1247 return {};
1248 }
1249
1250 QStringList names;
1251 for (uint32_t i = 0; i < nPeers; ++i) {
1252 TOX_ERR_CONFERENCE_PEER_QUERY error;
1253 size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, i, &error);
1254 if (!parsePeerQueryError(error)) {
1255 names.append(QString());
1256 continue;
1257 }
1258
1259 QByteArray name(length, Qt::Uninitialized);
1260 uint8_t* namePtr = reinterpret_cast<uint8_t*>(name.data());
1261 bool ok = tox_conference_peer_get_name(tox.get(), groupId, i, namePtr, &error);
1262 if (ok && parsePeerQueryError(error)) {
1263 names.append(ToxString(name).getQString());
1264 } else {
1265 names.append(QString());
1266 }
1267 }
1268
1269 assert(names.size() == nPeers);
1270
1271 return names;
1272 }
1273
1274 /**
1275 * @brief Check, that group has audio or video stream
1276 * @param groupId Id of group to check
1277 * @return True for AV groups, false for text-only groups
1278 */
getGroupAvEnabled(int groupId) const1279 bool Core::getGroupAvEnabled(int groupId) const
1280 {
1281 QMutexLocker ml{&coreLoopLock};
1282 TOX_ERR_CONFERENCE_GET_TYPE error;
1283 TOX_CONFERENCE_TYPE type = tox_conference_get_type(tox.get(), groupId, &error);
1284 switch (error) {
1285 case TOX_ERR_CONFERENCE_GET_TYPE_OK:
1286 break;
1287 case TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND:
1288 qWarning() << "Conference not found";
1289 break;
1290 default:
1291 qWarning() << "Unknown error code:" << QString::number(error);
1292 break;
1293 }
1294
1295 return type == TOX_CONFERENCE_TYPE_AV;
1296 }
1297
1298 /**
1299 * @brief Print in console text of error.
1300 * @param error Error to handle.
1301 * @return True if no error, false otherwise.
1302 */
parseConferenceJoinError(Tox_Err_Conference_Join error) const1303 bool Core::parseConferenceJoinError(Tox_Err_Conference_Join error) const
1304 {
1305 switch (error) {
1306 case TOX_ERR_CONFERENCE_JOIN_OK:
1307 return true;
1308
1309 case TOX_ERR_CONFERENCE_JOIN_DUPLICATE:
1310 qCritical() << "Conference duplicate";
1311 return false;
1312
1313 case TOX_ERR_CONFERENCE_JOIN_FAIL_SEND:
1314 qCritical() << "Conference join failed to send";
1315 return false;
1316
1317 case TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND:
1318 qCritical() << "Friend not found";
1319 return false;
1320
1321 case TOX_ERR_CONFERENCE_JOIN_INIT_FAIL:
1322 qCritical() << "Init fail";
1323 return false;
1324
1325 case TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH:
1326 qCritical() << "Invalid length";
1327 return false;
1328
1329 case TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE:
1330 qCritical() << "Wrong conference type";
1331 return false;
1332
1333 default:
1334 qCritical() << "Unknow error code:" << error;
1335 return false;
1336 }
1337 }
1338
1339 /**
1340 * @brief Accept a groupchat invite.
1341 * @param inviteInfo Object which contains info about group invitation
1342 *
1343 * @return Conference number on success, UINT32_MAX on failure.
1344 */
joinGroupchat(const GroupInvite & inviteInfo)1345 uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo)
1346 {
1347 QMutexLocker ml{&coreLoopLock};
1348
1349 const uint32_t friendId = inviteInfo.getFriendId();
1350 const uint8_t confType = inviteInfo.getType();
1351 const QByteArray invite = inviteInfo.getInvite();
1352 const uint8_t* const cookie = reinterpret_cast<const uint8_t*>(invite.data());
1353 const size_t cookieLength = invite.length();
1354 uint32_t groupNum{std::numeric_limits<uint32_t>::max()};
1355 switch (confType) {
1356 case TOX_CONFERENCE_TYPE_TEXT: {
1357 qDebug() << QString("Trying to join text groupchat invite sent by friend %1").arg(friendId);
1358 Tox_Err_Conference_Join error;
1359 groupNum = tox_conference_join(tox.get(), friendId, cookie, cookieLength, &error);
1360 if (!parseConferenceJoinError(error)) {
1361 groupNum = std::numeric_limits<uint32_t>::max();
1362 }
1363 break;
1364 }
1365 case TOX_CONFERENCE_TYPE_AV: {
1366 qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendId);
1367 groupNum = toxav_join_av_groupchat(tox.get(), friendId, cookie, cookieLength,
1368 CoreAV::groupCallCallback, const_cast<Core*>(this));
1369 break;
1370 }
1371 default:
1372 qWarning() << "joinGroupchat: Unknown groupchat type " << confType;
1373 }
1374 if (groupNum != std::numeric_limits<uint32_t>::max()) {
1375 emit saveRequest();
1376 emit groupJoined(groupNum, getGroupPersistentId(groupNum));
1377 }
1378 return groupNum;
1379 }
1380
groupInviteFriend(uint32_t friendId,int groupId)1381 void Core::groupInviteFriend(uint32_t friendId, int groupId)
1382 {
1383 QMutexLocker ml{&coreLoopLock};
1384
1385 Tox_Err_Conference_Invite error;
1386 tox_conference_invite(tox.get(), friendId, groupId, &error);
1387
1388 switch (error) {
1389 case TOX_ERR_CONFERENCE_INVITE_OK:
1390 break;
1391
1392 case TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND:
1393 qCritical() << "Conference not found";
1394 break;
1395
1396 case TOX_ERR_CONFERENCE_INVITE_FAIL_SEND:
1397 qCritical() << "Conference invite failed to send";
1398 break;
1399
1400 default:
1401 break;
1402 }
1403 }
1404
createGroup(uint8_t type)1405 int Core::createGroup(uint8_t type)
1406 {
1407 QMutexLocker ml{&coreLoopLock};
1408
1409 if (type == TOX_CONFERENCE_TYPE_TEXT) {
1410 Tox_Err_Conference_New error;
1411 uint32_t groupId = tox_conference_new(tox.get(), &error);
1412
1413 switch (error) {
1414 case TOX_ERR_CONFERENCE_NEW_OK:
1415 emit saveRequest();
1416 emit emptyGroupCreated(groupId, getGroupPersistentId(groupId));
1417 return groupId;
1418
1419 case TOX_ERR_CONFERENCE_NEW_INIT:
1420 qCritical() << "The conference instance failed to initialize";
1421 return std::numeric_limits<uint32_t>::max();
1422
1423 default:
1424 return std::numeric_limits<uint32_t>::max();
1425 }
1426 } else if (type == TOX_CONFERENCE_TYPE_AV) {
1427 uint32_t groupId = toxav_add_av_groupchat(tox.get(), CoreAV::groupCallCallback, this);
1428 emit saveRequest();
1429 emit emptyGroupCreated(groupId, getGroupPersistentId(groupId));
1430 return groupId;
1431 } else {
1432 qWarning() << "createGroup: Unknown type " << type;
1433 return -1;
1434 }
1435 }
1436
1437 /**
1438 * @brief Checks if a friend is online. Unknown friends are considered offline.
1439 */
isFriendOnline(uint32_t friendId) const1440 bool Core::isFriendOnline(uint32_t friendId) const
1441 {
1442 QMutexLocker ml{&coreLoopLock};
1443
1444 Tox_Connection connection = tox_friend_get_connection_status(tox.get(), friendId, nullptr);
1445 return connection != TOX_CONNECTION_NONE;
1446 }
1447
1448 /**
1449 * @brief Checks if we have a friend by public key
1450 */
hasFriendWithPublicKey(const ToxPk & publicKey) const1451 bool Core::hasFriendWithPublicKey(const ToxPk& publicKey) const
1452 {
1453 QMutexLocker ml{&coreLoopLock};
1454
1455 if (publicKey.isEmpty()) {
1456 return false;
1457 }
1458
1459 // TODO: error handling
1460 uint32_t friendId = tox_friend_by_public_key(tox.get(), publicKey.getData(), nullptr);
1461 return friendId != std::numeric_limits<uint32_t>::max();
1462 }
1463
1464 /**
1465 * @brief Get the public key part of the ToxID only
1466 */
getFriendPublicKey(uint32_t friendNumber) const1467 ToxPk Core::getFriendPublicKey(uint32_t friendNumber) const
1468 {
1469 QMutexLocker ml{&coreLoopLock};
1470
1471 uint8_t rawid[TOX_PUBLIC_KEY_SIZE];
1472 if (!tox_friend_get_public_key(tox.get(), friendNumber, rawid, nullptr)) {
1473 qWarning() << "getFriendPublicKey: Getting public key failed";
1474 return ToxPk();
1475 }
1476
1477 return ToxPk(rawid);
1478 }
1479
1480 /**
1481 * @brief Get the username of a friend
1482 */
getFriendUsername(uint32_t friendnumber) const1483 QString Core::getFriendUsername(uint32_t friendnumber) const
1484 {
1485 QMutexLocker ml{&coreLoopLock};
1486
1487 size_t namesize = tox_friend_get_name_size(tox.get(), friendnumber, nullptr);
1488 if (namesize == SIZE_MAX) {
1489 qWarning() << "getFriendUsername: Failed to get name size for friend " << friendnumber;
1490 return QString();
1491 }
1492
1493 uint8_t* name = new uint8_t[namesize];
1494 tox_friend_get_name(tox.get(), friendnumber, name, nullptr);
1495 ToxString sname(name, namesize);
1496 delete[] name;
1497 return sname.getQString();
1498 }
1499
splitMessage(const QString & message)1500 QStringList Core::splitMessage(const QString& message)
1501 {
1502 QStringList splittedMsgs;
1503 QByteArray ba_message{message.toUtf8()};
1504
1505 /*
1506 * TODO: Remove this hack; the reported max message length we receive from c-toxcore
1507 * as of 08-02-2019 is inaccurate, causing us to generate too large messages when splitting
1508 * them up.
1509 *
1510 * The inconsistency lies in c-toxcore group.c:2480 using MAX_GROUP_MESSAGE_DATA_LEN to verify
1511 * message size is within limit, but tox_max_message_length giving a different size limit to us.
1512 *
1513 * (uint32_t tox_max_message_length(void); declared in tox.h, unable to see explicit definition)
1514 */
1515 const auto maxLen = tox_max_message_length() - 50;
1516
1517 while (ba_message.size() > maxLen) {
1518 int splitPos = ba_message.lastIndexOf('\n', maxLen - 1);
1519
1520 if (splitPos <= 0) {
1521 splitPos = ba_message.lastIndexOf(' ', maxLen - 1);
1522 }
1523
1524 if (splitPos <= 0) {
1525 constexpr uint8_t firstOfMultiByteMask = 0xC0;
1526 constexpr uint8_t multiByteMask = 0x80;
1527 splitPos = maxLen;
1528 // don't split a utf8 character
1529 if ((ba_message[splitPos] & multiByteMask) == multiByteMask) {
1530 while ((ba_message[splitPos] & firstOfMultiByteMask) != firstOfMultiByteMask) {
1531 --splitPos;
1532 }
1533 }
1534 --splitPos;
1535 }
1536 splittedMsgs.append(QString{ba_message.left(splitPos + 1)});
1537 ba_message = ba_message.mid(splitPos + 1);
1538 }
1539
1540 splittedMsgs.append(QString{ba_message});
1541 return splittedMsgs;
1542 }
1543
getPeerName(const ToxPk & id) const1544 QString Core::getPeerName(const ToxPk& id) const
1545 {
1546 QMutexLocker ml{&coreLoopLock};
1547
1548 QString name;
1549 uint32_t friendId = tox_friend_by_public_key(tox.get(), id.getData(), nullptr);
1550 if (friendId == std::numeric_limits<uint32_t>::max()) {
1551 qWarning() << "getPeerName: No such peer";
1552 return name;
1553 }
1554
1555 const size_t nameSize = tox_friend_get_name_size(tox.get(), friendId, nullptr);
1556 if (nameSize == SIZE_MAX) {
1557 return name;
1558 }
1559
1560 uint8_t* cname = new uint8_t[nameSize < tox_max_name_length() ? tox_max_name_length() : nameSize];
1561 if (!tox_friend_get_name(tox.get(), friendId, cname, nullptr)) {
1562 qWarning() << "getPeerName: Can't get name of friend " + QString().setNum(friendId);
1563 delete[] cname;
1564 return name;
1565 }
1566
1567 name = ToxString(cname, nameSize).getQString();
1568 delete[] cname;
1569 return name;
1570 }
1571
1572 /**
1573 * @brief Sets the NoSpam value to prevent friend request spam
1574 * @param nospam an arbitrary which becomes part of the Tox ID
1575 */
setNospam(uint32_t nospam)1576 void Core::setNospam(uint32_t nospam)
1577 {
1578 QMutexLocker ml{&coreLoopLock};
1579
1580 tox_self_set_nospam(tox.get(), nospam);
1581 emit idSet(getSelfId());
1582 }
1583