client-group-chat-room.cpp 30.2 KB
Newer Older
1 2
/*
 * client-group-chat-room.cpp
Ronan's avatar
Ronan committed
3
 * Copyright (C) 2010-2018 Belledonne Communications SARL
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * 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.
9 10 11 12 13 14 15
 *
 * 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
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 19
 */

20 21
#include <algorithm>

22 23
#include "linphone/utils/utils.h"

24
#include "address/address-p.h"
25
#include "basic-to-client-group-chat-room.h"
26
#include "chat/encryption/lime-v2.h"
27
#include "c-wrapper/c-wrapper.h"
28
#include "client-group-chat-room-p.h"
29 30
#include "conference/handlers/remote-conference-event-handler-p.h"
#include "conference/handlers/remote-conference-list-event-handler.h"
31
#include "conference/participant-p.h"
32
#include "conference/participant-device.h"
33 34
#include "conference/remote-conference-p.h"
#include "conference/session/call-session-p.h"
35 36
#include "content/content-disposition.h"
#include "content/content-type.h"
37
#include "core/core-p.h"
38
#include "logger/logger.h"
39
#include "sal/refer-op.h"
40

Ronan's avatar
Ronan committed
41
// =============================================================================
42

43 44 45 46
using namespace std;

LINPHONE_BEGIN_NAMESPACE

47 48
// -----------------------------------------------------------------------------

49
list<IdentityAddress> ClientGroupChatRoomPrivate::cleanAddressesList (const list<IdentityAddress> &addresses) const {
50
	L_Q();
51
	list<IdentityAddress> cleanedList(addresses);
52 53 54
	cleanedList.sort();
	cleanedList.unique();
	for (auto it = cleanedList.begin(); it != cleanedList.end();) {
55
		if (q->findParticipant(*it) || (q->getMe()->getAddress() == *it))
56 57 58 59 60 61 62
			it = cleanedList.erase(it);
		else
			it++;
	}
	return cleanedList;
}

63 64
shared_ptr<CallSession> ClientGroupChatRoomPrivate::createSession () {
	L_Q();
65
	L_Q_T(RemoteConference, qConference);
66

67 68
	CallSessionParams csp;
	csp.addCustomHeader("Require", "recipient-list-invite");
69
	csp.addCustomContactParameter("text");
70 71
	if (capabilities & ClientGroupChatRoom::Capabilities::OneToOne)
		csp.addCustomHeader("One-To-One-Chat-Room", "true");
72

73 74 75 76 77
	ParticipantPrivate *dFocus = qConference->getPrivate()->focus->getPrivate();
	shared_ptr<CallSession> session = dFocus->createSession(*q, &csp, false, callSessionListener);
	Address myCleanedAddress(q->getMe()->getAddress());
	myCleanedAddress.removeUriParam("gr"); // Remove gr parameter for INVITE.
	session->configure(LinphoneCallOutgoing, nullptr, nullptr, myCleanedAddress, dFocus->getDevices().front()->getAddress());
78
	session->initiateOutgoing();
Ghislain MARY's avatar
Ghislain MARY committed
79
	session->getPrivate()->createOp();
80 81 82
	return session;
}

Ghislain MARY's avatar
Ghislain MARY committed
83
void ClientGroupChatRoomPrivate::notifyReceived (const string &body) {
84 85
	L_Q_T(RemoteConference, qConference);
	qConference->getPrivate()->eventHandler->notifyReceived(body);
86 87
}

88 89 90 91 92
void ClientGroupChatRoomPrivate::multipartNotifyReceived (const string &body) {
	L_Q_T(RemoteConference, qConference);
	qConference->getPrivate()->eventHandler->multipartNotifyReceived(body);
}

93 94
// -----------------------------------------------------------------------------

95 96 97
void ClientGroupChatRoomPrivate::setCallSessionListener (CallSessionListener *listener) {
	L_Q();
	L_Q_T(RemoteConference, qConference);
98

99 100 101 102 103 104 105 106 107 108 109
	callSessionListener = listener;
	shared_ptr<CallSession> session = qConference->getPrivate()->focus->getPrivate()->getSession();
	if (session)
		session->getPrivate()->setCallSessionListener(listener);
	for (const auto &participant : q->getParticipants()) {
		session = participant->getPrivate()->getSession();
		if (session)
			session->getPrivate()->setCallSessionListener(listener);
	}
}

