diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml index 5d95f18557e6a2962416f295c1b57b7bffe45871..e87142360738fd76e03236599b633426602c15b4 100644 --- a/bin/dbus/cx.ring.Ring.CallManager.xml +++ b/bin/dbus/cx.ring.Ring.CallManager.xml @@ -1026,7 +1026,15 @@ <p>Report mediation negotation status.</p> </tp:docstring> <arg type="s" name="callID" /> - <arg type="s" name="event" /> + <arg type="s" name="event" > + <tp:docstring> + The acceptable states are: + <ul> + <li>NEGOTIATION_SUCCESS: Medias are ready</li> + <li>NEGOTIATION_FAIL: Medias negotion failed</li> + </ul> + </tp:docstring> + </arg> </signal> </interface> </node> diff --git a/compat/msvc/config.h b/compat/msvc/config.h index ed4b174ae857cf006ffef694aabd40714625660f..186b5506755e46920c32c571f3fb351eacc849cc 100644 --- a/compat/msvc/config.h +++ b/compat/msvc/config.h @@ -152,10 +152,10 @@ systems. This function is required for `alloca.c' support on those systems. #define PACKAGE_BUGREPORT "jami@lists.savoirfairelinux.net" /* Define to the full name of this package. */ -#define PACKAGE_NAME "Jami" +#define PACKAGE_NAME "Jami Daemon" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "Jami" +#define PACKAGE_STRING "Jami Daemon 10.0.2" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "jami" @@ -164,7 +164,7 @@ systems. This function is required for `alloca.c' support on those systems. #define PACKAGE_URL "" /* Define to the version of this package. */ -#define PACKAGE_VERSION "2.3.0" +#define PACKAGE_VERSION "10.0.2" /* Define to necessary symbol if this constant uses a non-standard name on your system. */ @@ -201,7 +201,7 @@ STACK_DIRECTION = 0 => direction of growth unknown */ #undef _MBCS /* Version number of package */ -#define VERSION "2.3.0" +#define VERSION "10.0.2" // UWP compatibility #define PROGSHAREDIR "" diff --git a/configure.ac b/configure.ac index db6e14710c94b358665164a79f2ec14bd8de5de3..757ca2eae3cf011dfc323a4f336d4391ff9cca0e 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ dnl Jami - configure.ac dnl Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([Jami Daemon],[10.0.1],[jami@gnu.org],[jami]) +AC_INIT([Jami Daemon],[10.0.2],[jami@gnu.org],[jami]) dnl Clear the implicit flags that default to '-g -O2', otherwise they dnl take precedence over the values we set via the diff --git a/meson.build b/meson.build index 530a14feb9480b320893637add6834be1d21a65e..109776557da65aca7bf7659aca9e3f386cfe9aff 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jami-daemon', ['c', 'cpp'], - version: '10.0.1', + version: '10.0.2', license: 'GPL3+', default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'], meson_version:'>= 0.54' diff --git a/src/account.cpp b/src/account.cpp index 88c161b307783d1741f0189189ca88db46aba73d..edbfda705af2cb08602a1d7463ee8eb0f407c0b5 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -709,4 +709,18 @@ Account::removeDefaultModerator(const std::string& uri) defaultModerators_.erase(uri); } +bool +Account::meetMinimumRequiredVersion(const std::vector<unsigned>& jamiVersion, + const std::vector<unsigned>& minRequiredVersion) +{ + if (jamiVersion.size() != 3 or minRequiredVersion.size() != 3) { + JAMI_ERR("Unexpected vector size"); + return false; + } + + return jamiVersion[0] > minRequiredVersion[0] + or (jamiVersion[0] == minRequiredVersion[0] and jamiVersion[1] > minRequiredVersion[1]) + or (jamiVersion[0] == minRequiredVersion[0] and jamiVersion[1] == minRequiredVersion[1] + and jamiVersion[2] >= minRequiredVersion[2]); +} } // namespace jami diff --git a/src/account.h b/src/account.h index afc086763a0b86172279353e84bae29846af9b33..b3ec6e267bfabc223d53ec1a549016c110f44721 100644 --- a/src/account.h +++ b/src/account.h @@ -353,6 +353,11 @@ public: // should be removed once the multi-stream feature is fully supported. bool isMultiStreamEnabled() const { return multiStreamEnabled_; } void enableMultiStream(bool enable) { multiStreamEnabled_ = enable; } + // Check if a Daemon version (typically peer's version) satisfies the + // minimum required version. This check is typically used to disable a + // feature if it's not backward compatible with the peer's version. + static bool meetMinimumRequiredVersion(const std::vector<unsigned>& jamiVersion, + const std::vector<unsigned>& minRequiredVersion); // Enable/disable compliancy with RFC-5245 for component IDs format. // The ICE component IDs are enumerated relative to the SDP session, diff --git a/src/sip/sip_utils.cpp b/src/sip/sip_utils.cpp index da79ad6ca55f153b275b362692c34ed9b2b1108d..6a2c24e9b2263df0abaa24a6114b856093e53622 100644 --- a/src/sip/sip_utils.cpp +++ b/src/sip/sip_utils.cpp @@ -205,20 +205,47 @@ addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata) pjsip_user_agent_hdr_create(tdata->pool, &STR_USER_AGENT, &pjUserAgent)); if (hdr != nullptr) { - JAMI_DBG("Add header to SIP message: \"%.*s: %.*s\"", (int) hdr->name.slen, hdr->name.ptr, - (int) pjUserAgent.slen, pjUserAgent.ptr); + JAMI_DBG("Add header to SIP message: \"%.*s: %.*s\"", + (int) hdr->name.slen, + hdr->name.ptr, + (int) pjUserAgent.slen, + pjUserAgent.ptr); pjsip_msg_add_hdr(tdata->msg, hdr); } } -void logMessageHeaders(const pjsip_hdr* hdr_list) +std::string +getPeerUserAgent(const pjsip_rx_data* rdata) +{ + if (rdata == nullptr or rdata->msg_info.msg == nullptr) { + JAMI_ERR("Unexpected null poiter!"); + return {}; + } + + std::string peerUa {}; + pjsip_generic_string_hdr* uaHdr {nullptr}; + constexpr auto USER_AGENT_STR = CONST_PJ_STR("User-Agent"); + + uaHdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, + &USER_AGENT_STR, + nullptr); + + if (uaHdr) { + peerUa = {uaHdr->hvalue.ptr, static_cast<size_t>(uaHdr->hvalue.slen)}; + } + + return peerUa; +} + +void +logMessageHeaders(const pjsip_hdr* hdr_list) { const pjsip_hdr* hdr = hdr_list->next; const pjsip_hdr* end = hdr_list; std::string msgHdrStr("Message headers:\n"); for (; hdr != end; hdr = hdr->next) { char buf[1024]; - int size = pjsip_hdr_print_on((void*)hdr, buf, sizeof(buf)); + int size = pjsip_hdr_print_on((void*) hdr, buf, sizeof(buf)); if (size > 0) { msgHdrStr.append(buf, size); msgHdrStr.push_back('\n'); @@ -233,7 +260,7 @@ sip_strerror(pj_status_t code) { char err_msg[PJ_ERR_MSG_SIZE]; auto ret = pj_strerror(code, err_msg, sizeof err_msg); - return std::string {ret.ptr, ret.ptr+ret.slen}; + return std::string {ret.ptr, ret.ptr + ret.slen}; } void diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h index b6bea44ef3f1db61f0b7905eac3805e68d2f1d6b..0828c405bd267d5a1b36583e04f4424997fcae0a 100644 --- a/src/sip/sip_utils.h +++ b/src/sip/sip_utils.h @@ -98,6 +98,7 @@ std::string_view getHostFromUri(std::string_view sipUri); void addContactHeader(const pj_str_t* contactStr, pjsip_tx_data* tdata); void addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata); +std::string getPeerUserAgent(const pjsip_rx_data* rdata); void logMessageHeaders(const pjsip_hdr* hdr_list); std::string sip_strerror(pj_status_t code); diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 21b7360b787162793ec593146e4b1f22cd3a7b13..9d67ac53f2b3f1861ca51a9ce015dbad82d78544 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -118,6 +118,7 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account, Call::CallType type, const std::vector<MediaAttribute>& mediaAttrList) : Call(account, callId, type) + , peerSupportMultiStream_(false) , sdp_(new Sdp(callId)) { if (account->getUPnPActive()) @@ -1454,6 +1455,77 @@ SIPCall::sendKeyframe() #endif } +void +SIPCall::setPeerUaVersion(const std::string& ua) +{ + if (peerUserAgent_ == ua or ua.empty()) { + // Silently ignore if it did not change or empty. + return; + } + + if (peerUserAgent_.empty()) { + JAMI_DBG("[call:%s] Set peer's User-Agent to [%s]", getCallId().c_str(), ua.c_str()); + } else if (not peerUserAgent_.empty()) { + // Unlikely, but should be handled since we dont have control over the peer. + // Even if it's unexpected, we still try to parse the UA version. + JAMI_WARN("[call:%s] Peer's User-Agent unexpectedely changed from [%s] to [%s]", + getCallId().c_str(), + peerUserAgent_.c_str(), + ua.c_str()); + } + + peerUserAgent_ = ua; + + // User-agent parsing + constexpr std::string_view PACK_NAME(PACKAGE_NAME " "); + std::string_view s {peerUserAgent_}; + std::string version; + + auto pos = s.find(PACK_NAME); + if (pos == std::string::npos) { + // Must have the expected package name. + JAMI_WARN("Could not find the expected package name in peer's User-Agent"); + return; + } + + s = s.substr(pos + PACK_NAME.length()); + + // Unstable (un-released) versions has a hiphen + commit Id after + // the version number. Find the commit Id if any, and ignore it. + pos = s.find("-"); + if (pos != std::string::npos) { + // Get the version and ignore the commit ID. + version = s.substr(0, pos); + } else { + // Extract the version number. + pos = s.find(" "); + if (pos != std::string::npos) { + version = s.substr(0, pos); + } + } + + if (version.empty()) { + JAMI_DBG("[call:%s] Could not parse peer's Jami version", getCallId().c_str()); + } + + auto peerJamiVersion = split_string_to_unsigned(version, '.'); + if (peerJamiVersion.size() != 3) { + JAMI_WARN("Could not parse peer's Jami version"); + return; + } + + // Check if peer's version is at least 10.0.2 to enable multi-stream. + peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerJamiVersion, {10, 0, 2}); + + if (not peerSupportMultiStream_) { + JAMI_DBG("Peer's version [%u.%u.%u] does not support multi-stream. Min required version: " + "[10.0.2]", + peerJamiVersion[0], + peerJamiVersion[1], + peerJamiVersion[2]); + } +} + void SIPCall::onPeerRinging() { @@ -1970,6 +2042,15 @@ SIPCall::isReinviteRequired(const std::vector<MediaAttribute>& mediaAttrList) bool SIPCall::requestMediaChange(const std::vector<MediaAttribute>& mediaAttrList) { + // 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. + if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) { + JAMI_WARN("[call:%s] Peer does not support multi-stream. Media change request ignored", + getCallId().c_str()); + return false; + } + JAMI_DBG("[call:%s] Requesting media change. List of new media:", getCallId().c_str()); unsigned idx = 0; @@ -2611,6 +2692,9 @@ SIPCall::merge(Call& call) tmpMediaTransport_ = std::move(subcall.tmpMediaTransport_); } + peerUserAgent_ = subcall.peerUserAgent_; + peerSupportMultiStream_ = subcall.peerSupportMultiStream_; + Call::merge(subcall); startIceMedia(); } diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index b0a77b7f70842e3dd48e4624ee08e9719f81b688..b019b529fc27f81de00f5a938e0f72cf4cd37d5e 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -165,6 +165,11 @@ public: void monitor() const override; + /** + * Set peer's User-Agent found in the message header + */ + void setPeerUaVersion(const std::string& ua); + /** * Return the SDP's manager of this call */ @@ -382,6 +387,11 @@ private: } inline std::weak_ptr<SIPCall> weak() { return std::weak_ptr<SIPCall>(shared()); } + // Peer's User-Agent. + std::string peerUserAgent_ {}; + // Flag to indicate the the peer's Daemon version support multi-stream. + bool peerSupportMultiStream_ {false}; + // Vector holding the current RTP sessions. std::vector<RtpStream> rtpStreams_; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 039f4b599c0e9ff3ba68151bebab74a14ccb26b4..60ccd5a068837b816acf22d314e2585aa4e45080 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -397,6 +397,9 @@ transaction_request_cb(pjsip_rx_data* rdata) if (!call) { return PJ_FALSE; } + + call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata)); + call->setTransport(transport); // JAMI_DBG("transaction_request_cb viaHostname %s toUsername %s addrToUse %s addrSdp %s @@ -866,6 +869,10 @@ getCallFromInvite(pjsip_inv_session* inv) static void invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev) { + if (inv == nullptr or ev == nullptr) { + throw VoipLinkException("unexpected null pointer"); + } + auto call = getCallFromInvite(inv); if (not call) return; @@ -907,6 +914,12 @@ invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev) inv->cause); } + if (ev->type == PJSIP_EVENT_RX_MSG) { + call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.rx_msg.rdata)); + } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.tsx_state.src.rdata)); + } + switch (inv->state) { case PJSIP_INV_STATE_EARLY: if (status_code == PJSIP_SC_RINGING)