core.cpp 17.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * core.cpp
 * Copyright (C) 2010-2018 Belledonne Communications SARL
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

20 21 22 23
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

24
#include <algorithm>
Benjamin REIS's avatar
Benjamin REIS committed
25 26
#include <iterator>

27
#include <mediastreamer2/mscommon.h>
28 29

#ifdef HAVE_ADVANCED_IM
30
#include <xercesc/util/PlatformUtils.hpp>
31
#endif
32 33 34 35 36 37 38

#include "address/address-p.h"
#include "call/call.h"
#include "chat/encryption/encryption-engine.h"
#ifdef HAVE_LIME_X3DH
#include "chat/encryption/lime-x3dh-encryption-engine.h"
#endif
39
#ifdef HAVE_ADVANCED_IM
40 41
#include "conference/handlers/local-conference-list-event-handler.h"
#include "conference/handlers/remote-conference-list-event-handler.h"
42
#endif
43 44 45 46
#include "core/core-listener.h"
#include "core/core-p.h"
#include "logger/logger.h"
#include "paths/paths.h"
47 48 49
#include "linphone/utils/utils.h"
#include "linphone/utils/algorithm.h"
#include "linphone/lpconfig.h"
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

// TODO: Remove me later.
#include "c-wrapper/c-wrapper.h"
#include "private.h"

#define LINPHONE_DB "linphone.db"

// =============================================================================

using namespace std;

LINPHONE_BEGIN_NAMESPACE

void CorePrivate::init () {
	L_Q();
65

66
	mainDb.reset(new MainDb(q->getSharedFromThis()));
67
#ifdef HAVE_ADVANCED_IM
68 69
	remoteListEventHandler = makeUnique<RemoteConferenceListEventHandler>(q->getSharedFromThis());
	localListEventHandler = makeUnique<LocalConferenceListEventHandler>(q->getSharedFromThis());
70
#endif
71

72 73 74 75 76 77 78 79 80 81
	if (linphone_factory_is_database_storage_available(linphone_factory_get())) {
		AbstractDb::Backend backend;
		string uri = L_C_TO_STRING(lp_config_get_string(linphone_core_get_config(L_GET_C_BACK_PTR(q)), "storage", "uri", nullptr));
		if (!uri.empty())
			backend = strcmp(lp_config_get_string(linphone_core_get_config(L_GET_C_BACK_PTR(q)), "storage", "backend", "sqlite3"), "mysql") == 0
				? MainDb::Mysql
				: MainDb::Sqlite3;
		else {
			backend = AbstractDb::Sqlite3;
			uri = q->getDataPath() + LINPHONE_DB;
82
		}
83

84 85 86
		if (uri != "null"){ //special uri "null" means don't open database. We need this for tests.
			if (backend == MainDb::Mysql && uri.find("charset") == string::npos) {
				lInfo() << "No charset defined forcing utf8 4 bytes specially for conference subjet storage";
87
				uri += " charset=utf8mb4";
88 89 90 91 92 93 94 95 96 97 98
			}
			lInfo() << "Opening linphone database " << uri << " with backend " << backend;
			if (!mainDb->connect(backend, uri)) {
				ostringstream os;
				os << "Unable to open linphone database with uri " << uri << " and backend " << backend;
				throw DatabaseConnectionFailure(os.str());
			}

			loadChatRooms();
		} else lWarning() << "Database explicitely not requested, this Core is built with no database support.";
	}
99 100

	isFriendListSubscriptionEnabled = !!lp_config_get_int(linphone_core_get_config(L_GET_C_BACK_PTR(q)), "net", "friendlist_subscription_enabled", 1);
101
}
102

103 104 105
void CorePrivate::registerListener (CoreListener *listener) {
	listeners.push_back(listener);
}
106

107 108 109
void CorePrivate::unregisterListener (CoreListener *listener) {
	listeners.remove(listener);
}
110

111 112 113 114 115 116
void CorePrivate::uninit () {
	L_Q();
	while (!calls.empty()) {
		calls.front()->terminate();
		linphone_core_iterate(L_GET_C_BACK_PTR(q));
		ms_usleep(10000);
117
	}
118

119 120
	chatRoomsById.clear();
	noCreatedClientGroupChatRooms.clear();
121 122 123 124
	listeners.clear();
	if (q->limeX3dhEnabled()) {
		q->enableLimeX3dh(false);
	}
125

126
#ifdef HAVE_ADVANCED_IM
127 128
	remoteListEventHandler = nullptr;
	localListEventHandler = nullptr;
129
#endif
Benjamin REIS's avatar
Benjamin REIS committed
130

131
	AddressPrivate::clearSipAddressesCache();
jehan's avatar
jehan committed
132 133 134
	if (mainDb != nullptr) {
		mainDb->disconnect();
	}
135
}
136

