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