110 111 112 113 114
unsigned int ClientGroupChatRoomPrivate::getLastNotifyId () const {
	L_Q_T(RemoteConference, qConference);
	return qConference->getPrivate()->eventHandler->getLastNotify();
}

115 116
// -----------------------------------------------------------------------------

117 118 119 120 121 122 123
void ClientGroupChatRoomPrivate::confirmJoining (SalCallOp *op) {
	L_Q();
	L_Q_T(RemoteConference, qConference);

	auto focus = qConference->getPrivate()->focus;
	bool previousSession = (focus->getPrivate()->getSession() != nullptr);
	auto session = focus->getPrivate()->createSession(*q, nullptr, false, this);
124
	session->configure(LinphoneCallIncoming, nullptr, op, Address(op->getFrom()), Address(op->getTo()));
125 126 127 128 129
	session->startIncomingNotification(false);

	if (!previousSession) {
		setState(ClientGroupChatRoom::State::CreationPending);
		// Handle participants addition
130
		list<IdentityAddress> identAddresses = ClientGroupChatRoom::parseResourceLists(op->getRemoteBody());
131 132 133
		for (const auto &addr : identAddresses) {
			auto participant = q->findParticipant(addr);
			if (!participant) {
134
				participant = make_shared<Participant>(q, addr);
135 136 137 138 139 140 141
				qConference->getPrivate()->participants.push_back(participant);
			}
		}
	}
	acceptSession(session);
}

142 143
// -----------------------------------------------------------------------------

144 145 146 147 148 149 150
void ClientGroupChatRoomPrivate::onChatRoomInsertRequested (const shared_ptr<AbstractChatRoom> &chatRoom) {
	L_Q();
	q->getCore()->getPrivate()->insertChatRoom(chatRoom);
}

void ClientGroupChatRoomPrivate::onChatRoomInsertInDatabaseRequested (const shared_ptr<AbstractChatRoom> &chatRoom) {
	L_Q();
151 152 153 154
	L_Q_T(RemoteConference, qConference);

	unsigned int notifyId = qConference->getPrivate()->eventHandler->getLastNotify();;
	q->getCore()->getPrivate()->insertChatRoomWithDb(chatRoom, notifyId);
155 156
}

157 158 159 160 161 162
void ClientGroupChatRoomPrivate::onChatRoomDeleteRequested (const shared_ptr<AbstractChatRoom> &chatRoom) {
	L_Q();
	q->getCore()->deleteChatRoom(q->getSharedFromThis());
	setState(ClientGroupChatRoom::State::Deleted);
}

163 164
// -----------------------------------------------------------------------------

165
void ClientGroupChatRoomPrivate::onCallSessionSetReleased (const shared_ptr<CallSession> &session) {
166 167 168 169 170 171 172 173
	L_Q_T(RemoteConference, qConference);

	ParticipantPrivate *participantPrivate = qConference->getPrivate()->focus->getPrivate();
	if (session == participantPrivate->getSession())
		participantPrivate->removeSession();
}

void ClientGroupChatRoomPrivate::onCallSessionStateChanged (
174
	const shared_ptr<CallSession> &session,
175
	CallSession::State newState,
176 177 178 179 180
	const string &message
) {
	L_Q();
	L_Q_T(RemoteConference, qConference);

181
	if (newState == CallSession::State::Connected) {
182
		if (q->getState() == ChatRoom::State::CreationPending) {
183
			onChatRoomCreated(*session->getRemoteContactAddress());
184
		} else if (q->getState() == ChatRoom::State::TerminationPending)
185
			qConference->getPrivate()->focus->getPrivate()->getSession()->terminate();
186
	} else if (newState == CallSession::State::End) {
187
		setState(ChatRoom::State::TerminationPending);
188 189 190 191 192 193 194 195 196 197 198
	} else if (newState == CallSession::State::Released) {
		if (q->getState() == ChatRoom::State::TerminationPending) {
			if (session->getReason() == LinphoneReasonNone) {
				// Everything is fine, the chat room has been left on the server side
				q->onConferenceTerminated(q->getConferenceAddress());
			} else {
				// Go to state TerminationFailed and then back to Created since it has not been terminated
				setState(ChatRoom::State::TerminationFailed);
				setState(ChatRoom::State::Created);
			}
		}
199 200 201 202
	} else if (newState == CallSession::State::Error) {
		if (q->getState() == ChatRoom::State::CreationPending)
			setState(ChatRoom::State::CreationFailed);
		else if (q->getState() == ChatRoom::State::TerminationPending) {
203 204 205 206 207 208 209 210
			if (session->getReason() == LinphoneReasonNotFound) {
				// Somehow the chat room is no longer know on the server, so terminate it
				q->onConferenceTerminated(q->getConferenceAddress());
			} else {
				// Go to state TerminationFailed and then back to Created since it has not been terminated
				setState(ChatRoom::State::TerminationFailed);
				setState(ChatRoom::State::Created);
			}
211
		}
212 213 214
	}
}