137
// -----------------------------------------------------------------------------
138

139
void CorePrivate::notifyGlobalStateChanged (LinphoneGlobalState state) {
140
	auto listenersCopy = listeners; // Allow removal of a listener in its own call
141 142 143
	for (const auto &listener : listenersCopy)
		listener->onGlobalStateChanged(state);
}
144

145
void CorePrivate::notifyNetworkReachable (bool sipNetworkReachable, bool mediaNetworkReachable) {
146
	auto listenersCopy = listeners; // Allow removal of a listener in its own call
147 148 149
	for (const auto &listener : listenersCopy)
		listener->onNetworkReachable(sipNetworkReachable, mediaNetworkReachable);
}
150

151
void CorePrivate::notifyRegistrationStateChanged (LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const string &message) {
152
	auto listenersCopy = listeners; // Allow removal of a listener in its own call
153 154 155
	for (const auto &listener : listenersCopy)
		listener->onRegistrationStateChanged(cfg, state, message);
}
156

157 158 159
void CorePrivate::notifyEnteringBackground () {
	if (isInBackground)
		return;
160

161
	isInBackground = true;
162
	auto listenersCopy = listeners; // Allow removal of a listener in its own call
163 164
	for (const auto &listener : listenersCopy)
		listener->onEnteringBackground();
165 166 167

	if (isFriendListSubscriptionEnabled)
		enableFriendListsSubscription(false);
168
}
169

170
void CorePrivate::notifyEnteringForeground () {
171
	L_Q();
172 173
	if (!isInBackground)
		return;
174

175
	isInBackground = false;
176 177 178 179 180

	LinphoneCore *lc = L_GET_C_BACK_PTR(q);
	LinphoneProxyConfig *lpc = linphone_core_get_default_proxy_config(lc);
	if (lpc && linphone_proxy_config_get_state(lpc) == LinphoneRegistrationFailed) {
		// This is to ensure an app bring to foreground that isn't registered correctly will try to fix that and not show a red registration dot to the user
181
		linphone_proxy_config_refresh_register(lpc);
182 183
	}

184
	auto listenersCopy = listeners; // Allow removal of a listener in its own call
185 186
	for (const auto &listener : listenersCopy)
		listener->onEnteringForeground();
187 188 189

	if (isFriendListSubscriptionEnabled)
		enableFriendListsSubscription(true);	
190
}
191

192 193 194 195 196 197 198 199 200
belle_sip_main_loop_t *CorePrivate::getMainLoop(){
	L_Q();
	return belle_sip_stack_get_main_loop(static_cast<belle_sip_stack_t*>(q->getCCore()->sal->getStackImpl()));
}

void CorePrivate::doLater(const std::function<void ()> &something){
	belle_sip_main_loop_cpp_do_later(getMainLoop(), something);
}

201 202 203 204 205 206 207 208 209 210 211
void CorePrivate::enableFriendListsSubscription(bool enable) {
	L_Q();

	LinphoneCore *lc = L_GET_C_BACK_PTR(q);
	bctbx_list_t *elem;
	for (elem = lc->friends_lists; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriendList *list = (LinphoneFriendList *)elem->data;
		linphone_friend_list_enable_subscriptions(list, enable);
	}
}

212 213 214 215 216 217
bool CorePrivate::basicToFlexisipChatroomMigrationEnabled()const{
	L_Q();
	return linphone_config_get_bool(linphone_core_get_config(q->getCCore()), "misc", "enable_basic_to_client_group_chat_room_migration", FALSE);
}


218
// =============================================================================
219

220 221 222
Core::Core () : Object(*new CorePrivate) {
	L_D();
	d->imee.reset();
223
#ifdef HAVE_ADVANCED_IM
224
	xercesc::XMLPlatformUtils::Initialize();
225
#endif
226
}
227

228 229
Core::~Core () {
	lInfo() << "Destroying core: " << this;
230
#ifdef HAVE_ADVANCED_IM
231
	xercesc::XMLPlatformUtils::Terminate();
232
#endif
233
}
234

