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