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

Generated by: LCOV version 1.14