215 216 217 218 219 220
void ClientGroupChatRoomPrivate::onChatRoomCreated (const Address &remoteContact) {
	L_Q();
	L_Q_T(RemoteConference, qConference);

	IdentityAddress addr(remoteContact);
	q->onConferenceCreated(addr);
Benjamin REIS's avatar
Benjamin REIS committed
221
	if (remoteContact.hasParam("isfocus")) {
222
		if (q->getCore()->getPrivate()->remoteListEventHandler->findHandler(q->getConferenceId())) {
Benjamin REIS's avatar
Benjamin REIS committed
223 224 225
			q->getCore()->getPrivate()->remoteListEventHandler->subscribe();
		} else {
			bgTask.start(q->getCore(), 32); // It will be stopped when receiving the first notify
226
			qConference->getPrivate()->eventHandler->subscribe(q->getConferenceId());
Benjamin REIS's avatar
Benjamin REIS committed
227
		}
228 229 230 231 232 233 234 235 236 237 238 239
	}
}

// -----------------------------------------------------------------------------

void ClientGroupChatRoomPrivate::acceptSession (const shared_ptr<CallSession> &session) {
	if (session->getState() == CallSession::State::UpdatedByRemote)
		session->acceptUpdate();
	else
		session->accept();
}

240 241
// =============================================================================

242
ClientGroupChatRoom::ClientGroupChatRoom (
243
	const shared_ptr<Core> &core,
244
	const string &uri,
245
	const IdentityAddress &me,
246 247
	const string &subject,
	const Content &content
248
) : ChatRoom(*new ClientGroupChatRoomPrivate, core, ConferenceId(IdentityAddress(), me)),
249
RemoteConference(core, me, nullptr) {
250
	L_D_T(RemoteConference, dConference);
251

252
	IdentityAddress focusAddr(uri);
253
	dConference->focus = make_shared<Participant>(this, focusAddr);
254
	dConference->focus->getPrivate()->addDevice(focusAddr);
255
	RemoteConference::setSubject(subject);
256 257
	list<IdentityAddress> identAddresses = Conference::parseResourceLists(content);
	for (const auto &addr : identAddresses)
258
		dConference->participants.push_back(make_shared<Participant>(this, addr));
259 260
}

261 262
ClientGroupChatRoom::ClientGroupChatRoom (
	const shared_ptr<Core> &core,
263
	const ConferenceId &conferenceId,
Ronan's avatar
Ronan committed
264
	shared_ptr<Participant> &me,
265
	AbstractChatRoom::CapabilitiesMask capabilities,
266
	const string &subject,
267
	list<shared_ptr<Participant>> &&participants,
Benjamin REIS's avatar
Benjamin REIS committed
268 269
	unsigned int lastNotifyId,
	bool hasBeenLeft
270
) : ChatRoom(*new ClientGroupChatRoomPrivate, core, conferenceId),
Ronan's avatar
Ronan committed
271
RemoteConference(core, me->getAddress(), nullptr) {
272
	L_D();
273
	L_D_T(RemoteConference, dConference);
274

275
	d->capabilities |= capabilities & ClientGroupChatRoom::Capabilities::OneToOne;
276
	const IdentityAddress &peerAddress = conferenceId.getPeerAddress();
277
	dConference->focus = make_shared<Participant>(this, peerAddress);
278
	dConference->focus->getPrivate()->addDevice(peerAddress);
279 280
	dConference->conferenceAddress = peerAddress;
	dConference->subject = subject;
281
	dConference->participants = move(participants);
282

Ronan's avatar
Ronan committed
283
	getMe()->getPrivate()->setAdmin(me->isAdmin());
284

285
	dConference->eventHandler->setConferenceId(conferenceId);
286
	dConference->eventHandler->setLastNotify(lastNotifyId);
Benjamin REIS's avatar
Benjamin REIS committed
287 288
	if (!hasBeenLeft)
		getCore()->getPrivate()->remoteListEventHandler->addHandler(dConference->eventHandler.get());
289 290
}

