LCOV - code coverage report
Current view: top level - src/sip - sdp.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 82.3 % 566 466
Test Date: 2026-07-29 09:02:12 Functions: 97.5 % 40 39

            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          163 : Sdp::Sdp(const std::string& id)
      55          326 :     : memPool_(nullptr, [](pj_pool_t* pool) { pj_pool_release(pool); })
      56          163 :     , publishedIpAddr_()
      57          163 :     , publishedIpAddrType_()
      58          163 :     , telephoneEventPayload_(101) // same as asterisk
      59          326 :     , sessionName_("Call ID " + id)
      60              : {
      61          163 :     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          163 :     if (not memPool_)
      67            0 :         throw std::runtime_error("pj_pool_create() failed");
      68          163 : }
      69              : 
      70          163 : Sdp::~Sdp()
      71              : {
      72          163 :     SIPAccount::releasePort(localAudioRtpPort_);
      73              : #ifdef ENABLE_VIDEO
      74          163 :     SIPAccount::releasePort(localVideoRtpPort_);
      75              : #endif
      76          163 : }
      77              : 
      78              : std::shared_ptr<SystemCodecInfo>
      79          410 : Sdp::findCodecBySpec(std::string_view codec, const unsigned clockrate) const
      80              : {
      81              :     // TODO : only manage a list?
      82          738 :     for (const auto& accountCodec : audio_codec_list_) {
      83          564 :         auto audioCodecInfo = std::static_pointer_cast<SystemAudioCodecInfo>(accountCodec);
      84          564 :         if (audioCodecInfo->name == codec
      85          800 :             and (audioCodecInfo->isPCMG722() ? (clockrate == 8000)
      86          236 :                                              : (audioCodecInfo->audioformat.sample_rate == clockrate)))
      87          236 :             return accountCodec;
      88          564 :     }
      89              : 
      90          174 :     for (const auto& accountCodec : video_codec_list_) {
      91          174 :         if (accountCodec->name == codec)
      92          174 :             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          223 : randomFill(std::vector<uint8_t>& dest)
     115              : {
     116          223 :     std::uniform_int_distribution<int> rand_byte {0, std::numeric_limits<uint8_t>::max()};
     117          223 :     std::random_device rdev;
     118          223 :     std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev)));
     119          223 : }
     120              : 
     121              : void
     122          359 : Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp)
     123              : {
     124          359 :     if (activeLocalSession_ != sdp)
     125          247 :         JAMI_LOG("Set active local session to [{}]. Was [{}]", fmt::ptr(sdp), fmt::ptr(activeLocalSession_));
     126          359 :     activeLocalSession_ = sdp;
     127          359 : }
     128              : 
     129              : void
     130          359 : Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session* sdp)
     131              : {
     132          359 :     if (activeLocalSession_ != sdp)
     133          171 :         JAMI_LOG("Set active remote session to [{}]. Was [{}]", fmt::ptr(sdp), fmt::ptr(activeRemoteSession_));
     134          359 :     activeRemoteSession_ = sdp;
     135          359 : }
     136              : 
     137              : pjmedia_sdp_attr*
     138          223 : Sdp::generateSdesAttribute()
     139              : {
     140              :     static constexpr const unsigned cryptoSuite = 0;
     141          223 :     std::vector<uint8_t> keyAndSalt;
     142          223 :     keyAndSalt.resize(jami::CryptoSuites[cryptoSuite].masterKeyLength / 8
     143          223 :                       + jami::CryptoSuites[cryptoSuite].masterSaltLength / 8);
     144              :     // generate keys
     145          223 :     randomFill(keyAndSalt);
     146              : 
     147          223 :     std::string crypto_attr = "1 "s + jami::CryptoSuites[cryptoSuite].name + " inline:" + base64::encode(keyAndSalt);
     148          223 :     pj_str_t val {sip_utils::CONST_PJ_STR(crypto_attr)};
     149          446 :     return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val);
     150          223 : }
     151              : 
     152              : char const*
     153          224 : Sdp::mediaDirection(const MediaAttribute& mediaAttr)
     154              : {
     155          224 :     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          224 :     if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
     163          128 :         return DIRECTION_STR[MediaDirection::SENDRECV];
     164              :     }
     165              : 
     166           96 :     if (mediaAttr.muted_) {
     167            9 :         if (mediaAttr.hold_) {
     168            0 :             return DIRECTION_STR[MediaDirection::INACTIVE];
     169              :         }
     170            9 :         return DIRECTION_STR[MediaDirection::RECVONLY];
     171              :     }
     172              : 
     173           87 :     if (mediaAttr.hold_) {
     174            4 :         return DIRECTION_STR[MediaDirection::SENDONLY];
     175              :     }
     176              : 
     177           83 :     return DIRECTION_STR[MediaDirection::SENDRECV];
     178              : }
     179              : 
     180              : MediaDirection
     181          692 : Sdp::getMediaDirection(pjmedia_sdp_media* media)
     182              : {
     183          692 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::SENDONLY], nullptr)
     184          692 :         != nullptr) {
     185           25 :         return MediaDirection::SENDONLY;
     186              :     }
     187              : 
     188          667 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::RECVONLY], nullptr)
     189          667 :         != nullptr) {
     190           32 :         return MediaDirection::RECVONLY;
     191              :     }
     192              : 
     193          635 :     if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::INACTIVE], nullptr)
     194          635 :         != nullptr) {
     195           10 :         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          625 :     return MediaDirection::SENDRECV;
     201              : }
     202              : 
     203              : MediaTransport
     204          282 : Sdp::getMediaTransport(pjmedia_sdp_media* media)
     205              : {
     206          282 :     if (pj_stricmp2(&media->desc.transport, "RTP/SAVP") == 0)
     207          281 :         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          281 : Sdp::getCrypto(pjmedia_sdp_media* media)
     216              : {
     217          281 :     std::vector<std::string> crypto;
     218         5145 :     for (unsigned j = 0; j < media->attr_count; j++) {
     219         4864 :         auto* const attribute = media->attr[j];
     220         4864 :         if (pj_stricmp2(&attribute->name, "crypto") == 0)
     221          279 :             crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
     222              :     }
     223              : 
     224          281 :     return crypto;
     225            0 : }
     226              : 
     227              : pjmedia_sdp_media*
     228          224 : Sdp::addMediaDescription(const MediaAttribute& mediaAttr)
     229              : {
     230          224 :     auto type = mediaAttr.type_;
     231          224 :     auto secure = mediaAttr.secure_;
     232              : 
     233          224 :     JAMI_LOG("Add media description [{}]", mediaAttr.toString(true));
     234              : 
     235          224 :     pjmedia_sdp_media* med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media);
     236              : 
     237          224 :     switch (type) {
     238          128 :     case MediaType::MEDIA_AUDIO:
     239          128 :         med->desc.media = sip_utils::CONST_PJ_STR("audio");
     240          128 :         med->desc.port = mediaAttr.enabled_ ? localAudioRtpPort_ : 0;
     241          128 :         med->desc.fmt_count = audio_codec_list_.size();
     242          128 :         break;
     243           96 :     case MediaType::MEDIA_VIDEO:
     244           96 :         med->desc.media = sip_utils::CONST_PJ_STR("video");
     245           96 :         med->desc.port = mediaAttr.enabled_ ? localVideoRtpPort_ : 0;
     246           96 :         med->desc.fmt_count = video_codec_list_.size();
     247           96 :         break;
     248            0 :     default:
     249            0 :         throw SdpException("Unsupported media type! Only audio and video are supported");
     250              :         break;
     251              :     }
     252              : 
     253          224 :     med->desc.port_count = 1;
     254              : 
     255              :     // Set the transport protocol of the media
     256          224 :     med->desc.transport = secure ? sip_utils::CONST_PJ_STR("RTP/SAVP") : sip_utils::CONST_PJ_STR("RTP/AVP");
     257              : 
     258          224 :     unsigned dynamic_payload = 96;
     259              : 
     260          741 :     for (unsigned i = 0; i < med->desc.fmt_count; i++) {
     261              :         pjmedia_sdp_rtpmap rtpmap;
     262          517 :         rtpmap.param.slen = 0;
     263              : 
     264          517 :         std::string channels; // must have the lifetime of rtpmap
     265          517 :         std::string enc_name;
     266              :         unsigned payload;
     267              : 
     268          517 :         if (type == MediaType::MEDIA_AUDIO) {
     269          303 :             auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(audio_codec_list_[i]);
     270          303 :             payload = accountAudioCodec->payloadType;
     271          303 :             enc_name = accountAudioCodec->name;
     272              : 
     273          303 :             if (accountAudioCodec->audioformat.nb_channels > 1) {
     274          128 :                 channels = std::to_string(accountAudioCodec->audioformat.nb_channels);
     275          128 :                 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          303 :             if (accountAudioCodec->isPCMG722())
     280           25 :                 rtpmap.clock_rate = 8000;
     281              :             else
     282          278 :                 rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate;
     283              : 
     284          303 :         } else {
     285              :             // FIXME: get this key from header
     286          214 :             payload = dynamic_payload++;
     287          214 :             enc_name = video_codec_list_[i]->name;
     288          214 :             rtpmap.clock_rate = 90000;
     289              :         }
     290              : 
     291          517 :         auto payloadStr = std::to_string(payload);
     292          517 :         auto pjPayload = sip_utils::CONST_PJ_STR(payloadStr);
     293          517 :         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          517 :         rtpmap.pt = med->desc.fmt[i];
     299          517 :         rtpmap.enc_name = sip_utils::CONST_PJ_STR(enc_name);
     300              : 
     301              :         pjmedia_sdp_attr* attr;
     302          517 :         pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr);
     303          517 :         med->attr[med->attr_count++] = attr;
     304              : 
     305              : #ifdef ENABLE_VIDEO
     306          517 :         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           96 :             const auto accountVideoCodec = std::static_pointer_cast<SystemVideoCodecInfo>(video_codec_list_[i]);
     310           96 :             const auto& profileLevelID = accountVideoCodec->parameters.empty()
     311           96 :                                              ? libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID
     312          192 :                                              : accountVideoCodec->parameters;
     313           96 :             auto value = fmt::format("fmtp:{} {}", payload, profileLevelID);
     314           96 :             med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), value.c_str(), NULL);
     315           96 :         }
     316              : #endif
     317          517 :     }
     318              : 
     319          224 :     if (type == MediaType::MEDIA_AUDIO) {
     320          128 :         setTelephoneEventRtpmap(med);
     321          128 :         if (localAudioRtcpPort_) {
     322          128 :             addRTCPAttribute(med, localAudioRtcpPort_);
     323              :         }
     324           96 :     } else if (type == MediaType::MEDIA_VIDEO and localVideoRtcpPort_) {
     325           96 :         addRTCPAttribute(med, localVideoRtcpPort_);
     326              :     }
     327              : 
     328          224 :     char const* direction = mediaDirection(mediaAttr);
     329              : 
     330          224 :     med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), direction, NULL);
     331              : 
     332          224 :     if (secure) {
     333          223 :         if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS)
     334            0 :             throw SdpException("Unable to add sdes attribute to media");
     335              :     }
     336              : 
     337          224 :     return med;
     338              : }
     339              : 
     340              : void
     341          224 : Sdp::addRTCPAttribute(pjmedia_sdp_media* med, uint16_t port)
     342              : {
     343          224 :     dhtnet::IpAddr addr {publishedIpAddr_};
     344          224 :     addr.setPort(port);
     345          224 :     pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), addr.pjPtr());
     346          224 :     if (attr)
     347          224 :         pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
     348          224 : }
     349              : 
     350              : void
     351           98 : Sdp::setPublishedIP(const std::string& addr, pj_uint16_t addr_type)
     352              : {
     353           98 :     publishedIpAddr_ = addr;
     354           98 :     publishedIpAddrType_ = addr_type;
     355           98 :     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_ERROR("Unable to validate SDP");
     364              :     }
     365           98 : }
     366              : 
     367              : void
     368           98 : Sdp::setPublishedIP(const dhtnet::IpAddr& ip_addr)
     369              : {
     370           98 :     setPublishedIP(ip_addr, ip_addr.getFamily());
     371           98 : }
     372              : 
     373              : void
     374          128 : Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media* med)
     375              : {
     376          128 :     ++med->desc.fmt_count;
     377          128 :     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          128 :         pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
     381          128 :     attr_rtpmap->name = sip_utils::CONST_PJ_STR("rtpmap");
     382          128 :     attr_rtpmap->value = sip_utils::CONST_PJ_STR("101 telephone-event/8000");
     383              : 
     384          128 :     med->attr[med->attr_count++] = attr_rtpmap;
     385              : 
     386              :     pjmedia_sdp_attr* attr_fmtp = static_cast<pjmedia_sdp_attr*>(
     387          128 :         pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
     388          128 :     attr_fmtp->name = sip_utils::CONST_PJ_STR("fmtp");
     389          128 :     attr_fmtp->value = sip_utils::CONST_PJ_STR("101 0-15");
     390              : 
     391          128 :     med->attr[med->attr_count++] = attr_fmtp;
     392          128 : }
     393              : 
     394              : void
     395          326 : Sdp::setLocalMediaCapabilities(MediaType type, const std::vector<std::shared_ptr<SystemCodecInfo>>& selectedCodecs)
     396              : {
     397          326 :     switch (type) {
     398          163 :     case MediaType::MEDIA_AUDIO:
     399          163 :         audio_codec_list_ = selectedCodecs;
     400          163 :         break;
     401              : 
     402          163 :     case MediaType::MEDIA_VIDEO:
     403              : #ifdef ENABLE_VIDEO
     404          163 :         video_codec_list_ = selectedCodecs;
     405              :         // Do not expose H265 if accel is disactivated
     406          163 :         if (not jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
     407          163 :             video_codec_list_.erase(std::remove_if(video_codec_list_.begin(),
     408              :                                                    video_codec_list_.end(),
     409          366 :                                                    [](const std::shared_ptr<SystemCodecInfo>& i) {
     410          366 :                                                        return i->name == "H265";
     411              :                                                    }),
     412          326 :                                     video_codec_list_.end());
     413              :         }
     414              : #else
     415              :         (void) selectedCodecs;
     416              : #endif
     417          163 :         break;
     418              : 
     419            0 :     default:
     420            0 :         throw SdpException("Unsupported media type");
     421              :         break;
     422              :     }
     423          326 : }
     424              : 
     425              : constexpr std::string_view
     426          469 : Sdp::getSdpDirectionStr(SdpDirection direction)
     427              : {
     428          469 :     if (direction == SdpDirection::OFFER)
     429          257 :         return "OFFER"sv;
     430          212 :     if (direction == SdpDirection::ANSWER)
     431          212 :         return "ANSWER"sv;
     432            0 :     return "NONE"sv;
     433              : }
     434              : 
     435              : void
     436          469 : Sdp::printSession(const pjmedia_sdp_session* session, const char* header, SdpDirection direction)
     437              : {
     438              :     static constexpr size_t BUF_SZ = 4095;
     439          469 :     sip_utils::PoolPtr tmpPool_(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
     440              :                                                "printSdp",
     441              :                                                BUF_SZ,
     442              :                                                BUF_SZ,
     443          469 :                                                nullptr));
     444              : 
     445          469 :     auto* cloned_session = pjmedia_sdp_session_clone(tmpPool_.get(), session);
     446          469 :     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         1323 :     for (unsigned i = 0; i < cloned_session->media_count; ++i) {
     453          854 :         pjmedia_sdp_media_remove_all_attr(cloned_session->media[i], "crypto");
     454              :     }
     455              : 
     456              :     std::array<char, BUF_SZ + 1> buffer;
     457          469 :     auto size = pjmedia_sdp_print(cloned_session, buffer.data(), BUF_SZ);
     458          469 :     if (size < 0) {
     459            0 :         JAMI_ERROR("SDP too big for dump: {}", header);
     460            0 :         return;
     461              :     }
     462              : 
     463          938 :     JAMI_LOG("[SDP {}] {}\n{:s}", getSdpDirectionStr(direction), header, std::string_view(buffer.data(), size));
     464          469 : }
     465              : 
     466              : void
     467          126 : Sdp::createLocalSession(SdpDirection direction)
     468              : {
     469          126 :     sdpDirection_ = direction;
     470          126 :     localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session);
     471          126 :     localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn);
     472              : 
     473              :     /* Initialize the fields of the struct */
     474          126 :     localSession_->origin.version = 0;
     475              :     pj_time_val tv;
     476          126 :     pj_gettimeofday(&tv);
     477              : 
     478          126 :     localSession_->origin.user = *pj_gethostname();
     479              : 
     480              :     // Use Network Time Protocol format timestamp to ensure uniqueness.
     481          126 :     localSession_->origin.id = tv.sec + 2208988800UL;
     482          126 :     localSession_->origin.net_type = sip_utils::CONST_PJ_STR("IN");
     483          126 :     if (publishedIpAddrType_ == pj_AF_INET6())
     484            0 :         localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP6");
     485              :     else
     486          126 :         localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP4");
     487          126 :     localSession_->origin.addr = sip_utils::CONST_PJ_STR(publishedIpAddr_);
     488              : 
     489              :     // Use the call IDs for s= line
     490          126 :     localSession_->name = sip_utils::CONST_PJ_STR(sessionName_);
     491              : 
     492          126 :     localSession_->conn->net_type = localSession_->origin.net_type;
     493          126 :     localSession_->conn->addr_type = localSession_->origin.addr_type;
     494          126 :     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          126 :     localSession_->time.start = 0;
     500          126 :     localSession_->time.stop = 0;
     501          126 : }
     502              : 
     503              : int
     504          252 : Sdp::validateSession() const
     505              : {
     506          252 :     return pjmedia_sdp_validate(localSession_);
     507              : }
     508              : 
     509              : bool
     510           68 : Sdp::createOffer(const std::vector<MediaAttribute>& mediaList)
     511              : {
     512           68 :     if (mediaList.size() >= PJMEDIA_MAX_SDP_MEDIA) {
     513            0 :         throw SdpException("Media list size exceeds SDP media maximum size");
     514              :     }
     515           68 :     JAMI_DEBUG("Creating SDP offer with {} media", mediaList.size());
     516              : 
     517           68 :     createLocalSession(SdpDirection::OFFER);
     518              : 
     519           68 :     if (validateSession() != PJ_SUCCESS) {
     520            0 :         JAMI_ERROR("Failed to create initial offer");
     521            0 :         return false;
     522              :     }
     523              : 
     524           68 :     localSession_->media_count = 0;
     525              : 
     526          190 :     for (auto const& media : mediaList) {
     527          122 :         if (media.enabled_) {
     528          122 :             localSession_->media[localSession_->media_count++] = addMediaDescription(media);
     529              :         }
     530              :     }
     531              : 
     532           68 :     if (validateSession() != PJ_SUCCESS) {
     533            0 :         JAMI_ERROR("Failed to add medias");
     534            0 :         return false;
     535              :     }
     536              : 
     537           68 :     if (pjmedia_sdp_neg_create_w_local_offer(memPool_.get(), localSession_, &negotiator_) != PJ_SUCCESS) {
     538            0 :         JAMI_ERROR("Failed to create an initial SDP negotiator");
     539            0 :         return false;
     540              :     }
     541              : 
     542           68 :     printSession(localSession_, "Local session (initial):", sdpDirection_);
     543              : 
     544           68 :     return true;
     545              : }
     546              : 
     547              : void
     548           68 : Sdp::setReceivedOffer(const pjmedia_sdp_session* remote)
     549              : {
     550           68 :     if (remote == nullptr) {
     551            0 :         JAMI_ERROR("Remote session is NULL");
     552            0 :         return;
     553              :     }
     554           68 :     remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote);
     555              : }
     556              : 
     557              : bool
     558           58 : Sdp::processIncomingOffer(const std::vector<MediaAttribute>& mediaList)
     559              : {
     560           58 :     if (not remoteSession_)
     561            0 :         return false;
     562              : 
     563           58 :     JAMI_DEBUG("Processing received offer for [{:s}] with {:d} media", sessionName_, mediaList.size());
     564              : 
     565           58 :     printSession(remoteSession_, "Remote session:", SdpDirection::OFFER);
     566              : 
     567           58 :     createLocalSession(SdpDirection::ANSWER);
     568           58 :     if (validateSession() != PJ_SUCCESS) {
     569            0 :         JAMI_ERROR("Failed to create local session");
     570            0 :         return false;
     571              :     }
     572              : 
     573           58 :     localSession_->media_count = 0;
     574              : 
     575          163 :     for (auto const& media : mediaList) {
     576          105 :         if (media.enabled_) {
     577          102 :             localSession_->media[localSession_->media_count++] = addMediaDescription(media);
     578              :         }
     579              :     }
     580              : 
     581           58 :     printSession(localSession_, "Local session:\n", sdpDirection_);
     582              : 
     583           58 :     if (validateSession() != PJ_SUCCESS) {
     584            0 :         JAMI_ERROR("Failed to add medias");
     585            0 :         return false;
     586              :     }
     587              : 
     588           58 :     if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(), localSession_, remoteSession_, &negotiator_)
     589           58 :         != PJ_SUCCESS) {
     590            0 :         JAMI_ERROR("Failed to initialize media negotiation");
     591            0 :         return false;
     592              :     }
     593              : 
     594           58 :     return true;
     595              : }
     596              : 
     597              : bool
     598           19 : Sdp::startNegotiation()
     599              : {
     600           19 :     JAMI_LOG("Starting media negotiation for [{}]", sessionName_);
     601              : 
     602           19 :     if (negotiator_ == NULL) {
     603            0 :         JAMI_ERROR("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           19 :     if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
     611            0 :         JAMI_WARNING("Negotiator not in right state for negotiation");
     612            0 :         return false;
     613              :     }
     614              : 
     615           19 :     if (pjmedia_sdp_neg_negotiate(memPool_.get(), negotiator_, 0) != PJ_SUCCESS) {
     616            0 :         JAMI_ERROR("Failed to start media negotiation");
     617            0 :         return false;
     618              :     }
     619              : 
     620           19 :     if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
     621            0 :         JAMI_ERROR("Unable to retrieve local active session");
     622              : 
     623           19 :     setActiveLocalSdpSession(active_local);
     624              : 
     625           19 :     if (active_local != nullptr) {
     626           19 :         printSession(active_local, "Local active session:", sdpDirection_);
     627              :     }
     628              : 
     629           19 :     if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS or active_remote == nullptr) {
     630            0 :         JAMI_ERROR("Unable to retrieve remote active session");
     631            0 :         return false;
     632              :     }
     633              : 
     634           19 :     setActiveRemoteSdpSession(active_remote);
     635              : 
     636           19 :     printSession(active_remote, "Remote active session:", sdpDirection_);
     637              : 
     638           19 :     return true;
     639              : }
     640              : 
     641              : std::string
     642          222 : 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          222 :         pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory, "tmpSdp", BUF_SZ, BUF_SZ, nullptr));
     647          222 :     auto* cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session);
     648          222 :     if (!cloned) {
     649            0 :         JAMI_ERROR("Unable to clone SDP");
     650            0 :         return "";
     651              :     }
     652              : 
     653              :     // deactivate non-video media
     654          222 :     bool hasKeep = false;
     655          650 :     for (unsigned i = 0; i < cloned->media_count; i++)
     656          428 :         if (i != media_keep) {
     657          206 :             if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS)
     658            0 :                 JAMI_ERROR("Unable to deactivate media");
     659              :         } else {
     660          222 :             hasKeep = true;
     661              :         }
     662              : 
     663          222 :     if (not hasKeep) {
     664            0 :         JAMI_LOG("No media to keep present in SDP");
     665            0 :         return "";
     666              :     }
     667              : 
     668              :     // Leaking medias will be dropped with tmpPool_
     669          650 :     for (unsigned i = 0; i < cloned->media_count; i++)
     670          428 :         if (cloned->media[i]->desc.port == 0) {
     671          206 :             std::move(cloned->media + i + 1, cloned->media + cloned->media_count, cloned->media + i);
     672          206 :             cloned->media_count--;
     673          206 :             i--;
     674              :         }
     675              : 
     676          444 :     for (unsigned i = 0; i < cloned->media_count; i++) {
     677          222 :         auto* media = cloned->media[i];
     678              : 
     679              :         // filter other codecs
     680          572 :         for (unsigned c = 0; c < media->desc.fmt_count; c++) {
     681          350 :             auto& pt = media->desc.fmt[c];
     682          350 :             if (pj_strtoul(&pt) == pt_keep)
     683          222 :                 continue;
     684              : 
     685          256 :             while (auto* attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtpmap", &pt))
     686          128 :                 pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
     687              : 
     688          128 :             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          128 :             std::move(media->desc.fmt + c + 1, media->desc.fmt + media->desc.fmt_count, media->desc.fmt + c);
     692          128 :             media->desc.fmt_count--;
     693          128 :             c--;
     694              :         }
     695              : 
     696              :         // we handle crypto ourselfs, don't tell libav about it
     697          222 :         pjmedia_sdp_media_remove_all_attr(media, "crypto");
     698              :     }
     699              : 
     700              :     char buffer[BUF_SZ];
     701          222 :     size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer));
     702          222 :     string sessionStr(buffer, std::min(size, sizeof(buffer)));
     703              : 
     704          222 :     return sessionStr;
     705          222 : }
     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          228 : Sdp::getMediaDescriptions(const pjmedia_sdp_session* session, bool remote) const
     718              : {
     719          228 :     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          228 :     std::vector<MediaDescription> ret;
     725          644 :     for (unsigned i = 0; i < session->media_count; i++) {
     726          416 :         auto* media = session->media[i];
     727          416 :         ret.emplace_back(MediaDescription());
     728          416 :         MediaDescription& descr = ret.back();
     729          416 :         if (!pj_stricmp2(&media->desc.media, "audio"))
     730          236 :             descr.type = MEDIA_AUDIO;
     731          180 :         else if (!pj_stricmp2(&media->desc.media, "video"))
     732          180 :             descr.type = MEDIA_VIDEO;
     733              :         else
     734            0 :             continue;
     735              : 
     736          416 :         descr.enabled = media->desc.port;
     737          416 :         if (!descr.enabled)
     738            6 :             continue;
     739              : 
     740              :         // get connection info
     741          410 :         pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn;
     742          410 :         if (not conn) {
     743            0 :             JAMI_ERROR("Unable to find connection information for media");
     744            0 :             continue;
     745              :         }
     746          410 :         descr.addr = std::string_view(conn->addr.ptr, conn->addr.slen);
     747          410 :         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          410 :         auto* attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtcp", NULL);
     752          410 :         if (attr) {
     753              :             pjmedia_sdp_rtcp_attr rtcp;
     754          410 :             auto status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
     755          410 :             if (status == PJ_SUCCESS && rtcp.addr.slen) {
     756          410 :                 descr.rtcp_addr = std::string_view(rtcp.addr.ptr, rtcp.addr.slen);
     757          410 :                 descr.rtcp_addr.setPort(rtcp.port);
     758              :             }
     759              :         }
     760              : 
     761         1230 :         descr.hold = pjmedia_sdp_attr_find2(media->attr_count,
     762          410 :                                             media->attr,
     763          410 :                                             DIRECTION_STR[MediaDirection::SENDONLY],
     764              :                                             nullptr)
     765          804 :                      || pjmedia_sdp_attr_find2(media->attr_count,
     766          394 :                                                media->attr,
     767          394 :                                                DIRECTION_STR[MediaDirection::INACTIVE],
     768              :                                                nullptr);
     769              : 
     770          410 :         descr.direction_ = getMediaDirection(media);
     771              : 
     772              :         // get codecs infos
     773          410 :         for (unsigned j = 0; j < media->desc.fmt_count; j++) {
     774          410 :             auto* const rtpMapAttribute = pjmedia_sdp_media_find_attr(media, &STR_RTPMAP, &media->desc.fmt[j]);
     775          410 :             if (!rtpMapAttribute) {
     776            0 :                 JAMI_ERROR("Unable to find rtpmap attribute");
     777            0 :                 descr.enabled = false;
     778            0 :                 continue;
     779              :             }
     780              :             pjmedia_sdp_rtpmap rtpmap;
     781          410 :             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              :             }
     786          410 :             auto codec_raw = sip_utils::as_view(rtpmap.enc_name);
     787          410 :             descr.rtp_clockrate = rtpmap.clock_rate;
     788          410 :             descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate);
     789          410 :             if (not descr.codec) {
     790            0 :                 JAMI_ERROR("Unable to find codec {}", codec_raw);
     791            0 :                 descr.enabled = false;
     792            0 :                 continue;
     793              :             }
     794          410 :             descr.payload_type = pj_strtoul(&rtpmap.pt);
     795          410 :             if (descr.type == MEDIA_VIDEO) {
     796          174 :                 auto* const fmtpAttr = pjmedia_sdp_media_find_attr(media, &STR_FMTP, &media->desc.fmt[j]);
     797              :                 // descr.bitrate = getOutgoingVideoField(codec, "bitrate");
     798          174 :                 if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) {
     799           80 :                     const auto& v = fmtpAttr->value;
     800          160 :                     descr.parameters = std::string(v.ptr, v.ptr + v.slen);
     801              :                 }
     802              :             }
     803              :             // for now, just keep the first codec only
     804          410 :             descr.enabled = true;
     805          410 :             break;
     806              :         }
     807              : 
     808          410 :         if (not remote)
     809          222 :             descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type);
     810              : 
     811              :         // get crypto info
     812          410 :         std::vector<std::string> crypto;
     813         7194 :         for (unsigned j = 0; j < media->attr_count; j++) {
     814         6784 :             auto* const attribute = media->attr[j];
     815         6784 :             if (pj_stricmp2(&attribute->name, "crypto") == 0)
     816          410 :                 crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
     817              :         }
     818          410 :         descr.crypto = SdesNegotiator::negotiate(crypto);
     819          410 :     }
     820          228 :     return ret;
     821          228 : }
     822              : 
     823              : std::vector<Sdp::MediaSlot>
     824           97 : Sdp::getMediaSlots() const
     825              : {
     826           97 :     auto loc = getMediaDescriptions(activeLocalSession_, false);
     827           97 :     auto rem = getMediaDescriptions(activeRemoteSession_, true);
     828           97 :     size_t slot_n = std::min(loc.size(), rem.size());
     829           97 :     std::vector<MediaSlot> s;
     830           97 :     s.reserve(slot_n);
     831          272 :     for (decltype(slot_n) i = 0; i < slot_n; i++)
     832          175 :         s.emplace_back(std::move(loc[i]), std::move(rem[i]));
     833          194 :     return s;
     834           97 : }
     835              : 
     836              : void
     837          404 : Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
     838              : {
     839          404 :     if (media_index >= localSession_->media_count) {
     840            0 :         JAMI_ERROR("addIceCandidates failed: unable to access media#{} (may be deactivated)", media_index);
     841            0 :         return;
     842              :     }
     843              : 
     844          404 :     auto* media = localSession_->media[media_index];
     845              : 
     846         2808 :     for (const auto& item : cands) {
     847         2404 :         const pj_str_t val = sip_utils::CONST_PJ_STR(item);
     848         2404 :         pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val);
     849              : 
     850         2404 :         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          187 : Sdp::getIceCandidates(unsigned media_index) const
     857              : {
     858          187 :     const auto* remoteSession = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_;
     859          187 :     const auto* localSession = activeLocalSession_ ? activeLocalSession_ : localSession_;
     860          187 :     if (not remoteSession) {
     861            0 :         JAMI_ERROR("getIceCandidates failed: no remote session");
     862            0 :         return {};
     863              :     }
     864          187 :     if (not localSession) {
     865            0 :         JAMI_ERROR("getIceCandidates failed: no local session");
     866            0 :         return {};
     867              :     }
     868          187 :     if (media_index >= remoteSession->media_count || media_index >= localSession->media_count) {
     869            0 :         JAMI_ERROR("getIceCandidates failed: unable to access media#{} (may be deactivated)", media_index);
     870            0 :         return {};
     871              :     }
     872          187 :     auto* media = remoteSession->media[media_index];
     873          187 :     auto* localMedia = localSession->media[media_index];
     874          187 :     if (media->desc.port == 0 || localMedia->desc.port == 0) {
     875            5 :         JAMI_WARNING("Media#{} is disabled. Media ports: local {}, remote {}",
     876              :                      media_index,
     877              :                      localMedia->desc.port,
     878              :                      media->desc.port);
     879            5 :         return {};
     880              :     }
     881              : 
     882          182 :     std::vector<std::string> candidates;
     883              : 
     884         3444 :     for (unsigned i = 0; i < media->attr_count; i++) {
     885         3262 :         pjmedia_sdp_attr* attribute = media->attr[i];
     886         3262 :         if (pj_stricmp2(&attribute->name, "candidate") == 0)
     887         4280 :             candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
     888              :     }
     889              : 
     890          182 :     return candidates;
     891          182 : }
     892              : 
     893              : void
     894          112 : Sdp::addIceAttributes(const dhtnet::IceTransport::Attribute&& ice_attrs)
     895              : {
     896          112 :     pj_str_t value = sip_utils::CONST_PJ_STR(ice_attrs.ufrag);
     897          112 :     pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value);
     898              : 
     899          112 :     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          112 :     value = sip_utils::CONST_PJ_STR(ice_attrs.pwd);
     903          112 :     attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value);
     904              : 
     905          112 :     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          112 : }
     908              : 
     909              : dhtnet::IceTransport::Attribute
     910          266 : Sdp::getIceAttributes() const
     911              : {
     912          266 :     if (const auto* session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_)
     913          266 :         return getIceAttributes(session);
     914            0 :     return {};
     915            0 : }
     916              : 
     917              : dhtnet::IceTransport::Attribute
     918          266 : Sdp::getIceAttributes(const pjmedia_sdp_session* session)
     919              : {
     920          266 :     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          519 :     for (unsigned i = 0; i < session->attr_count; i++) {
     925          506 :         pjmedia_sdp_attr* attribute = session->attr[i];
     926          506 :         if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
     927          253 :             ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
     928          253 :         else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
     929          253 :             ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
     930          506 :         if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
     931          253 :             return ice_attrs;
     932              :     }
     933           36 :     for (unsigned i = 0; i < session->media_count; i++) {
     934           23 :         auto* media = session->media[i];
     935          177 :         for (unsigned j = 0; j < media->attr_count; j++) {
     936          154 :             pjmedia_sdp_attr* attribute = media->attr[j];
     937          154 :             if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
     938            0 :                 ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
     939          154 :             else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
     940            0 :                 ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
     941          154 :             if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
     942            0 :                 return ice_attrs;
     943              :         }
     944              :     }
     945              : 
     946           13 :     return ice_attrs;
     947            0 : }
     948              : 
     949              : void
     950           38 : Sdp::clearIce()
     951              : {
     952           38 :     clearIce(localSession_);
     953           38 :     clearIce(remoteSession_);
     954           38 :     setActiveRemoteSdpSession(nullptr);
     955           38 :     setActiveLocalSdpSession(nullptr);
     956           38 : }
     957              : 
     958              : void
     959           76 : Sdp::clearIce(pjmedia_sdp_session* session)
     960              : {
     961           76 :     if (not session)
     962           19 :         return;
     963           57 :     pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag");
     964           57 :     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           57 :     pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate");
     967          156 :     for (unsigned i = 0; i < session->media_count; i++) {
     968           99 :         auto* media = session->media[i];
     969           99 :         pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate");
     970              :     }
     971              : }
     972              : 
     973              : std::vector<MediaAttribute>
     974          156 : Sdp::getMediaAttributeListFromSdp(const pjmedia_sdp_session* sdpSession, bool ignoreDisabled)
     975              : {
     976          156 :     if (sdpSession == nullptr) {
     977            0 :         return {};
     978              :     }
     979              : 
     980          156 :     std::vector<MediaAttribute> mediaList;
     981          156 :     unsigned audioIdx = 0;
     982          156 :     unsigned videoIdx = 0;
     983          438 :     for (unsigned idx = 0; idx < sdpSession->media_count; idx++) {
     984          282 :         mediaList.emplace_back(MediaAttribute {});
     985          282 :         auto& mediaAttr = mediaList.back();
     986              : 
     987          282 :         auto const& media = sdpSession->media[idx];
     988              : 
     989              :         // Get media type.
     990          282 :         if (!pj_stricmp2(&media->desc.media, "audio"))
     991          161 :             mediaAttr.type_ = MediaType::MEDIA_AUDIO;
     992          121 :         else if (!pj_stricmp2(&media->desc.media, "video"))
     993          121 :             mediaAttr.type_ = MediaType::MEDIA_VIDEO;
     994              :         else {
     995            0 :             JAMI_WARNING("Media#{} 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          282 :         mediaAttr.enabled_ = media->desc.port > 0;
    1003              : 
    1004          282 :         if (!mediaAttr.enabled_ && ignoreDisabled) {
    1005            0 :             mediaList.pop_back();
    1006            0 :             continue;
    1007              :         }
    1008              : 
    1009              :         // Get mute state.
    1010          282 :         auto direction = getMediaDirection(media);
    1011          282 :         mediaAttr.muted_ = direction != MediaDirection::SENDRECV and direction != MediaDirection::SENDONLY;
    1012              : 
    1013              :         // Get transport.
    1014          282 :         auto transp = getMediaTransport(media);
    1015          282 :         if (transp == MediaTransport::UNKNOWN) {
    1016            0 :             JAMI_WARNING("Media#{} 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          282 :         mediaAttr.secure_ = transp == MediaTransport::RTP_SAVP and not getCrypto(media).empty();
    1022              : 
    1023          282 :         if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
    1024          161 :             mediaAttr.label_ = "audio_" + std::to_string(audioIdx++);
    1025          121 :         } else if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
    1026          121 :             mediaAttr.label_ = "video_" + std::to_string(videoIdx++);
    1027              :         }
    1028              :     }
    1029              : 
    1030          156 :     return mediaList;
    1031          156 : }
    1032              : 
    1033              : } // namespace jami
        

Generated by: LCOV version 2.0-1