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 359 : Sdp::Sdp(const std::string& id)
56 718 : : memPool_(nullptr, [](pj_pool_t* pool) { pj_pool_release(pool); })
57 359 : , publishedIpAddr_()
58 359 : , publishedIpAddrType_()
59 359 : , telephoneEventPayload_(101) // same as asterisk
60 718 : , sessionName_("Call ID " + id)
61 : {
62 359 : 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 359 : if (not memPool_)
68 0 : throw std::runtime_error("pj_pool_create() failed");
69 359 : }
70 :
71 359 : Sdp::~Sdp()
72 : {
73 359 : SIPAccount::releasePort(localAudioRtpPort_);
74 : #ifdef ENABLE_VIDEO
75 359 : SIPAccount::releasePort(localVideoRtpPort_);
76 : #endif
77 359 : }
78 :
79 : std::shared_ptr<SystemCodecInfo>
80 687 : Sdp::findCodecBySpec(std::string_view codec, const unsigned clockrate) const
81 : {
82 : // TODO : only manage a list?
83 1007 : for (const auto& accountCodec : audio_codec_list_) {
84 694 : auto audioCodecInfo = std::static_pointer_cast<SystemAudioCodecInfo>(accountCodec);
85 694 : if (audioCodecInfo->name == codec
86 1068 : and (audioCodecInfo->isPCMG722() ? (clockrate == 8000)
87 374 : : (audioCodecInfo->audioformat.sample_rate == clockrate)))
88 374 : return accountCodec;
89 694 : }
90 :
91 313 : for (const auto& accountCodec : video_codec_list_) {
92 313 : if (accountCodec->name == codec)
93 313 : 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 409 : randomFill(std::vector<uint8_t>& dest)
116 : {
117 409 : std::uniform_int_distribution<int> rand_byte {0, std::numeric_limits<uint8_t>::max()};
118 409 : std::random_device rdev;
119 409 : std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev)));
120 409 : }
121 :
122 : void
123 616 : Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp)
124 : {
125 616 : if (activeLocalSession_ != sdp)
126 439 : JAMI_DBG("Set active local session to [%p]. Was [%p]", sdp, activeLocalSession_);
127 616 : activeLocalSession_ = sdp;
128 616 : }
129 :
130 : void
131 616 : Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session* sdp)
132 : {
133 616 : if (activeLocalSession_ != sdp)
134 273 : JAMI_DBG("Set active remote session to [%p]. Was [%p]", sdp, activeRemoteSession_);
135 616 : activeRemoteSession_ = sdp;
136 616 : }
137 :
138 : pjmedia_sdp_attr*
139 409 : Sdp::generateSdesAttribute()
140 : {
141 : static constexpr const unsigned cryptoSuite = 0;
142 409 : std::vector<uint8_t> keyAndSalt;
143 409 : keyAndSalt.resize(jami::CryptoSuites[cryptoSuite].masterKeyLength / 8
144 409 : + jami::CryptoSuites[cryptoSuite].masterSaltLength / 8);
145 : // generate keys
146 409 : randomFill(keyAndSalt);
147 :
148 818 : std::string crypto_attr = "1 "s + jami::CryptoSuites[cryptoSuite].name + " inline:" + base64::encode(keyAndSalt);
149 409 : pj_str_t val {sip_utils::CONST_PJ_STR(crypto_attr)};
150 818 : return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val);
151 409 : }
152 :
153 : char const*
154 410 : Sdp::mediaDirection(const MediaAttribute& mediaAttr)
155 : {
156 410 : 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 410 : if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
164 227 : return DIRECTION_STR[MediaDirection::SENDRECV];
165 : }
166 :
167 183 : if (mediaAttr.muted_) {
168 8 : if (mediaAttr.hold_) {
169 0 : return DIRECTION_STR[MediaDirection::INACTIVE];
170 : }
171 8 : return DIRECTION_STR[MediaDirection::RECVONLY];
172 : }
173 :
174 175 : if (mediaAttr.hold_) {
175 4 : return DIRECTION_STR[MediaDirection::SENDONLY];
176 : }
177 :
178 171 : return DIRECTION_STR[MediaDirection::SENDRECV];
179 : }
180 :
181 : MediaDirection
182 1206 : Sdp::getMediaDirection(pjmedia_sdp_media* media)
183 : {
184 1206 : if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::SENDONLY], nullptr)
185 1206 : != nullptr) {
186 19 : return MediaDirection::SENDONLY;
187 : }
188 :
189 1187 : if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::RECVONLY], nullptr)
190 1187 : != nullptr) {
191 26 : return MediaDirection::RECVONLY;
192 : }
193 :
194 1161 : if (pjmedia_sdp_attr_find2(media->attr_count, media->attr, DIRECTION_STR[MediaDirection::INACTIVE], nullptr)
195 1161 : != 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 1155 : return MediaDirection::SENDRECV;
202 : }
203 :
204 : MediaTransport
205 519 : Sdp::getMediaTransport(pjmedia_sdp_media* media)
206 : {
207 519 : if (pj_stricmp2(&media->desc.transport, "RTP/SAVP") == 0)
208 518 : 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 518 : Sdp::getCrypto(pjmedia_sdp_media* media)
217 : {
218 518 : std::vector<std::string> crypto;
219 8315 : for (unsigned j = 0; j < media->attr_count; j++) {
220 7797 : const auto attribute = media->attr[j];
221 7797 : if (pj_stricmp2(&attribute->name, "crypto") == 0)
222 517 : crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
223 : }
224 :
225 518 : return crypto;
226 0 : }
227 :
228 : pjmedia_sdp_media*
229 410 : Sdp::addMediaDescription(const MediaAttribute& mediaAttr)
230 : {
231 410 : auto type = mediaAttr.type_;
232 410 : auto secure = mediaAttr.secure_;
233 :
234 410 : JAMI_DBG("Add media description [%s]", mediaAttr.toString(true).c_str());
235 :
236 410 : pjmedia_sdp_media* med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media);
237 :
238 410 : switch (type) {
239 227 : case MediaType::MEDIA_AUDIO:
240 227 : med->desc.media = sip_utils::CONST_PJ_STR("audio");
241 227 : med->desc.port = mediaAttr.enabled_ ? localAudioRtpPort_ : 0;
242 227 : med->desc.fmt_count = audio_codec_list_.size();
243 227 : break;
244 183 : case MediaType::MEDIA_VIDEO:
245 183 : med->desc.media = sip_utils::CONST_PJ_STR("video");
246 183 : med->desc.port = mediaAttr.enabled_ ? localVideoRtpPort_ : 0;
247 183 : med->desc.fmt_count = video_codec_list_.size();
248 183 : break;
249 0 : default:
250 0 : throw SdpException("Unsupported media type! Only audio and video are supported");
251 : break;
252 : }
253 :
254 410 : med->desc.port_count = 1;
255 :
256 : // Set the transport protocol of the media
257 410 : med->desc.transport = secure ? sip_utils::CONST_PJ_STR("RTP/SAVP") : sip_utils::CONST_PJ_STR("RTP/AVP");
258 :
259 410 : unsigned dynamic_payload = 96;
260 :
261 1082 : for (unsigned i = 0; i < med->desc.fmt_count; i++) {
262 : pjmedia_sdp_rtpmap rtpmap;
263 672 : rtpmap.param.slen = 0;
264 :
265 672 : std::string channels; // must have the lifetime of rtpmap
266 672 : std::string enc_name;
267 : unsigned payload;
268 :
269 672 : if (type == MediaType::MEDIA_AUDIO) {
270 304 : auto accountAudioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(audio_codec_list_[i]);
271 304 : payload = accountAudioCodec->payloadType;
272 304 : enc_name = accountAudioCodec->name;
273 :
274 304 : if (accountAudioCodec->audioformat.nb_channels > 1) {
275 227 : channels = std::to_string(accountAudioCodec->audioformat.nb_channels);
276 227 : 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 304 : if (accountAudioCodec->isPCMG722())
281 11 : rtpmap.clock_rate = 8000;
282 : else
283 293 : rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate;
284 :
285 304 : } else {
286 : // FIXME: get this key from header
287 368 : payload = dynamic_payload++;
288 368 : enc_name = video_codec_list_[i]->name;
289 368 : rtpmap.clock_rate = 90000;
290 : }
291 :
292 672 : auto payloadStr = std::to_string(payload);
293 672 : auto pjPayload = sip_utils::CONST_PJ_STR(payloadStr);
294 672 : 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 672 : rtpmap.pt = med->desc.fmt[i];
300 672 : rtpmap.enc_name = sip_utils::CONST_PJ_STR(enc_name);
301 :
302 : pjmedia_sdp_attr* attr;
303 672 : pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr);
304 672 : med->attr[med->attr_count++] = attr;
305 :
306 : #ifdef ENABLE_VIDEO
307 672 : 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 183 : const auto accountVideoCodec = std::static_pointer_cast<SystemVideoCodecInfo>(video_codec_list_[i]);
311 183 : const auto& profileLevelID = accountVideoCodec->parameters.empty()
312 : ? libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID
313 183 : : accountVideoCodec->parameters;
314 183 : auto value = fmt::format("fmtp:{} {}", payload, profileLevelID);
315 183 : med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), value.c_str(), NULL);
316 183 : }
317 : #endif
318 672 : }
319 :
320 410 : if (type == MediaType::MEDIA_AUDIO) {
321 227 : setTelephoneEventRtpmap(med);
322 227 : if (localAudioRtcpPort_) {
323 227 : addRTCPAttribute(med, localAudioRtcpPort_);
324 : }
325 183 : } else if (type == MediaType::MEDIA_VIDEO and localVideoRtcpPort_) {
326 183 : addRTCPAttribute(med, localVideoRtcpPort_);
327 : }
328 :
329 410 : char const* direction = mediaDirection(mediaAttr);
330 :
331 410 : med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), direction, NULL);
332 :
333 410 : if (secure) {
334 409 : if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS)
335 0 : throw SdpException("Unable to add sdes attribute to media");
336 : }
337 :
338 410 : return med;
339 : }
340 :
341 : void
342 410 : Sdp::addRTCPAttribute(pjmedia_sdp_media* med, uint16_t port)
343 : {
344 410 : dhtnet::IpAddr addr {publishedIpAddr_};
345 410 : addr.setPort(port);
346 410 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), addr.pjPtr());
347 410 : if (attr)
348 410 : pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
349 410 : }
350 :
351 : void
352 190 : Sdp::setPublishedIP(const std::string& addr, pj_uint16_t addr_type)
353 : {
354 190 : publishedIpAddr_ = addr;
355 190 : publishedIpAddrType_ = addr_type;
356 190 : 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 190 : }
367 :
368 : void
369 190 : Sdp::setPublishedIP(const dhtnet::IpAddr& ip_addr)
370 : {
371 190 : setPublishedIP(ip_addr, ip_addr.getFamily());
372 190 : }
373 :
374 : void
375 227 : Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media* med)
376 : {
377 227 : ++med->desc.fmt_count;
378 227 : 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 227 : pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
382 227 : attr_rtpmap->name = sip_utils::CONST_PJ_STR("rtpmap");
383 227 : attr_rtpmap->value = sip_utils::CONST_PJ_STR("101 telephone-event/8000");
384 :
385 227 : med->attr[med->attr_count++] = attr_rtpmap;
386 :
387 : pjmedia_sdp_attr* attr_fmtp = static_cast<pjmedia_sdp_attr*>(
388 227 : pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
389 227 : attr_fmtp->name = sip_utils::CONST_PJ_STR("fmtp");
390 227 : attr_fmtp->value = sip_utils::CONST_PJ_STR("101 0-15");
391 :
392 227 : med->attr[med->attr_count++] = attr_fmtp;
393 227 : }
394 :
395 : void
396 718 : Sdp::setLocalMediaCapabilities(MediaType type, const std::vector<std::shared_ptr<SystemCodecInfo>>& selectedCodecs)
397 : {
398 718 : switch (type) {
399 359 : case MediaType::MEDIA_AUDIO:
400 359 : audio_codec_list_ = selectedCodecs;
401 359 : break;
402 :
403 359 : case MediaType::MEDIA_VIDEO:
404 : #ifdef ENABLE_VIDEO
405 359 : video_codec_list_ = selectedCodecs;
406 : // Do not expose H265 if accel is disactivated
407 359 : if (not jami::Manager::instance().videoPreferences.getEncodingAccelerated()) {
408 359 : video_codec_list_.erase(std::remove_if(video_codec_list_.begin(),
409 : video_codec_list_.end(),
410 734 : [](const std::shared_ptr<SystemCodecInfo>& i) {
411 734 : return i->name == "H265";
412 : }),
413 718 : video_codec_list_.end());
414 : }
415 : #else
416 : (void) selectedCodecs;
417 : #endif
418 359 : break;
419 :
420 0 : default:
421 0 : throw SdpException("Unsupported media type");
422 : break;
423 : }
424 718 : }
425 :
426 : constexpr std::string_view
427 810 : Sdp::getSdpDirectionStr(SdpDirection direction)
428 : {
429 810 : if (direction == SdpDirection::OFFER)
430 450 : return "OFFER"sv;
431 360 : if (direction == SdpDirection::ANSWER)
432 360 : return "ANSWER"sv;
433 0 : return "NONE"sv;
434 : }
435 :
436 : void
437 810 : Sdp::printSession(const pjmedia_sdp_session* session, const char* header, SdpDirection direction)
438 : {
439 : static constexpr size_t BUF_SZ = 4095;
440 810 : sip_utils::PoolPtr tmpPool_(pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory,
441 : "printSdp",
442 : BUF_SZ,
443 : BUF_SZ,
444 810 : nullptr));
445 :
446 810 : auto cloned_session = pjmedia_sdp_session_clone(tmpPool_.get(), session);
447 810 : 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 2319 : for (unsigned i = 0; i < cloned_session->media_count; ++i) {
454 1509 : pjmedia_sdp_media_remove_all_attr(cloned_session->media[i], "crypto");
455 : }
456 :
457 : std::array<char, BUF_SZ + 1> buffer;
458 810 : auto size = pjmedia_sdp_print(cloned_session, buffer.data(), BUF_SZ);
459 810 : if (size < 0) {
460 0 : JAMI_ERROR("SDP too big for dump: {}", header);
461 0 : return;
462 : }
463 :
464 3240 : JAMI_LOG("[SDP {}] {}\n{:s}", getSdpDirectionStr(direction), header, std::string_view(buffer.data(), size));
465 810 : }
466 :
467 : void
468 223 : Sdp::createLocalSession(SdpDirection direction)
469 : {
470 223 : sdpDirection_ = direction;
471 223 : localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session);
472 223 : localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn);
473 :
474 : /* Initialize the fields of the struct */
475 223 : localSession_->origin.version = 0;
476 : pj_time_val tv;
477 223 : pj_gettimeofday(&tv);
478 :
479 223 : localSession_->origin.user = *pj_gethostname();
480 :
481 : // Use Network Time Protocol format timestamp to ensure uniqueness.
482 223 : localSession_->origin.id = tv.sec + 2208988800UL;
483 223 : localSession_->origin.net_type = sip_utils::CONST_PJ_STR("IN");
484 223 : if (publishedIpAddrType_ == pj_AF_INET6())
485 0 : localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP6");
486 : else
487 223 : localSession_->origin.addr_type = sip_utils::CONST_PJ_STR("IP4");
488 223 : localSession_->origin.addr = sip_utils::CONST_PJ_STR(publishedIpAddr_);
489 :
490 : // Use the call IDs for s= line
491 223 : localSession_->name = sip_utils::CONST_PJ_STR(sessionName_);
492 :
493 223 : localSession_->conn->net_type = localSession_->origin.net_type;
494 223 : localSession_->conn->addr_type = localSession_->origin.addr_type;
495 223 : 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 223 : localSession_->time.start = 0;
501 223 : localSession_->time.stop = 0;
502 223 : }
503 :
504 : int
505 446 : Sdp::validateSession() const
506 : {
507 446 : return pjmedia_sdp_validate(localSession_);
508 : }
509 :
510 : bool
511 117 : Sdp::createOffer(const std::vector<MediaAttribute>& mediaList)
512 : {
513 117 : if (mediaList.size() >= PJMEDIA_MAX_SDP_MEDIA) {
514 0 : throw SdpException("Media list size exceeds SDP media maximum size");
515 : }
516 468 : JAMI_DEBUG("Creating SDP offer with {} media", mediaList.size());
517 :
518 117 : createLocalSession(SdpDirection::OFFER);
519 :
520 117 : if (validateSession() != PJ_SUCCESS) {
521 0 : JAMI_ERR("Failed to create initial offer");
522 0 : return false;
523 : }
524 :
525 117 : localSession_->media_count = 0;
526 :
527 332 : for (auto const& media : mediaList) {
528 215 : if (media.enabled_) {
529 215 : localSession_->media[localSession_->media_count++] = addMediaDescription(media);
530 : }
531 : }
532 :
533 117 : if (validateSession() != PJ_SUCCESS) {
534 0 : JAMI_ERR("Failed to add medias");
535 0 : return false;
536 : }
537 :
538 117 : 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 117 : printSession(localSession_, "Local session (initial):", sdpDirection_);
544 :
545 117 : return true;
546 : }
547 :
548 : void
549 116 : Sdp::setReceivedOffer(const pjmedia_sdp_session* remote)
550 : {
551 116 : if (remote == nullptr) {
552 0 : JAMI_ERR("Remote session is NULL");
553 0 : return;
554 : }
555 116 : remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote);
556 : }
557 :
558 : bool
559 106 : Sdp::processIncomingOffer(const std::vector<MediaAttribute>& mediaList)
560 : {
561 106 : if (not remoteSession_)
562 0 : return false;
563 :
564 424 : JAMI_DEBUG("Processing received offer for [{:s}] with {:d} media", sessionName_, mediaList.size());
565 :
566 106 : printSession(remoteSession_, "Remote session:", SdpDirection::OFFER);
567 :
568 106 : createLocalSession(SdpDirection::ANSWER);
569 106 : if (validateSession() != PJ_SUCCESS) {
570 0 : JAMI_ERR("Failed to create local session");
571 0 : return false;
572 : }
573 :
574 106 : localSession_->media_count = 0;
575 :
576 302 : for (auto const& media : mediaList) {
577 196 : if (media.enabled_) {
578 195 : localSession_->media[localSession_->media_count++] = addMediaDescription(media);
579 : }
580 : }
581 :
582 106 : printSession(localSession_, "Local session:\n", sdpDirection_);
583 :
584 106 : if (validateSession() != PJ_SUCCESS) {
585 0 : JAMI_ERR("Failed to add medias");
586 0 : return false;
587 : }
588 :
589 106 : if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(), localSession_, remoteSession_, &negotiator_)
590 106 : != PJ_SUCCESS) {
591 0 : JAMI_ERR("Failed to initialize media negotiation");
592 0 : return false;
593 : }
594 :
595 106 : return true;
596 : }
597 :
598 : bool
599 21 : Sdp::startNegotiation()
600 : {
601 21 : JAMI_DBG("Starting media negotiation for [%s]", sessionName_.c_str());
602 :
603 21 : 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 21 : 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 21 : 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 21 : if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
622 0 : JAMI_ERR("Unable to retrieve local active session");
623 :
624 21 : setActiveLocalSdpSession(active_local);
625 :
626 21 : if (active_local != nullptr) {
627 21 : printSession(active_local, "Local active session:", sdpDirection_);
628 : }
629 :
630 21 : 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 21 : setActiveRemoteSdpSession(active_remote);
636 :
637 21 : printSession(active_remote, "Remote active session:", sdpDirection_);
638 :
639 21 : return true;
640 : }
641 :
642 : std::string
643 361 : 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 361 : pj_pool_create(&Manager::instance().sipVoIPLink().getCachingPool()->factory, "tmpSdp", BUF_SZ, BUF_SZ, nullptr));
648 361 : auto cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session);
649 361 : if (!cloned) {
650 0 : JAMI_ERR("Unable to clone SDP");
651 0 : return "";
652 : }
653 :
654 : // deactivate non-video media
655 361 : bool hasKeep = false;
656 1070 : for (unsigned i = 0; i < cloned->media_count; i++)
657 709 : if (i != media_keep) {
658 348 : if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS)
659 0 : JAMI_ERR("Unable to deactivate media");
660 : } else {
661 361 : hasKeep = true;
662 : }
663 :
664 361 : 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 1070 : for (unsigned i = 0; i < cloned->media_count; i++)
671 709 : if (cloned->media[i]->desc.port == 0) {
672 348 : std::move(cloned->media + i + 1, cloned->media + cloned->media_count, cloned->media + i);
673 348 : cloned->media_count--;
674 348 : i--;
675 : }
676 :
677 722 : for (unsigned i = 0; i < cloned->media_count; i++) {
678 361 : auto media = cloned->media[i];
679 :
680 : // filter other codecs
681 919 : for (unsigned c = 0; c < media->desc.fmt_count; c++) {
682 558 : auto& pt = media->desc.fmt[c];
683 558 : if (pj_strtoul(&pt) == pt_keep)
684 361 : continue;
685 :
686 394 : while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtpmap", &pt))
687 197 : pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);
688 :
689 197 : 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 197 : std::move(media->desc.fmt + c + 1, media->desc.fmt + media->desc.fmt_count, media->desc.fmt + c);
693 197 : media->desc.fmt_count--;
694 197 : c--;
695 : }
696 :
697 : // we handle crypto ourselfs, don't tell libav about it
698 361 : pjmedia_sdp_media_remove_all_attr(media, "crypto");
699 : }
700 :
701 : char buffer[BUF_SZ];
702 361 : size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer));
703 361 : string sessionStr(buffer, std::min(size, sizeof(buffer)));
704 :
705 361 : return sessionStr;
706 361 : }
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 366 : Sdp::getMediaDescriptions(const pjmedia_sdp_session* session, bool remote) const
719 : {
720 366 : 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 366 : std::vector<MediaDescription> ret;
726 1056 : for (unsigned i = 0; i < session->media_count; i++) {
727 690 : auto media = session->media[i];
728 690 : ret.emplace_back(MediaDescription());
729 690 : MediaDescription& descr = ret.back();
730 690 : if (!pj_stricmp2(&media->desc.media, "audio"))
731 374 : descr.type = MEDIA_AUDIO;
732 316 : else if (!pj_stricmp2(&media->desc.media, "video"))
733 316 : descr.type = MEDIA_VIDEO;
734 : else
735 3 : continue;
736 :
737 690 : descr.enabled = media->desc.port;
738 690 : if (!descr.enabled)
739 3 : continue;
740 :
741 : // get connection info
742 687 : pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn;
743 687 : if (not conn) {
744 0 : JAMI_ERR("Unable to find connection information for media");
745 0 : continue;
746 : }
747 687 : descr.addr = std::string_view(conn->addr.ptr, conn->addr.slen);
748 687 : 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 687 : auto attr = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "rtcp", NULL);
753 687 : if (attr) {
754 : pjmedia_sdp_rtcp_attr rtcp;
755 687 : auto status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp);
756 687 : if (status == PJ_SUCCESS && rtcp.addr.slen) {
757 687 : descr.rtcp_addr = std::string_view(rtcp.addr.ptr, rtcp.addr.slen);
758 687 : descr.rtcp_addr.setPort(rtcp.port);
759 : }
760 : }
761 :
762 2061 : descr.hold = pjmedia_sdp_attr_find2(media->attr_count,
763 687 : media->attr,
764 687 : DIRECTION_STR[MediaDirection::SENDONLY],
765 : nullptr)
766 1362 : || pjmedia_sdp_attr_find2(media->attr_count,
767 675 : media->attr,
768 675 : DIRECTION_STR[MediaDirection::INACTIVE],
769 : nullptr);
770 :
771 687 : descr.direction_ = getMediaDirection(media);
772 :
773 : // get codecs infos
774 687 : for (unsigned j = 0; j < media->desc.fmt_count; j++) {
775 687 : const auto rtpMapAttribute = pjmedia_sdp_media_find_attr(media, &STR_RTPMAP, &media->desc.fmt[j]);
776 687 : 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 687 : 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 687 : auto codec_raw = sip_utils::as_view(rtpmap.enc_name);
788 687 : descr.rtp_clockrate = rtpmap.clock_rate;
789 687 : descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate);
790 687 : 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 687 : descr.payload_type = pj_strtoul(&rtpmap.pt);
796 687 : if (descr.type == MEDIA_VIDEO) {
797 313 : const auto fmtpAttr = pjmedia_sdp_media_find_attr(media, &STR_FMTP, &media->desc.fmt[j]);
798 : // descr.bitrate = getOutgoingVideoField(codec, "bitrate");
799 313 : if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) {
800 149 : const auto& v = fmtpAttr->value;
801 149 : descr.parameters = std::string(v.ptr, v.ptr + v.slen);
802 : }
803 : }
804 : // for now, just keep the first codec only
805 687 : descr.enabled = true;
806 687 : break;
807 : }
808 :
809 687 : if (not remote)
810 361 : descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type);
811 :
812 : // get crypto info
813 687 : std::vector<std::string> crypto;
814 11028 : for (unsigned j = 0; j < media->attr_count; j++) {
815 10341 : const auto attribute = media->attr[j];
816 10341 : if (pj_stricmp2(&attribute->name, "crypto") == 0)
817 687 : crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
818 : }
819 687 : descr.crypto = SdesNegotiator::negotiate(crypto);
820 687 : }
821 366 : return ret;
822 366 : }
823 :
824 : std::vector<Sdp::MediaSlot>
825 166 : Sdp::getMediaSlots() const
826 : {
827 166 : auto loc = getMediaDescriptions(activeLocalSession_, false);
828 166 : auto rem = getMediaDescriptions(activeRemoteSession_, true);
829 166 : size_t slot_n = std::min(loc.size(), rem.size());
830 166 : std::vector<MediaSlot> s;
831 166 : s.reserve(slot_n);
832 478 : for (decltype(slot_n) i = 0; i < slot_n; i++)
833 312 : s.emplace_back(std::move(loc[i]), std::move(rem[i]));
834 332 : return s;
835 166 : }
836 :
837 : void
838 784 : Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
839 : {
840 784 : 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 784 : auto media = localSession_->media[media_index];
846 :
847 4448 : for (const auto& item : cands) {
848 3664 : const pj_str_t val = sip_utils::CONST_PJ_STR(item);
849 3664 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val);
850 :
851 3664 : 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 370 : Sdp::getIceCandidates(unsigned media_index) const
858 : {
859 370 : auto remoteSession = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_;
860 370 : auto localSession = activeLocalSession_ ? activeLocalSession_ : localSession_;
861 370 : if (not remoteSession) {
862 0 : JAMI_ERR("getIceCandidates failed: no remote session");
863 0 : return {};
864 : }
865 370 : if (not localSession) {
866 0 : JAMI_ERR("getIceCandidates failed: no local session");
867 0 : return {};
868 : }
869 370 : 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 370 : auto media = remoteSession->media[media_index];
874 370 : auto localMedia = localSession->media[media_index];
875 370 : if (media->desc.port == 0 || localMedia->desc.port == 0) {
876 2 : JAMI_WARN("Media#%u is disabled. Media ports: local %u, remote %u",
877 : media_index,
878 : localMedia->desc.port,
879 : media->desc.port);
880 2 : return {};
881 : }
882 :
883 368 : std::vector<std::string> candidates;
884 :
885 5989 : for (unsigned i = 0; i < media->attr_count; i++) {
886 5621 : pjmedia_sdp_attr* attribute = media->attr[i];
887 5621 : if (pj_stricmp2(&attribute->name, "candidate") == 0)
888 3460 : candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
889 : }
890 :
891 368 : return candidates;
892 368 : }
893 :
894 : void
895 213 : Sdp::addIceAttributes(const dhtnet::IceTransport::Attribute&& ice_attrs)
896 : {
897 213 : pj_str_t value = sip_utils::CONST_PJ_STR(ice_attrs.ufrag);
898 213 : pjmedia_sdp_attr* attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value);
899 :
900 213 : 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 213 : value = sip_utils::CONST_PJ_STR(ice_attrs.pwd);
904 213 : attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value);
905 :
906 213 : 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 213 : }
909 :
910 : dhtnet::IceTransport::Attribute
911 512 : Sdp::getIceAttributes() const
912 : {
913 512 : if (auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_)
914 512 : return getIceAttributes(session);
915 0 : return {};
916 : }
917 :
918 : dhtnet::IceTransport::Attribute
919 512 : Sdp::getIceAttributes(const pjmedia_sdp_session* session)
920 : {
921 512 : 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 1013 : for (unsigned i = 0; i < session->attr_count; i++) {
926 1002 : pjmedia_sdp_attr* attribute = session->attr[i];
927 1002 : if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
928 501 : ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
929 501 : else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
930 501 : ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
931 1002 : if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
932 501 : return ice_attrs;
933 : }
934 30 : for (unsigned i = 0; i < session->media_count; i++) {
935 19 : auto* media = session->media[i];
936 131 : for (unsigned j = 0; j < media->attr_count; j++) {
937 112 : pjmedia_sdp_attr* attribute = media->attr[j];
938 112 : if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
939 0 : ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
940 112 : else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
941 0 : ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
942 112 : if (!ice_attrs.ufrag.empty() && !ice_attrs.pwd.empty())
943 0 : return ice_attrs;
944 : }
945 : }
946 :
947 11 : return ice_attrs;
948 0 : }
949 :
950 : void
951 43 : Sdp::clearIce()
952 : {
953 43 : clearIce(localSession_);
954 43 : clearIce(remoteSession_);
955 43 : setActiveRemoteSdpSession(nullptr);
956 43 : setActiveLocalSdpSession(nullptr);
957 43 : }
958 :
959 : void
960 86 : Sdp::clearIce(pjmedia_sdp_session* session)
961 : {
962 86 : if (not session)
963 21 : return;
964 65 : pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag");
965 65 : 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 65 : pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate");
968 183 : for (unsigned i = 0; i < session->media_count; i++) {
969 118 : auto media = session->media[i];
970 118 : pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate");
971 : }
972 : }
973 :
974 : std::vector<MediaAttribute>
975 278 : Sdp::getMediaAttributeListFromSdp(const pjmedia_sdp_session* sdpSession, bool ignoreDisabled)
976 : {
977 278 : if (sdpSession == nullptr) {
978 0 : return {};
979 : }
980 :
981 278 : std::vector<MediaAttribute> mediaList;
982 278 : unsigned audioIdx = 0;
983 278 : unsigned videoIdx = 0;
984 798 : for (unsigned idx = 0; idx < sdpSession->media_count; idx++) {
985 520 : mediaList.emplace_back(MediaAttribute {});
986 520 : auto& mediaAttr = mediaList.back();
987 :
988 520 : auto const& media = sdpSession->media[idx];
989 :
990 : // Get media type.
991 520 : if (!pj_stricmp2(&media->desc.media, "audio"))
992 284 : mediaAttr.type_ = MediaType::MEDIA_AUDIO;
993 236 : else if (!pj_stricmp2(&media->desc.media, "video"))
994 236 : 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 520 : mediaAttr.enabled_ = media->desc.port > 0;
1004 :
1005 520 : if (!mediaAttr.enabled_ && ignoreDisabled) {
1006 1 : mediaList.pop_back();
1007 1 : continue;
1008 : }
1009 :
1010 : // Get mute state.
1011 519 : auto direction = getMediaDirection(media);
1012 519 : mediaAttr.muted_ = direction != MediaDirection::SENDRECV and direction != MediaDirection::SENDONLY;
1013 :
1014 : // Get transport.
1015 519 : auto transp = getMediaTransport(media);
1016 519 : 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 519 : mediaAttr.secure_ = transp == MediaTransport::RTP_SAVP and not getCrypto(media).empty();
1023 :
1024 519 : if (mediaAttr.type_ == MediaType::MEDIA_AUDIO) {
1025 284 : mediaAttr.label_ = "audio_" + std::to_string(audioIdx++);
1026 235 : } else if (mediaAttr.type_ == MediaType::MEDIA_VIDEO) {
1027 235 : mediaAttr.label_ = "video_" + std::to_string(videoIdx++);
1028 : }
1029 : }
1030 :
1031 278 : return mediaList;
1032 278 : }
1033 :
1034 : } // namespace jami
|