291 292
ClientGroupChatRoom::~ClientGroupChatRoom () {
	L_D();
Benjamin REIS's avatar
Benjamin REIS committed
293 294
	L_D_T(RemoteConference, dConference);

295 296 297 298 299 300
	try {
		if (getCore()->getPrivate()->remoteListEventHandler)
			getCore()->getPrivate()->remoteListEventHandler->removeHandler(dConference->eventHandler.get());
	} catch (const bad_weak_ptr &) {
		// Unable to unregister listener here. Core is destroyed and the listener doesn't exist.
	}
301 302 303
	d->setCallSessionListener(nullptr);
}

304 305 306 307
shared_ptr<Core> ClientGroupChatRoom::getCore () const {
	return ChatRoom::getCore();
}

Sylvain Berfini's avatar
Sylvain Berfini committed
308
void ClientGroupChatRoom::allowCpim (bool value) {
309

Sylvain Berfini's avatar
Sylvain Berfini committed
310 311 312
}

void ClientGroupChatRoom::allowMultipart (bool value) {
313

Sylvain Berfini's avatar
Sylvain Berfini committed
314 315
}

316 317 318 319 320 321 322 323
bool ClientGroupChatRoom::canHandleCpim () const {
	return true;
}

bool ClientGroupChatRoom::canHandleMultipart () const {
	return true;
}

324
ClientGroupChatRoom::CapabilitiesMask ClientGroupChatRoom::getCapabilities () const {
325 326
	L_D();
	return d->capabilities;
327 328
}

329 330 331 332 333 334
ChatRoom::SecurityLevel ClientGroupChatRoom::getSecurityLevel () const {
	bool isSafe = true;
	for (const auto &participant : getParticipants()) {
		auto level = participant->getSecurityLevel();
		switch (level) {
			case AbstractChatRoom::SecurityLevel::Unsafe:
335
				lInfo() << "Chatroom SecurityLevel = Unsafe";
336 337
				return level; // if one device is Unsafe the whole participant is Unsafe (red)
			case AbstractChatRoom::SecurityLevel::ClearText:
338
				lInfo() << "Chatroom SecurityLevel = ClearText";
339 340 341 342 343 344 345 346
				return level; // TODO if all devices are in ClearText the whole participant is in ClearText (grey)
			case AbstractChatRoom::SecurityLevel::Encrypted:
				isSafe = false; // if one device is Encrypted the whole participant is Encrypted (orange)
				break;
			case AbstractChatRoom::SecurityLevel::Safe:
				break; // if all devices are Safe the whole participant is Safe (green)
		}
	}
347
	if (isSafe) {
348
		lInfo() << "Chatroom SecurityLevel = Safe";
349
		return AbstractChatRoom::SecurityLevel::Safe;
350
	} else {
351
		lInfo() << "Chatroom SecurityLevel = Encrypted";
352
		return AbstractChatRoom::SecurityLevel::Encrypted;
353
	}
354 355
}

356
bool ClientGroupChatRoom::hasBeenLeft () const {
357
	return (getState() != State::Created);
358 359
}

360 361 362 363
bool ClientGroupChatRoom::canHandleParticipants () const {
	return RemoteConference::canHandleParticipants();
}

364
const IdentityAddress &ClientGroupChatRoom::getConferenceAddress () const {
365
	return RemoteConference::getConferenceAddress();
366
}
367

