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