235 236 237 238 239 240
shared_ptr<Core> Core::create (LinphoneCore *cCore) {
	// Do not use `make_shared` => Private constructor.
	shared_ptr<Core> core = shared_ptr<Core>(new Core);
	L_SET_CPP_PTR_FROM_C_OBJECT(cCore, core);
	return core;
}
241

242 243 244
// ---------------------------------------------------------------------------
// Application lifecycle.
// ---------------------------------------------------------------------------
245

246 247 248 249
void Core::enterBackground () {
	L_D();
	d->notifyEnteringBackground();
}
Ronan's avatar
Ronan committed
250

251 252 253 254
void Core::enterForeground () {
	L_D();
	d->notifyEnteringForeground();
}
255

256 257 258 259 260
bool Core::isInBackground () {
	L_D();
	return d->isInBackground;
}

261 262 263
// ---------------------------------------------------------------------------
// C-Core.
// ---------------------------------------------------------------------------
264

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
LinphoneCore *Core::getCCore () const {
	return L_GET_C_BACK_PTR(this);
}

// -----------------------------------------------------------------------------
// Paths.
// -----------------------------------------------------------------------------

string Core::getDataPath () const {
	return Paths::getPath(Paths::Data, static_cast<PlatformHelpers *>(L_GET_C_BACK_PTR(this)->platform_helper));
}

string Core::getConfigPath () const {
	return Paths::getPath(Paths::Config, static_cast<PlatformHelpers *>(L_GET_C_BACK_PTR(this)->platform_helper));
}
280 281 282 283

string Core::getDownloadPath() const {
	return Paths::getPath(Paths::Download, static_cast<PlatformHelpers *>(L_GET_C_BACK_PTR(this)->platform_helper));
}
284

285
void Core::setEncryptionEngine (EncryptionEngine *imee) {
286
	L_D();
287 288 289 290
	CoreListener *listener = dynamic_cast<CoreListener *>(getEncryptionEngine());
	if (listener) {
		d->unregisterListener(listener);
	}
291
	d->imee.reset(imee);
292 293
}

294
EncryptionEngine *Core::getEncryptionEngine () const {
295
	L_D();
296
	return d->imee.get();
297 298
}

299
void Core::enableLimeX3dh (bool enable) {
300
#ifdef HAVE_LIME_X3DH
301 302
	L_D();
	if (!enable) {
303 304 305 306 307
		if (d->imee != nullptr) {
			CoreListener *listener = dynamic_cast<CoreListener *>(getEncryptionEngine());
			if (listener) {
				d->unregisterListener(listener);
			}
308
			d->imee.release();
309
		}
310
		removeSpec("lime");
311 312
		return;
	}
313

314 315
	if (limeX3dhEnabled())
		return;
316

317 318 319 320 321 322
	if (d->imee != nullptr) {
		lWarning() << "Enabling LIME X3DH over previous non LIME X3DH encryption engine";
		CoreListener *listener = dynamic_cast<CoreListener *>(getEncryptionEngine());
		if (listener) {
			d->unregisterListener(listener);
		}
323
		d->imee.release();
324
	}
325

326 327
	if (d->imee == nullptr) {
		LinphoneConfig *lpconfig = linphone_core_get_config(getCCore());
328
		string serverUrl = lp_config_get_string(lpconfig, "lime", "lime_server_url", lp_config_get_string(lpconfig, "lime", "x3dh_server_url", ""));
Nicolas Michon's avatar
Nicolas Michon committed
329
		if (serverUrl.empty()) {
jehan's avatar
jehan committed
330
			lInfo() << "Lime X3DH server URL not set, can't enable";
Nicolas Michon's avatar
Nicolas Michon committed
331 332 333
			//Do not enable encryption engine if url is undefined
			return;
		}
334 335 336 337
		string dbAccess = lp_config_get_string(lpconfig, "lime", "x3dh_db_path", "");
		if (dbAccess.empty()) {
			dbAccess = getDataPath() + "x3dh.c25519.sqlite3";
		}
338 339
		belle_http_provider_t *prov = linphone_core_get_http_provider(getCCore());

Nicolas Michon's avatar
Nicolas Michon committed
340
		LimeX3dhEncryptionEngine *engine = new LimeX3dhEncryptionEngine(dbAccess, serverUrl, prov, getSharedFromThis());
341 342
		setEncryptionEngine(engine);
		d->registerListener(engine);
343
		addSpec("lime");
344
	}
345 346 347
#else
	lWarning() << "Lime X3DH support is not available";
#endif
348 349
}