368 369 370 371 372 373 374 375 376 377
void ClientGroupChatRoom::deleteFromDb () {
	L_D();
	if (!hasBeenLeft()) {
		d->deletionOnTerminationEnabled = true;
		leave();
		return;
	}
	d->chatRoomListener->onChatRoomDeleteRequested(getSharedFromThis());
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
list<shared_ptr<EventLog>> ClientGroupChatRoom::getHistory (int nLast) const {
	L_D();
	return getCore()->getPrivate()->mainDb->getHistory(
		getConferenceId(),
		nLast,
		(d->capabilities & Capabilities::OneToOne) ?
			MainDb::Filter::ConferenceChatMessageSecurityFilter :
			MainDb::FilterMask({MainDb::Filter::ConferenceChatMessageFilter, MainDb::Filter::ConferenceInfoNoDeviceFilter})
	);
}

list<shared_ptr<EventLog>> ClientGroupChatRoom::getHistoryRange (int begin, int end) const {
	L_D();
	return getCore()->getPrivate()->mainDb->getHistoryRange(
		getConferenceId(),
		begin,
		end,
		(d->capabilities & Capabilities::OneToOne) ?
			MainDb::Filter::ConferenceChatMessageSecurityFilter :
			MainDb::FilterMask({MainDb::Filter::ConferenceChatMessageFilter, MainDb::Filter::ConferenceInfoNoDeviceFilter})
	);
}

401
void ClientGroupChatRoom::addParticipant (const IdentityAddress &addr, const CallSessionParams *params, bool hasMedia) {
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
	L_D();

	if ((getState() != ChatRoom::State::Instantiated) && (getState() != ChatRoom::State::Created)) {
		lError() << "Cannot add participants to the ClientGroupChatRoom in a state other than Instantiated or Created";
		return;
	}

	if ((getState() == ChatRoom::State::Created) && (d->capabilities & ClientGroupChatRoom::Capabilities::OneToOne)) {
		lError() << "Cannot add more participants to a OneToOne ClientGroupChatRoom";
		return;
	}

	LinphoneCore *cCore = getCore()->getCCore();
	if (getState() == ChatRoom::State::Instantiated) {
		list<IdentityAddress> addressesList;
		addressesList.push_back(addr);
		Content content;
		content.setBody(getResourceLists(addressesList));
		content.setContentType(ContentType::ResourceLists);
		content.setContentDisposition(ContentDisposition::RecipientList);

		auto session = d->createSession();
		session->startInvite(nullptr, getSubject(), &content);
		d->setState(ChatRoom::State::CreationPending);
	} else {
		SalReferOp *referOp = new SalReferOp(cCore->sal);
		LinphoneAddress *lAddr = linphone_address_new(getConferenceAddress().asString().c_str());
		linphone_configure_op(cCore, referOp, lAddr, nullptr, true);
		linphone_address_unref(lAddr);
		Address referToAddr = addr;
		referToAddr.setParam("text");
433
		referOp->sendRefer(referToAddr.getPrivate()->getInternalAddress());
434 435
		referOp->unref();
	}
436 437
}

438
void ClientGroupChatRoom::addParticipants (
439
	const list<IdentityAddress> &addresses,
440 441 442
	const CallSessionParams *params,
	bool hasMedia
) {
443
	L_D();
444

445
	list<IdentityAddress> addressesList = d->cleanAddressesList(addresses);
446
	if (addressesList.empty())
447
		return;
448

449 450 451 452 453 454 455 456
	if ((getState() == ChatRoom::State::Instantiated)
		&& (addressesList.size() == 1)
		&& (linphone_config_get_bool(linphone_core_get_config(L_GET_C_BACK_PTR(getCore())),
			"misc", "one_to_one_chat_room_enabled", TRUE))
	) {
		d->capabilities |= ClientGroupChatRoom::Capabilities::OneToOne;
	}

457 458 459 460 461
	if (getState() == ChatRoom::State::Instantiated) {
		Content content;
		content.setBody(getResourceLists(addressesList));
		content.setContentType(ContentType::ResourceLists);
		content.setContentDisposition(ContentDisposition::RecipientList);
462 463
		if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate"))
			content.setContentEncoding("deflate");
464

465
		auto session = d->createSession();
466
		session->startInvite(nullptr, getSubject(), &content);
467 468 469 470
		d->setState(ChatRoom::State::CreationPending);
	} else {
		for (const auto &addr : addresses)
			addParticipant(addr, params, hasMedia);
471
	}
472 473
}

474
void ClientGroupChatRoom::removeParticipant (const shared_ptr<Participant> &participant) {
475
	LinphoneCore *cCore = getCore()->getCCore();
476 477

	SalReferOp *referOp = new SalReferOp(cCore->sal);
478
	LinphoneAddress *lAddr = linphone_address_new(getConferenceAddress().asString().c_str());
479
	linphone_configure_op(cCore, referOp, lAddr, nullptr, false);
480 481 482 483
	linphone_address_unref(lAddr);
	Address referToAddr = participant->getAddress();
	referToAddr.setParam("text");
	referToAddr.setUriParam("method", "BYE");
484
	referOp->sendRefer(referToAddr.getPrivate()->getInternalAddress());
485
	referOp->unref();
486 487
}

488
void ClientGroupChatRoom::removeParticipants (const list<shared_ptr<Participant>> &participants) {
489
	RemoteConference::removeParticipants(participants);
490
}
491

492
shared_ptr<Participant> ClientGroupChatRoom::findParticipant (const IdentityAddress &addr) const {
493 494 495 496 497 498 499
	return RemoteConference::findParticipant(addr);
}

shared_ptr<Participant> ClientGroupChatRoom::getMe () const {
	return RemoteConference::getMe();
}

500 501
int ClientGroupChatRoom::getParticipantCount () const {
	return RemoteConference::getParticipantCount();
502 503
}

Ghislain MARY's avatar
Ghislain MARY committed
504
const list<shared_ptr<Participant>> &ClientGroupChatRoom::getParticipants () const {
505 506 507
	return RemoteConference::getParticipants();
}

508
void ClientGroupChatRoom::setParticipantAdminStatus (const shared_ptr<Participant> &participant, bool isAdmin) {
509 510
	if (isAdmin == participant->isAdmin())
		return;
511 512

	if (!getMe()->isAdmin()) {
513 514 515
		lError() << "Cannot change the participant admin status because I am not admin";
		return;
	}
516

517
	LinphoneCore *cCore = getCore()->getCCore();
518 519

	SalReferOp *referOp = new SalReferOp(cCore->sal);
520
	LinphoneAddress *lAddr = linphone_address_new(getConferenceAddress().asString().c_str());
521
	linphone_configure_op(cCore, referOp, lAddr, nullptr, false);
522 523 524 525
	linphone_address_unref(lAddr);
	Address referToAddr = participant->getAddress();
	referToAddr.setParam("text");
	referToAddr.setParam("admin", Utils::toString(isAdmin));
526
	referOp->sendRefer(referToAddr.getPrivate()->getInternalAddress());
527 528 529
	referOp->unref();
}

530 531 532 533
const string &ClientGroupChatRoom::getSubject () const {
	return RemoteConference::getSubject();
}

534 535
void ClientGroupChatRoom::setSubject (const string &subject) {
	L_D();
536
	L_D_T(RemoteConference, dConference);
537

538
	if (getState() != ChatRoom::State::Created) {
539 540 541
		lError() << "Cannot change the ClientGroupChatRoom subject in a state other than Created";
		return;
	}
542 543

	if (!getMe()->isAdmin()) {
544 545 546
		lError() << "Cannot change the ClientGroupChatRoom subject because I am not admin";
		return;
	}
547

548
	shared_ptr<CallSession> session = dConference->focus->getPrivate()->getSession();
549 550 551 552 553 554
	if (session)
		session->update(nullptr, subject);
	else {
		session = d->createSession();
		session->startInvite(nullptr, subject, nullptr);
	}
555 556
}

557 558 559 560 561
void ClientGroupChatRoom::join () {
	L_D();
	L_D_T(RemoteConference, dConference);

	shared_ptr<CallSession> session = dConference->focus->getPrivate()->getSession();
562
	if (!session && ((getState() == ChatRoom::State::Instantiated) || (getState() == ChatRoom::State::Terminated))) {
563
		session = d->createSession();
564 565 566 567
	}
	if (session) {
		if (getState() != ChatRoom::State::TerminationPending)
			session->startInvite(nullptr, "", nullptr);
568 569
		if (getState() != ChatRoom::State::Created)
			d->setState(ChatRoom::State::CreationPending);
570 571 572 573 574 575 576
	}
}

void ClientGroupChatRoom::leave () {
	L_D();
	L_D_T(RemoteConference, dConference);

577
	dConference->eventHandler->unsubscribe();
578 579 580 581 582 583 584
	shared_ptr<CallSession> session = dConference->focus->getPrivate()->getSession();
	if (session)
		session->terminate();
	else {
		session = d->createSession();
		session->startInvite(nullptr, "", nullptr);
	}
585

586
	d->setState(ChatRoom::State::TerminationPending);
587 588
}

589 590
// -----------------------------------------------------------------------------

591
void ClientGroupChatRoom::onConferenceCreated (const IdentityAddress &addr) {
592
	L_D();
593 594
	L_D_T(RemoteConference, dConference);
	dConference->conferenceAddress = addr;
595
	dConference->focus->getPrivate()->setAddress(addr);
596 597
	dConference->focus->getPrivate()->clearDevices();
	dConference->focus->getPrivate()->addDevice(addr);
598
	d->conferenceId = ConferenceId(addr, d->conferenceId.getLocalAddress());
599
	d->chatRoomListener->onChatRoomInsertRequested(getSharedFromThis());
600
	d->setState(ChatRoom::State::Created);
601 602
}

603 604
void ClientGroupChatRoom::onConferenceKeywordsChanged (const vector<string> &keywords) {
	L_D();
605 606
	if (find(keywords.cbegin(), keywords.cend(), "one-to-one") != keywords.cend())
		d->capabilities |= ClientGroupChatRoom::Capabilities::OneToOne;
607 608
}

609
void ClientGroupChatRoom::onConferenceTerminated (const IdentityAddress &addr) {
610
	L_D();
611
	L_D_T(RemoteConference, dConference);
612

613
	dConference->eventHandler->unsubscribe();
614
	dConference->eventHandler->resetLastNotify();
615
	d->setState(ChatRoom::State::Terminated);
616 617

	auto event = make_shared<ConferenceEvent>(
618 619
		EventLog::Type::ConferenceTerminated,
		time(nullptr),
620
		d->conferenceId
621 622 623 624 625 626
	);
	d->addEvent(event);

	LinphoneChatRoom *cr = d->getCChatRoom();
	_linphone_chat_room_notify_conference_left(cr, L_GET_C_BACK_PTR(event));

627 628 629 630
	if (d->deletionOnTerminationEnabled) {
		d->deletionOnTerminationEnabled = false;
		d->chatRoomListener->onChatRoomDeleteRequested(getSharedFromThis());
	}
631 632
}

633
void ClientGroupChatRoom::onFirstNotifyReceived (const IdentityAddress &addr) {
634
	L_D();
635

636 637 638 639 640
	if (getState() != ChatRoom::State::Created) {
		lWarning() << "First notify received in ClientGroupChatRoom that is not in the Created state, ignoring it!";
		return;
	}

641 642
	bool performMigration = false;
	shared_ptr<AbstractChatRoom> chatRoom;
643
	if (getParticipantCount() == 1 && d->capabilities & ClientGroupChatRoom::Capabilities::OneToOne) {
644
		ConferenceId id(getParticipants().front()->getAddress(), getMe()->getAddress());
645 646 647
		chatRoom = getCore()->findChatRoom(id);
		if (chatRoom && (chatRoom->getCapabilities() & ChatRoom::Capabilities::Basic))
			performMigration = true;
648 649
	}

650 651 652 653 654
	if (performMigration)
		BasicToClientGroupChatRoom::migrate(getSharedFromThis(), chatRoom);
	else
		d->chatRoomListener->onChatRoomInsertInDatabaseRequested(getSharedFromThis());

655
	auto event = make_shared<ConferenceEvent>(
656 657
		EventLog::Type::ConferenceCreated,
		time(nullptr),
658
		d->conferenceId
659 660 661 662 663
	);
	d->addEvent(event);

	LinphoneChatRoom *cr = d->getCChatRoom();
	_linphone_chat_room_notify_conference_joined(cr, L_GET_C_BACK_PTR(event));
664 665

	d->bgTask.stop();
666 667
}

668
void ClientGroupChatRoom::onParticipantAdded (const shared_ptr<ConferenceParticipantEvent> &event, bool isFullState) {
669
	L_D();
670 671
	L_D_T(RemoteConference, dConference);

672
	const IdentityAddress &addr = event->getParticipantAddress();
673 674
	if (isMe(addr))
		return;
675

676 677 678 679 680
	shared_ptr<Participant> participant = findParticipant(addr);
	if (participant) {
		lWarning() << "Participant " << participant << " added but already in the list of participants!";
		return;
	}
681

682
	participant = make_shared<Participant>(this, addr);
683
	dConference->participants.push_back(participant);
684 685 686 687

	if (isFullState)
		return;

688
	d->addEvent(event);
Benjamin REIS's avatar
Benjamin REIS committed
689

690
	LinphoneChatRoom *cr = d->getCChatRoom();
691
	_linphone_chat_room_notify_participant_added(cr, L_GET_C_BACK_PTR(event));
692 693
}

694
void ClientGroupChatRoom::onParticipantRemoved (const shared_ptr<ConferenceParticipantEvent> &event, bool isFullState) {
695
	(void)isFullState;
696

697
	L_D();
698 699
	L_D_T(RemoteConference, dConference);

700
	const IdentityAddress &addr = event->getParticipantAddress();
701 702
	shared_ptr<Participant> participant = findParticipant(addr);
	if (!participant) {
703
		lWarning() << "Participant " << addr.asString() << " removed but not in the list of participants!";
704 705
		return;
	}
706

707
	dConference->participants.remove(participant);
708
	d->addEvent(event);
709

710
	LinphoneChatRoom *cr = d->getCChatRoom();
711
	_linphone_chat_room_notify_participant_removed(cr, L_GET_C_BACK_PTR(event));
712 713
}

714
void ClientGroupChatRoom::onParticipantSetAdmin (const shared_ptr<ConferenceParticipantEvent> &event, bool isFullState) {
715 716
	L_D();

717
	const IdentityAddress &addr = event->getParticipantAddress();
718
	shared_ptr<Participant> participant;
719
	if (isMe(addr))
720
		participant = getMe();
721 722
	else
		participant = findParticipant(addr);
723
	if (!participant) {
724
		lWarning() << "Participant " << addr.asString() << " admin status has been changed but is not in the list of participants!";
725 726
		return;
	}
727

728 729 730 731 732
	bool isAdmin = event->getType() == EventLog::Type::ConferenceParticipantSetAdmin;
	if (participant->isAdmin() == isAdmin)
		return; // No change in the local admin status, do not notify
	participant->getPrivate()->setAdmin(isAdmin);

733 734 735
	if (isFullState)
		return;

736
	d->addEvent(event);
Benjamin REIS's avatar
Benjamin REIS committed
737

738
	LinphoneChatRoom *cr = d->getCChatRoom();
739
	_linphone_chat_room_notify_participant_admin_status_changed(cr, L_GET_C_BACK_PTR(event));
740 741
}

742
void ClientGroupChatRoom::onSecurityEvent (const shared_ptr<ConferenceSecurityEvent> &event) {
743
	L_D();
744 745 746 747 748 749 750 751 752 753 754 755 756
	shared_ptr<ConferenceSecurityEvent> finalEvent = nullptr;
	shared_ptr<ConferenceSecurityEvent> cleanEvent = nullptr;

	// Remove faulty device if its address is invalid
	IdentityAddress faultyDevice = event->getFaultyDevice();
	if (!faultyDevice.isValid()) {
		cleanEvent = make_shared<ConferenceSecurityEvent>(
			event->getCreationTime(),
			event->getConferenceId(),
			event->getSecurityEventType()
		);
	}
	finalEvent = cleanEvent ? cleanEvent : event;
757

758
	// Add security events or alerts based on the type of security event
759
	switch (finalEvent->getSecurityEventType()) {
760
		case ConferenceSecurityEvent::SecurityEventType::MultideviceParticipantDetected:
761
			// Unexpected behaviour, set faulty device PeerDeviceStatus to unsafe
762
			if (getCore()->limeV2Enabled() && finalEvent->getFaultyDevice().isValid()) {
763
				LimeV2 *limeV2Engine = static_cast<LimeV2 *>(getCore()->getEncryptionEngine());
764
				limeV2Engine->getLimeManager()->set_peerDeviceStatus(finalEvent->getFaultyDevice().asString(), lime::PeerDeviceStatus::unsafe);
765 766 767
				// WARNING has no effect if faulty device is not in X3DH database
			}
			break;
768 769 770
		default:
			// Other security event types are already managed
			// Or This event is not a security event
771 772 773
			break;
	}

774
	d->addEvent(event);
775 776

	LinphoneChatRoom *cr = d->getCChatRoom();
777
	_linphone_chat_room_notify_security_event(cr, L_GET_C_BACK_PTR(event));
778 779
}

780
void ClientGroupChatRoom::onSubjectChanged (const shared_ptr<ConferenceSubjectEvent> &event, bool isFullState) {
781 782
	L_D();

783 784
	if (getSubject() == event->getSubject())
		return; // No change in the local subject, do not notify
785
	RemoteConference::setSubject(event->getSubject());
786

787 788 789
	if (isFullState)
		return;

790
	d->addEvent(event);
Benjamin REIS's avatar
Benjamin REIS committed
791

792
	LinphoneChatRoom *cr = d->getCChatRoom();
793
	_linphone_chat_room_notify_subject_changed(cr, L_GET_C_BACK_PTR(event));
794 795
}

796
void ClientGroupChatRoom::onParticipantDeviceAdded (const shared_ptr<ConferenceParticipantDeviceEvent> &event, bool isFullState) {
797 798
	L_D();

799
	const IdentityAddress &addr = event->getParticipantAddress();
800
	shared_ptr<Participant> participant;
801
	if (isMe(addr))
802
		participant = getMe();
803 804 805
	else
		participant = findParticipant(addr);
	if (!participant) {
<