Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 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 778 : Sdp::findCodecBySpec(std::string_view codec, const unsigned clockrate) const
82 : {
83 : // TODO : only manage a list?
84 1278 : for (const auto& accountCodec : audio_codec_list_) {
85 932 : auto audioCodecInfo = std::static_pointer_cast<SystemAudioCodecInfo>(accountCodec);
86 932 : if (audioCodecInfo->name == codec
87 1364 : and (audioCodecInfo->isPCMG722() ? (clockrate == 8000)
88 432 : : (audioCodecInfo->audioformat.sample_rate == clockrate)))
89 432 : return accountCodec;
90 932 : }
91 :
92 346 : for (const auto& accountCodec : video_codec_list_) {
93 346 : if (accountCodec->name == codec)
94 346 : 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 471 : randomFill(std::vector<uint8_t>& dest)
117 : {
118 471 : std::uniform_int_distribution<int> rand_byte {0, std::numeric_limits<uint8_t>::max()};
119 471 : std::random_device rdev;
120 471 : std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev)));
121 471 : }
122 :
123 : void
124 719 : Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp)
125 : {
126 719 : if (activeLocalSession_ != sdp)
127 522 : JAMI_DBG("Set active local session to [%p]. Was [%p]", sdp, activeLocalSession_);
128 719 : activeLocalSession_ = sdp;
129 719 : }
130 :
131 : void
132 719 : Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session* sdp)
133 : {
134 719 : if (activeLocalSession_ != sdp)
135 332 : JAMI_DBG("Set active remote session to [%p]. Was [%p]", sdp, activeRemoteSession_);
136 719 : activeRemoteSession_ = sdp;
137 719 : }
138 :
139 : pjmedia_sdp_attr*
140 471 : Sdp::generateSdesAttribute()
141 : {
142 : static constexpr const unsigned cryptoSuite = 0;
143 471 : std::vector<uint8_t> keyAndSalt;
144 471 : keyAndSalt.resize(jami::CryptoSuites[cryptoSuite].masterKeyLength / 8
145 471 : + jami::CryptoSuites[cryptoSuite].masterSaltLength / 8);
146 : // generate keys
147 471 : randomFill(keyAndSalt);
148 :
149 942 : std::string crypto_attr = "1 "s + jami::CryptoSuites[cryptoSuite].name
150 1413 : + " inline:" + base64::encode(keyAndSalt);
151 471 : pj_str_t val {sip_utils::CONST_PJ_STR(crypto_attr)};
152 942 : return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val);
153 471 : }
154 :
155 : char const*
156 472 : Sdp::mediaDirection(const MediaAttribute& mediaAttr)
157 : {
158 472 : 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 472 : if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
166 263 : return DIRECTION_STR[MediaDirection::SENDRECV];
167 : }
168 :
169 209 : 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 198 : if (mediaAttr.onHold_) {
177 9 : return DIRECTION_STR[MediaDirection::SENDONLY];
178 : }
179 :
180 189 : return DIRECTION_STR[MediaDirection::SENDRECV];
181 : }
182 :
183 : MediaDirection
184 1405 : Sdp::getMediaDirection(pjmedia_sdp_media* media)
185 : {
186 2810 : if (pjmedia_sdp_attr_find2(media->attr_count,
187 1405 : media->attr,
188 1405 : DIRECTION_STR[MediaDirection::SENDRECV],
189 : nullptr)
190 1405 : != nullptr) {
191 1328 : return MediaDirection::SENDRECV;
192 : }
193 :
194 154 : if (pjmedia_sdp_attr_find2(media->attr_count,
195 77 : media->attr,
196 77 : DIRECTION_STR[MediaDirection::SENDONLY],
197 : nullptr)
198 77 : != nullptr) {
199 33 : return MediaDirection::SENDONLY;
200 : }
201 :
202 88 : if (pjmedia_sdp_attr_find2(media->attr_count,
203 44 : media->attr,
204 44 : DIRECTION_STR[MediaDirection::RECVONLY],
205 : nullptr)
206 44 : != nullptr) {
207 38 : return MediaDirection::RECVONLY;
208 : }
209 :
210 12 : if (pjmedia_sdp_attr_find2(media->attr_count,
211 6 : media->attr,
212 6 : DIRECTION_STR[MediaDirection::INACTIVE],
213 : nullptr)
214 6 : != nullptr) {
215 4 : return MediaDirection::INACTIVE;
216 : }
217 :
218 2 : return MediaDirection::UNKNOWN;
219 : }
220 :
221 : MediaTransport
222 627 : Sdp::getMediaTransport(pjmedia_sdp_media* media)
223 : {
224 627 : if (pj_stricmp2(&media->desc.transport, "RTP/SAVP") == 0)
225 626 : 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 626 : Sdp::getCrypto(pjmedia_sdp_media* media)
234 : {
235 626 : std::vector<std::string> crypto;
236 9484 : for (unsigned j = 0; j < media->attr_count; j++) {
237 8858 : const auto attribute = media->attr[j];
238 8858 : if (pj_stricmp2(&attribute->name, "crypto") == 0)
239 624 : crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
240 : }
241 :
242 626 : return crypto;
243 0 : }
244 :
245 : pjmedia_sdp_media*
246 472 : Sdp::addMediaDescription(const MediaAttribute& mediaAttr)
247 : {
248 472 : auto type = mediaAttr.type_;
249 472 : auto secure = mediaAttr.secure_;
250 :
251 472 : JAMI_DBG("Add media description [%s]", mediaAttr.toString(true).c_str());
252 :
253 472 : pjmedia_sdp_media* med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media);
254 :
255 472 : switch (type) {
256 263 : case MediaType::MEDIA_AUDIO:
257 263 : med->desc.media = sip_utils::CONST_PJ_STR("audio");
258 263 : med->desc.port = mediaAttr.enabled_ ? localAudioRtpPort_ : 0;
259 263 : med->desc.fmt_count = audio_codec_list_.size();
260 263 : break;
261 209 : case MediaType::MEDIA_VIDEO:
262 209 : med->desc.media = sip_utils::CONST_PJ_STR("video");
263 209 : med->desc.port = mediaAttr.enabled_ ? localVideoRtpPort_ : 0;
264 209 : med->desc.fmt_count = video_codec_list_.size();
265 209 : break;
266 0 : default:
267 0 : throw SdpException("Unsupported media type! Only audio and video are supported");
268 : break;
269 : }
270 :
271 472 : med->desc.port_count = 1;
272 :
273 : // Set the transport protocol of the media
274 472 : med->desc.transport = secure ? sip_utils::CONST_PJ_STR("RTP/SAVP")
275 1 : : sip_utils::CONST_PJ_STR("RTP/AVP");
276 :
277 472 : unsigned dynamic_payload = 96;
278 :
279 1350 : for (unsigned i = 0; i < med->desc.fmt_count; i++) {
280 : pjmedia_sdp_rtpmap rtpmap;
281 878 : rtpmap.param.slen = 0;
282 :
283 878 : std::string channels; // must have the lifetime of rtpmap
284 878 : std::string enc_name;
285 : unsigned payload;
286 :
287 878 : if (type == MediaType::MEDIA_AUDIO) {
288 : auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(
289 438 : audio_codec_list_[i]);
290 438 : payload = accountAudioCodec->payloadType;
291 438 : enc_name = accountAudioCodec->name;
292 :
293 438 : if (accountAudioCodec->audioformat.nb_channels > 1) {
294 263 : channels = std::to_string(accountAudioCodec->audioformat.nb_channels);
295 263 : 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 438 : if (accountAudioCodec->isPCMG722())
300 25 : rtpmap.clock_rate = 8000;
301 : else
302 413 : rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate;
303 :
304 438 : } else {
305 : // FIXME: get this key from header
306 440 : payload = dynamic_payload++;
307 440 : enc_name = video_codec_list_[i]->name;
308 440 : rtpmap.clock_rate = 90000;
309 : }
310 :
311 878 : auto payloadStr = std::to_string(payload);
312 878 : auto pjPayload = sip_utils::CONST_PJ_STR(payloadStr);
313 878 : 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 878 : rtpmap.pt = med->desc.fmt[i];
319 878 : rtpmap.enc_name = sip_utils::CONST_PJ_STR(enc_name);
320 :
321 : pjmedia_sdp_attr* attr;
322 878 : pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr);
323 878 : med->attr[med->attr_count++] = attr;
324 :
325 : #ifdef ENABLE_VIDEO
326 878 : 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 209 : video_codec_list_[i]);
331 209 : const auto& profileLevelID = accountVideoCodec->parameters.empty()
332 : ? libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID
333 209 : : accountVideoCodec->parameters;
334 0 : auto value = fmt::format("fmtp:{} {}", payload, profileLevelID);
335 209 : med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(),
336 : value.c_str(),
337 : NULL);
338 209 : }
339 : #endif
340 878 : }
341 :
342 472 : if (type == MediaType::MEDIA_AUDIO) {
343 263 : setTelephoneEventRtpmap(med);
344 263 : if (localAudioRtcpPort_) {
345 263 : addRTCPAttribute(med, localAudioRtcpPort_);
346 : }
347 209 : } else if (type == MediaType::MEDIA_VIDEO and localVideoRtcpPort_) {
348 209 : addRTCPAttribute(med, localVideoRtcpPort_);
349 : }
350 :
351 472 : char const* direction = mediaDirection(mediaAttr);
352 :
353 472 : med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), direction, NULL);
354 :
355 472 : if (secure) {
356 471 : if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS)
357 0 : throw SdpException("Unable to add sdes attribute to media");
358 : }
359 :
360 472 : return med;
361 : }
362 :
363 : void
364 472 : Sdp::addRTCPAttribute(pjmedia_sdp_media* med, uint16_t port)
365 : {
366 472 : dhtnet::IpAddr addr {publishedIpAddr_};
367 472 : addr.setPort(port);
368 472 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), addr.pjPtr());
369 472 : if (attr)
370 472 : pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
371 472 : }
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 263 : Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media* med)
398 : {
399 263 : ++med->desc.fmt_count;
400 526 : pj_strdup2(memPool_.get(),
401 263 : &med->desc.fmt[med->desc.fmt_count - 1],
402 526 : std::to_string(telephoneEventPayload_).c_str());
403 :
404 : pjmedia_sdp_attr* attr_rtpmap = static_cast<pjmedia_sdp_attr*>(
405 263 : pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
406 263 : attr_rtpmap->name = sip_utils::CONST_PJ_STR("rtpmap");
407 263 : attr_rtpmap->value = sip_utils::CONST_PJ_STR("101 telephone-event/8000");
408 :
409 263 : med->attr[med->attr_count++] = attr_rtpmap;
410 :
411 : pjmedia_sdp_attr* attr_fmtp = static_cast<pjmedia_sdp_attr*>(
412 263 : pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
413 263 : attr_fmtp->name = sip_utils::CONST_PJ_STR("fmtp");
414 263 : attr_fmtp->value = sip_utils::CONST_PJ_STR("101 0-15");
415 :
416 263 : med->attr[med->attr_count++] = attr_fmtp;
417 263 : }
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 964 : Sdp::getSdpDirectionStr(SdpDirection direction)
453 : {
454 964 : if (direction == SdpDirection::OFFER)
455 533 : return "OFFER"sv;
456 431 : if (direction == SdpDirection::ANSWER)
457 431 : return "ANSWER"sv;
458 0 : return "NONE"sv;
459 : }
460 :
461 : void
462 964 : Sdp::printSession(const pjmedia_sdp_session* session, const char* header, SdpDirection direction)
463 : {
464 : static constexpr size_t BUF_SZ = 4095;
465 : std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>
466 964 : tmpPool_(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
467 : "printSdp",
468 : BUF_SZ,
469 : BUF_SZ,
470 : nullptr),
471 964 : pj_pool_release);
472 :
473 964 : auto cloned_session = pjmedia_sdp_session_clone(tmpPool_.get(), session);
474 964 : if (!cloned_session) {
475 0 : JAMI_ERROR("Unable to clone SDP for printing");
476 0 : return;
477 : }
478 :
479 : // Filter-out sensible data like SRTP master key.
480 2739 : for (unsigned i = 0; i < cloned_session->media_count; ++i) {
481 1775 : pjmedia_sdp_media_remove_all_attr(cloned_session->media[i], "crypto");
482 : }
483 :
484 : std::array<char, BUF_SZ + 1> buffer;
485 964 : auto size = pjmedia_sdp_print(cloned_session, buffer.data(), BUF_SZ);
486 964 : if (size < 0) {
487 0 : JAMI_ERROR("SDP too big for dump: {}", header);
488 0 : return;
489 : }
490 :
491 2892 : JAMI_LOG("[SDP {}] {}\n{:s}", getSdpDirectionStr(direction), header,
492 : std::string_view(buffer.data(), size));
493 964 : }
494 :
495 : void
496 261 : Sdp::createLocalSession(SdpDirection direction)
497 : {
498 261 : sdpDirection_ = direction;
499 261 : localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session);
500 261 : localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn);
501 :
502 : /* Initialize the fields of the struct */
503 261 : localSession_->origin.version = 0;
504 : pj_time_val tv;
505 261 : pj_gettimeofday(&tv);
506 :
507 261 : localSession_->origin.user = *pj_gethostname();
508 :
509 : // Use Network Time Protocol format timestamp to ensure uniqueness.
510 261 : localSession_->origin.id = tv.sec + 2208988800UL;
511 261 : localSession_->origin.net_type = sip_utils::CONST_PJ_STR("IN");
512 261 : if (publishedIpAddrType_ == pj_AF_INET6())
513 0 : localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP6");
514 : else
515 261 : localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP4");
516 261 : localSession_->origin.addr = sip_utils::CONST_PJ_STR(publishedIpAddr_);
517 :
518 : // Use the call IDs for s= line
519 261 : localSession_->name = sip_utils::CONST_PJ_STR(sessionName_);
520 :
521 261 : localSession_->conn->net_type = localSession_->origin.net_type;
522 261 : localSession_->conn->addr_type = localSession_->origin.addr_type;
523 261 : localSession_->conn->addr = localSession_->origin.addr;
524 :
525 : // RFC 3264: An offer/answer model session description protocol
526 : // As the session is created and destroyed through an external signaling mean (SIP), the line
527 : // should have a value of "0 0".
528 261 : localSession_->time.start = 0;
529 261 : localSession_->time.stop = 0;
530 261 : }
531 :
532 : int
533 522 : Sdp::validateSession() const
534 : {
535 522 : return pjmedia_sdp_validate(localSession_);
536 : }
537 :
538 : bool
539 136 : Sdp::createOffer(const std::vector<MediaAttribute>& mediaList)
540 : {
541 136 : if (mediaList.size() >= PJMEDIA_MAX_SDP_MEDIA) {
542 0 : throw SdpException("Media list size exceeds SDP media maximum size");
543 : }
544 408 : JAMI_DEBUG("Creating SDP offer with {} media", mediaList.size());
545 :
546 136 : createLocalSession(SdpDirection::OFFER);
547 :
548 136 : if (validateSession() != PJ_SUCCESS) {
549 0 : JAMI_ERR("Failed to create initial offer");
550 0 : return false;
551 : }
552 :
553 136 : localSession_->media_count = 0;
554 :
555 383 : for (auto const& media : mediaList) {
556 247 : if (media.enabled_) {
557 247 : localSession_->media[localSession_->media_count++] = addMediaDescription(media);
558 : }
559 : }
560 :
561 136 : if (validateSession() != PJ_SUCCESS) {
562 0 : JAMI_ERR("Failed to add medias");
563 0 : return false;
564 : }
565 :
566 136 : if (pjmedia_sdp_neg_create_w_local_offer(memPool_.get(), localSession_, &negotiator_)
567 136 : != PJ_SUCCESS) {
568 0 : JAMI_ERR("Failed to create an initial SDP negotiator");
569 0 : return false;
570 : }
571 :
572 136 : printSession(localSession_, "Local session (initial):", sdpDirection_);
573 :
574 136 : return true;
575 : }
576 :
577 : void
578 135 : Sdp::setReceivedOffer(const pjmedia_sdp_session* remote)
579 : {
580 135 : if (remote == nullptr) {
581 0 : JAMI_ERR("Remote session is NULL");
582 0 : return;
583 : }
584 135 : remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote);
585 : }
586 :
587 : bool
588 125 : Sdp::processIncomingOffer(const std::vector<MediaAttribute>& mediaList)
589 : {
590 125 : if (not remoteSession_)
591 0 : return false;
592 :
593 375 : JAMI_DEBUG("Processing received offer for [{:s}] with {:d} media",
594 : sessionName_,
595 : mediaList.size());
596 :
597 125 : printSession(remoteSession_, "Remote session:", SdpDirection::OFFER);
598 :
599 125 : createLocalSession(SdpDirection::ANSWER);
600 125 : if (validateSession() != PJ_SUCCESS) {
601 0 : JAMI_ERR("Failed to create local session");
602 0 : return false;
603 : }
604 :
605 125 : localSession_->media_count = 0;
606 :
607 353 : for (auto const& media : mediaList) {
608 228 : if (media.enabled_) {
609 225 : localSession_->media[localSession_->media_count++] = addMediaDescription(media);
610 : }
611 : }
612 :
613 125 : printSession(localSession_, "Local session:\n", sdpDirection_);
614 :
615 125 : if (validateSession() != PJ_SUCCESS) {
616 0 : JAMI_ERR("Failed to add medias");
617 0 : return false;
618 : }
619 :
620 125 : if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(),
621 125 : localSession_,
622 125 : remoteSession_,
623 : &negotiator_)
624 125 : != PJ_SUCCESS) {
625 0 : JAMI_ERR("Failed to initialize media negotiation");
626 0 : return false;
627 : }
628 :
629 125 : return true;
630 : }
631 :
632 : bool
633 28 : Sdp::startNegotiation()
634 : {
635 28 : JAMI_DBG("Starting media negotiation for [%s]", sessionName_.c_str());
636 :
637 28 : if (negotiator_ == NULL) {
638 0 : JAMI_ERR("Unable to start negotiation with invalid negotiator");
639 0 : return false;
640 : }
641 :
642 : const pjmedia_sdp_session* active_local;
643 : const pjmedia_sdp_session* active_remote;
644 :
645 28 : if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
646 0 : JAMI_WARN("Negotiator not in right state for negotiation");
647 0 : return false;
648 : }
649 :
650 28 : if (pjmedia_sdp_neg_negotiate(memPool_.get(), negotiator_, 0) != PJ_SUCCESS) {
651 0 : JAMI_ERR("Failed to start media negotiation");
652 0 : return false;
653 : }
654 :
655 28 : if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
656 0 : JAMI_ERR("Unable to retrieve local active session");
657 :
658 28 : setActiveLocalSdpSession(active_local);
659 :
660 28 : if (active_local != nullptr) {
661 28 : printSession(active_local, "Local active session:", sdpDirection_);
662 : }
663 :
664 28 : if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS
665 28 : or active_remote == nullptr) {
666 0 : JAMI_ERR("Unable to retrieve remote active session");
667 0 : return false;
668 : }
669 :
670 28 : setActiveRemoteSdpSession(active_remote);
671 :
672 28 : printSession(active_remote, "Remote active session:", sdpDirection_);
673 :
674 28 : return true;
675 : }
676 :
677 : std::string
678 406 : Sdp::getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep)
679 : {
680 : static constexpr size_t BUF_SZ = 4096;
681 : std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>
682 406 : tmpPool_(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
683 : "tmpSdp",
684 : BUF_SZ,
685 : BUF_SZ,
686 : nullptr),
687 406 : pj_pool_release);
688 406 : auto cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session);
689 406 : if (!cloned) {
690 0 : JAMI_ERR("Unable to clone SDP");
691 0 : return "";
692 : }
693 :
694 : // deactivate non-video media
695 406 : bool hasKeep = false;
696 1194 : for (unsigned i = 0; i < cloned->media_count; i++)
697 788 : if (i != media_keep) {
698 382 : if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS)
699 0 : JAMI_ERR("Unable to deactivate media");
700 : } else {
701 406 : hasKeep = true;
702 : }
703 :
704 406 : if (not hasKeep) {
705 0 : JAMI_DBG("No media to keep present in SDP");
706 0 : return "";
707 : }
708 :
709 : // Leaking medias will be dropped with tmpPool_
710 1194 : for (unsigned i = 0; i < cloned->media_count; i++)
711 788 : if (cloned->media[i]->desc.port == 0) {
712 382 : std::move(cloned->media + i + 1, cloned->media + cloned->media_count, cloned->media + i);
713 382 : cloned->media_count--;
714 382 : i--;
715 : }
716 :
717 812 : for (unsigned i = 0; i < cloned->media_count; i++) {
718 406 : auto media = cloned->media[i];
719 :
720 : // filter other codecs
721 1038 : for (unsigned c = 0; c < media->desc.fmt_count; c++) {
722 632 : auto& pt = media->desc.fmt[c];
723 632 : if (pj_strtoul(&pt) == pt_keep)
724 406 : continue;
725 :
726 452 : while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtpmap", &pt))
727 226 : pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
728 :
729 226 : while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "fmt", &pt))
730 0 : pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
731 :
732 226 : std::move(media->desc.fmt + c + 1,
733 226 : media->desc.fmt + media->desc.fmt_count,
734 226 : media->desc.fmt + c);
735 226 : media->desc.fmt_count--;
736 226 : c--;
737 : }
738 :
739 : // we handle crypto ourselfs, don't tell libav about it
740 406 : pjmedia_sdp_media_remove_all_attr(media, "crypto");
741 : }
742 :
743 : char buffer[BUF_SZ];
744 406 : size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer));
745 406 : string sessionStr(buffer, std::min(size, sizeof(buffer)));
746 :
747 406 : return sessionStr;
748 406 : }
749 :
750 : std::vector<MediaDescription>
751 34 : Sdp::getActiveMediaDescription(bool remote) const
752 : {
753 34 : if (remote)
754 7 : return getMediaDescriptions(activeRemoteSession_, true);
755 :
756 27 : return getMediaDescriptions(activeLocalSession_, false);
757 : }
758 :
759 : std::vector<MediaDescription>
760 424 : Sdp::getMediaDescriptions(const pjmedia_sdp_session* session, bool remote) const
761 : {
762 424 : if (!session)
763 0 : return {};
764 : static constexpr pj_str_t STR_RTPMAP {sip_utils::CONST_PJ_STR("rtpmap")};
765 : static constexpr pj_str_t STR_FMTP {sip_utils::CONST_PJ_STR("fmtp")};
766 :
767 424 : std::vector<MediaDescription> ret;
768 1208 : for (unsigned i = 0; i < session->media_count; i++) {
769 784 : auto media = session->media[i];
770 784 : ret.emplace_back(MediaDescription());
771 784 : MediaDescription& descr = ret.back();
772 784 : if (!pj_stricmp2(&media->desc.media, "audio"))
773 432 : descr.type = MEDIA_AUDIO;
774 352 : else if (!pj_stricmp2(&media->desc.media, "video"))
775 352 : descr.type = MEDIA_VIDEO;
776 : else
777 6 : continue;
778 :
779 784 : descr.enabled = media->desc.port;
780 784 : if (!descr.enabled)
781 6 : continue;
782 :
783 : // get connection info
784 778 : pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn;
785 778 : if (not conn) {
786 0 : JAMI_ERR("Unable to find connection information for media");
787 0 : continue;
788 : }
789 778 : descr.addr = std::string_view(conn->addr.ptr, conn->addr.slen);
790 778 : descr.addr.setPort(media->desc.port);
791 :
792 : // Get the "rtcp" address from the SDP if present. Otherwise,
793 : // infere it from endpoint (RTP) address.
794 778 : auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtcp", NULL);
795 778 : if (attr) {
796 : pjmedia_sdp_rtcp_attr rtcp;
797 778 : auto status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
798 778 : if (status == PJ_SUCCESS && rtcp.addr.slen) {
799 778 : descr.rtcp_addr = std::string_view(rtcp.addr.ptr, rtcp.addr.slen);
800 778 : descr.rtcp_addr.setPort(rtcp.port);
801 : }
802 : }
803 :
804 2334 : descr.onHold = pjmedia_sdp_attr_find2(media->attr_count,
805 778 : media->attr,
806 778 : DIRECTION_STR[MediaDirection::SENDONLY],
807 : nullptr)
808 1540 : || pjmedia_sdp_attr_find2(media->attr_count,
809 762 : media->attr,
810 762 : DIRECTION_STR[MediaDirection::INACTIVE],
811 : nullptr);
812 :
813 778 : descr.direction_ = getMediaDirection(media);
814 778 : if (descr.direction_ == MediaDirection::UNKNOWN) {
815 0 : JAMI_ERR("Did not find media direction attribute in remote SDP");
816 : }
817 :
818 : // get codecs infos
819 778 : for (unsigned j = 0; j < media->desc.fmt_count; j++) {
820 1556 : const auto rtpMapAttribute = pjmedia_sdp_media_find_attr(media,
821 : &STR_RTPMAP,
822 778 : &media->desc.fmt[j]);
823 778 : if (!rtpMapAttribute) {
824 0 : JAMI_ERR("Unable to find rtpmap attribute");
825 0 : descr.enabled = false;
826 0 : continue;
827 : }
828 : pjmedia_sdp_rtpmap rtpmap;
829 778 : if (pjmedia_sdp_attr_get_rtpmap(rtpMapAttribute, &rtpmap) != PJ_SUCCESS
830 778 : || rtpmap.enc_name.slen == 0) {
831 0 : JAMI_ERROR("Unable to find payload type {} in SDP",
832 : sip_utils::as_view(media->desc.fmt[j]));
833 0 : descr.enabled = false;
834 0 : continue;
835 0 : }
836 778 : auto codec_raw = sip_utils::as_view(rtpmap.enc_name);
837 778 : descr.rtp_clockrate = rtpmap.clock_rate;
838 778 : descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate);
839 778 : if (not descr.codec) {
840 0 : JAMI_ERROR("Unable to find codec {}", codec_raw);
841 0 : descr.enabled = false;
842 0 : continue;
843 0 : }
844 778 : descr.payload_type = pj_strtoul(&rtpmap.pt);
845 778 : if (descr.type == MEDIA_VIDEO) {
846 692 : const auto fmtpAttr = pjmedia_sdp_media_find_attr(media,
847 : &STR_FMTP,
848 346 : &media->desc.fmt[j]);
849 : // descr.bitrate = getOutgoingVideoField(codec, "bitrate");
850 346 : if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) {
851 166 : const auto& v = fmtpAttr->value;
852 166 : descr.parameters = std::string(v.ptr, v.ptr + v.slen);
853 : }
854 : }
855 : // for now, just keep the first codec only
856 778 : descr.enabled = true;
857 778 : break;
858 : }
859 :
860 778 : if (not remote)
861 406 : descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type);
862 :
863 : // get crypto info
864 778 : std::vector<std::string> crypto;
865 12068 : for (unsigned j = 0; j < media->attr_count; j++) {
866 11290 : const auto attribute = media->attr[j];
867 11290 : if (pj_stricmp2(&attribute->name, "crypto") == 0)
868 778 : crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
869 : }
870 778 : descr.crypto = SdesNegotiator::negotiate(crypto);
871 778 : }
872 424 : return ret;
873 424 : }
874 :
875 : std::vector<Sdp::MediaSlot>
876 195 : Sdp::getMediaSlots() const
877 : {
878 195 : auto loc = getMediaDescriptions(activeLocalSession_, false);
879 195 : auto rem = getMediaDescriptions(activeRemoteSession_, true);
880 195 : size_t slot_n = std::min(loc.size(), rem.size());
881 195 : std::vector<MediaSlot> s;
882 195 : s.reserve(slot_n);
883 554 : for (decltype(slot_n) i = 0; i < slot_n; i++)
884 359 : s.emplace_back(std::move(loc[i]), std::move(rem[i]));
885 390 : return s;
886 195 : }
887 :
888 : void
889 828 : Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
890 : {
891 828 : if (media_index >= localSession_->media_count) {
892 0 : JAMI_ERR("addIceCandidates failed: unable to access media#%u (may be deactivated)",
893 : media_index);
894 0 : return;
895 : }
896 :
897 828 : auto media = localSession_->media[media_index];
898 :
899 4704 : for (const auto& item : cands) {
900 3876 : const pj_str_t val = sip_utils::CONST_PJ_STR(item);
901 3876 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val);
902 :
903 3876 : if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
904 0 : throw SdpException("Unable to add ICE candidates attribute to media");
905 : }
906 : }
907 :
908 : std::vector<std::string>
909 393 : Sdp::getIceCandidates(unsigned media_index) const
910 : {
911 393 : auto remoteSession = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_;
912 393 : auto localSession = activeLocalSession_ ? activeLocalSession_ : localSession_;
913 393 : if (not remoteSession) {
914 0 : JAMI_ERR("getIceCandidates failed: no remote session");
915 0 : return {};
916 : }
917 393 : if (not localSession) {
918 0 : JAMI_ERR("getIceCandidates failed: no local session");
919 0 : return {};
920 : }
921 393 : if (media_index >= remoteSession->media_count || media_index >= localSession->media_count) {
922 0 : JAMI_ERR("getIceCandidates failed: unable to access media#%u (may be deactivated)",
923 : media_index);
924 0 : return {};
925 : }
926 393 : auto media = remoteSession->media[media_index];
927 393 : auto localMedia = localSession->media[media_index];
928 393 : if (media->desc.port == 0 || localMedia->desc.port == 0) {
929 5 : JAMI_WARN("Media#%u is disabled. Media ports: local %u, remote %u",
930 : media_index,
931 : localMedia->desc.port,
932 : media->desc.port);
933 5 : return {};
934 : }
935 :
936 388 : std::vector<std::string> candidates;
937 :
938 6360 : for (unsigned i = 0; i < media->attr_count; i++) {
939 5972 : pjmedia_sdp_attr* attribute = media->attr[i];
940 5972 : if (pj_stricmp2(&attribute->name, "candidate") == 0)
941 3660 : candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
942 : }
943 :
944 388 : return candidates;
945 388 : }
946 :
947 : void
948 229 : Sdp::addIceAttributes(const dhtnet::IceTransport::Attribute&& ice_attrs)
949 : {
950 229 : pj_str_t value = sip_utils::CONST_PJ_STR(ice_attrs.ufrag);
951 229 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value);
952 :
953 229 : if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
954 0 : throw SdpException("Unable to add ICE.ufrag attribute to local SDP");
955 :
956 229 : value = sip_utils::CONST_PJ_STR(ice_attrs.pwd);
957 229 : attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value);
958 :
959 229 : if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
960 0 : throw SdpException("Unable to add ICE.pwd attribute to local SDP");
961 229 : }
962 :
963 : dhtnet::IceTransport::Attribute
964 577 : Sdp::getIceAttributes() const
965 : {
966 577 : if (auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_)
967 577 : return getIceAttributes(session);
968 0 : return {};
969 : }
970 :
971 : dhtnet::IceTransport::Attribute
972 577 : Sdp::getIceAttributes(const pjmedia_sdp_session* session)
973 : {
974 577 : dhtnet::IceTransport::Attribute ice_attrs;
975 : // Per RFC8839, ice-ufrag/ice-pwd can be present either at
976 : // media or session level.
977 : // This seems to be the case for Asterisk servers (ICE is at media-session).
978 1116 : for (unsigned i = 0; i < session->attr_count; i++) {
979 1078 : pjmedia_sdp_attr* attribute = session->attr[i];
980 1078 : if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
981 539 : ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
982 539 : else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
983 539 : ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
984 1078 : if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
985 539 : return ice_attrs;
986 : }
987 111 : for (unsigned i = 0; i < session->media_count; i++) {
988 73 : auto* media = session->media[i];
989 518 : for (unsigned j = 0; j < media->attr_count; j++) {
990 445 : pjmedia_sdp_attr* attribute = media->attr[j];
991 445 : if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
992 0 : ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
993 445 : else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
994 0 : ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
995 445 : if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
996 0 : return ice_attrs;
997 : }
998 : }
999 :
1000 38 : return ice_attrs;
1001 0 : }
1002 :
1003 : void
1004 57 : Sdp::clearIce()
1005 : {
1006 57 : clearIce(localSession_);
1007 57 : clearIce(remoteSession_);
1008 57 : setActiveRemoteSdpSession(nullptr);
1009 57 : setActiveLocalSdpSession(nullptr);
1010 57 : }
1011 :
1012 : void
1013 114 : Sdp::clearIce(pjmedia_sdp_session* session)
1014 : {
1015 114 : if (not session)
1016 28 : return;
1017 86 : pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag");
1018 86 : pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-pwd");
1019 : // TODO. Why this? we should not have "candidate" attribute at session level.
1020 86 : pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate");
1021 243 : for (unsigned i = 0; i < session->media_count; i++) {
1022 157 : auto media = session->media[i];
1023 157 : pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate");
1024 : }
1025 : }
1026 :
1027 : std::vector<MediaAttribute>
1028 341 : Sdp::getMediaAttributeListFromSdp(const pjmedia_sdp_session* sdpSession, bool ignoreDisabled)
1029 : {
1030 341 : if (sdpSession == nullptr) {
1031 0 : return {};
1032 : }
1033 :
1034 341 : std::vector<MediaAttribute> mediaList;
1035 341 : unsigned audioIdx = 0;
1036 341 : unsigned videoIdx = 0;
1037 969 : for (unsigned idx = 0; idx < sdpSession->media_count; idx++) {
1038 628 : mediaList.emplace_back(MediaAttribute {});
1039 628 : auto& mediaAttr = mediaList.back();
1040 :
1041 628 : auto const& media = sdpSession->media[idx];
1042 :
1043 : // Get media type.
1044 628 : if (!pj_stricmp2(&media->desc.media, "audio"))
1045 346 : mediaAttr.type_ = MediaType::MEDIA_AUDIO;
1046 282 : else if (!pj_stricmp2(&media->desc.media, "video"))
1047 282 : mediaAttr.type_ = MediaType::MEDIA_VIDEO;
1048 : else {
1049 0 : JAMI_WARN("Media#%u only 'audio' and 'video' types are supported!", idx);
1050 : // Disable the media. No need to parse the attributes.
1051 0 : mediaAttr.enabled_ = false;
1052 0 : continue;
1053 : }
1054 :
1055 : // Set enabled flag
1056 628 : mediaAttr.enabled_ = media->desc.port > 0;
1057 :
1058 628 : if (!mediaAttr.enabled_ && ignoreDisabled) {
1059 1 : mediaList.pop_back();
1060 1 : continue;
1061 : }
1062 :
1063 : // Get mute state.
1064 627 : auto direction = getMediaDirection(media);
1065 627 : mediaAttr.muted_ = direction != MediaDirection::SENDRECV
1066 627 : and direction != MediaDirection::SENDONLY;
1067 :
1068 : // Get transport.
1069 627 : auto transp = getMediaTransport(media);
1070 627 : if (transp == MediaTransport::UNKNOWN) {
1071 0 : JAMI_WARN("Media#%u is unable to determine transport type!", idx);
1072 : }
1073 :
1074 : // A media is secure if the transport is of type RTP/SAVP
1075 : // and the crypto materials are present.
1076 627 : mediaAttr.secure_ = transp == MediaTransport::RTP_SAVP and not getCrypto(media).empty();
1077 :
1078 627 : if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
1079 346 : mediaAttr.label_ = "audio_" + std::to_string(audioIdx++);
1080 281 : } else if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
1081 281 : mediaAttr.label_ = "video_" + std::to_string(videoIdx++);
1082 : }
1083 : }
1084 :
1085 341 : return mediaList;
1086 341 : }
1087 :
1088 : } // namespace jami
|