diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 6c31b377be4c4aea832cb081f5dee7e4eef48075..e7472e36e9060d2f0074c72ba882468d95606e0e 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -828,7 +828,7 @@ static void refer_received(SalOp *op, const SalAddress *refer_to){ Address fromAddr(op->getFrom()); shared_ptr<Participant> participant = chatRoom->findParticipant(fromAddr); if (!participant || !participant->isAdmin()) { - static_cast<SalReferOp *>(op)->reply(SalReasonDeclined); + static_cast<SalReferOp *>(op)->reply(SalReasonForbidden); return; } if (addr.hasParam("admin")) { @@ -840,15 +840,11 @@ static void refer_received(SalOp *op, const SalAddress *refer_to){ return; } } else { - participant = L_GET_PRIVATE(static_pointer_cast<ServerGroupChatRoom>(chatRoom))->findFilteredParticipant(addr); + participant = L_GET_PRIVATE(static_pointer_cast<ServerGroupChatRoom>(chatRoom))->findAuthorizedParticipant(addr); if (!participant) { - list<IdentityAddress> identAddresses; - identAddresses.push_back(addr); - L_GET_PRIVATE(static_pointer_cast<ServerGroupChatRoom>(chatRoom))->checkCompatibleParticipants( - IdentityAddress(op->getRemoteContact()), - identAddresses - ); - static_cast<SalReferOp *>(op)->reply(SalReasonNone); + bool ret = static_pointer_cast<ServerGroupChatRoom>(chatRoom)->addParticipant( + IdentityAddress(addr), nullptr, false); + static_cast<SalReferOp *>(op)->reply(ret ? SalReasonNone : SalReasonNotAcceptable); return; } } diff --git a/include/linphone/api/c-callbacks.h b/include/linphone/api/c-callbacks.h index 2394e5d205dba9282e28de7c8ffeec841e0eadd8..2a2492d3d640cdcb46d8f7b5da179b24a2c86430 100644 --- a/include/linphone/api/c-callbacks.h +++ b/include/linphone/api/c-callbacks.h @@ -282,21 +282,6 @@ typedef void (*LinphoneChatRoomCbsConferenceLeftCb) (LinphoneChatRoom *cr, const */ typedef void (*LinphoneChatRoomCbsConferenceAddressGenerationCb) (LinphoneChatRoom *cr); -/** - * Callback used when a group chat room server is adding participant to fetch all device information from participant. - * @param[in] cr #LinphoneChatRoom object - * @param[in] participantAddr #LinphoneAddress object - */ -typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr); - -/** - * Callback used when a group chat room server is checking participants capabilities. - * @param[in] cr #LinphoneChatRoom object - * @param[in] deviceAddr #LinphoneAddress object - * @param[in] participantsAddr \bctbx_list{LinphoneAddress} - */ -typedef void (*LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb) (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr); - /** * Callback used when a group chat room server is subscribing to registration state of a participant. * @param[in] cr #LinphoneChatRoom object diff --git a/include/linphone/api/c-chat-room-cbs.h b/include/linphone/api/c-chat-room-cbs.h index cf8299181030d984b2692250514d4a9218d81d5d..2f545a8e8d4a97c7097756847f7b68e1930e0690 100644 --- a/include/linphone/api/c-chat-room-cbs.h +++ b/include/linphone/api/c-chat-room-cbs.h @@ -285,34 +285,6 @@ LINPHONE_PUBLIC LinphoneChatRoomCbsConferenceAddressGenerationCb linphone_chat_r */ LINPHONE_PUBLIC void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsConferenceAddressGenerationCb cb); -/** - * Get the participant device fetching callback. - * @param[in] cbs #LinphoneChatRoomCbs object - * @return The participant device fetching callback - */ -LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs); - -/** - * Set the participant device fetching callback. - * @param[in] cbs #LinphoneChatRoomCbs object - * @param[in] cb The participant device fetching callback to be used - */ -LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb); - -/** - * Get the participants capabilities callback. - * @param[in] cbs #LinphoneChatRoomCbs object - * @return The participants capabilities getting callback - */ -LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_chat_room_cbs_get_participants_capabilities_checked (const LinphoneChatRoomCbs *cbs); - -/** - * Set the participants capabilities callback. - * @param[in] cbs #LinphoneChatRoomCbs object - * @param[in] cb The participants capabilities callback to be used - */ -LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb cb); - /** * Get the participant registration subscription callback. * @param[in] cbs LinphoneChatRoomCbs object diff --git a/include/linphone/api/c-chat-room.h b/include/linphone/api/c-chat-room.h index a54b988dca2090669443fac09eee875a648bd0b4..15345da0a3730286f332c6d7624433b4900b7d4e 100644 --- a/include/linphone/api/c-chat-room.h +++ b/include/linphone/api/c-chat-room.h @@ -467,28 +467,29 @@ LINPHONE_PUBLIC const bctbx_list_t * linphone_chat_room_get_composing_addresses( /** * Set the conference address of a group chat room. This function needs to be called from the * #LinphoneChatRoomCbsConferenceAddressGenerationCb callback and only there. + * This function is meaningful only for server implementation of chatroom, and shall not by used by client applications. * @param[in] cr A #LinphoneChatRoom object * @param[in] confAddr The conference address to be used by the group chat room */ LINPHONE_PUBLIC void linphone_chat_room_set_conference_address (LinphoneChatRoom *cr, const LinphoneAddress *confAddr); /** - * Set the participant device. This function needs to be called from the - * #LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb callback and only there. + * Set the list of participant devices in the form of SIP URIs with GRUUs for a given participant. + * This function is meaningful only for server implementation of chatroom, and shall not by used by client applications. * @param[in] cr A #LinphoneChatRoom object * @param[in] partAddr The participant address * @param[in] deviceIdentities \bctbx_list{LinphoneParticipantDeviceIdentity} list of the participant devices to be used by the group chat room */ -LINPHONE_PUBLIC void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const LinphoneAddress *partAddr, const bctbx_list_t *deviceIdentities); +LINPHONE_PUBLIC void linphone_chat_room_set_participant_devices(LinphoneChatRoom *cr, const LinphoneAddress *partAddr, const bctbx_list_t *deviceIdentities); + /** - * Set the participant device. This function needs to be called from the - * #LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb callback and only there. + * Notify the chatroom that a participant device has just registered. + * This function is meaningful only for server implementation of chatroom, and shall not by used by client applications. * @param[in] cr A #LinphoneChatRoom object - * @param[in] deviceAddr The device address - * @param[in] participantsCompatible \bctbx_list{LinphoneAddress} + * @param[in] partDevice list of the participant devices to be used by the group chat room */ -LINPHONE_PUBLIC void linphone_chat_room_add_compatible_participants (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsCompatible); +LINPHONE_PUBLIC void linphone_chat_room_notify_participant_device_registration(LinphoneChatRoom *cr, const LinphoneAddress *participant_device); /** * Returns back pointer to #LinphoneCore object. diff --git a/src/c-wrapper/api/c-chat-room-cbs.cpp b/src/c-wrapper/api/c-chat-room-cbs.cpp index 9b92df72988e4b3927ee387b9a166a017c77f6e0..9ae3654908f6e6cd3df7f22d7d44337e2f2adefb 100644 --- a/src/c-wrapper/api/c-chat-room-cbs.cpp +++ b/src/c-wrapper/api/c-chat-room-cbs.cpp @@ -42,8 +42,6 @@ struct _LinphoneChatRoomCbs { LinphoneChatRoomCbsChatMessageReceivedCb chatMessageReceivedCb; LinphoneChatRoomCbsChatMessageSentCb chatMessageSentCb; LinphoneChatRoomCbsConferenceAddressGenerationCb conferenceAddressGenerationCb; - LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb participantDeviceFetchRequestedCb; - LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb participantsCapabilitiesChecked; LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb participantRegistrationSubscriptionRequestedCb; LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb participantRegistrationUnsubscriptionRequestedCb; LinphoneChatRoomCbsShouldChatMessageBeStoredCb shouldMessageBeStoredCb; @@ -211,22 +209,6 @@ void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomC cbs->conferenceAddressGenerationCb = cb; } -LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs) { - return cbs->participantDeviceFetchRequestedCb; -} - -void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb) { - cbs->participantDeviceFetchRequestedCb = cb; -} - -LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_chat_room_cbs_get_participants_capabilities_checked (const LinphoneChatRoomCbs *cbs) { - return cbs->participantsCapabilitiesChecked; -} - -void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb cb) { - cbs->participantsCapabilitiesChecked = cb; -} - LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_subscription_requested (const LinphoneChatRoomCbs *cbs) { return cbs->participantRegistrationSubscriptionRequestedCb; } diff --git a/src/c-wrapper/api/c-chat-room.cpp b/src/c-wrapper/api/c-chat-room.cpp index afcea3fed436eca2e2237107f0e257e70aa94c54..4a7a943cefe85ac343a180f2a869a63d2026a43f 100644 --- a/src/c-wrapper/api/c-chat-room.cpp +++ b/src/c-wrapper/api/c-chat-room.cpp @@ -69,12 +69,15 @@ static void _linphone_chat_room_destructor (LinphoneChatRoom *cr) { _linphone_chat_room_clear_callbacks(cr); } +/* + static list<LinphonePrivate::IdentityAddress> _get_identity_address_list_from_address_list(list<LinphonePrivate::Address> addressList) { list<LinphonePrivate::IdentityAddress> lIdent; for (const auto &addr : addressList) lIdent.push_back(LinphonePrivate::IdentityAddress(addr)); return lIdent; } +*/ // ============================================================================= // Public functions. @@ -391,16 +394,13 @@ void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const Lin bctbx_free(addrStr); } -void linphone_chat_room_add_compatible_participants (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsCompatible) { - list<LinphonePrivate::Address> lPartsComp = L_GET_RESOLVED_CPP_LIST_FROM_C_LIST(participantsCompatible, Address); +void linphone_chat_room_notify_participant_device_registration(LinphoneChatRoom *cr, const LinphoneAddress *participant_device){ + char *addrStr = linphone_address_as_string(participant_device); + list<LinphonePrivate::IdentityAddress> lIdentAddr; LinphonePrivate::ServerGroupChatRoomPrivate *sgcr = dynamic_cast<LinphonePrivate::ServerGroupChatRoomPrivate *>(L_GET_PRIVATE_FROM_C_OBJECT(cr)); - if (sgcr) { - char *deviceAddrStr = linphone_address_as_string_uri_only(deviceAddr); - sgcr->addCompatibleParticipants(LinphonePrivate::IdentityAddress(deviceAddrStr), - _get_identity_address_list_from_address_list(lPartsComp) - ); - bctbx_free(deviceAddrStr); - } + if (sgcr) + sgcr->notifyParticipantDeviceRegistration(LinphonePrivate::IdentityAddress(addrStr)); + bctbx_free(addrStr); } // ============================================================================= @@ -508,14 +508,6 @@ void _linphone_chat_room_notify_conference_address_generation(LinphoneChatRoom * NOTIFY_IF_EXIST(ConferenceAddressGeneration, conference_address_generation, cr) } -void _linphone_chat_room_notify_participant_device_fetch_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { - NOTIFY_IF_EXIST(ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, participantAddr) -} - -void _linphone_chat_room_notify_participants_capabilities_checked(LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr) { - NOTIFY_IF_EXIST(ParticipantsCapabilitiesChecked, participants_capabilities_checked, cr, deviceAddr, participantsAddr) -} - void _linphone_chat_room_notify_participant_registration_subscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) { NOTIFY_IF_EXIST(ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, participantAddr) } diff --git a/src/chat/chat-room/server-group-chat-room-p.h b/src/chat/chat-room/server-group-chat-room-p.h index 431680008c45fa31f84e720358bb15591b8e6f92..7ea1737d25134867ee939058457a9c2d993a2cbe 100644 --- a/src/chat/chat-room/server-group-chat-room-p.h +++ b/src/chat/chat-room/server-group-chat-room-p.h @@ -23,6 +23,7 @@ #include <chrono> #include <queue> #include <unordered_map> +#include <map> #include "chat-room-p.h" #include "server-group-chat-room.h" @@ -52,8 +53,8 @@ public: return new ParticipantDeviceIdentity(*this); } - const Address &getDeviceAddress () const; - const std::string &getDeviceName () const; + const Address &getAddress () const; + const std::string &getName () const; private: L_DECLARE_PRIVATE(ParticipantDeviceIdentity); }; @@ -65,13 +66,14 @@ public: std::shared_ptr<Participant> addParticipant (const IdentityAddress &participantAddress); void removeParticipant (const std::shared_ptr<const Participant> &participant); - std::shared_ptr<Participant> findFilteredParticipant (const std::shared_ptr<const CallSession> &session) const; - std::shared_ptr<Participant> findFilteredParticipant (const IdentityAddress &participantAddress) const; + std::shared_ptr<Participant> findAuthorizedParticipant (const std::shared_ptr<const CallSession> &session) const; + std::shared_ptr<Participant> findAuthorizedParticipant (const IdentityAddress &participantAddress) const; ParticipantDevice::State getParticipantDeviceState (const std::shared_ptr<const ParticipantDevice> &device) const; void setParticipantDeviceState (const std::shared_ptr<ParticipantDevice> &device, ParticipantDevice::State state); void acceptSession (const std::shared_ptr<CallSession> &session); + // we go here when receiving the first INVITE, the one that will redirect to newly allocated conference URI. void confirmCreation (); void confirmJoining (SalCallOp *op); void confirmRecreation (SalCallOp *op); @@ -81,14 +83,25 @@ public: void subscribeReceived (LinphoneEvent *event); void subscriptionStateChanged (LinphoneEvent *event, LinphoneSubscriptionState state); - bool update (SalCallOp *op); + bool initializeParticipants(const std::shared_ptr<Participant> & initiator, SalCallOp *op); + void resumeParticipant(const std::shared_ptr<Participant> &participant); + bool subscribeRegistrationForParticipants(const std::list<IdentityAddress> &participants); + void unSubscribeRegistrationForParticipant(const IdentityAddress &identAddresses); + void handleSubjectChange(SalCallOp *op); void setConferenceAddress (const IdentityAddress &conferenceAddress); - void setParticipantDevices (const IdentityAddress &addr, const std::list<ParticipantDeviceIdentity> &devices); - void addCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list<IdentityAddress> &compatibleParticipants); - void checkCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list<IdentityAddress> &addressesToCheck); + void updateParticipantDevices (const IdentityAddress &addr, const std::list<ParticipantDeviceIdentity> &devices); + void setParticipantDevicesAtCreation(const IdentityAddress &addr, const std::list<ParticipantDeviceIdentity> &devices); + void updateParticipantDeviceSession(const std::shared_ptr<ParticipantDevice> &device, bool freslyRegistered = false); + void updateParticipantsSessions(); + void conclude(); + void requestDeletion(); LinphoneReason onSipMessageReceived (SalOp *op, const SalMessage *message) override; + + /*These are the two methods called by the registration subscription module*/ + void setParticipantDevices(const IdentityAddress &addr, const std::list<ParticipantDeviceIdentity> &devices); + void notifyParticipantDeviceRegistration(const IdentityAddress &participantDevice); private: struct Message { @@ -115,12 +128,13 @@ private: static void copyMessageHeaders (const std::shared_ptr<Message> &fromMessage, const std::shared_ptr<ChatMessage> &toMessage); - void addParticipantDevice (const std::shared_ptr<Participant> &participant, const IdentityAddress &deviceAddress, const std::string &name = ""); - void byeDevice (const std::shared_ptr<ParticipantDevice> &device); + void addParticipantDevice (const std::shared_ptr<Participant> &participant, const ParticipantDeviceIdentity &deviceInfo); void designateAdmin (); void dispatchMessage (const std::shared_ptr<Message> &message, const std::string &uri); void finalizeCreation (); + std::shared_ptr<CallSession> makeSession(const std::shared_ptr<ParticipantDevice> &device); void inviteDevice (const std::shared_ptr<ParticipantDevice> &device); + void byeDevice (const std::shared_ptr<ParticipantDevice> &device); bool isAdminLeft () const; void queueMessage (const std::shared_ptr<Message> &message); void queueMessage (const std::shared_ptr<Message> &msg, const IdentityAddress &deviceAddress); @@ -140,10 +154,20 @@ private: const std::string &message ) override; void onCallSessionSetReleased (const std::shared_ptr<CallSession> &session) override; - - std::list<std::shared_ptr<Participant>> filteredParticipants; + void onAckReceived (const std::shared_ptr<CallSession> &session, LinphoneHeaders *headers) override; + struct RegistrationSubscriptionContext{ + bool notified = false; + void *context = nullptr; // TODO: unused currently, but can store a context pointer from the implementation of reginfo subscription. + // This will remove the need for a map in conference server for holding subscriptions. + }; + + std::list<std::shared_ptr<Participant>> authorizedParticipants; /*list of participant authorized to send messages to the chatroom. + This typically excludes participants that in the process of being removed.*/ ChatRoomListener *chatRoomListener = this; ServerGroupChatRoom::CapabilitiesMask capabilities = ServerGroupChatRoom::Capabilities::Conference; + std::map<std::string, RegistrationSubscriptionContext> registrationSubscriptions; /*map of registrationSubscriptions for each participant*/ + int unnotifiedRegistrationSubscriptions = 0; /*count of not-yet notified registration subscriptions*/ + std::shared_ptr<ParticipantDevice> mInitiatorDevice; /*pointer to the ParticipantDevice that is creating the chat room*/ bool joiningPendingAfterCreation = false; std::unordered_map<std::string, std::queue<std::shared_ptr<Message>>> queuedMessages; diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp index d02ec74fa755a82cc98311bdb31b5074fcbb7995..2cf488f99971574f49ebb36f56ffd511aef2dffb 100644 --- a/src/chat/chat-room/server-group-chat-room.cpp +++ b/src/chat/chat-room/server-group-chat-room.cpp @@ -57,16 +57,16 @@ ParticipantDeviceIdentity::ParticipantDeviceIdentity (const Address &address, co ParticipantDeviceIdentity::ParticipantDeviceIdentity (const ParticipantDeviceIdentity &other) : ClonableObject(*new ParticipantDeviceIdentityPrivate) { L_D(); - d->deviceAddress = other.getDeviceAddress(); - d->deviceName = other.getDeviceName(); + d->deviceAddress = other.getAddress(); + d->deviceName = other.getName(); } -const Address &ParticipantDeviceIdentity::getDeviceAddress () const { +const Address &ParticipantDeviceIdentity::getAddress () const { L_D(); return d->deviceAddress; } -const string &ParticipantDeviceIdentity::getDeviceName () const { +const string &ParticipantDeviceIdentity::getName () const { L_D(); return d->deviceName; } @@ -91,15 +91,19 @@ void ServerGroupChatRoomPrivate::setState (ChatRoom::State state) { if (state == ChatRoom::State::Created) { // Handle transitional states (joining and leaving of participants) // This is needed when the chat room is loaded from its state in database + list<IdentityAddress> participantAddresses; for (const auto &participant : qConference->getPrivate()->participants) { + participantAddresses.emplace_back(participant->getAddress()); bool atLeastOneDeviceLeaving = false; bool atLeastOneDeviceJoining = false; bool atLeastOneDevicePresent = false; for (const auto &device : participant->getPrivate()->getDevices()) { switch (getParticipantDeviceState(device)) { + case ParticipantDevice::State::ScheduledForLeaving: case ParticipantDevice::State::Leaving: atLeastOneDeviceLeaving = true; break; + case ParticipantDevice::State::ScheduledForJoining: case ParticipantDevice::State::Joining: atLeastOneDeviceJoining = true; break; @@ -114,42 +118,53 @@ void ServerGroupChatRoomPrivate::setState (ChatRoom::State state) { if (atLeastOneDeviceLeaving) { q->removeParticipant(participant); } else { - if (atLeastOneDeviceJoining) { - for (const auto &device : participant->getPrivate()->getDevices()) { - if (getParticipantDeviceState(device) == ParticipantDevice::State::Joining) - inviteDevice(device); - } - } if (atLeastOneDevicePresent || atLeastOneDeviceJoining) - filteredParticipants.push_back(participant); + authorizedParticipants.push_back(participant); } } - + updateParticipantsSessions(); + // Subscribe to the registration events from the proxy - for (const auto &participant : qConference->getPrivate()->participants) { - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneAddress *laddr = linphone_address_new(participant->getAddress().asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, laddr); - linphone_address_unref(laddr); - } + subscribeRegistrationForParticipants(participantAddresses); } } shared_ptr<Participant> ServerGroupChatRoomPrivate::addParticipant (const IdentityAddress &addr) { L_Q(); L_Q_T(LocalConference, qConference); + shared_ptr<Participant> participant = q->findParticipant(addr); if (!participant) { participant = make_shared<Participant>(qConference, addr); qConference->getPrivate()->participants.push_back(participant); + } + /* Case of participant that is still referenced in the chatroom, but no longer authorized because it has been removed + * previously OR a totally new participant. */ + if (findAuthorizedParticipant(addr) == nullptr){ + authorizedParticipants.push_back(participant); shared_ptr<ConferenceParticipantEvent> event = qConference->getPrivate()->eventHandler->notifyParticipantAdded(addr); q->getCore()->getPrivate()->mainDb->addEvent(event); } - filteredParticipants.remove(participant); - filteredParticipants.push_back(participant); return participant; } +/* This function is used to re-join devices of a participant that has left previously. Its device are still referenced until they 're all left. */ +void ServerGroupChatRoomPrivate::resumeParticipant(const std::shared_ptr<Participant> &participant){ + addParticipant(participant->getAddress()); + for (auto device : participant->getPrivate()->getDevices()){ + switch(device->getState()){ + case ParticipantDevice::State::Leaving: + case ParticipantDevice::State::Left: + case ParticipantDevice::State::ScheduledForLeaving: + device->setState(ParticipantDevice::State::ScheduledForJoining); + updateParticipantDeviceSession(device); + break; + default: + break; + } + } +} + ParticipantDevice::State ServerGroupChatRoomPrivate::getParticipantDeviceState (const shared_ptr<const ParticipantDevice> &device) const { return device->getState(); } @@ -160,8 +175,18 @@ void ServerGroupChatRoomPrivate::setParticipantDeviceState (const shared_ptr<Par lInfo() << q << ": Set participant device '" << address << "' state to " << state; device->setState(state); q->getCore()->getPrivate()->mainDb->updateChatRoomParticipantDevice(q->getSharedFromThis(), device); - if (state == ParticipantDevice::State::Leaving) - queuedMessages.erase(address); + switch (state){ + case ParticipantDevice::State::ScheduledForLeaving: + case ParticipantDevice::State::Leaving: + queuedMessages.erase(address); + break; + case ParticipantDevice::State::Left: + queuedMessages.erase(address); + onParticipantDeviceLeft(device); + break; + default: + break; + } } void ServerGroupChatRoomPrivate::acceptSession (const shared_ptr<CallSession> &session) { @@ -169,7 +194,6 @@ void ServerGroupChatRoomPrivate::acceptSession (const shared_ptr<CallSession> &s session->acceptUpdate(); else session->accept(); - joiningPendingAfterCreation = false; } void ServerGroupChatRoomPrivate::confirmCreation () { @@ -183,6 +207,22 @@ void ServerGroupChatRoomPrivate::confirmCreation () { CALL_CHAT_ROOM_CBS(cr, ConferenceAddressGeneration, conference_address_generation, cr); } +void ServerGroupChatRoomPrivate::requestDeletion(){ + L_Q(); + + for (auto participant : q->getParticipants()){ + unSubscribeRegistrationForParticipant(participant->getAddress()); + } + chatRoomListener->onChatRoomDeleteRequested(q->getSharedFromThis()); +} + +/* + * We go in this method in two cases: + * - when the client who created the conference INVITEs the conference address that was specified + * in the redirect response consecutive to the first INVITE that was made to the factory uri. + * In this case, joiningPendingAfterCreation is set to true. + * - when an already joined participant device reconnects for whatever reason. + */ void ServerGroupChatRoomPrivate::confirmJoining (SalCallOp *op) { L_Q(); L_Q_T(LocalConference, qConference); @@ -205,6 +245,9 @@ void ServerGroupChatRoomPrivate::confirmJoining (SalCallOp *op) { participant->getPrivate()->setAdmin(true); device = participant->getPrivate()->addDevice(gruu); session = device->getSession(); + mInitiatorDevice = device; + + /*Since the initiator of the chatroom has not yet subscribed at this stage, this won't generate NOTIFY, the events will be queued. */ shared_ptr<ConferenceParticipantDeviceEvent> deviceEvent = qConference->getPrivate()->eventHandler->notifyParticipantDeviceAdded(participant->getAddress(), gruu); q->getCore()->getPrivate()->mainDb->addEvent(deviceEvent); if (!(capabilities & ServerGroupChatRoom::Capabilities::OneToOne)) { @@ -220,6 +263,11 @@ void ServerGroupChatRoomPrivate::confirmJoining (SalCallOp *op) { joiningPendingAfterCreation = false; return; } + if (op->getRemoteBody().getContentType() == ContentType::ResourceLists){ + lError() << q << "Receiving ressource list body while not in creation step."; + op->decline(SalReasonNotAcceptable); + return; + } if (capabilities & ServerGroupChatRoom::Capabilities::OneToOne) participant->getPrivate()->setAdmin(true); device = participant->getPrivate()->addDevice(gruu); @@ -238,20 +286,22 @@ void ServerGroupChatRoomPrivate::confirmJoining (SalCallOp *op) { // Changes are only allowed from admin participants if (participant->isAdmin()) { - bool res = update(op); - if (!res) { - if (joiningPendingAfterCreation) { - lError() << q << ": Declining INVITE because we expected a non-empty list of participants to invite"; - op->decline(SalReasonNotAcceptable, ""); - chatRoomListener->onChatRoomDeleteRequested(q->getSharedFromThis()); - } else { - acceptSession(session); - } - return; - } - } else { - acceptSession(session); - } + if (joiningPendingAfterCreation){ + if (!initializeParticipants(participant, op)){ + op->decline(SalReasonNotAcceptable, nullptr); + requestDeletion(); + } + /* we don't accept the session yet: initializeParticipants() has launched queries for device information + * that will later populate the chatroom*/ + }else{ + /* after creation, only subject change is allowed*/ + handleSubjectChange(op); + acceptSession(session); + } + } else { + /*it is a non-admin participant that reconnected to the chatroom*/ + acceptSession(session); + } } void ServerGroupChatRoomPrivate::confirmRecreation (SalCallOp *op) { @@ -271,7 +321,6 @@ void ServerGroupChatRoomPrivate::confirmRecreation (SalCallOp *op) { void ServerGroupChatRoomPrivate::declineSession (const shared_ptr<CallSession> &session, LinphoneReason reason) { session->decline(reason); - joiningPendingAfterCreation = false; } void ServerGroupChatRoomPrivate::dispatchQueuedMessages () { @@ -312,9 +361,18 @@ void ServerGroupChatRoomPrivate::removeParticipant (const shared_ptr<const Parti L_Q(); L_Q_T(LocalConference, qConference); - for (const auto &p : filteredParticipants) { + for (const auto &device : participant->getPrivate()->getDevices()) { + if ((getParticipantDeviceState(device) == ParticipantDevice::State::Leaving) + || (getParticipantDeviceState(device) == ParticipantDevice::State::Left) + ) + continue; + setParticipantDeviceState(device, ParticipantDevice::State::ScheduledForLeaving); + updateParticipantDeviceSession(device); + } + + for (const auto &p : authorizedParticipants) { if (participant->getAddress() == p->getAddress()) { - filteredParticipants.remove(p); + authorizedParticipants.remove(p); break; } } @@ -328,18 +386,10 @@ void ServerGroupChatRoomPrivate::removeParticipant (const shared_ptr<const Parti } if (!isAdminLeft()) designateAdmin(); - - // Cancel ongoing INVITEs, and start new ones with the correct participant list - for (const auto &p : qConference->getPrivate()->participants) { - for (const auto &device : p->getPrivate()->getDevices()) { - if (getParticipantDeviceState(device) == ParticipantDevice::State::Joining) - inviteDevice(device); - } - } } -shared_ptr<Participant> ServerGroupChatRoomPrivate::findFilteredParticipant (const shared_ptr<const CallSession> &session) const { - for (const auto &participant : filteredParticipants) { +shared_ptr<Participant> ServerGroupChatRoomPrivate::findAuthorizedParticipant (const shared_ptr<const CallSession> &session) const { + for (const auto &participant : authorizedParticipants) { shared_ptr<ParticipantDevice> device = participant->getPrivate()->findDevice(session); if (device || (participant->getPrivate()->getSession() == session)) return participant; @@ -347,10 +397,10 @@ shared_ptr<Participant> ServerGroupChatRoomPrivate::findFilteredParticipant (con return nullptr; } -shared_ptr<Participant> ServerGroupChatRoomPrivate::findFilteredParticipant (const IdentityAddress &participantAddress) const { +shared_ptr<Participant> ServerGroupChatRoomPrivate::findAuthorizedParticipant (const IdentityAddress &participantAddress) const { IdentityAddress searchedAddr(participantAddress); searchedAddr.setGruu(""); - for (const auto &participant : filteredParticipants) { + for (const auto &participant : authorizedParticipants) { if (participant->getAddress() == searchedAddr) return participant; } @@ -367,19 +417,85 @@ void ServerGroupChatRoomPrivate::subscriptionStateChanged (LinphoneEvent *event, qConference->getPrivate()->eventHandler->subscriptionStateChanged(event, state); } -bool ServerGroupChatRoomPrivate::update (SalCallOp *op) { +void ServerGroupChatRoomPrivate::handleSubjectChange(SalCallOp *op){ L_Q(); if (sal_custom_header_find(op->getRecvCustomHeaders(), "Subject")) { // Handle subject change lInfo() << q << ": New subject \"" << op->getSubject() << "\""; q->setSubject(op->getSubject()); } +} + +/* + * This function setups registration subscriptions if not already there. + * If no registration subscription is started (because they were all running already), it returns false. + */ +bool ServerGroupChatRoomPrivate::subscribeRegistrationForParticipants(const std::list<IdentityAddress> &identAddresses){ + L_Q(); + std::list<IdentityAddress> requestedAddresses; + bool subscriptionsPending = false; + + // Subscribe to the registration events from the proxy + for (const auto &addr : identAddresses) { + if (registrationSubscriptions.find(addr.asString()) == registrationSubscriptions.end()){ + requestedAddresses.emplace_back(addr); + unnotifiedRegistrationSubscriptions++; + subscriptionsPending = true; + } + } + + for (const auto &addr : requestedAddresses){ + LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); + LinphoneAddress *laddr = linphone_address_new(addr.asString().c_str()); + registrationSubscriptions[addr.asString()].context = nullptr; // we 'll put here later a context pointer returned by the callback. + CALL_CHAT_ROOM_CBS(cr, ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, laddr); + linphone_address_unref(laddr); + } + return subscriptionsPending; +} + +void ServerGroupChatRoomPrivate::unSubscribeRegistrationForParticipant(const IdentityAddress &identAddress){ + L_Q(); + auto p = registrationSubscriptions.find(identAddress.asString()); + if (p == registrationSubscriptions.end()){ + lError() << q << " no active subscription for " << identAddress; + return; + } + registrationSubscriptions.erase(p); + + LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); + LinphoneAddress *laddr = linphone_address_new(identAddress.asString().c_str()); + CALL_CHAT_ROOM_CBS(cr, ParticipantRegistrationUnsubscriptionRequested, participant_registration_unsubscription_requested, cr, laddr); + linphone_address_unref(laddr); +} + +bool ServerGroupChatRoomPrivate::initializeParticipants (const shared_ptr<Participant> & initiator, SalCallOp *op) { + + handleSubjectChange(op); // Handle participants addition list<IdentityAddress> identAddresses = ServerGroupChatRoom::parseResourceLists(op->getRemoteBody()); - if (identAddresses.empty()) + if (identAddresses.empty()){ + lError()<<"ServerGroupChatRoomPrivate::initializeParticipants(): empty list !"; return false; - - checkCompatibleParticipants(IdentityAddress(op->getRemoteContact()), identAddresses); + } + + identAddresses.unique(); //Protection for the case where the client has sent a list with duplicates + + if (capabilities & ServerGroupChatRoom::Capabilities::OneToOne){ + if (identAddresses.size() > 1){ + lError()<<"ServerGroupChatRoomPrivate::initializeParticipants(): chatroom is one to one but the list contains multiple participants !"; + return false; + } + } + identAddresses.push_back(initiator->getAddress()); + if (initiator->getPrivate()->getDevices().size() > 1){ + /* This happens only for one to one chatrooms, when a participant has left and then resumes the chatroom.*/ + resumeParticipant(initiator); + } + if (!subscribeRegistrationForParticipants(identAddresses)){ + /* If we are not waiting for any registration information, then we can conclude immediately. */ + conclude(); + } return true; } @@ -389,7 +505,7 @@ LinphoneReason ServerGroupChatRoomPrivate::onSipMessageReceived (SalOp *op, cons L_Q(); // Check that the message is coming from a participant of the chat room IdentityAddress fromAddr(op->getFrom()); - if (!findFilteredParticipant(fromAddr)) { + if (!findAuthorizedParticipant(fromAddr)) { return LinphoneReasonNotAcceptable; } @@ -409,9 +525,7 @@ LinphoneReason ServerGroupChatRoomPrivate::onSipMessageReceived (SalOp *op, cons q->getParticipants().front()->getAddress() ); if (queuedMessages[missingAddr.asString()].size() == 0) { - list<IdentityAddress> identAddresses; - identAddresses.push_back(missingAddr); - checkCompatibleParticipants(IdentityAddress(op->getFrom()), identAddresses); + q->addParticipant(missingAddr, nullptr, false); } queueMessage(msg, missingAddr); return LinphoneReasonNone; @@ -429,7 +543,10 @@ void ServerGroupChatRoomPrivate::setConferenceAddress (const IdentityAddress &co if (!conferenceAddress.isValid()) { shared_ptr<CallSession> session = q->getMe()->getPrivate()->getSession(); - session->decline(LinphoneReasonServerTimeout); + LinphoneErrorInfo *ei = linphone_error_info_new(); + linphone_error_info_set(ei, "SIP", LinphoneReasonUnknown, 500, "Server internal error", NULL); + session->decline(ei); + linphone_error_info_unref(ei); setState(ChatRoom::State::CreationFailed); return; } @@ -443,102 +560,98 @@ void ServerGroupChatRoomPrivate::setConferenceAddress (const IdentityAddress &co finalizeCreation(); } -void ServerGroupChatRoomPrivate::setParticipantDevices (const IdentityAddress &participantAddress, const list<ParticipantDeviceIdentity> &deviceIdentities) { - L_Q(); - shared_ptr<Participant> participant = q->findParticipant(participantAddress); - if (!participant) - return; - - lInfo() << q << ": Setting " << deviceIdentities.size() << " participant device(s) for " << participantAddress.asString(); - - // Remove devices that are in the chatroom but no longer in the given list - list<shared_ptr<ParticipantDevice>> devicesToRemove; - for (const auto &device : participant->getPrivate()->getDevices()) { - auto predicate = [device] (ParticipantDeviceIdentity deviceIdentity) { - return device->getAddress() == deviceIdentity.getDeviceAddress(); - }; - const auto it = find_if(deviceIdentities.cbegin(), deviceIdentities.cend(), predicate); - if (it == deviceIdentities.cend()) - devicesToRemove.push_back(device); - } - for (const auto &device : devicesToRemove) - removeParticipantDevice(participant, device->getAddress()); - - // Add all the devices in the given list - for (const auto &deviceIdentity : deviceIdentities) - addParticipantDevice(participant, deviceIdentity.getDeviceAddress(), deviceIdentity.getDeviceName()); - - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneAddress *laddr = linphone_address_new(participantAddress.asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, laddr); - linphone_address_unref(laddr); -} -void ServerGroupChatRoomPrivate::addCompatibleParticipants (const IdentityAddress &deviceAddr, const list<IdentityAddress> &compatibleParticipants) { +void ServerGroupChatRoomPrivate::updateParticipantDevices(const IdentityAddress &participantAddress, const list<ParticipantDeviceIdentity> &devices){ L_Q(); - shared_ptr<Participant> participant = findFilteredParticipant(deviceAddr); - if (!participant) { - lError() << q << ": The device address that asked for compatible participants checking cannot be found in the filtered participants list"; - return; - } - shared_ptr<ParticipantDevice> device = participant->getPrivate()->findDevice(deviceAddr); - shared_ptr<CallSession> session = device->getSession(); - if (compatibleParticipants.size() == 0) { - lError() << q << ": No compatible participants have been found"; - if (session) { - bool toDelete = joiningPendingAfterCreation; - declineSession(session, LinphoneReasonNotAcceptable); - if (toDelete) - chatRoomListener->onChatRoomDeleteRequested(q->getSharedFromThis()); + bool newParticipantReginfo = false; + + auto it = registrationSubscriptions.find(participantAddress.asString()); + + /* + * For security, since registration information might come from outside, make sure that the device list we are asked to add + * are from a participant for which we have requested device information, ie a participant that is in the process of being + * added to the chatroom + */ + if (it == registrationSubscriptions.end()){ + lError() << "updateParticipantDevices(): " << participantAddress << " registration info was not requested."; + }else{ + // Mark this registration subscription as being notified if not already the case. + if ( !(*it).second.notified) { + (*it).second.notified = true; + newParticipantReginfo = true; + unnotifiedRegistrationSubscriptions--; } - } else { - lInfo() << q << ": Adding " << compatibleParticipants.size() << " compatible participant(s)"; - if (capabilities & ServerGroupChatRoom::Capabilities::OneToOne) { - list<IdentityAddress> addressesToAdd(compatibleParticipants); - addressesToAdd.sort(); - list<IdentityAddress> addresses; - for (const auto &p : q->getParticipants()) { - addresses.push_back(p->getAddress()); - } - addresses.sort(); - addresses.merge(addressesToAdd); - addresses.unique(); - if (session && (addresses.size() > 2)) { - // Decline the participants addition to prevent having more than 2 participants in a one-to-one chat room. - declineSession(session, LinphoneReasonNotAcceptable); + } + + if (!devices.empty()){ + shared_ptr<Participant> participant = addParticipant(participantAddress); + + lInfo() << q << ": Setting " << devices.size() << " participant device(s) for " << participantAddress.asString(); + + // Remove devices that are in the chatroom but no longer in the given list + list<shared_ptr<ParticipantDevice>> devicesToRemove; + for (const auto &device : participant->getPrivate()->getDevices()) { + auto predicate = [device] (const ParticipantDeviceIdentity & deviceIdentity) { + return device->getAddress() == deviceIdentity.getAddress(); + }; + auto it = find_if(devices.cbegin(), devices.cend(), predicate); + if (it == devices.cend()){ + lInfo() << q << "Device " << device << " is no longer registered, it will be removed from the chatroom."; + devicesToRemove.push_back(device); } } - if (session) - acceptSession(session); + for (auto &device : devicesToRemove) + removeParticipantDevice(participant, device->getAddress()); + + // Add all the devices in the given list, if already present they will be ignored + for (const auto &device : devices) + addParticipantDevice(participant, device); + }else{ + if (newParticipantReginfo){ + lInfo() << q << participantAddress << " has no compatible devices."; + unSubscribeRegistrationForParticipant(participantAddress); + } + } +} - lInfo() << q << ": Fetching participant devices"; - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneAddress *laddr = linphone_address_new(participant->getAddress().asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, laddr); - linphone_address_unref(laddr); - q->addParticipants(compatibleParticipants, nullptr, false); +void ServerGroupChatRoomPrivate::conclude(){ + L_Q(); + lInfo() << q << "All devices are known, the chatroom creation can be concluded."; + shared_ptr<CallSession> session = mInitiatorDevice->getSession(); + + if (q->getParticipants().size() < 2){ + lError() << q << ": there are less than 2 participants in this chatroom, refusing creation."; + declineSession(session, LinphoneReasonNotAcceptable); + requestDeletion(); + }else{ + /* Ok we are going to accept the session with 200Ok. However we want to wait for the ACK to be sure + * that the initiator is aware that he's now part of the conference, before we invite the others. + */ + acceptSession(session); if ((capabilities & ServerGroupChatRoom::Capabilities::OneToOne) && (q->getParticipantCount() == 2)) { // Insert the one-to-one chat room in Db if participants count is 2. - bool encrypted = ((capabilities & ServerGroupChatRoom::Capabilities::Encrypted) != 0); - q->getCore()->getPrivate()->mainDb->insertOneToOneConferenceChatRoom(q->getSharedFromThis(), encrypted); + q->getCore()->getPrivate()->mainDb->insertOneToOneConferenceChatRoom(q->getSharedFromThis(), + !!(capabilities & ServerGroupChatRoom::Capabilities::Encrypted) ); } } } -void ServerGroupChatRoomPrivate::checkCompatibleParticipants (const IdentityAddress &deviceAddr, const list<IdentityAddress> &addressesToCheck) { - L_Q(); - list<Address> addresses; - for (const auto &addr : addressesToCheck) { - addresses.push_back(Address(addr)); +void ServerGroupChatRoomPrivate::setParticipantDevicesAtCreation(const IdentityAddress &participantAddress, const list<ParticipantDeviceIdentity> &devices) { + + updateParticipantDevices(participantAddress, devices); + if (unnotifiedRegistrationSubscriptions == 0){ + conclude(); } +} - lInfo() << q << ": Checking compatible participants"; - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - bctbx_list_t * cAddresses = L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(addresses); - LinphoneAddress *cDeviceAddr = linphone_address_new(deviceAddr.asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantsCapabilitiesChecked, participants_capabilities_checked, cr, cDeviceAddr, cAddresses); - linphone_address_unref(cDeviceAddr); - bctbx_list_free_with_data(cAddresses, (bctbx_list_free_func)linphone_address_unref); +void ServerGroupChatRoomPrivate::setParticipantDevices(const IdentityAddress &participantAddress, const list<ParticipantDeviceIdentity> &devices) { + + if (joiningPendingAfterCreation){ + setParticipantDevicesAtCreation(participantAddress, devices); + }else{ + updateParticipantDevices(participantAddress, devices); + updateParticipantsSessions(); + } } // ----------------------------------------------------------------------------- @@ -556,69 +669,69 @@ void ServerGroupChatRoomPrivate::copyMessageHeaders (const shared_ptr<Message> & } } -void ServerGroupChatRoomPrivate::addParticipantDevice (const shared_ptr<Participant> &participant, const IdentityAddress &deviceAddress, const string &name) { +/* + * This method is in charge of applying the state of a participant device to the SIP session + */ +void ServerGroupChatRoomPrivate::updateParticipantDeviceSession(const shared_ptr<ParticipantDevice> &device, bool freshlyRegistered){ + switch (device->getState()) { + case ParticipantDevice::State::ScheduledForJoining: + inviteDevice(device); + break; + case ParticipantDevice::State::Joining: + if (freshlyRegistered) inviteDevice(device); + break; + case ParticipantDevice::State::Left: + break; + case ParticipantDevice::State::ScheduledForLeaving: + byeDevice(device); + break; + case ParticipantDevice::State::Leaving: + if (freshlyRegistered) byeDevice(device); + break; + case ParticipantDevice::State::Present: + //nothing to do + break; + } +} + +/* + * This method is in charge of applying to the SIP session + * the state of all participant devices belonging to a participant. + */ +void ServerGroupChatRoomPrivate::updateParticipantsSessions(){ L_Q(); - L_Q_T(LocalConference, qConference); - shared_ptr<ParticipantDevice> device = participant->getPrivate()->findDevice(deviceAddress); - if (device) { - lInfo() << q << ": Adding participant device that is currently in state [" << device->getState() << "]"; - if (!name.empty()) - device->setName(name); - switch (device->getState()) { - case ParticipantDevice::State::Joining: - inviteDevice(device); - break; - case ParticipantDevice::State::Left: - if (findFilteredParticipant(participant->getAddress())) - inviteDevice(device); - break; - case ParticipantDevice::State::Leaving: - byeDevice(device); - break; - default: - break; + + for (const auto &p : q->getParticipants()){ + for (const auto &device : p->getPrivate()->getDevices()){ + updateParticipantDeviceSession(device); } - } else if (findFilteredParticipant(participant->getAddress())) { - // Add device only if participant is not currently being removed - device = participant->getPrivate()->addDevice(deviceAddress, name); - setParticipantDeviceState(device, ParticipantDevice::State::Joining); - shared_ptr<ConferenceParticipantDeviceEvent> event = qConference->getPrivate()->eventHandler->notifyParticipantDeviceAdded(participant->getAddress(), deviceAddress); - q->getCore()->getPrivate()->mainDb->addEvent(event); - inviteDevice(device); } } -void ServerGroupChatRoomPrivate::byeDevice (const std::shared_ptr<ParticipantDevice> &device) { +void ServerGroupChatRoomPrivate::addParticipantDevice (const shared_ptr<Participant> &participant, const ParticipantDeviceIdentity &deviceInfo) { L_Q(); L_Q_T(LocalConference, qConference); - - shared_ptr<CallSession> session = device->getSession(); - if (session && (session->getState() != CallSession::State::Released)) { - session->terminate(); - return; - } - // If 481 is received when sending the BYE, perform a new INVITE to send the BYE correctly - if (!session || (session->getReason() == LinphoneReasonNoMatch)) { - CallSessionParams csp; - auto participant = device->getParticipant(); - session = participant->getPrivate()->createSession(*q, &csp, false, this); - session->configure(LinphoneCallOutgoing, nullptr, nullptr, qConference->getPrivate()->conferenceAddress, device->getAddress()); - device->setSession(session); - session->initiateOutgoing(); - session->getPrivate()->createOp(); - Address contactAddr(qConference->getPrivate()->conferenceAddress); - contactAddr.setParam("isfocus"); - contactAddr.setParam("text"); - session->getPrivate()->getOp()->setContactAddress(contactAddr.getPrivate()->getInternalAddress()); - session->startInvite(nullptr, q->getSubject(), nullptr); + shared_ptr<ParticipantDevice> device = participant->getPrivate()->findDevice(deviceInfo.getAddress()); + + if (device) { + // Nothing to do, but set the name because the user-agent is not known for the initiator device. + device->setName(deviceInfo.getName()); + } else if (findAuthorizedParticipant(participant->getAddress())) { + /* + * This is a really new device. + */ + device = participant->getPrivate()->addDevice(deviceInfo.getAddress(), deviceInfo.getName()); + setParticipantDeviceState(device, ParticipantDevice::State::ScheduledForJoining); + shared_ptr<ConferenceParticipantDeviceEvent> event = qConference->getPrivate()->eventHandler->notifyParticipantDeviceAdded(participant->getAddress(), deviceInfo.getAddress()); + q->getCore()->getPrivate()->mainDb->addEvent(event); } } void ServerGroupChatRoomPrivate::designateAdmin () { L_Q(); // Do not designate new admin for one-to-one chat room - if (!(capabilities & ServerGroupChatRoom::Capabilities::OneToOne) && !filteredParticipants.empty()) { - q->setParticipantAdminStatus(filteredParticipants.front(), true); + if (!(capabilities & ServerGroupChatRoom::Capabilities::OneToOne) && !authorizedParticipants.empty()) { + q->setParticipantAdminStatus(authorizedParticipants.front(), true); lInfo() << q << ": New admin designated"; } } @@ -661,39 +774,61 @@ void ServerGroupChatRoomPrivate::finalizeCreation () { chatRoomListener->onChatRoomInsertInDatabaseRequested(q->getSharedFromThis()); } -void ServerGroupChatRoomPrivate::inviteDevice (const shared_ptr<ParticipantDevice> &device) { +//returns true if a new session has been created, false if there is already an existing and valid one. +shared_ptr<CallSession> ServerGroupChatRoomPrivate::makeSession(const std::shared_ptr<ParticipantDevice> &device){ L_Q(); L_Q_T(LocalConference, qConference); - lInfo() << q << ": Inviting device '" << device->getAddress().asString() << "'"; - shared_ptr<Participant> participant = const_pointer_cast<Participant>(device->getParticipant()->getSharedFromThis()); shared_ptr<CallSession> session = device->getSession(); - if (session && (session->getDirection() == LinphoneCallIncoming)) - return; // Do not try to invite the device that is currently creating the chat room - device->setState(ParticipantDevice::State::Joining); - if (!session - || (session->getState() == CallSession::State::End) - || (session->getState() == CallSession::State::Error) - || (session->getState() == CallSession::State::Released) - ) { + + if (session){ + switch (session->getState()){ + case CallSession::State::End: + case CallSession::State::Error: + case CallSession::State::Released: + session = nullptr; //our session is dead, we'll make a new one. + break; + default: + break; + } + } + if (!session) { CallSessionParams csp; if (capabilities & ServerGroupChatRoom::Capabilities::OneToOne) csp.addCustomHeader("One-To-One-Chat-Room", "true"); if (capabilities & ServerGroupChatRoom::Capabilities::Encrypted) csp.addCustomHeader("End-To-End-Encrypted", "true"); + shared_ptr<Participant> participant = const_pointer_cast<Participant>(device->getParticipant()->getSharedFromThis()); session = participant->getPrivate()->createSession(*q, &csp, false, this); session->configure(LinphoneCallOutgoing, nullptr, nullptr, qConference->getPrivate()->conferenceAddress, device->getAddress()); device->setSession(session); session->initiateOutgoing(); session->getPrivate()->createOp(); + Address contactAddr(qConference->getPrivate()->conferenceAddress); + contactAddr.setParam("isfocus"); + contactAddr.setParam("text"); + session->getPrivate()->getOp()->setContactAddress(contactAddr.getPrivate()->getInternalAddress()); } + return session; +} - Address contactAddr(qConference->getPrivate()->conferenceAddress); - contactAddr.setParam("isfocus"); - contactAddr.setParam("text"); - session->getPrivate()->getOp()->setContactAddress(contactAddr.getPrivate()->getInternalAddress()); +void ServerGroupChatRoomPrivate::inviteDevice (const shared_ptr<ParticipantDevice> &device) { + L_Q(); + lInfo() << q << ": Inviting device '" << device->getAddress().asString() << "'"; + shared_ptr<Participant> participant = const_pointer_cast<Participant>(device->getParticipant()->getSharedFromThis()); + shared_ptr<CallSession> session = makeSession(device); + if (device->getState() == ParticipantDevice::State::Joining && session->getState() == CallSession::State::OutgoingProgress){ + lInfo() << q << "outgoing INVITE already in progress."; + return; + } + device->setState(ParticipantDevice::State::Joining); + if (session && session->getState() == CallSession::State::IncomingReceived){ + lInfo() << q << "incoming INVITE in progress."; + return; + } + list<IdentityAddress> addressesList; - for (const auto &invitedParticipant : filteredParticipants) { + for (const auto &invitedParticipant : authorizedParticipants) { if (invitedParticipant != participant) addressesList.push_back(invitedParticipant->getAddress()); } @@ -706,8 +841,45 @@ void ServerGroupChatRoomPrivate::inviteDevice (const shared_ptr<ParticipantDevic session->startInvite(nullptr, q->getSubject(), &content); } +void ServerGroupChatRoomPrivate::byeDevice (const std::shared_ptr<ParticipantDevice> &device) { + L_Q(); + + lInfo() << q << ": Asking device '" << device->getAddress().asString() << "' to leave"; + device->setState(ParticipantDevice::State::Leaving); + shared_ptr<CallSession> session = makeSession(device); + switch(session->getState()){ + case CallSession::State::OutgoingInit: + session->startInvite(nullptr, q->getSubject(), nullptr); + break; + case CallSession::State::Connected: + case CallSession::State::StreamsRunning: + session->terminate(); + break; + default: + break; + } +} + +/* + * This method is to be called by the conference server when it is notified that a device has just registered*/ +void ServerGroupChatRoomPrivate::notifyParticipantDeviceRegistration(const IdentityAddress &participantDevice){ + L_Q(); + + shared_ptr<Participant> participant = q->findParticipant(participantDevice); + if (!participant){ + lError() << q << ": " << participantDevice << " is not part of the chatroom."; + return; + } + shared_ptr<ParticipantDevice> pd = participant->getPrivate()->findDevice(participantDevice); + if (!pd){ + lError() << q << ": device " << participantDevice << " is not part of any participant of the chatroom."; + return; + } + updateParticipantDeviceSession(pd, true); +} + bool ServerGroupChatRoomPrivate::isAdminLeft () const { - for (const auto &participant : filteredParticipants) { + for (const auto &participant : authorizedParticipants) { if (participant->isAdmin()) return true; } @@ -756,7 +928,6 @@ void ServerGroupChatRoomPrivate::onParticipantDeviceLeft (const std::shared_ptr< lInfo() << q << ": Participant device '" << device->getAddress().asString() << "' left"; shared_ptr<Participant> participant = const_pointer_cast<Participant>(device->getParticipant()->getSharedFromThis()); - setParticipantDeviceState(device, ParticipantDevice::State::Left); bool allDevicesLeft = true; for (const auto &device : participant->getPrivate()->getDevices()) { @@ -765,28 +936,21 @@ void ServerGroupChatRoomPrivate::onParticipantDeviceLeft (const std::shared_ptr< break; } } - if (allDevicesLeft && !findFilteredParticipant(participant->getAddress())) { - lInfo() << q << ": Removing participant '" << participant->getAddress().asString() << "' since it has no device left"; - - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneAddress *laddr = linphone_address_new(participant->getAddress().asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantRegistrationUnsubscriptionRequested, participant_registration_unsubscription_requested, cr, laddr); - linphone_address_unref(laddr); - - qConference->getPrivate()->participants.remove(participant); - filteredParticipants.remove(participant); - } else if (findFilteredParticipant(participant->getAddress())) { - lInfo() << q << ": Removing last device for participant '" << participant->getAddress().asString() - << "' that is still in the chatroom, fetch devices to reinvite them"; - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(q); - LinphoneAddress *laddr = linphone_address_new(participant->getAddress().asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, laddr); - linphone_address_unref(laddr); + if (allDevicesLeft) { + /* Handle the case where a participant is removed forcibly because all its devices have been retired. */ + if (findAuthorizedParticipant(participant->getAddress()) != nullptr) { + lInfo() << q << ": Removing participant '" << participant->getAddress().asString() << "' since it has no device left"; + q->removeParticipant(participant); + } + /* When all devices have left, we can get rid of our reginfo subscription. */ + unSubscribeRegistrationForParticipant(participant->getAddress()); + /* And remove the existence of this participant in the chatroom*/ + q->LocalConference::removeParticipant(participant); } if (qConference->getPrivate()->participants.size() == 0) { lInfo() << q << ": No participant left, deleting the chat room"; - chatRoomListener->onChatRoomDeleteRequested(q->getSharedFromThis()); + requestDeletion(); } } @@ -811,38 +975,50 @@ void ServerGroupChatRoomPrivate::onChatRoomDeleteRequested (const shared_ptr<Abs void ServerGroupChatRoomPrivate::onCallSessionStateChanged (const shared_ptr<CallSession> &session, CallSession::State newState, const string &message) { L_Q(); - if ((newState == CallSession::State::End) && (session->getPreviousState() == CallSession::State::Connected)) { - auto device = q->findParticipantDevice(session); - if (device && (device->getState() == ParticipantDevice::State::Present)) { - // Participant leaves the chat room on its own by sending a BYE - setParticipantDeviceState(device, ParticipantDevice::State::Leaving); - q->removeParticipant(device->getParticipant()->getSharedFromThis()); - } - } else if (newState == CallSession::State::Released) { - if (session->getReason() == LinphoneReasonNone) { - auto device = q->findParticipantDevice(session); - if (device && (device->getState() == ParticipantDevice::State::Leaving)) - onParticipantDeviceLeft(device); - } else if (session->getReason() == LinphoneReasonNoMatch) { - auto device = q->findParticipantDevice(session); - if (device) { - if (device->getState() == ParticipantDevice::State::Joining) - inviteDevice(device); - else if (device->getState() == ParticipantDevice::State::Leaving) + auto device = q->findParticipantDevice(session); + if (!device) { + lError() << q << "onCallSessionStateChanged on unknown device."; + return; + } + switch(newState){ + case CallSession::State::Connected: + if (device->getState() == ParticipantDevice::State::Leaving) + byeDevice(device); + break; + case CallSession::State::End: + if (device->getState() == ParticipantDevice::State::Present){ + lInfo() << q << device->getParticipant()->getAddress().asString() << " is leaving the chatroom."; + // Participant leaves the chat room on its own by sending a BYE. + // Set it the Leaving state first, so that q->removeParticipant() does nothing with it. + setParticipantDeviceState(device, ParticipantDevice::State::Leaving); + q->removeParticipant(device->getParticipant()->getSharedFromThis()); + // But since we received its BYE, it is actually already left. + setParticipantDeviceState(device, ParticipantDevice::State::Left); + } + break; + case CallSession::State::Released: + /* Handle the case of participant we've send a BYE. */ + if (device->getState() == ParticipantDevice::State::Leaving && session->getPreviousState() == CallSession::State::End){ + if (session->getReason() == LinphoneReasonNone){ + /* We've received a 200 Ok for our BYE, so it is assumed to be left. */ + setParticipantDeviceState(device, ParticipantDevice::State::Left); + }else if (session->getReason() == LinphoneReasonNoMatch){ + /* Our current session was lost, but the device is currently reachable, so retry to send the BYE now. */ byeDevice(device); + } + } + break; + case CallSession::State::UpdatedByRemote: + { + shared_ptr<Participant> participant = findAuthorizedParticipant(session); + if (participant && participant->isAdmin()) { + /* The only thing that a participant can change with re-INVITE is the subject. */ + handleSubjectChange(session->getPrivate()->getOp()); } } - } else if (newState == CallSession::State::UpdatedByRemote) { - shared_ptr<Participant> participant = findFilteredParticipant(session); - if (participant && participant->isAdmin()) { - bool res = update(session->getPrivate()->getOp()); - if (res) - session->deferUpdate(); - } - } else if (newState == CallSession::State::Connected) { - auto device = q->findParticipantDevice(session); - if (device && (device->getState() == ParticipantDevice::State::Leaving)) - device->getSession()->terminate(); + break; + default: + break; } } @@ -853,6 +1029,15 @@ void ServerGroupChatRoomPrivate::onCallSessionSetReleased (const shared_ptr<Call device->setSession(nullptr); } +void ServerGroupChatRoomPrivate::onAckReceived (const std::shared_ptr<CallSession> &session, LinphoneHeaders *headers){ + L_Q(); + if (joiningPendingAfterCreation && mInitiatorDevice && mInitiatorDevice->getSession() == session){ + lInfo() << q << " got ACK from initiator of the chatroom, things can start now."; + joiningPendingAfterCreation = false; + updateParticipantsSessions(); + } +} + // ============================================================================= ServerGroupChatRoom::ServerGroupChatRoom (const shared_ptr<Core> &core, SalCallOp *op) @@ -865,7 +1050,6 @@ LocalConference(getCore(), IdentityAddress(linphone_proxy_config_get_conference_ const char *oneToOneChatRoomStr = sal_custom_header_find(op->getRecvCustomHeaders(), "One-To-One-Chat-Room"); if (oneToOneChatRoomStr && (strcmp(oneToOneChatRoomStr, "true") == 0)) d->capabilities |= ServerGroupChatRoom::Capabilities::OneToOne; - string endToEndEncrypted = L_C_TO_STRING(sal_custom_header_find(op->getRecvCustomHeaders(), "End-To-End-Encrypted")); if (endToEndEncrypted == "true") d->capabilities |= ServerGroupChatRoom::Capabilities::Encrypted; @@ -937,33 +1121,39 @@ bool ServerGroupChatRoom::hasBeenLeft () const { return false; } -// ----------------------------------------------------------------------------- - bool ServerGroupChatRoom::addParticipant (const IdentityAddress &addr, const CallSessionParams *params, bool hasMedia) { L_D(); - L_D_T(LocalConference, dConference); - if (d->findFilteredParticipant(addr)) { + + if (addr.hasGruu()){ + lInfo() << this << ": Not adding participant '" << addr.asString() << "' because it is a gruu address."; + return false; + } + + if (d->findAuthorizedParticipant(addr)) { lInfo() << this << ": Not adding participant '" << addr.asString() << "' because it is already a participant"; return false; } - if ((d->capabilities & ServerGroupChatRoom::Capabilities::OneToOne) && (getParticipantCount() == 2)) { + shared_ptr<Participant> participant = findParticipant(addr); + + if (participant == nullptr && (d->capabilities & ServerGroupChatRoom::Capabilities::OneToOne) && getParticipantCount() == 2) { lInfo() << this << ": Not adding participant '" << addr.asString() << "' because this OneToOne chat room already has 2 participants"; return false; } - - lInfo() << this << ": Adding participant '" << addr.asString() << "'"; - if (!findParticipant(addr)) - LocalConference::addParticipant(addr, params, hasMedia); - d->filteredParticipants.push_back(findParticipant(addr)); - shared_ptr<ConferenceParticipantEvent> event = dConference->eventHandler->notifyParticipantAdded(addr); - getCore()->getPrivate()->mainDb->addEvent(event); - - LinphoneChatRoom *cr = L_GET_C_BACK_PTR(this); - LinphoneAddress *laddr = linphone_address_new(addr.asString().c_str()); - CALL_CHAT_ROOM_CBS(cr, ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, laddr); - linphone_address_unref(laddr); - + + /* Handle the case where a participant is removed then re-added to chat room. In such case, until all devices have left the chatroom, + * the participant is still referenced in the chatroom, with devices either left or leaving. + * Furthermore, registration subscription is still active, so we don't need to wait for a notify, and we can instead + * proceed immediately with the INVITE of its devices. + */ + if (participant){ + d->resumeParticipant(participant); + }else{ + lInfo() << this << ": Requested to add participant '" << addr.asString() << "', checking capabilities first."; + list<IdentityAddress> participantsList; + participantsList.push_back(addr); + d->subscribeRegistrationForParticipants(participantsList); + } return true; } @@ -993,7 +1183,7 @@ int ServerGroupChatRoom::getParticipantCount () const { const list<shared_ptr<Participant>> &ServerGroupChatRoom::getParticipants () const { L_D(); - return d->filteredParticipants; + return d->authorizedParticipants; } const string &ServerGroupChatRoom::getSubject () const { @@ -1019,15 +1209,6 @@ void ServerGroupChatRoom::onFirstNotifyReceived (const IdentityAddress &addr) { bool ServerGroupChatRoom::removeParticipant (const shared_ptr<Participant> &participant) { L_D(); - for (const auto &device : participant->getPrivate()->getDevices()) { - if ((d->getParticipantDeviceState(device) == ParticipantDevice::State::Leaving) - || (d->getParticipantDeviceState(device) == ParticipantDevice::State::Left) - ) - continue; - d->setParticipantDeviceState(device, ParticipantDevice::State::Leaving); - lInfo() << this << ": Asking device '" << device->getAddress().asString() << "' to leave"; - d->byeDevice(device); - } d->removeParticipant(participant); return true; } diff --git a/src/conference/participant-device.cpp b/src/conference/participant-device.cpp index 285e88660bf950606f85dc8d1bf3b9260d4b9dea..706da7472b492f2761000c0591516e6658aeb9ed 100644 --- a/src/conference/participant-device.cpp +++ b/src/conference/participant-device.cpp @@ -74,10 +74,14 @@ AbstractChatRoom::SecurityLevel ParticipantDevice::getSecurityLevel () const { ostream &operator<< (ostream &stream, ParticipantDevice::State state) { switch (state) { + case ParticipantDevice::State::ScheduledForJoining: + return stream << "ScheduledForJoining"; case ParticipantDevice::State::Joining: return stream << "Joining"; case ParticipantDevice::State::Present: return stream << "Present"; + case ParticipantDevice::State::ScheduledForLeaving: + return stream << "ScheduledForLeaving"; case ParticipantDevice::State::Leaving: return stream << "Leaving"; case ParticipantDevice::State::Left: diff --git a/src/conference/participant-device.h b/src/conference/participant-device.h index 9f61cdd7813b18513ebdccf02b110015e5ae98bf..9e32063cae4c2d9a09e9fedb857bbe7330726f74 100644 --- a/src/conference/participant-device.h +++ b/src/conference/participant-device.h @@ -42,10 +42,12 @@ class ParticipantDevicePrivate; class ParticipantDevice : public Object { public: enum class State { - Joining, - Present, - Leaving, - Left + Joining, //an INVITE has been sent + Present, //the SIP session has been concluded, participant is part of the conference + Leaving, //A BYE is pending + Left, //The Session is terminated + ScheduledForJoining, //Initial state for the server group chatroom, when the participant has not yet been INVITEd. + ScheduledForLeaving, //Transitional state for a participant that will receive a BYE shortly. }; ParticipantDevice ();