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