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