diff --git a/src/account.h b/src/account.h index 623d1e8407983dab782dfed58d64d96d91da5e43..c0b6c28f8310dce17c86e6c16615dcbbb418ebe9 100644 --- a/src/account.h +++ b/src/account.h @@ -162,7 +162,7 @@ public: * @return The created call */ virtual std::shared_ptr<Call> newOutgoingCall(std::string_view toUrl, - const std::vector<MediaAttribute>& mediaList) + const std::vector<DRing::MediaMap>& mediaList) = 0; /** diff --git a/src/call.h b/src/call.h index 8ac8f49ede6fc90951a854c765cd740f684b4fc4..a3ed2d47a95f213cfa1628f7b2d601f8cef23c0c 100644 --- a/src/call.h +++ b/src/call.h @@ -216,7 +216,7 @@ public: * determine the response sent to the peer and the configuration * of the local media. */ - virtual void answer(const std::vector<MediaAttribute>& mediaList) = 0; + virtual void answer(const std::vector<DRing::MediaMap>& mediaList) = 0; /** * Answer to a media update request. The media attributes set by the @@ -227,7 +227,7 @@ public: * call continue with the current media. It's up to the implementation * to determine wether an answer will be sent to the peer. */ - virtual void answerMediaChangeRequest(const std::vector<MediaAttribute>& mediaList) = 0; + virtual void answerMediaChangeRequest(const std::vector<DRing::MediaMap>& mediaList) = 0; /** * Hang up the call * @param reason @@ -343,7 +343,7 @@ public: // media management * @param mediaList the new media list * @return true on success */ - virtual bool requestMediaChange(const std::vector<MediaAttribute>& mediaList) = 0; + virtual bool requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) = 0; /** * Send a message to a call identified by its callid diff --git a/src/call_factory.cpp b/src/call_factory.cpp index 6e3ff7ab60cf1394140a004c5087e93ed37489de..a846e42336ef787dc159711fcae4df3a46e6f8fd 100644 --- a/src/call_factory.cpp +++ b/src/call_factory.cpp @@ -59,7 +59,7 @@ CallFactory::newSipCall(const std::shared_ptr<SIPAccountBase>& account, std::shared_ptr<SIPCall> CallFactory::newSipCall(const std::shared_ptr<SIPAccountBase>& account, Call::CallType type, - const std::vector<MediaAttribute>& mediaList) + const std::vector<DRing::MediaMap>& mediaList) { if (not allowNewCall_) { JAMI_WARN("Creation of new calls is not allowed"); diff --git a/src/call_factory.h b/src/call_factory.h index 8260e8f005c85ac18372ca78dda8f22c91d87cc1..4acfb15c3ed45d43fccf211043d8c20822539121 100644 --- a/src/call_factory.h +++ b/src/call_factory.h @@ -64,7 +64,7 @@ public: */ std::shared_ptr<SIPCall> newSipCall(const std::shared_ptr<SIPAccountBase>& account, Call::CallType type, - const std::vector<MediaAttribute>& mediaList); + const std::vector<DRing::MediaMap>& mediaList); /** * Forbid creation of new calls. diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index d02084c88c9aff11f468ba380b30079dd7b3e6ae..345235fe67ba114e724545182e9d9ee42fa0dd9c 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -391,38 +391,7 @@ JamiAccount::flush() std::shared_ptr<SIPCall> JamiAccount::newIncomingCall(const std::string& from, - const std::map<std::string, std::string>& details, - const std::shared_ptr<SipTransport>& sipTr) -{ - if (sipTr) { - std::unique_lock<std::mutex> lk(sipConnsMtx_); - for (auto& [key, value] : sipConns_) { - if (key.first == from) { - // For each SipConnection of the device - for (auto cit = value.rbegin(); cit != value.rend(); ++cit) { - // Search linked Sip Transport - if (cit->transport != sipTr) - continue; - - auto call = Manager::instance().callFactory.newSipCall(shared(), - Call::CallType::INCOMING); - call->setPeerUri(JAMI_URI_PREFIX + from); - call->setPeerNumber(from); - call->updateDetails(details); - return call; - } - } - } - lk.unlock(); - } - - JAMI_ERR("newIncomingCall: can't find matching call for %s", from.c_str()); - return nullptr; -} - -std::shared_ptr<SIPCall> -JamiAccount::newIncomingCall(const std::string& from, - const std::vector<MediaAttribute>& mediaList, + const std::vector<DRing::MediaMap>& mediaList, const std::shared_ptr<SipTransport>& sipTransp) { JAMI_DBG("New incoming call from %s with %lu media", from.c_str(), mediaList.size()); @@ -468,15 +437,14 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, } std::shared_ptr<Call> -JamiAccount::newOutgoingCall(std::string_view toUrl, - const std::vector<MediaAttribute>& mediaAttrList) +JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList) { auto suffix = stripPrefix(toUrl); JAMI_DBG() << *this << "Calling peer " << suffix; auto& manager = Manager::instance(); - auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaAttrList); + auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList); if (not call) return {}; @@ -492,8 +460,6 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::st auto suffix = stripPrefix(toUri); JAMI_DBG() << *this << "Calling DHT peer " << suffix; - call->setSecure(isTlsEnabled()); - try { const std::string uri {parseJamiUri(suffix)}; startOutgoingCall(call, uri); @@ -531,12 +497,12 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::st std::shared_ptr<SIPCall> JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall) { - auto mediaAttrList = mainCall->getMediaAttributeList(); + auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList()); - if (not mediaAttrList.empty()) { + if (not mediaList.empty()) { return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, - mediaAttrList); + mediaList); } else { return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, @@ -607,7 +573,6 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: // cached connection is failing with ICE (close event still not detected). auto dummyCall = createSubCall(call); - dummyCall->setSecure(isTlsEnabled()); call->addSubCall(*dummyCall); auto sendRequest = [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId, @@ -628,7 +593,6 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: auto dev_call = createSubCall(call); dev_call->setIPToIP(true); - dev_call->setSecure(isTlsEnabled()); dev_call->setState(Call::ConnectionState::TRYING); call->addStateListener( [w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) { @@ -674,7 +638,6 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: auto dev_call = createSubCall(call); - dev_call->setSecure(isTlsEnabled()); dev_call->setTransport(transport); call->addSubCall(*dev_call); // Set the call in PROGRESSING State because the ICE session diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 8134ad36fdacf7cc4c2105cefd632755539ba899..b1cddc62457771cf98bcb3fc0111cf9fdd800502 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -266,19 +266,7 @@ public: * @return A shared pointer on the created call. */ std::shared_ptr<Call> newOutgoingCall(std::string_view toUrl, - const std::vector<MediaAttribute>& mediaList) override; - - /** - * Create incoming SIPCall. - * @param[in] from The origin of the call - * @param details Call details - * @param sipTr: SIP Transport - * @return A shared pointer on the created call. - */ - std::shared_ptr<SIPCall> newIncomingCall( - const std::string& from, - const std::map<std::string, std::string>& details = {}, - const std::shared_ptr<SipTransport>& sipTr = nullptr) override; + const std::vector<DRing::MediaMap>& mediaList) override; /** * Create incoming SIPCall. @@ -289,7 +277,7 @@ public: */ std::shared_ptr<SIPCall> newIncomingCall( const std::string& from, - const std::vector<MediaAttribute>& mediaList, + const std::vector<DRing::MediaMap>& mediaList, const std::shared_ptr<SipTransport>& sipTr = {}) override; void onTextMessage(const std::string& id, @@ -533,7 +521,8 @@ public: std::vector<uint8_t> conversationVCard(const std::string& conversationId) const; // Member management - void saveMembers(const std::string& convId, const std::vector<std::string>& members); // Save confInfos + void saveMembers(const std::string& convId, + const std::vector<std::string>& members); // Save confInfos void addConversationMember(const std::string& conversationId, const std::string& contactUri, bool sendRequest = true); diff --git a/src/manager.cpp b/src/manager.cpp index 3b6670d80228ab807bfb27b5c711ae596798cf3c..2133335f844c09b42d3b7556ca3271af1a61c4f7 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1078,9 +1078,7 @@ Manager::requestMediaChange(const std::string& callID, const std::vector<DRing:: return false; } - auto const& mediaAttrList = MediaAttribute::parseMediaList(mediaList); - - return call->requestMediaChange(mediaAttrList); + return call->requestMediaChange(mediaList); } // THREAD=Main : for outgoing Call @@ -1159,8 +1157,7 @@ Manager::answerCallWithMedia(const std::string& callId, stopTone(); try { - auto mediaAttrList = MediaAttribute::parseMediaList(mediaList); - call->answer(mediaAttrList); + call->answer(mediaList); } catch (const std::runtime_error& e) { JAMI_ERR("%s", e.what()); result = false; @@ -1205,8 +1202,7 @@ Manager::answerMediaChangeRequest(const std::string& callId, } try { - auto mediaAttrList = MediaAttribute::parseMediaList(mediaList); - call->answerMediaChangeRequest(mediaAttrList); + call->answerMediaChangeRequest(mediaList); } catch (const std::runtime_error& e) { JAMI_ERR("%s", e.what()); result = false; @@ -3379,7 +3375,7 @@ Manager::newOutgoingCall(std::string_view toUrl, return {}; } - return account->newOutgoingCall(toUrl, MediaAttribute::parseMediaList(mediaList)); + return account->newOutgoingCall(toUrl, mediaList); } #ifdef ENABLE_VIDEO diff --git a/src/media/media_attribute.cpp b/src/media/media_attribute.cpp index 8f4ce363c7eadf8e4ab7a73f3177007b141d44b5..d27557595c3e012aefeeac4fe69bd7152dd77ecb 100644 --- a/src/media/media_attribute.cpp +++ b/src/media/media_attribute.cpp @@ -23,7 +23,7 @@ namespace jami { -MediaAttribute::MediaAttribute(const DRing::MediaMap& mediaMap) +MediaAttribute::MediaAttribute(const DRing::MediaMap& mediaMap, bool secure) { std::pair<bool, MediaType> pairType = getMediaType(mediaMap); if (pairType.first) @@ -51,16 +51,18 @@ MediaAttribute::MediaAttribute(const DRing::MediaMap& mediaMap) pairBool = getBoolValue(mediaMap, DRing::Media::MediaAttributeKey::ON_HOLD); if (pairBool.first) onHold_ = pairBool.second; + + secure_ = secure; } std::vector<MediaAttribute> -MediaAttribute::parseMediaList(const std::vector<DRing::MediaMap>& mediaList) +MediaAttribute::buildMediaAtrributesList(const std::vector<DRing::MediaMap>& mediaList, bool secure) { std::vector<MediaAttribute> mediaAttrList; mediaAttrList.reserve(mediaList.size()); for (auto const& mediaMap : mediaList) { - mediaAttrList.emplace_back(MediaAttribute(mediaMap)); + mediaAttrList.emplace_back(MediaAttribute(mediaMap, secure)); } return mediaAttrList; diff --git a/src/media/media_attribute.h b/src/media/media_attribute.h index c21cc196cf38a0c8b2ddda5dd0abd0290216a113..bf63141d9189c0eb61f3a76a9ae68b8ec2ef07b4 100644 --- a/src/media/media_attribute.h +++ b/src/media/media_attribute.h @@ -47,9 +47,10 @@ public: , onHold_(onHold) {} - MediaAttribute(const DRing::MediaMap& mediaMap); + MediaAttribute(const DRing::MediaMap& mediaMap, bool secure); - static std::vector<MediaAttribute> parseMediaList(const std::vector<DRing::MediaMap>& mediaList); + static std::vector<MediaAttribute> buildMediaAtrributesList( + const std::vector<DRing::MediaMap>& mediaList, bool secure); static MediaType stringToMediaType(const std::string& mediaType); diff --git a/src/sip/sdp.cpp b/src/sip/sdp.cpp index e060524148b596bcf9255bd6b6ea839319a1724b..279d669f3b9b1f4d8274484e902d1a6cf74629bc 100644 --- a/src/sip/sdp.cpp +++ b/src/sip/sdp.cpp @@ -989,6 +989,7 @@ Sdp::clearIce(pjmedia_sdp_session* session) return; pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag"); pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-pwd"); + // TODO. Why this? we should not have "candidate" attribute at session level. pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate"); for (unsigned i = 0; i < session->media_count; i++) { auto media = session->media[i]; diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp index a76b1746f830701e3a2f7f5954844f98941d0639..5f4a6eaf2418eb778b8bae87f5dea91e7754ed57 100644 --- a/src/sip/sipaccount.cpp +++ b/src/sip/sipaccount.cpp @@ -173,21 +173,10 @@ SIPAccount::~SIPAccount() noexcept std::shared_ptr<SIPCall> SIPAccount::newIncomingCall(const std::string& from UNUSED, - const std::map<std::string, std::string>& details, + const std::vector<DRing::MediaMap>& mediaList, const std::shared_ptr<SipTransport>&) { - auto& manager = Manager::instance(); - return manager.callFactory.newSipCall(shared(), Call::CallType::INCOMING, details); -} - -std::shared_ptr<SIPCall> -SIPAccount::newIncomingCall(const std::string& from UNUSED, - const std::vector<MediaAttribute>& mediaAttrList, - const std::shared_ptr<SipTransport>&) -{ - return Manager::instance().callFactory.newSipCall(shared(), - Call::CallType::INCOMING, - mediaAttrList); + return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::INCOMING, mediaList); } template<> @@ -204,8 +193,6 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, volatileCallDetails); - call->setSecure(isTlsEnabled()); - if (isIP2IP()) { bool ipv6 = IpAddr::isIpv6(toUrl); to = ipv6 ? IpAddr(toUrl).toString(false, true) : toUrl; @@ -282,7 +269,7 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, } std::shared_ptr<Call> -SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<MediaAttribute>& mediaAttrList) +SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList) { std::string to; int family; @@ -291,12 +278,9 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<MediaAttri auto& manager = Manager::instance(); - auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaAttrList); + auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList); if (not call) throw std::runtime_error("Failed to create the call"); - ; - - call->setSecure(isTlsEnabled()); if (isIP2IP()) { bool ipv6 = IpAddr::isIpv6(toUrl); @@ -326,7 +310,7 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<MediaAttri // Do not init ICE yet if the the media list is empty. This may occur // if we are sending an invite with no SDP offer. - if (call->isIceEnabled() and not mediaAttrList.empty()) { + if (call->isIceEnabled() and not mediaList.empty()) { call->initIceMediaTransport(true); } @@ -356,7 +340,9 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<MediaAttri else sdp.setPublishedIP(getPublishedAddress()); - const bool created = sdp.createOffer(mediaAttrList); + // TODO. We should not dot his here. Move it to SIPCall. + const bool created = sdp.createOffer( + MediaAttribute::buildMediaAtrributesList(mediaList, isSrtpEnabled())); if (created) { std::weak_ptr<SIPCall> weak_call = call; diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h index 4aac96371d1e9a2e068d83f9769b5266407d4573..2e505912bcc282118001f6590f2414193f1c3d91 100644 --- a/src/sip/sipaccount.h +++ b/src/sip/sipaccount.h @@ -437,19 +437,7 @@ public: * @return a shared pointer on the created call. */ std::shared_ptr<Call> newOutgoingCall(std::string_view toUrl, - const std::vector<MediaAttribute>& mediaList) override; - - /** - * Create incoming SIPCall. - * @param[in] from The origin uri of the call - * @param details use to set some specific details - * @return std::shared_ptr<T> A shared pointer on the created call. - * The type of this instance is given in template argument. - * This type can be any base class of SIPCall class (included). - */ - std::shared_ptr<SIPCall> newIncomingCall(const std::string& from, - const std::map<std::string, std::string>& details = {}, - const std::shared_ptr<SipTransport>& = nullptr) override; + const std::vector<DRing::MediaMap>& mediaList) override; /** * Create incoming SIPCall. @@ -460,7 +448,7 @@ public: */ std::shared_ptr<SIPCall> newIncomingCall( const std::string& from, - const std::vector<MediaAttribute>& mediaList, + const std::vector<DRing::MediaMap>& mediaList, const std::shared_ptr<SipTransport>& sipTr = {}) override; void onRegister(pjsip_regc_cbparam* param); diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index c96e92997be3dd426bb327a995ecf70baa3595e2..38f11bc5cff93a07296dedf4a2028e4276dd2f30 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -133,20 +133,6 @@ public: virtual ~SIPAccountBase() noexcept; - /** - * Create incoming SIPCall. - * @param[in] id The ID of the call - * @param details use to set some specific details - * @return std::shared_ptr<T> A shared pointer on the created call. - * The type of this instance is given in template argument. - * This type can be any base class of SIPCall class (included). - */ - virtual std::shared_ptr<SIPCall> newIncomingCall( - const std::string& from, - const std::map<std::string, std::string>& details = {}, - const std::shared_ptr<SipTransport>& = nullptr) - = 0; - /** * Create incoming SIPCall. * @param[in] from The origin of the call @@ -155,7 +141,7 @@ public: * @return A shared pointer on the created call. */ virtual std::shared_ptr<SIPCall> newIncomingCall(const std::string& from, - const std::vector<MediaAttribute>& mediaList, + const std::vector<DRing::MediaMap>& mediaList, const std::shared_ptr<SipTransport>& sipTr = {}) = 0; diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 13c75c855987ec7534775e72d6e3762cd8f20bd7..c2ab034280dee1e427909250eef4021c6c5a57b5 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -91,6 +91,7 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, : Call(account, callId, type, details) , sdp_(new Sdp(callId)) , enableIce_(account->isIceForMediaEnabled()) + , srtpEnabled_(account->isSrtpEnabled()) { if (account->getUPnPActive()) upnp_.reset(new upnp::Controller()); @@ -120,11 +121,12 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, const std::string& callId, Call::CallType type, - const std::vector<MediaAttribute>& mediaAttrList) + const std::vector<DRing::MediaMap>& mediaList) : Call(account, callId, type) , peerSupportMultiStream_(false) , sdp_(new Sdp(callId)) , enableIce_(account->isIceForMediaEnabled()) + , srtpEnabled_(account->isSrtpEnabled()) { if (account->getUPnPActive()) upnp_.reset(new upnp::Controller()); @@ -139,18 +141,20 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, account->getActiveAccountCodecInfoList(MEDIA_VIDEO)); #endif - std::vector<MediaAttribute> mediaList; - if (mediaAttrList.size() > 0) { - mediaList = mediaAttrList; - } else if (type_ == Call::CallType::INCOMING) { - // Handle incoming call without media offer. - JAMI_WARN("[call:%s] No media offered in the incoming invite. An offer will be provided in " - "the answer", - getCallId().c_str()); - mediaList = getSIPAccount()->createDefaultMediaList(getSIPAccount()->isVideoEnabled(), - getState() == CallState::HOLD); - } else { - JAMI_WARN("[call:%s] Creating an outgoing call with empty offer", getCallId().c_str()); + auto mediaAttrList = MediaAttribute::buildMediaAtrributesList(mediaList, isSrtpEnabled()); + + if (mediaAttrList.size() == 0) { + if (type_ == Call::CallType::INCOMING) { + // Handle incoming call without media offer. + JAMI_WARN( + "[call:%s] No media offered in the incoming invite. An offer will be provided in " + "the answer", + getCallId().c_str()); + mediaAttrList = getSIPAccount()->createDefaultMediaList(getSIPAccount()->isVideoEnabled(), + getState() == CallState::HOLD); + } else { + JAMI_WARN("[call:%s] Creating an outgoing call with empty offer", getCallId().c_str()); + } } JAMI_DBG("[call:%s] Create a new [%s] SIP call with %lu media", @@ -160,7 +164,7 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, : (type == Call::CallType::OUTGOING ? "OUTGOING" : "MISSED"), mediaList.size()); - initMediaStreams(mediaList); + initMediaStreams(mediaAttrList); } SIPCall::~SIPCall() @@ -422,37 +426,39 @@ SIPCall::setContactHeader(pj_str_t* contact) void SIPCall::setTransport(const std::shared_ptr<SipTransport>& t) { - if (isSecure() and t and not t->isSecure()) { - JAMI_ERR("Can't set un-secure transport to secure call."); + transport_ = t; + if (not transport_) { + JAMI_WARN("[call:%s] Transport was set to null", getCallId().c_str()); return; } - const auto list_id = reinterpret_cast<uintptr_t>(this); - if (transport_) - transport_->removeStateListener(list_id); - transport_ = t; + if (isSrtpEnabled() and not transport_->isSecure()) { + JAMI_WARN("[call:%s] Crypto (SRTP) is negotiated over an un-encrypted signaling channel", + getCallId().c_str()); + } - if (transport_) { - setSecure(transport_->isSecure()); - - // listen for transport destruction - transport_->addStateListener( - list_id, - [wthis_ = weak()](pjsip_transport_state state, const pjsip_transport_state_info*) { - if (auto this_ = wthis_.lock()) { - // end the call if the SIP transport is shut down - auto isAlive = SipTransport::isAlive(state); - if (not isAlive - and this_->getConnectionState() != ConnectionState::DISCONNECTED) { - JAMI_WARN( - "[call:%s] Ending call because underlying SIP transport was closed", - this_->getCallId().c_str()); - this_->stopAllMedia(); - this_->onFailure(ECONNRESET); - } - } - }); + if (not isSrtpEnabled() and transport_->isSecure()) { + JAMI_WARN("[call:%s] The signaling channel is encrypted but the media is not encrypted", + getCallId().c_str()); } + + const auto list_id = reinterpret_cast<uintptr_t>(this); + transport_->removeStateListener(list_id); + + // listen for transport destruction + transport_->addStateListener( + list_id, [wthis_ = weak()](pjsip_transport_state state, const pjsip_transport_state_info*) { + if (auto this_ = wthis_.lock()) { + // end the call if the SIP transport is shut down + auto isAlive = SipTransport::isAlive(state); + if (not isAlive and this_->getConnectionState() != ConnectionState::DISCONNECTED) { + JAMI_WARN("[call:%s] Ending call because underlying SIP transport was closed", + this_->getCallId().c_str()); + this_->stopAllMedia(); + this_->onFailure(ECONNRESET); + } + } + }); } void @@ -760,7 +766,7 @@ SIPCall::answer() } void -SIPCall::answer(const std::vector<MediaAttribute>& mediaAttrList) +SIPCall::answer(const std::vector<DRing::MediaMap>& mediaList) { std::lock_guard<std::recursive_mutex> lk {callMutex_}; auto account = getSIPAccount(); @@ -769,6 +775,8 @@ SIPCall::answer(const std::vector<MediaAttribute>& mediaAttrList) return; } + auto mediaAttrList = MediaAttribute::buildMediaAtrributesList(mediaList, isSrtpEnabled()); + if (mediaAttrList.empty()) { JAMI_DBG("[call:%s] Media list must not be empty!", getCallId().c_str()); return; @@ -894,7 +902,7 @@ SIPCall::answer(const std::vector<MediaAttribute>& mediaAttrList) } void -SIPCall::answerMediaChangeRequest(const std::vector<MediaAttribute>& mediaAttrList) +SIPCall::answerMediaChangeRequest(const std::vector<DRing::MediaMap>& mediaList) { std::lock_guard<std::recursive_mutex> lk {callMutex_}; @@ -904,6 +912,8 @@ SIPCall::answerMediaChangeRequest(const std::vector<MediaAttribute>& mediaAttrLi return; } + auto mediaAttrList = MediaAttribute::buildMediaAtrributesList(mediaList, isSrtpEnabled()); + if (mediaAttrList.empty()) { JAMI_DBG("[call:%s] Media list size is empty. Ignoring the media change request", getCallId().c_str()); @@ -1879,7 +1889,7 @@ SIPCall::updateNegotiatedMedia() continue; } - if (isSecure() and local.enabled and not local.crypto) { + if (isSrtpEnabled() and local.enabled and not local.crypto) { JAMI_WARN("[call:%s] [SDP:slot#%u] Secure mode but no local crypto attributes. " "Ignoring the media", getCallId().c_str(), @@ -1887,7 +1897,7 @@ SIPCall::updateNegotiatedMedia() continue; } - if (isSecure() and remote.enabled and not remote.crypto) { + if (isSrtpEnabled() and remote.enabled and not remote.crypto) { JAMI_WARN("[call:%s] [SDP:slot#%u] Secure mode but no crypto remote attributes. " "Ignoring the media", getCallId().c_str(), @@ -1924,11 +1934,9 @@ SIPCall::startAllMedia() return; } - if (isSecure() && not transport_->isSecure()) { - JAMI_ERR("[call:%s] Can't perform secure call over insecure SIP transport", - getCallId().c_str()); - onFailure(EPROTONOSUPPORT); - return; + if (isSrtpEnabled() && not transport_->isSecure()) { + JAMI_WARN("[call:%s] Crypto (SRTP) is negotiated over an insecure signaling transport", + getCallId().c_str()); } // reset @@ -2078,7 +2086,7 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute) } // Apply - requestMediaChange(mediaList); + requestMediaChange(MediaAttribute::mediaAttributesToMediaMaps(mediaList)); } void @@ -2187,8 +2195,10 @@ SIPCall::isReinviteRequired(const std::vector<MediaAttribute>& mediaAttrList) } bool -SIPCall::requestMediaChange(const std::vector<MediaAttribute>& mediaAttrList) +SIPCall::requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) { + auto mediaAttrList = MediaAttribute::buildMediaAtrributesList(mediaList, isSrtpEnabled()); + // If the peer does not support multi-stream and the size of the new // media list is different from the current media list, the media // change request will be ignored. @@ -2396,7 +2406,7 @@ SIPCall::onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdat if (acc->isMultiStreamEnabled() and remoteMediaList.size() != rtpStreams_.size()) { Manager::instance().mediaChangeRequested(getCallId(), getAccountId(), remoteMediaList); } else { - auto localMediaList = getMediaAttributeList(); + auto localMediaList = MediaAttribute::mediaAttributesToMediaMaps(getMediaAttributeList()); answerMediaChangeRequest(localMediaList); } @@ -2798,17 +2808,6 @@ SIPCall::deinitRecorder() } } -void -SIPCall::setSecure(bool sec) -{ - if (srtpEnabled_) - return; - if (sec && getConnectionState() != ConnectionState::DISCONNECTED) { - throw std::runtime_error("Can't enable security since call is already connected"); - } - srtpEnabled_ = sec; -} - void SIPCall::InvSessionDeleter::operator()(pjsip_inv_session* inv) const noexcept { diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index be2b71d7fe1f37b16ed5902761c874564f22f01b..fd16200ff403aab596c366347754060fd77e4346 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -115,7 +115,7 @@ public: SIPCall(const std::shared_ptr<SIPAccountBase>& account, const std::string& id, Call::CallType type, - const std::vector<MediaAttribute>& mediaList); + const std::vector<DRing::MediaMap>& mediaList); // Inherited from Call class LinkType getLinkType() const override { return LINK_TYPE; } @@ -126,8 +126,8 @@ private: public: void answer() override; - void answer(const std::vector<MediaAttribute>& mediaList) override; - void answerMediaChangeRequest(const std::vector<MediaAttribute>& mediaList) override; + void answer(const std::vector<DRing::MediaMap>& mediaList) override; + void answerMediaChangeRequest(const std::vector<DRing::MediaMap>& mediaList) override; void hangup(int reason) override; void refuse() override; void transfer(const std::string& to) override; @@ -137,7 +137,7 @@ public: void switchInput(const std::string& resource = {}) override; void peerHungup() override; void carryingDTMFdigits(char code) override; - bool requestMediaChange(const std::vector<MediaAttribute>& mediaList) override; + bool requestMediaChange(const std::vector<DRing::MediaMap>& mediaList) override; void sendTextMessage(const std::map<std::string, std::string>& messages, const std::string& from) override; void removeCall() override; @@ -257,9 +257,7 @@ public: std::shared_ptr<video::VideoRtpSession> addDummyVideoRtpSession(); #endif - void setSecure(bool sec); - - bool isSecure() const { return srtpEnabled_; } + bool isSrtpEnabled() const { return srtpEnabled_; } void generateMediaPorts(); diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 55885536cbe82c1e93bf9cace125baa37af635f4..cd84971513015f0e29339850dd9619f093685986 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -410,7 +410,9 @@ transaction_request_cb(pjsip_rx_data* rdata) } } - auto call = account->newIncomingCall(std::string(remote_user), localMediaList, transport); + auto call = account->newIncomingCall(std::string(remote_user), + MediaAttribute::mediaAttributesToMediaMaps(localMediaList), + transport); if (!call) { return PJ_FALSE; diff --git a/test/unitTest/Makefile.am b/test/unitTest/Makefile.am index 9ec11786e7688787c0970a1b48f0cf43a68787aa..95940fdc040aa06a72781ae8e75f9566b83e95c8 100644 --- a/test/unitTest/Makefile.am +++ b/test/unitTest/Makefile.am @@ -178,5 +178,7 @@ check_PROGRAMS += ut_sip_basic_calls ut_sip_basic_calls_SOURCES = sip_account/sip_basic_calls.cpp check_PROGRAMS += ut_sip_empty_offer ut_sip_empty_offer_SOURCES = sip_account/sip_empty_offer.cpp +check_PROGRAMS += ut_sip_srtp +ut_sip_srtp_SOURCES = sip_account/sip_srtp.cpp TESTS = $(check_PROGRAMS) diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp index 48c6a6bdd2a09c681826436e7ff44e671742c835..32a40984a09e285cd8119ef2c5870656e60240e5 100644 --- a/test/unitTest/media_negotiation/media_negotiation.cpp +++ b/test/unitTest/media_negotiation/media_negotiation.cpp @@ -574,7 +574,8 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData, auto const& aliceCall = std::dynamic_pointer_cast<SIPCall>( (Manager::instance().getAccount<JamiAccount>(aliceData.accountId_)) - ->newOutgoingCall(bobData.userName_, scenario.offer_)); + ->newOutgoingCall(bobData.userName_, + MediaAttribute::mediaAttributesToMediaMaps(scenario.offer_))); CPPUNIT_ASSERT(aliceCall); aliceData.callId_ = aliceCall->getCallId(); diff --git a/test/unitTest/sip_account/sip_basic_calls.cpp b/test/unitTest/sip_account/sip_basic_calls.cpp index 59fc15892f393b2163b736960587df54f9fec76f..7637ec3d527cfa15f440b9372a1de45625b554d6 100644 --- a/test/unitTest/sip_account/sip_basic_calls.cpp +++ b/test/unitTest/sip_account/sip_basic_calls.cpp @@ -529,24 +529,20 @@ SipBasicCallTest::audio_only_test() // Configure Alice audio.enabled_ = true; audio.label_ = "audio_0"; - audio.secure_ = aliceAcc->isSrtpEnabled(); offer.emplace_back(audio); video.enabled_ = true; video.label_ = "video_0"; - video.secure_ = aliceAcc->isSrtpEnabled(); aliceAcc->enableVideo(true); offer.emplace_back(video); // Configure Bob audio.enabled_ = true; audio.label_ = "audio_0"; - audio.secure_ = bobAcc->isSrtpEnabled(); answer.emplace_back(audio); video.enabled_ = false; video.label_ = "video_0"; - video.secure_ = bobAcc->isSrtpEnabled(); bobAcc->enableVideo(false); answer.emplace_back(video); @@ -569,24 +565,20 @@ SipBasicCallTest::audio_video_test() // Configure Alice audio.enabled_ = true; audio.label_ = "audio_0"; - audio.secure_ = aliceAcc->isSrtpEnabled(); offer.emplace_back(audio); video.enabled_ = true; video.label_ = "video_0"; - video.secure_ = aliceAcc->isSrtpEnabled(); aliceAcc->enableVideo(true); offer.emplace_back(video); // Configure Bob audio.enabled_ = true; audio.label_ = "audio_0"; - audio.secure_ = bobAcc->isSrtpEnabled(); answer.emplace_back(audio); video.enabled_ = true; video.label_ = "video_0"; - video.secure_ = bobAcc->isSrtpEnabled(); bobAcc->enableVideo(true); answer.emplace_back(video); diff --git a/test/unitTest/sip_account/sip_srtp.cpp b/test/unitTest/sip_account/sip_srtp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..835685e32877bd46a4b7b99cc3ba9b6838aa061d --- /dev/null +++ b/test/unitTest/sip_account/sip_srtp.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com> + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <condition_variable> +#include <string> + +#include "callmanager_interface.h" +#include "manager.h" +#include "sip/sipaccount.h" +#include "../../test_runner.h" +#include "dring.h" +#include "dring/media_const.h" +#include "call_const.h" +#include "account_const.h" +#include "sip/sipcall.h" +#include "sip/sdp.h" +using namespace DRing::Account; +using namespace DRing::Call; + +namespace jami { +namespace test { + +struct CallData +{ + struct Signal + { + Signal(const std::string& name, const std::string& event = {}) + : name_(std::move(name)) + , event_(std::move(event)) {}; + + std::string name_ {}; + std::string event_ {}; + }; + + std::string accountId_ {}; + uint16_t listeningPort_ {0}; + std::string userName_ {}; + std::string alias_ {}; + std::string callId_ {}; + std::vector<Signal> signals_; + std::condition_variable cv_ {}; + std::mutex mtx_; +}; + +/** + * Call tests for SIP accounts. + */ +class SipSrtpTest : public CppUnit::TestFixture +{ +public: + SipSrtpTest() + { + // Init daemon + DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); + if (not Manager::instance().initialized) + CPPUNIT_ASSERT(DRing::start("dring-sample.yml")); + } + ~SipSrtpTest() { DRing::fini(); } + + static std::string name() { return "SipSrtpTest"; } + void setUp(); + void tearDown(); + +private: + // Test cases. + void audio_video_srtp_enabled_test(); + + CPPUNIT_TEST_SUITE(SipSrtpTest); + CPPUNIT_TEST(audio_video_srtp_enabled_test); + + CPPUNIT_TEST_SUITE_END(); + + // Event/Signal handlers + static void onCallStateChange(const std::string& callId, + const std::string& state, + CallData& callData); + static void onIncomingCallWithMedia(const std::string& accountId, + const std::string& callId, + const std::vector<DRing::MediaMap> mediaList, + CallData& callData); + static void onMediaNegotiationStatus(const std::string& callId, + const std::string& event, + CallData& callData); + + // Helpers + void audio_video_call(std::vector<MediaAttribute> offer, + std::vector<MediaAttribute> answer, + bool validateMedia = true); + static void configureTest(CallData& bob, CallData& alice); + static std::string getUserAlias(const std::string& callId); + // Wait for a signal from the callbacks. Some signals also report the event that + // triggered the signal a like the StateChange signal. + static bool waitForSignal(CallData& callData, + const std::string& signal, + const std::string& expectedEvent = {}); + +private: + CallData aliceData_; + CallData bobData_; +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SipSrtpTest, SipSrtpTest::name()); + +void +SipSrtpTest::setUp() +{ + aliceData_.listeningPort_ = 5080; + std::map<std::string, std::string> details = DRing::getAccountTemplate("SIP"); + details[ConfProperties::TYPE] = "SIP"; + details[ConfProperties::DISPLAYNAME] = "ALICE"; + details[ConfProperties::ALIAS] = "ALICE"; + details[ConfProperties::LOCAL_PORT] = std::to_string(aliceData_.listeningPort_); + details[ConfProperties::UPNP_ENABLED] = "false"; + details[ConfProperties::SRTP::KEY_EXCHANGE] = "sdes"; + aliceData_.accountId_ = Manager::instance().addAccount(details); + + bobData_.listeningPort_ = 5082; + details = DRing::getAccountTemplate("SIP"); + details[ConfProperties::TYPE] = "SIP"; + details[ConfProperties::DISPLAYNAME] = "BOB"; + details[ConfProperties::ALIAS] = "BOB"; + details[ConfProperties::LOCAL_PORT] = std::to_string(bobData_.listeningPort_); + details[ConfProperties::UPNP_ENABLED] = "false"; + details[ConfProperties::SRTP::KEY_EXCHANGE] = "sdes"; + bobData_.accountId_ = Manager::instance().addAccount(details); + + JAMI_INFO("Initialize accounts ..."); + auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_); + aliceAccount->enableMultiStream(true); + auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_); + bobAccount->enableMultiStream(true); +} + +void +SipSrtpTest::tearDown() +{ + JAMI_INFO("Remove created accounts..."); + + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + auto currentAccSize = Manager::instance().getAccountList().size(); + std::atomic_bool accountsRemoved {false}; + confHandlers.insert( + DRing::exportable_callback<DRing::ConfigurationSignal::AccountsChanged>([&]() { + if (Manager::instance().getAccountList().size() <= currentAccSize - 2) { + accountsRemoved = true; + cv.notify_one(); + } + })); + DRing::registerSignalHandlers(confHandlers); + + Manager::instance().removeAccount(aliceData_.accountId_, true); + Manager::instance().removeAccount(bobData_.accountId_, true); + // Because cppunit is not linked with dbus, just poll if removed + CPPUNIT_ASSERT( + cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); })); + + DRing::unregisterSignalHandlers(); +} + +std::string +SipSrtpTest::getUserAlias(const std::string& callId) +{ + auto call = Manager::instance().getCallFromCallID(callId); + + if (not call) { + JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); + return {}; + } + + auto const& account = call->getAccount().lock(); + if (not account) { + return {}; + } + + return account->getAccountDetails()[ConfProperties::ALIAS]; +} + +void +SipSrtpTest::onIncomingCallWithMedia(const std::string& accountId, + const std::string& callId, + const std::vector<DRing::MediaMap> mediaList, + CallData& callData) +{ + CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId); + + JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]", + DRing::CallSignal::IncomingCallWithMedia::name, + callData.alias_.c_str(), + callId.c_str(), + mediaList.size()); + + // NOTE. + // We shouldn't access shared_ptr<Call> as this event is supposed to mimic + // the client, and the client have no access to this type. But here, we only + // needed to check if the call exists. This is the most straightforward and + // reliable way to do it until we add a new API (like hasCall(id)). + if (not Manager::instance().getCallFromCallID(callId)) { + JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str()); + callData.callId_ = {}; + return; + } + + std::unique_lock<std::mutex> lock {callData.mtx_}; + callData.callId_ = callId; + callData.signals_.emplace_back(CallData::Signal(DRing::CallSignal::IncomingCallWithMedia::name)); + + callData.cv_.notify_one(); +} + +void +SipSrtpTest::onCallStateChange(const std::string& callId, + const std::string& state, + CallData& callData) +{ + auto call = Manager::instance().getCallFromCallID(callId); + if (not call) { + JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str()); + return; + } + + auto account = call->getAccount().lock(); + if (not account) { + JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str()); + return; + } + + JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]", + DRing::CallSignal::StateChange::name, + callData.alias_.c_str(), + callId.c_str(), + state.c_str()); + + if (account->getAccountID() != callData.accountId_) + return; + + { + std::unique_lock<std::mutex> lock {callData.mtx_}; + callData.signals_.emplace_back( + CallData::Signal(DRing::CallSignal::StateChange::name, state)); + } + // NOTE. Only states that we are interested on will notify the CV. If this + // unit test is modified to process other states, they must be added here. + if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") { + callData.cv_.notify_one(); + } +} + +void +SipSrtpTest::onMediaNegotiationStatus(const std::string& callId, + const std::string& event, + CallData& callData) +{ + auto call = Manager::instance().getCallFromCallID(callId); + if (not call) { + JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str()); + return; + } + + auto account = call->getAccount().lock(); + if (not account) { + JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str()); + return; + } + + JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]", + DRing::CallSignal::MediaNegotiationStatus::name, + account->getAccountDetails()[ConfProperties::ALIAS].c_str(), + call->getCallId().c_str(), + event.c_str()); + + if (account->getAccountID() != callData.accountId_) + return; + + { + std::unique_lock<std::mutex> lock {callData.mtx_}; + callData.signals_.emplace_back( + CallData::Signal(DRing::CallSignal::MediaNegotiationStatus::name, event)); + } + + callData.cv_.notify_one(); +} + +bool +SipSrtpTest::waitForSignal(CallData& callData, + const std::string& expectedSignal, + const std::string& expectedEvent) +{ + const std::chrono::seconds TIME_OUT {30}; + std::unique_lock<std::mutex> lock {callData.mtx_}; + + // Combined signal + event (if any). + std::string sigEvent(expectedSignal); + if (not expectedEvent.empty()) + sigEvent += "::" + expectedEvent; + + JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str()); + + auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] { + // Search for the expected signal in list of received signals. + bool pred = false; + for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) { + // The predicate is true if the signal names match, and if the + // expectedEvent is not empty, the events must also match. + if (it->name_ == expectedSignal + and (expectedEvent.empty() or it->event_ == expectedEvent)) { + pred = true; + // Done with this signal. + callData.signals_.erase(it); + break; + } + } + + return pred; + }); + + if (not res) { + JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!", + callData.alias_.c_str(), + sigEvent.c_str()); + + JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str()); + + for (auto const& sig : callData.signals_) { + JAMI_INFO() << "Signal [" << sig.name_ + << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]"; + } + } + + return res; +} + +void +SipSrtpTest::configureTest(CallData& aliceData, CallData& bobData) +{ + { + CPPUNIT_ASSERT(not aliceData.accountId_.empty()); + auto const& account = Manager::instance().getAccount<SIPAccount>(aliceData.accountId_); + aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->setLocalPort(aliceData.listeningPort_); + account->enableIceForMedia(true); + } + + { + CPPUNIT_ASSERT(not bobData.accountId_.empty()); + auto const& account = Manager::instance().getAccount<SIPAccount>(bobData.accountId_); + bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->setLocalPort(bobData.listeningPort_); + } + + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers; + + // Insert needed signal handlers. + signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCallWithMedia>( + [&](const std::string& accountId, + const std::string& callId, + const std::string&, + const std::vector<DRing::MediaMap> mediaList) { + auto user = getUserAlias(callId); + if (not user.empty()) + onIncomingCallWithMedia(accountId, + callId, + mediaList, + user == aliceData.alias_ ? aliceData : bobData); + })); + + signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>( + [&](const std::string& callId, const std::string& state, signed) { + auto user = getUserAlias(callId); + if (not user.empty()) + onCallStateChange(callId, state, user == aliceData.alias_ ? aliceData : bobData); + })); + + signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::MediaNegotiationStatus>( + [&](const std::string& callId, + const std::string& event, + const std::vector<std::map<std::string, std::string>>& /* mediaList */) { + auto user = getUserAlias(callId); + if (not user.empty()) + onMediaNegotiationStatus(callId, + event, + user == aliceData.alias_ ? aliceData : bobData); + })); + + DRing::registerSignalHandlers(signalHandlers); +} + +void +SipSrtpTest::audio_video_call(std::vector<MediaAttribute> offer, + std::vector<MediaAttribute> answer, + bool validateMedia) +{ + JAMI_INFO("=== Begin test %s ===", __FUNCTION__); + + configureTest(aliceData_, bobData_); + + JAMI_INFO("=== Start a call and validate ==="); + + std::string bobUri = "127.0.0.1:" + std::to_string(bobData_.listeningPort_); + + aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_, + bobUri, + MediaAttribute::mediaAttributesToMediaMaps( + offer)); + + CPPUNIT_ASSERT(not aliceData_.callId_.empty()); + + JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", + aliceData_.accountId_.c_str(), + bobData_.accountId_.c_str()); + + // Give it some time to ring + std::this_thread::sleep_for(std::chrono::seconds(2)); + + // Wait for call to be processed. + CPPUNIT_ASSERT( + waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING)); + + // Wait for incoming call signal. + CPPUNIT_ASSERT(waitForSignal(bobData_, DRing::CallSignal::IncomingCallWithMedia::name)); + + // Answer the call. + DRing::acceptWithMedia(bobData_.callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); + + // Wait for media negotiation complete signal. + CPPUNIT_ASSERT(waitForSignal(bobData_, + DRing::CallSignal::MediaNegotiationStatus::name, + DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); + + // Wait for the StateChange signal. + CPPUNIT_ASSERT( + waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT)); + + JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str()); + + // Wait for media negotiation complete signal. + CPPUNIT_ASSERT(waitForSignal(aliceData_, + DRing::CallSignal::MediaNegotiationStatus::name, + DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS)); + + // Validate Alice's media + if (validateMedia) { + auto activeMediaList = Manager::instance().getMediaAttributeList(aliceData_.callId_); + CPPUNIT_ASSERT_EQUAL(offer.size(), activeMediaList.size()); + // Audio + CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_AUDIO, activeMediaList[0].type_); + CPPUNIT_ASSERT_EQUAL(offer[0].enabled_, activeMediaList[0].enabled_); + + // Video + if (offer.size() > 1) { + CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_VIDEO, activeMediaList[1].type_); + CPPUNIT_ASSERT_EQUAL(offer[1].enabled_, activeMediaList[1].enabled_); + } + } + + // Validate Bob's media + if (validateMedia) { + auto activeMediaList = Manager::instance().getMediaAttributeList(bobData_.callId_); + CPPUNIT_ASSERT_EQUAL(answer.size(), activeMediaList.size()); + // Audio + CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_AUDIO, activeMediaList[0].type_); + CPPUNIT_ASSERT_EQUAL(answer[0].enabled_, activeMediaList[0].enabled_); + + // Video + if (offer.size() > 1) { + CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_VIDEO, activeMediaList[1].type_); + CPPUNIT_ASSERT_EQUAL(answer[1].enabled_, activeMediaList[1].enabled_); + } + } + + // Give some time to media to start and flow + std::this_thread::sleep_for(std::chrono::seconds(3)); + + // Bob hang-up. + JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up"); + DRing::hangUp(bobData_.callId_); + + CPPUNIT_ASSERT( + waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP)); + + JAMI_INFO("Call terminated on both sides"); +} + +void +SipSrtpTest::audio_video_srtp_enabled_test() +{ + // Test with video enabled on Alice's side and disabled + // on Bob's side. + + auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_); + CPPUNIT_ASSERT(aliceAcc->isSrtpEnabled()); + + auto const bobAcc = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_); + CPPUNIT_ASSERT(bobAcc->isSrtpEnabled()); + + std::vector<MediaAttribute> offer; + std::vector<MediaAttribute> answer; + + MediaAttribute audio(MediaType::MEDIA_AUDIO); + MediaAttribute video(MediaType::MEDIA_VIDEO); + + // Configure Alice + audio.enabled_ = true; + audio.label_ = "audio_0"; + offer.emplace_back(audio); + + video.enabled_ = true; + video.label_ = "video_0"; + aliceAcc->enableVideo(true); + offer.emplace_back(video); + + // Configure Bob + audio.enabled_ = true; + audio.label_ = "audio_0"; + answer.emplace_back(audio); + + video.enabled_ = false; + video.label_ = "video_0"; + bobAcc->enableVideo(false); + answer.emplace_back(video); + + // Run the scenario + audio_video_call(offer, answer); +} + +} // namespace test +} // namespace jami + +RING_TEST_RUNNER(jami::test::SipSrtpTest::name())