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