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