LCOV - code coverage report
Current view: top level - foo/src/sip - sdp.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 467 566 82.5 %
Date: 2026-01-22 10:39:23 Functions: 45 54 83.3 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "sdp.h"
      19             : 
      20             : #ifdef HAVE_CONFIG_H
      21             : #include "config.h"
      22             : #endif
      23             : 
      24             : #include "sip/sipaccount.h"
      25             : #include "sip/sipvoiplink.h"
      26             : #include "string_utils.h"
      27             : #include "base64.h"
      28             : 
      29             : #include "manager.h"
      30             : #include "logger.h"
      31             : #include "libav_utils.h"
      32             : 
      33             : #include "media_codec.h"
      34             : #include "system_codec_container.h"
      35             : #include "compiler_intrinsics.h" // for UNUSED
      36             : 
      37             : #include <opendht/rng.h>
      38             : 
      39             : #include <algorithm>
      40             : #include <cassert>
      41             : 
      42             : namespace jami {
      43             : 
      44             : using std::string;
      45             : using std::vector;
      46             : 
      47             : static constexpr int POOL_INITIAL_SIZE = 16384;
      48             : static constexpr int POOL_INCREMENT_SIZE = POOL_INITIAL_SIZE;
      49             : 
      50             : static std::map<MediaDirection, const char*> DIRECTION_STR {{MediaDirection::SENDRECV, "sendrecv"},
      51             :                                                             {MediaDirection::SENDONLY, "sendonly"},
      52             :                                                             {MediaDirection::RECVONLY, "recvonly"},
      53             :                                                             {MediaDirection::INACTIVE, "inactive"}};
      54             : 
      55         371 : Sdp::Sdp(const std::string& id)
      56         742 :     : memPool_(nullptr, [](pj_pool_t* pool) { pj_pool_release(pool); })
      57         371 :     , publishedIpAddr_()
      58         371 :     , publishedIpAddrType_()
      59         371 :     , telephoneEventPayload_(101) // same as asterisk
      60         742 :     , sessionName_("Call ID " + id)
      61             : {
      62         371 :     memPool_.reset(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
      63             :                                   id.c_str(),
      64             :                                   POOL_INITIAL_SIZE,
      65             :                                   POOL_INCREMENT_SIZE,
      66             :                                   NULL));
      67         371 :     if (not memPool_)
      68           0 :         throw std::runtime_error("pj_pool_create() failed");
      69         371 : }
      70             : 
      71         371 : Sdp::~Sdp()
      72             : {
      73         371 :     SIPAccount::releasePort(localAudioRtpPort_);
      74             : #ifdef ENABLE_VIDEO
      75         371 :     SIPAccount::releasePort(localVideoRtpPort_);
      76             : #endif
      77         371 : }
      78             : 
      79             : std::shared_ptr<SystemCodecInfo>
      80         750 : Sdp::findCodecBySpec(std::string_view codec, const unsigned clockrate) const
      81             : {
      82             :     // TODO : only manage a list?
      83        1242 :     for (const auto& accountCodec : audio_codec_list_) {
      84         904 :         auto audioCodecInfo = std::static_pointer_cast<SystemAudioCodecInfo>(accountCodec);
      85         904 :         if (audioCodecInfo->name == codec
      86        1316 :             and (audioCodecInfo->isPCMG722() ? (clockrate == 8000)
      87         412 :                                              : (audioCodecInfo->audioformat.sample_rate == clockrate)))
      88         412 :             return accountCodec;
      89         904 :     }
      90             : 
      91         338 :     for (const auto& accountCodec : video_codec_list_) {
      92         338 :         if (accountCodec->name == codec)
      93         338 :             return accountCodec;
      94             :     }
      95           0 :     return nullptr;
      96             : }
      97             : 
      98             : std::shared_ptr<SystemCodecInfo>
      99           0 : Sdp::findCodecByPayload(const unsigned payloadType)
     100             : {
     101             :     // TODO : only manage a list?
     102           0 :     for (const auto& accountCodec : audio_codec_list_) {
     103           0 :         if (accountCodec->payloadType == payloadType)
     104           0 :             return accountCodec;
     105             :     }
     106             : 
     107           0 :     for (const auto& accountCodec : video_codec_list_) {
     108           0 :         if (accountCodec->payloadType == payloadType)
     109           0 :             return accountCodec;
     110             :     }
     111           0 :     return nullptr;
     112             : }
     113             : 
     114             : static void
     115         437 : randomFill(std::vector<uint8_t>& dest)
     116             : {
     117         437 :     std::uniform_int_distribution<int> rand_byte {0, std::numeric_limits<uint8_t>::max()};
     118         437 :     std::random_device rdev;
     119         437 :     std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev)));
     120         437 : }
     121             : 
     122             : void
     123         661 : Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp)
     124             : {
     125         661 :     if (activeLocalSession_ != sdp)
     126         478 :         JAMI_DBG("Set active local session to [%p]. Was [%p]", sdp, activeLocalSession_);
     127         661 :     activeLocalSession_ = sdp;
     128         661 : }
     129             : 
     130             : void
     131         661 : Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session* sdp)
     132             : {
     133         661 :     if (activeLocalSession_ != sdp)
     134         300 :         JAMI_DBG("Set active remote session to [%p]. Was [%p]", sdp, activeRemoteSession_);
     135         661 :     activeRemoteSession_ = sdp;
     136         661 : }
     137             : 
     138             : pjmedia_sdp_attr*
     139         437 : Sdp::generateSdesAttribute()
     140             : {
     141             :     static constexpr const unsigned cryptoSuite = 0;
     142         437 :     std::vector<uint8_t> keyAndSalt;
     143         437 :     keyAndSalt.resize(jami::CryptoSuites[cryptoSuite].masterKeyLength / 8
     144         437 :                       + jami::CryptoSuites[cryptoSuite].masterSaltLength / 8);
     145             :     // generate keys
     146         437 :     randomFill(keyAndSalt);
     147             : 
     148         874 :     std::string crypto_attr = "1 "s + jami::CryptoSuites[cryptoSuite].name + " inline:" + base64::encode(keyAndSalt);
     149         437 :     pj_str_t val {sip_utils::CONST_PJ_STR(crypto_attr)};
     150         874 :     return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val);
     151         437 : }
     152             : 
     153             : char const*
     154         438 : Sdp::mediaDirection(const MediaAttribute& mediaAttr)
     155             : {
     156         438 :     if (not mediaAttr.enabled_) {
     157           0 :         return DIRECTION_STR[MediaDirection::INACTIVE];
     158             :     }
     159             : 
     160             :     // Since mute/un-mute audio is only done locally (RTP packets
     161             :     // are still sent to the peer), the media direction must be
     162             :     // set to "sendrecv" regardless of the mute state.
     163         438 :     if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
     164         243 :         return DIRECTION_STR[MediaDirection::SENDRECV];
     165             :     }
     166             : 
     167         195 :     if (mediaAttr.muted_) {
     168           8 :         if (mediaAttr.onHold_) {
     169           0 :             return DIRECTION_STR[MediaDirection::INACTIVE];
     170             :         }
     171           8 :         return DIRECTION_STR[MediaDirection::RECVONLY];
     172             :     }
     173             : 
     174         187 :     if (mediaAttr.onHold_) {
     175           6 :         return DIRECTION_STR[MediaDirection::SENDONLY];
     176             :     }
     177             : 
     178         181 :     return DIRECTION_STR[MediaDirection::SENDRECV];
     179             : }
     180             : 
     181             : MediaDirection
     182        1308 : Sdp::getMediaDirection(pjmedia_sdp_media* media)
     183             : {
     184        1308 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::SENDONLY], nullptr)
     185        1308 :         != nullptr) {
     186          27 :         return MediaDirection::SENDONLY;
     187             :     }
     188             : 
     189        1281 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::RECVONLY], nullptr)
     190        1281 :         != nullptr) {
     191          30 :         return MediaDirection::RECVONLY;
     192             :     }
     193             : 
     194        1251 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::INACTIVE], nullptr)
     195        1251 :         != nullptr) {
     196           6 :         return MediaDirection::INACTIVE;
     197             :     }
     198             : 
     199             :     // According to RFC 3264 (https://datatracker.ietf.org/doc/html/rfc3264#section-5.1),
     200             :     // "a=sendrecv" is the default media direction attribute.
     201        1245 :     return MediaDirection::SENDRECV;
     202             : }
     203             : 
     204             : MediaTransport
     205         558 : Sdp::getMediaTransport(pjmedia_sdp_media* media)
     206             : {
     207         558 :     if (pj_stricmp2(&media->desc.transport, "RTP/SAVP") == 0)
     208         557 :         return MediaTransport::RTP_SAVP;
     209           1 :     else if (pj_stricmp2(&media->desc.transport, "RTP/AVP") == 0)
     210           1 :         return MediaTransport::RTP_AVP;
     211             : 
     212           0 :     return MediaTransport::UNKNOWN;
     213             : }
     214             : 
     215             : std::vector<std::string>
     216         557 : Sdp::getCrypto(pjmedia_sdp_media* media)
     217             : {
     218         557 :     std::vector<std::string> crypto;
     219        8884 :     for (unsigned j = 0; j < media->attr_count; j++) {
     220        8327 :         const auto attribute = media->attr[j];
     221        8327 :         if (pj_stricmp2(&attribute->name, "crypto") == 0)
     222         555 :             crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
     223             :     }
     224             : 
     225         557 :     return crypto;
     226           0 : }
     227             : 
     228             : pjmedia_sdp_media*
     229         438 : Sdp::addMediaDescription(const MediaAttribute& mediaAttr)
     230             : {
     231         438 :     auto type = mediaAttr.type_;
     232         438 :     auto secure = mediaAttr.secure_;
     233             : 
     234         438 :     JAMI_DBG("Add media description [%s]", mediaAttr.toString(true).c_str());
     235             : 
     236         438 :     pjmedia_sdp_media* med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media);
     237             : 
     238         438 :     switch (type) {
     239         243 :     case MediaType::MEDIA_AUDIO:
     240         243 :         med->desc.media = sip_utils::CONST_PJ_STR("audio");
     241         243 :         med->desc.port = mediaAttr.enabled_ ? localAudioRtpPort_ : 0;
     242         243 :         med->desc.fmt_count = audio_codec_list_.size();
     243         243 :         break;
     244         195 :     case MediaType::MEDIA_VIDEO:
     245         195 :         med->desc.media = sip_utils::CONST_PJ_STR("video");
     246         195 :         med->desc.port = mediaAttr.enabled_ ? localVideoRtpPort_ : 0;
     247         195 :         med->desc.fmt_count = video_codec_list_.size();
     248         195 :         break;
     249           0 :     default:
     250           0 :         throw SdpException("Unsupported media type! Only audio and video are supported");
     251             :         break;
     252             :     }
     253             : 
     254         438 :     med->desc.port_count = 1;
     255             : 
     256             :     // Set the transport protocol of the media
     257         438 :     med->desc.transport = secure ? sip_utils::CONST_PJ_STR("RTP/SAVP") : sip_utils::CONST_PJ_STR("RTP/AVP");
     258             : 
     259         438 :     unsigned dynamic_payload = 96;
     260             : 
     261        1268 :     for (unsigned i = 0; i < med->desc.fmt_count; i++) {
     262             :         pjmedia_sdp_rtpmap rtpmap;
     263         830 :         rtpmap.param.slen = 0;
     264             : 
     265         830 :         std::string channels; // must have the lifetime of rtpmap
     266         830 :         std::string enc_name;
     267             :         unsigned payload;
     268             : 
     269         830 :         if (type == MediaType::MEDIA_AUDIO) {
     270         418 :             auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(audio_codec_list_[i]);
     271         418 :             payload = accountAudioCodec->payloadType;
     272         418 :             enc_name = accountAudioCodec->name;
     273             : 
     274         418 :             if (accountAudioCodec->audioformat.nb_channels > 1) {
     275         243 :                 channels = std::to_string(accountAudioCodec->audioformat.nb_channels);
     276         243 :                 rtpmap.param = sip_utils::CONST_PJ_STR(channels);
     277             :             }
     278             :             // G722 requires G722/8000 media description even though it's @ 16000 Hz
     279             :             // See http://tools.ietf.org/html/rfc3551#section-4.5.2
     280         418 :             if (accountAudioCodec->isPCMG722())
     281          25 :                 rtpmap.clock_rate = 8000;
     282             :             else
     283         393 :                 rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate;
     284             : 
     285         418 :         } else {
     286             :             // FIXME: get this key from header
     287         412 :             payload = dynamic_payload++;
     288         412 :             enc_name = video_codec_list_[i]->name;
     289         412 :             rtpmap.clock_rate = 90000;
     290             :         }
     291             : 
     292         830 :         auto payloadStr = std::to_string(payload);
     293         830 :         auto pjPayload = sip_utils::CONST_PJ_STR(payloadStr);
     294         830 :         pj_strdup(memPool_.get(), &med->desc.fmt[i], &pjPayload);
     295             : 
     296             :         // Add a rtpmap field for each codec
     297             :         // We could add one only for dynamic payloads because the codecs with static RTP payloads
     298             :         // are entirely defined in the RFC 3351
     299         830 :         rtpmap.pt = med->desc.fmt[i];
     300         830 :         rtpmap.enc_name = sip_utils::CONST_PJ_STR(enc_name);
     301             : 
     302             :         pjmedia_sdp_attr* attr;
     303         830 :         pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr);
     304         830 :         med->attr[med->attr_count++] = attr;
     305             : 
     306             : #ifdef ENABLE_VIDEO
     307         830 :         if (enc_name == "H264") {
     308             :             // FIXME: this should not be hardcoded, it will determine what profile and level
     309             :             // our peer will send us
     310         195 :             const auto accountVideoCodec = std::static_pointer_cast<SystemVideoCodecInfo>(video_codec_list_[i]);
     311         195 :             const auto& profileLevelID = accountVideoCodec->parameters.empty()
     312             :                                              ? libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID
     313         195 :                                              : accountVideoCodec->parameters;
     314           0 :             auto value = fmt::format("fmtp:{} {}", payload, profileLevelID);
     315         195 :             med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), value.c_str(), NULL);
     316         195 :         }
     317             : #endif
     318         830 :     }
     319             : 
     320         438 :     if (type == MediaType::MEDIA_AUDIO) {
     321         243 :         setTelephoneEventRtpmap(med);
     322         243 :         if (localAudioRtcpPort_) {
     323         243 :             addRTCPAttribute(med, localAudioRtcpPort_);
     324             :         }
     325         195 :     } else if (type == MediaType::MEDIA_VIDEO and localVideoRtcpPort_) {
     326         195 :         addRTCPAttribute(med, localVideoRtcpPort_);
     327             :     }
     328             : 
     329         438 :     char const* direction = mediaDirection(mediaAttr);
     330             : 
     331         438 :     med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), direction, NULL);
     332             : 
     333         438 :     if (secure) {
     334         437 :         if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS)
     335           0 :             throw SdpException("Unable to add sdes attribute to media");
     336             :     }
     337             : 
     338         438 :     return med;
     339             : }
     340             : 
     341             : void
     342         438 : Sdp::addRTCPAttribute(pjmedia_sdp_media* med, uint16_t port)
     343             : {
     344         438 :     dhtnet::IpAddr addr {publishedIpAddr_};
     345         438 :     addr.setPort(port);
     346         438 :     pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), addr.pjPtr());
     347         438 :     if (attr)
     348         438 :         pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
     349         438 : }
     350             : 
     351             : void
     352         202 : Sdp::setPublishedIP(const std::string& addr, pj_uint16_t addr_type)
     353             : {
     354         202 :     publishedIpAddr_ = addr;
     355         202 :     publishedIpAddrType_ = addr_type;
     356         202 :     if (localSession_) {
     357           0 :         if (addr_type == pj_AF_INET6())
     358           0 :             localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP6");
     359             :         else
     360           0 :             localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP4");
     361           0 :         localSession_->origin.addr = sip_utils::CONST_PJ_STR(publishedIpAddr_);
     362           0 :         localSession_->conn->addr = localSession_->origin.addr;
     363           0 :         if (pjmedia_sdp_validate(localSession_) != PJ_SUCCESS)
     364           0 :             JAMI_ERR("Unable to validate SDP");
     365             :     }
     366         202 : }
     367             : 
     368             : void
     369         202 : Sdp::setPublishedIP(const dhtnet::IpAddr& ip_addr)
     370             : {
     371         202 :     setPublishedIP(ip_addr, ip_addr.getFamily());
     372         202 : }
     373             : 
     374             : void
     375         243 : Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media* med)
     376             : {
     377         243 :     ++med->desc.fmt_count;
     378         243 :     pj_strdup2(memPool_.get(), &med->desc.fmt[med->desc.fmt_count - 1], std::to_string(telephoneEventPayload_).c_str());
     379             : 
     380             :     pjmedia_sdp_attr* attr_rtpmap = static_cast<pjmedia_sdp_attr*>(
     381         243 :         pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
     382         243 :     attr_rtpmap->name = sip_utils::CONST_PJ_STR("rtpmap");
     383         243 :     attr_rtpmap->value = sip_utils::CONST_PJ_STR("101 telephone-event/8000");
     384             : 
     385         243 :     med->attr[med->attr_count++] = attr_rtpmap;
     386             : 
     387             :     pjmedia_sdp_attr* attr_fmtp = static_cast<pjmedia_sdp_attr*>(
     388         243 :         pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
     389         243 :     attr_fmtp->name = sip_utils::CONST_PJ_STR("fmtp");
     390         243 :     attr_fmtp->value = sip_utils::CONST_PJ_STR("101 0-15");
     391             : 
     392         243 :     med->attr[med->attr_count++] = attr_fmtp;
     393         243 : }
     394             : 
     395             : void
     396         742 : Sdp::setLocalMediaCapabilities(MediaType type, const std::vector<std::shared_ptr<SystemCodecInfo>>& selectedCodecs)
     397             : {
     398         742 :     switch (type) {
     399         371 :     case MediaType::MEDIA_AUDIO:
     400         371 :         audio_codec_list_ = selectedCodecs;
     401         371 :         break;
     402             : 
     403         371 :     case MediaType::MEDIA_VIDEO:
     404             : #ifdef ENABLE_VIDEO
     405         371 :         video_codec_list_ = selectedCodecs;
     406             :         // Do not expose H265 if accel is disactivated
     407         371 :         if (not jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
     408         371 :             video_codec_list_.erase(std::remove_if(video_codec_list_.begin(),
     409             :                                                    video_codec_list_.end(),
     410         782 :                                                    [](const std::shared_ptr<SystemCodecInfo>& i) {
     411         782 :                                                        return i->name == "H265";
     412             :                                                    }),
     413         742 :                                     video_codec_list_.end());
     414             :         }
     415             : #else
     416             :         (void) selectedCodecs;
     417             : #endif
     418         371 :         break;
     419             : 
     420           0 :     default:
     421           0 :         throw SdpException("Unsupported media type");
     422             :         break;
     423             :     }
     424         742 : }
     425             : 
     426             : constexpr std::string_view
     427         882 : Sdp::getSdpDirectionStr(SdpDirection direction)
     428             : {
     429         882 :     if (direction == SdpDirection::OFFER)
     430         489 :         return "OFFER"sv;
     431         393 :     if (direction == SdpDirection::ANSWER)
     432         393 :         return "ANSWER"sv;
     433           0 :     return "NONE"sv;
     434             : }
     435             : 
     436             : void
     437         882 : Sdp::printSession(const pjmedia_sdp_session* session, const char* header, SdpDirection direction)
     438             : {
     439             :     static constexpr size_t BUF_SZ = 4095;
     440         882 :     sip_utils::PoolPtr tmpPool_(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
     441             :                                                "printSdp",
     442             :                                                BUF_SZ,
     443             :                                                BUF_SZ,
     444         882 :                                                nullptr));
     445             : 
     446         882 :     auto cloned_session = pjmedia_sdp_session_clone(tmpPool_.get(), session);
     447         882 :     if (!cloned_session) {
     448           0 :         JAMI_ERROR("Unable to clone SDP for printing");
     449           0 :         return;
     450             :     }
     451             : 
     452             :     // Filter-out sensible data like SRTP master key.
     453        2514 :     for (unsigned i = 0; i < cloned_session->media_count; ++i) {
     454        1632 :         pjmedia_sdp_media_remove_all_attr(cloned_session->media[i], "crypto");
     455             :     }
     456             : 
     457             :     std::array<char, BUF_SZ + 1> buffer;
     458         882 :     auto size = pjmedia_sdp_print(cloned_session, buffer.data(), BUF_SZ);
     459         882 :     if (size < 0) {
     460           0 :         JAMI_ERROR("SDP too big for dump: {}", header);
     461           0 :         return;
     462             :     }
     463             : 
     464        3528 :     JAMI_LOG("[SDP {}] {}\n{:s}", getSdpDirectionStr(direction), header, std::string_view(buffer.data(), size));
     465         882 : }
     466             : 
     467             : void
     468         241 : Sdp::createLocalSession(SdpDirection direction)
     469             : {
     470         241 :     sdpDirection_ = direction;
     471         241 :     localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session);
     472         241 :     localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn);
     473             : 
     474             :     /* Initialize the fields of the struct */
     475         241 :     localSession_->origin.version = 0;
     476             :     pj_time_val tv;
     477         241 :     pj_gettimeofday(&tv);
     478             : 
     479         241 :     localSession_->origin.user = *pj_gethostname();
     480             : 
     481             :     // Use Network Time Protocol format timestamp to ensure uniqueness.
     482         241 :     localSession_->origin.id = tv.sec + 2208988800UL;
     483         241 :     localSession_->origin.net_type = sip_utils::CONST_PJ_STR("IN");
     484         241 :     if (publishedIpAddrType_ == pj_AF_INET6())
     485           0 :         localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP6");
     486             :     else
     487         241 :         localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP4");
     488         241 :     localSession_->origin.addr = sip_utils::CONST_PJ_STR(publishedIpAddr_);
     489             : 
     490             :     // Use the call IDs for s= line
     491         241 :     localSession_->name = sip_utils::CONST_PJ_STR(sessionName_);
     492             : 
     493         241 :     localSession_->conn->net_type = localSession_->origin.net_type;
     494         241 :     localSession_->conn->addr_type = localSession_->origin.addr_type;
     495         241 :     localSession_->conn->addr = localSession_->origin.addr;
     496             : 
     497             :     // RFC 3264: An offer/answer model session description protocol
     498             :     // As the session is created and destroyed through an external signaling mean (SIP), the line
     499             :     // should have a value of "0 0".
     500         241 :     localSession_->time.start = 0;
     501         241 :     localSession_->time.stop = 0;
     502         241 : }
     503             : 
     504             : int
     505         482 : Sdp::validateSession() const
     506             : {
     507         482 :     return pjmedia_sdp_validate(localSession_);
     508             : }
     509             : 
     510             : bool
     511         126 : Sdp::createOffer(const std::vector<MediaAttribute>& mediaList)
     512             : {
     513         126 :     if (mediaList.size() >= PJMEDIA_MAX_SDP_MEDIA) {
     514           0 :         throw SdpException("Media list size exceeds SDP media maximum size");
     515             :     }
     516         504 :     JAMI_DEBUG("Creating SDP offer with {} media", mediaList.size());
     517             : 
     518         126 :     createLocalSession(SdpDirection::OFFER);
     519             : 
     520         126 :     if (validateSession() != PJ_SUCCESS) {
     521           0 :         JAMI_ERR("Failed to create initial offer");
     522           0 :         return false;
     523             :     }
     524             : 
     525         126 :     localSession_->media_count = 0;
     526             : 
     527         356 :     for (auto const& media : mediaList) {
     528         230 :         if (media.enabled_) {
     529         230 :             localSession_->media[localSession_->media_count++] = addMediaDescription(media);
     530             :         }
     531             :     }
     532             : 
     533         126 :     if (validateSession() != PJ_SUCCESS) {
     534           0 :         JAMI_ERR("Failed to add medias");
     535           0 :         return false;
     536             :     }
     537             : 
     538         126 :     if (pjmedia_sdp_neg_create_w_local_offer(memPool_.get(), localSession_, &negotiator_) != PJ_SUCCESS) {
     539           0 :         JAMI_ERR("Failed to create an initial SDP negotiator");
     540           0 :         return false;
     541             :     }
     542             : 
     543         126 :     printSession(localSession_, "Local session (initial):", sdpDirection_);
     544             : 
     545         126 :     return true;
     546             : }
     547             : 
     548             : void
     549         125 : Sdp::setReceivedOffer(const pjmedia_sdp_session* remote)
     550             : {
     551         125 :     if (remote == nullptr) {
     552           0 :         JAMI_ERR("Remote session is NULL");
     553           0 :         return;
     554             :     }
     555         125 :     remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote);
     556             : }
     557             : 
     558             : bool
     559         115 : Sdp::processIncomingOffer(const std::vector<MediaAttribute>& mediaList)
     560             : {
     561         115 :     if (not remoteSession_)
     562           0 :         return false;
     563             : 
     564         460 :     JAMI_DEBUG("Processing received offer for [{:s}] with {:d} media", sessionName_, mediaList.size());
     565             : 
     566         115 :     printSession(remoteSession_, "Remote session:", SdpDirection::OFFER);
     567             : 
     568         115 :     createLocalSession(SdpDirection::ANSWER);
     569         115 :     if (validateSession() != PJ_SUCCESS) {
     570           0 :         JAMI_ERR("Failed to create local session");
     571           0 :         return false;
     572             :     }
     573             : 
     574         115 :     localSession_->media_count = 0;
     575             : 
     576         326 :     for (auto const& media : mediaList) {
     577         211 :         if (media.enabled_) {
     578         208 :             localSession_->media[localSession_->media_count++] = addMediaDescription(media);
     579             :         }
     580             :     }
     581             : 
     582         115 :     printSession(localSession_, "Local session:\n", sdpDirection_);
     583             : 
     584         115 :     if (validateSession() != PJ_SUCCESS) {
     585           0 :         JAMI_ERR("Failed to add medias");
     586           0 :         return false;
     587             :     }
     588             : 
     589         115 :     if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(), localSession_, remoteSession_, &negotiator_)
     590         115 :         != PJ_SUCCESS) {
     591           0 :         JAMI_ERR("Failed to initialize media negotiation");
     592           0 :         return false;
     593             :     }
     594             : 
     595         115 :     return true;
     596             : }
     597             : 
     598             : bool
     599          24 : Sdp::startNegotiation()
     600             : {
     601          24 :     JAMI_DBG("Starting media negotiation for [%s]", sessionName_.c_str());
     602             : 
     603          24 :     if (negotiator_ == NULL) {
     604           0 :         JAMI_ERR("Unable to start negotiation with invalid negotiator");
     605           0 :         return false;
     606             :     }
     607             : 
     608             :     const pjmedia_sdp_session* active_local;
     609             :     const pjmedia_sdp_session* active_remote;
     610             : 
     611          24 :     if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
     612           0 :         JAMI_WARN("Negotiator not in right state for negotiation");
     613           0 :         return false;
     614             :     }
     615             : 
     616          24 :     if (pjmedia_sdp_neg_negotiate(memPool_.get(), negotiator_, 0) != PJ_SUCCESS) {
     617           0 :         JAMI_ERR("Failed to start media negotiation");
     618           0 :         return false;
     619             :     }
     620             : 
     621          24 :     if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
     622           0 :         JAMI_ERR("Unable to retrieve local active session");
     623             : 
     624          24 :     setActiveLocalSdpSession(active_local);
     625             : 
     626          24 :     if (active_local != nullptr) {
     627          24 :         printSession(active_local, "Local active session:", sdpDirection_);
     628             :     }
     629             : 
     630          24 :     if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS or active_remote == nullptr) {
     631           0 :         JAMI_ERR("Unable to retrieve remote active session");
     632           0 :         return false;
     633             :     }
     634             : 
     635          24 :     setActiveRemoteSdpSession(active_remote);
     636             : 
     637          24 :     printSession(active_remote, "Remote active session:", sdpDirection_);
     638             : 
     639          24 :     return true;
     640             : }
     641             : 
     642             : std::string
     643         392 : Sdp::getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep)
     644             : {
     645             :     static constexpr size_t BUF_SZ = 4096;
     646             :     sip_utils::PoolPtr tmpPool_(
     647         392 :         pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory, "tmpSdp", BUF_SZ, BUF_SZ, nullptr));
     648         392 :     auto cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session);
     649         392 :     if (!cloned) {
     650           0 :         JAMI_ERR("Unable to clone SDP");
     651           0 :         return "";
     652             :     }
     653             : 
     654             :     // deactivate non-video media
     655         392 :     bool hasKeep = false;
     656        1158 :     for (unsigned i = 0; i < cloned->media_count; i++)
     657         766 :         if (i != media_keep) {
     658         374 :             if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS)
     659           0 :                 JAMI_ERR("Unable to deactivate media");
     660             :         } else {
     661         392 :             hasKeep = true;
     662             :         }
     663             : 
     664         392 :     if (not hasKeep) {
     665           0 :         JAMI_DBG("No media to keep present in SDP");
     666           0 :         return "";
     667             :     }
     668             : 
     669             :     // Leaking medias will be dropped with tmpPool_
     670        1158 :     for (unsigned i = 0; i < cloned->media_count; i++)
     671         766 :         if (cloned->media[i]->desc.port == 0) {
     672         374 :             std::move(cloned->media + i + 1, cloned->media + cloned->media_count, cloned->media + i);
     673         374 :             cloned->media_count--;
     674         374 :             i--;
     675             :         }
     676             : 
     677         784 :     for (unsigned i = 0; i < cloned->media_count; i++) {
     678         392 :         auto media = cloned->media[i];
     679             : 
     680             :         // filter other codecs
     681        1000 :         for (unsigned c = 0; c < media->desc.fmt_count; c++) {
     682         608 :             auto& pt = media->desc.fmt[c];
     683         608 :             if (pj_strtoul(&pt) == pt_keep)
     684         392 :                 continue;
     685             : 
     686         432 :             while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtpmap", &pt))
     687         216 :                 pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
     688             : 
     689         216 :             while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "fmt", &pt))
     690           0 :                 pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
     691             : 
     692         216 :             std::move(media->desc.fmt + c + 1, media->desc.fmt + media->desc.fmt_count, media->desc.fmt + c);
     693         216 :             media->desc.fmt_count--;
     694         216 :             c--;
     695             :         }
     696             : 
     697             :         // we handle crypto ourselfs, don't tell libav about it
     698         392 :         pjmedia_sdp_media_remove_all_attr(media, "crypto");
     699             :     }
     700             : 
     701             :     char buffer[BUF_SZ];
     702         392 :     size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer));
     703         392 :     string sessionStr(buffer, std::min(size, sizeof(buffer)));
     704             : 
     705         392 :     return sessionStr;
     706         392 : }
     707             : 
     708             : std::vector<MediaDescription>
     709          34 : Sdp::getActiveMediaDescription(bool remote) const
     710             : {
     711          34 :     if (remote)
     712           7 :         return getMediaDescriptions(activeRemoteSession_, true);
     713             : 
     714          27 :     return getMediaDescriptions(activeLocalSession_, false);
     715             : }
     716             : 
     717             : std::vector<MediaDescription>
     718         404 : Sdp::getMediaDescriptions(const pjmedia_sdp_session* session, bool remote) const
     719             : {
     720         404 :     if (!session)
     721           0 :         return {};
     722             :     static constexpr pj_str_t STR_RTPMAP {sip_utils::CONST_PJ_STR("rtpmap")};
     723             :     static constexpr pj_str_t STR_FMTP {sip_utils::CONST_PJ_STR("fmtp")};
     724             : 
     725         404 :     std::vector<MediaDescription> ret;
     726        1160 :     for (unsigned i = 0; i < session->media_count; i++) {
     727         756 :         auto media = session->media[i];
     728         756 :         ret.emplace_back(MediaDescription());
     729         756 :         MediaDescription& descr = ret.back();
     730         756 :         if (!pj_stricmp2(&media->desc.media, "audio"))
     731         412 :             descr.type = MEDIA_AUDIO;
     732         344 :         else if (!pj_stricmp2(&media->desc.media, "video"))
     733         344 :             descr.type = MEDIA_VIDEO;
     734             :         else
     735           6 :             continue;
     736             : 
     737         756 :         descr.enabled = media->desc.port;
     738         756 :         if (!descr.enabled)
     739           6 :             continue;
     740             : 
     741             :         // get connection info
     742         750 :         pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn;
     743         750 :         if (not conn) {
     744           0 :             JAMI_ERR("Unable to find connection information for media");
     745           0 :             continue;
     746             :         }
     747         750 :         descr.addr = std::string_view(conn->addr.ptr, conn->addr.slen);
     748         750 :         descr.addr.setPort(media->desc.port);
     749             : 
     750             :         // Get the "rtcp" address from the SDP if present. Otherwise,
     751             :         // infere it from endpoint (RTP) address.
     752         750 :         auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtcp", NULL);
     753         750 :         if (attr) {
     754             :             pjmedia_sdp_rtcp_attr rtcp;
     755         750 :             auto status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
     756         750 :             if (status == PJ_SUCCESS && rtcp.addr.slen) {
     757         750 :                 descr.rtcp_addr = std::string_view(rtcp.addr.ptr, rtcp.addr.slen);
     758         750 :                 descr.rtcp_addr.setPort(rtcp.port);
     759             :             }
     760             :         }
     761             : 
     762        2250 :         descr.onHold = pjmedia_sdp_attr_find2(media->attr_count,
     763         750 :                                               media->attr,
     764         750 :                                               DIRECTION_STR[MediaDirection::SENDONLY],
     765             :                                               nullptr)
     766        1484 :                        || pjmedia_sdp_attr_find2(media->attr_count,
     767         734 :                                                  media->attr,
     768         734 :                                                  DIRECTION_STR[MediaDirection::INACTIVE],
     769             :                                                  nullptr);
     770             : 
     771         750 :         descr.direction_ = getMediaDirection(media);
     772             : 
     773             :         // get codecs infos
     774         750 :         for (unsigned j = 0; j < media->desc.fmt_count; j++) {
     775         750 :             const auto rtpMapAttribute = pjmedia_sdp_media_find_attr(media, &STR_RTPMAP, &media->desc.fmt[j]);
     776         750 :             if (!rtpMapAttribute) {
     777           0 :                 JAMI_ERR("Unable to find rtpmap attribute");
     778           0 :                 descr.enabled = false;
     779           0 :                 continue;
     780             :             }
     781             :             pjmedia_sdp_rtpmap rtpmap;
     782         750 :             if (pjmedia_sdp_attr_get_rtpmap(rtpMapAttribute, &rtpmap) != PJ_SUCCESS || rtpmap.enc_name.slen == 0) {
     783           0 :                 JAMI_ERROR("Unable to find payload type {} in SDP", sip_utils::as_view(media->desc.fmt[j]));
     784           0 :                 descr.enabled = false;
     785           0 :                 continue;
     786           0 :             }
     787         750 :             auto codec_raw = sip_utils::as_view(rtpmap.enc_name);
     788         750 :             descr.rtp_clockrate = rtpmap.clock_rate;
     789         750 :             descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate);
     790         750 :             if (not descr.codec) {
     791           0 :                 JAMI_ERROR("Unable to find codec {}", codec_raw);
     792           0 :                 descr.enabled = false;
     793           0 :                 continue;
     794           0 :             }
     795         750 :             descr.payload_type = pj_strtoul(&rtpmap.pt);
     796         750 :             if (descr.type == MEDIA_VIDEO) {
     797         338 :                 const auto fmtpAttr = pjmedia_sdp_media_find_attr(media, &STR_FMTP, &media->desc.fmt[j]);
     798             :                 // descr.bitrate = getOutgoingVideoField(codec, "bitrate");
     799         338 :                 if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) {
     800         162 :                     const auto& v = fmtpAttr->value;
     801         162 :                     descr.parameters = std::string(v.ptr, v.ptr + v.slen);
     802             :                 }
     803             :             }
     804             :             // for now, just keep the first codec only
     805         750 :             descr.enabled = true;
     806         750 :             break;
     807             :         }
     808             : 
     809         750 :         if (not remote)
     810         392 :             descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type);
     811             : 
     812             :         // get crypto info
     813         750 :         std::vector<std::string> crypto;
     814       11833 :         for (unsigned j = 0; j < media->attr_count; j++) {
     815       11083 :             const auto attribute = media->attr[j];
     816       11083 :             if (pj_stricmp2(&attribute->name, "crypto") == 0)
     817         750 :                 crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
     818             :         }
     819         750 :         descr.crypto = SdesNegotiator::negotiate(crypto);
     820         750 :     }
     821         404 :     return ret;
     822         404 : }
     823             : 
     824             : std::vector<Sdp::MediaSlot>
     825         185 : Sdp::getMediaSlots() const
     826             : {
     827         185 :     auto loc = getMediaDescriptions(activeLocalSession_, false);
     828         185 :     auto rem = getMediaDescriptions(activeRemoteSession_, true);
     829         185 :     size_t slot_n = std::min(loc.size(), rem.size());
     830         185 :     std::vector<MediaSlot> s;
     831         185 :     s.reserve(slot_n);
     832         530 :     for (decltype(slot_n) i = 0; i < slot_n; i++)
     833         345 :         s.emplace_back(std::move(loc[i]), std::move(rem[i]));
     834         370 :     return s;
     835         185 : }
     836             : 
     837             : void
     838         816 : Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
     839             : {
     840         816 :     if (media_index >= localSession_->media_count) {
     841           0 :         JAMI_ERR("addIceCandidates failed: unable to access media#%u (may be deactivated)", media_index);
     842           0 :         return;
     843             :     }
     844             : 
     845         816 :     auto media = localSession_->media[media_index];
     846             : 
     847        4628 :     for (const auto& item : cands) {
     848        3812 :         const pj_str_t val = sip_utils::CONST_PJ_STR(item);
     849        3812 :         pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val);
     850             : 
     851        3812 :         if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
     852           0 :             throw SdpException("Unable to add ICE candidates attribute to media");
     853             :     }
     854             : }
     855             : 
     856             : std::vector<std::string>
     857         387 : Sdp::getIceCandidates(unsigned media_index) const
     858             : {
     859         387 :     auto remoteSession = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_;
     860         387 :     auto localSession = activeLocalSession_ ? activeLocalSession_ : localSession_;
     861         387 :     if (not remoteSession) {
     862           0 :         JAMI_ERR("getIceCandidates failed: no remote session");
     863           0 :         return {};
     864             :     }
     865         387 :     if (not localSession) {
     866           0 :         JAMI_ERR("getIceCandidates failed: no local session");
     867           0 :         return {};
     868             :     }
     869         387 :     if (media_index >= remoteSession->media_count || media_index >= localSession->media_count) {
     870           0 :         JAMI_ERR("getIceCandidates failed: unable to access media#%u (may be deactivated)", media_index);
     871           0 :         return {};
     872             :     }
     873         387 :     auto media = remoteSession->media[media_index];
     874         387 :     auto localMedia = localSession->media[media_index];
     875         387 :     if (media->desc.port == 0 || localMedia->desc.port == 0) {
     876           5 :         JAMI_WARN("Media#%u is disabled. Media ports: local %u, remote %u",
     877             :                   media_index,
     878             :                   localMedia->desc.port,
     879             :                   media->desc.port);
     880           5 :         return {};
     881             :     }
     882             : 
     883         382 :     std::vector<std::string> candidates;
     884             : 
     885        6254 :     for (unsigned i = 0; i < media->attr_count; i++) {
     886        5872 :         pjmedia_sdp_attr* attribute = media->attr[i];
     887        5872 :         if (pj_stricmp2(&attribute->name, "candidate") == 0)
     888        3596 :             candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
     889             :     }
     890             : 
     891         382 :     return candidates;
     892         382 : }
     893             : 
     894             : void
     895         223 : Sdp::addIceAttributes(const dhtnet::IceTransport::Attribute&& ice_attrs)
     896             : {
     897         223 :     pj_str_t value = sip_utils::CONST_PJ_STR(ice_attrs.ufrag);
     898         223 :     pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value);
     899             : 
     900         223 :     if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
     901           0 :         throw SdpException("Unable to add ICE.ufrag attribute to local SDP");
     902             : 
     903         223 :     value = sip_utils::CONST_PJ_STR(ice_attrs.pwd);
     904         223 :     attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value);
     905             : 
     906         223 :     if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
     907           0 :         throw SdpException("Unable to add ICE.pwd attribute to local SDP");
     908         223 : }
     909             : 
     910             : dhtnet::IceTransport::Attribute
     911         541 : Sdp::getIceAttributes() const
     912             : {
     913         541 :     if (auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_)
     914         541 :         return getIceAttributes(session);
     915           0 :     return {};
     916             : }
     917             : 
     918             : dhtnet::IceTransport::Attribute
     919         541 : Sdp::getIceAttributes(const pjmedia_sdp_session* session)
     920             : {
     921         541 :     dhtnet::IceTransport::Attribute ice_attrs;
     922             :     // Per RFC8839, ice-ufrag/ice-pwd can be present either at
     923             :     // media or session level.
     924             :     // This seems to be the case for Asterisk servers (ICE is at media-session).
     925        1065 :     for (unsigned i = 0; i < session->attr_count; i++) {
     926        1048 :         pjmedia_sdp_attr* attribute = session->attr[i];
     927        1048 :         if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
     928         524 :             ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
     929         524 :         else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
     930         524 :             ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
     931        1048 :         if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
     932         524 :             return ice_attrs;
     933             :     }
     934          48 :     for (unsigned i = 0; i < session->media_count; i++) {
     935          31 :         auto* media = session->media[i];
     936         232 :         for (unsigned j = 0; j < media->attr_count; j++) {
     937         201 :             pjmedia_sdp_attr* attribute = media->attr[j];
     938         201 :             if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
     939           0 :                 ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
     940         201 :             else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
     941           0 :                 ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
     942         201 :             if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
     943           0 :                 return ice_attrs;
     944             :         }
     945             :     }
     946             : 
     947          17 :     return ice_attrs;
     948           0 : }
     949             : 
     950             : void
     951          49 : Sdp::clearIce()
     952             : {
     953          49 :     clearIce(localSession_);
     954          49 :     clearIce(remoteSession_);
     955          49 :     setActiveRemoteSdpSession(nullptr);
     956          49 :     setActiveLocalSdpSession(nullptr);
     957          49 : }
     958             : 
     959             : void
     960          98 : Sdp::clearIce(pjmedia_sdp_session* session)
     961             : {
     962          98 :     if (not session)
     963          24 :         return;
     964          74 :     pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag");
     965          74 :     pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-pwd");
     966             :     // TODO. Why this? we should not have "candidate" attribute at session level.
     967          74 :     pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate");
     968         210 :     for (unsigned i = 0; i < session->media_count; i++) {
     969         136 :         auto media = session->media[i];
     970         136 :         pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate");
     971             :     }
     972             : }
     973             : 
     974             : std::vector<MediaAttribute>
     975         301 : Sdp::getMediaAttributeListFromSdp(const pjmedia_sdp_session* sdpSession, bool ignoreDisabled)
     976             : {
     977         301 :     if (sdpSession == nullptr) {
     978           0 :         return {};
     979             :     }
     980             : 
     981         301 :     std::vector<MediaAttribute> mediaList;
     982         301 :     unsigned audioIdx = 0;
     983         301 :     unsigned videoIdx = 0;
     984         860 :     for (unsigned idx = 0; idx < sdpSession->media_count; idx++) {
     985         559 :         mediaList.emplace_back(MediaAttribute {});
     986         559 :         auto& mediaAttr = mediaList.back();
     987             : 
     988         559 :         auto const& media = sdpSession->media[idx];
     989             : 
     990             :         // Get media type.
     991         559 :         if (!pj_stricmp2(&media->desc.media, "audio"))
     992         306 :             mediaAttr.type_ = MediaType::MEDIA_AUDIO;
     993         253 :         else if (!pj_stricmp2(&media->desc.media, "video"))
     994         253 :             mediaAttr.type_ = MediaType::MEDIA_VIDEO;
     995             :         else {
     996           0 :             JAMI_WARN("Media#%u only 'audio' and 'video' types are supported!", idx);
     997             :             // Disable the media. No need to parse the attributes.
     998           0 :             mediaAttr.enabled_ = false;
     999           0 :             continue;
    1000             :         }
    1001             : 
    1002             :         // Set enabled flag
    1003         559 :         mediaAttr.enabled_ = media->desc.port > 0;
    1004             : 
    1005         559 :         if (!mediaAttr.enabled_ && ignoreDisabled) {
    1006           1 :             mediaList.pop_back();
    1007           1 :             continue;
    1008             :         }
    1009             : 
    1010             :         // Get mute state.
    1011         558 :         auto direction = getMediaDirection(media);
    1012         558 :         mediaAttr.muted_ = direction != MediaDirection::SENDRECV and direction != MediaDirection::SENDONLY;
    1013             : 
    1014             :         // Get transport.
    1015         558 :         auto transp = getMediaTransport(media);
    1016         558 :         if (transp == MediaTransport::UNKNOWN) {
    1017           0 :             JAMI_WARN("Media#%u is unable to determine transport type!", idx);
    1018             :         }
    1019             : 
    1020             :         // A media is secure if the transport is of type RTP/SAVP
    1021             :         // and the crypto materials are present.
    1022         558 :         mediaAttr.secure_ = transp == MediaTransport::RTP_SAVP and not getCrypto(media).empty();
    1023             : 
    1024         558 :         if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
    1025         306 :             mediaAttr.label_ = "audio_" + std::to_string(audioIdx++);
    1026         252 :         } else if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
    1027         252 :             mediaAttr.label_ = "video_" + std::to_string(videoIdx++);
    1028             :         }
    1029             :     }
    1030             : 
    1031         301 :     return mediaList;
    1032         301 : }
    1033             : 
    1034             : } // namespace jami

Generated by: LCOV version 1.14