Nicolas Michon's avatar
Nicolas Michon committed
350 351 352 353 354 355
//Note: this will re-initialise	or start x3dh encryption engine if url is different from existing one
void Core::setX3dhServerUrl(const std::string &url) {
	if (!limeX3dhAvailable()) {
		return;
	}
	LinphoneConfig *lpconfig = linphone_core_get_config(getCCore());
356 357 358
	string prevUrl = lp_config_get_string(lpconfig, "lime", "lime_server_url", lp_config_get_string(lpconfig, "lime", "x3dh_server_url", ""));
	lp_config_set_string(lpconfig, "lime", "lime_server_url", url.c_str());
	lp_config_clean_entry(lpconfig, "lime", "x3dh_server_url");
Nicolas Michon's avatar
Nicolas Michon committed
359 360 361 362 363 364 365 366 367
	if (url.empty()) {
		enableLimeX3dh(false);
	} else if (url.compare(prevUrl)) {
		//Force re-initialisation
		enableLimeX3dh(false);
		enableLimeX3dh(true);
	}
}

368 369 370 371 372 373
std::string Core::getX3dhServerUrl() const {
	LinphoneConfig *lpconfig = linphone_core_get_config(getCCore());
	string serverUrl = lp_config_get_string(lpconfig, "lime", "lime_server_url", lp_config_get_string(lpconfig, "lime", "x3dh_server_url", ""));
	return serverUrl;
}

374
bool Core::limeX3dhEnabled () const {
375
#ifdef HAVE_LIME_X3DH
376 377 378
	L_D();
	if (d->imee && d->imee->getEngineType() == EncryptionEngine::EngineType::LimeX3dh)
		return true;
379
#endif
380
	return false;
381 382
}

383 384 385 386 387 388
bool Core::limeX3dhAvailable() const {
#ifdef HAVE_LIME_X3DH
	return true;
#else
	return false;
#endif
389 390
}

391 392 393 394 395 396
// -----------------------------------------------------------------------------
// Specs.
// -----------------------------------------------------------------------------
void Core::setSpecsList (const std::list<std::string> &specsList) {
	L_D();
	d->specs = specsList;
397
	d->specs.sort();
398
	d->specs.unique();
399
	const string &tmpSpecs = getSpecs();
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
	LinphoneConfig *lpconfig = linphone_core_get_config(getCCore());
	linphone_config_set_string(lpconfig, "sip", "linphone_specs", tmpSpecs.c_str());
	getCCore()->sal->setContactLinphoneSpecs(tmpSpecs);
}

void Core::addSpec (const std::string &spec) {
	L_D();
	d->specs.push_back(spec);
	setSpecsList(d->specs);
}

void Core::removeSpec(const std::string &pSpec) {
	L_D();
	d->specs.remove_if([&pSpec](const std::string &spec) { return spec.compare(pSpec) == 0; });
	setSpecsList(d->specs);
}

const std::list<std::string> &Core::getSpecsList () const {
	L_D();
	return d->specs;
}

422
//Used to set specs for linphone_config
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
void Core::setSpecs (const std::string &pSpecs) {
	L_D();
	if (pSpecs.empty()) {
		d->specs.clear();
		setSpecsList(d->specs);
	} else {
		//Assume a list of coma-separated values
		setSpecsList(Utils::toList(Utils::split(pSpecs, ",")));
	}
}

//Initial use of the public API of this function has been deprecated, but will still be kept as utility function for setSpecsList()
std::string Core::getSpecs() const {
	L_D();
	return Utils::join(Utils::toVector(d->specs), ",");
}

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
// ---------------------------------------------------------------------------
// Friends.
// ---------------------------------------------------------------------------

void Core::enableFriendListSubscription (bool enable) {
	L_D();
	if (d->isFriendListSubscriptionEnabled != enable) {
		d->isFriendListSubscriptionEnabled = enable;
		lp_config_set_int(linphone_core_get_config(getCCore()), "net", "friendlist_subscription_enabled", enable ? 1 : 0);
	}
	d->enableFriendListsSubscription(enable);
}

bool Core::isFriendListSubscriptionEnabled () const {
	L_D();
	return d->isFriendListSubscriptionEnabled;
}

458 459 460 461
// -----------------------------------------------------------------------------
// Misc.
// -----------------------------------------------------------------------------

462 463 464 465 466 467
void Core::pushNotificationReceived () const {
	LinphoneCore *lc = getCCore();
	const bctbx_list_t *proxies = linphone_core_get_proxy_config_list(lc);
	bctbx_list_t *it = (bctbx_list_t *)proxies;

	lInfo() << "Push notification received";
468 469 470 471 472

	// We can assume network should be reachable when a push notification is received.
	// If the app was put in DOZE mode, internal network reachability will have been disabled and thus may prevent registration 
	linphone_core_set_network_reachable_internal(lc, TRUE);

473 474 475 476 477 478 479 480 481 482 483 484 485 486
	/*
	 * The following is a bit hacky. But sometimes 3 lines of code are better than
	 * a heavy refactoring.
	 * pushNotificationReceived() is a critical piece of code where any action to reconnect to the
	 * SIP server must be taken immediately, which, thanks to belle-sip transactions automatically
	 * starts a background task that prevents iOS and Android systems to suspend the process.
	 */
	linphone_core_iterate(lc); // First iterate to handle disconnection errors on sockets
	linphone_core_iterate(lc); // Second iterate required by belle-sip to notify about disconnections
	linphone_core_iterate(lc); // Third iterate required by refresher to restart a connection/registration if needed.
	/*
	 * Finally if any of the connection is already pending a retry, the following code will request an immediate
	 * attempt to connect and register.
	 */
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
	while (it) {
		LinphoneProxyConfig *proxy = (LinphoneProxyConfig *) bctbx_list_get_data(it);
		LinphoneRegistrationState state = linphone_proxy_config_get_state(proxy);
		if (state == LinphoneRegistrationFailed) {
			lInfo() << "Proxy config [" << proxy << "] is in failed state, refreshing REGISTER";
			if (linphone_proxy_config_register_enabled(proxy) && linphone_proxy_config_get_expires(proxy) > 0) {
				linphone_proxy_config_refresh_register(proxy);
			}
		} else if (state == LinphoneRegistrationOk) {
			// TODO: send a keep-alive to ensure the socket isn't broken
		}
		it = bctbx_list_next(it);
	}
}

502 503 504 505 506 507 508 509
int Core::getUnreadChatMessageCount () const {
	L_D();
	return d->mainDb->getUnreadChatMessageCount();
}

int Core::getUnreadChatMessageCount (const IdentityAddress &localAddress) const {
	L_D();
	int count = 0;
510 511
	for (auto it = d->chatRoomsById.begin(); it != d->chatRoomsById.end(); it++) {
		const auto &chatRoom = it->second;
512 513
		if (chatRoom->getLocalAddress() == localAddress)
			count += chatRoom->getUnreadChatMessageCount();
514
	}
515 516 517 518 519 520 521
	return count;
}

int Core::getUnreadChatMessageCountFromActiveLocals () const {
	L_D();

	int count = 0;
522 523 524 525 526 527 528 529 530
	for (auto it = d->chatRoomsById.begin(); it != d->chatRoomsById.end(); it++) {
		const auto &chatRoom = it->second;
		for (auto it = linphone_core_get_proxy_config_list(getCCore()); it != NULL; it = it->next) {
			LinphoneProxyConfig *cfg = (LinphoneProxyConfig *)it->data;
			const LinphoneAddress *identityAddr = linphone_proxy_config_get_identity_address(cfg);
			if (L_GET_CPP_PTR_FROM_C_OBJECT(identityAddr)->weakEqual(chatRoom->getLocalAddress())) {
				count += chatRoom->getUnreadChatMessageCount();
			}
		}
531 532 533 534
	}
	return count;
}

535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
// -----------------------------------------------------------------------------

Address Core::interpretUrl (const std::string &url) const {
	LinphoneAddress *cAddress = linphone_core_interpret_url(getCCore(), url.c_str());
	if (!cAddress) return Address();

	char *str = linphone_address_as_string(cAddress);
	linphone_address_unref(cAddress);

	Address address(str);
	bctbx_free(str);

	return address;
}

550 551 552 553 554
void Core::doLater(const std::function<void ()> &something){
	getPrivate()->doLater(something);
}


Ronan's avatar
Ronan committed
555
LINPHONE_END_NAMESPACE