sdp.cpp 27.5 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
3
4
 *
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
5
 *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
Guillaume Roguez's avatar
Guillaume Roguez committed
6
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
7
 *  Author: Eloi Bail <eloi.bail@savoirfairelinux.com>
8
9
10
11
12
13
14
15
16
17
18
19
20
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
21
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
22
23
 */

24
25
#include "sdp.h"

26
27
28
29
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

30
#include "sipaccount.h"
31
#include "sipvoiplink.h"
Guillaume Roguez's avatar
Guillaume Roguez committed
32
33
34
#include "string_utils.h"
#include "base64.h"

35
36
#include "manager.h"
#include "logger.h"
37
#include "libav_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
38
#include "array_size.h"
39

Eloi Bail's avatar
Eloi Bail committed
40
41
#include "media_codec.h"
#include "system_codec_container.h"
42
#include "compiler_intrinsics.h" // for UNUSED
Eloi Bail's avatar
Eloi Bail committed
43

44
45
46
#include <opendht/rng.h>
using random_device = dht::crypto::random_device;

Adrien Béraud's avatar
Adrien Béraud committed
47
#include <algorithm>
48
#include <cassert>
Adrien Béraud's avatar
Adrien Béraud committed
49

Adrien Béraud's avatar
Adrien Béraud committed
50
namespace jami {
Guillaume Roguez's avatar
Guillaume Roguez committed
51

52
53
54
55
using std::string;
using std::vector;
using std::stringstream;

56
57
58
59
static constexpr int POOL_INITIAL_SIZE = 16384;
static constexpr int POOL_INCREMENT_SIZE = POOL_INITIAL_SIZE;

Sdp::Sdp(const std::string& id)
60
    : memPool_(nullptr, [](pj_pool_t* pool) { sip_utils::register_thread(); pj_pool_release(pool); })
61
    , publishedIpAddr_()
Adrien Béraud's avatar
Adrien Béraud committed
62
    , publishedIpAddrType_()
63
    , sdesNego_ {CryptoSuites}
64
    , telephoneEventPayload_(101) // same as asterisk
65
{
Adrien Béraud's avatar
Adrien Béraud committed
66
    sip_utils::register_thread();
67
68
69
70
71
72
    memPool_.reset(pj_pool_create(&getSIPVoIPLink()->getCachingPool()->factory,
                                  id.c_str(), POOL_INITIAL_SIZE,
                                  POOL_INCREMENT_SIZE, NULL));
    if (not memPool_)
        throw std::runtime_error("pj_pool_create() failed");
}
73

74
75
76
Sdp::~Sdp()
{
    SIPAccount::releasePort(localAudioDataPort_);
Adrien Béraud's avatar
Adrien Béraud committed
77
#ifdef ENABLE_VIDEO
78
79
80
81
    SIPAccount::releasePort(localVideoDataPort_);
#endif
}

82
std::shared_ptr<AccountCodecInfo>
83
Sdp::findCodecBySpec(const std::string &codec, const unsigned clockrate) const
84
{
85
86
    //TODO : only manage a list?
    for (const auto& accountCodec : audio_codec_list_) {
87
88
89
90
91
92
        auto audioCodecInfo = std::static_pointer_cast<AccountAudioCodecInfo>(accountCodec);
        auto& sysCodecInfo = *static_cast<const SystemAudioCodecInfo*>(&audioCodecInfo->systemCodecInfo);
        if (sysCodecInfo.name.compare(codec) == 0 and
            (audioCodecInfo->isPCMG722() ?
                (clockrate == 8000) :
                (sysCodecInfo.audioformat.sample_rate == clockrate)))
93
94
95
96
97
98
99
100
101
102
            return accountCodec;
    }

    for (const auto& accountCodec : video_codec_list_) {
        auto sysCodecInfo = accountCodec->systemCodecInfo;
        if (sysCodecInfo.name.compare(codec) == 0)
            return accountCodec;
    }
    return nullptr;
}
103

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
std::shared_ptr<AccountCodecInfo>
Sdp::findCodecByPayload(const unsigned payloadType)
{
    //TODO : only manage a list?
    for (const auto& accountCodec : audio_codec_list_) {
        auto sysCodecInfo = accountCodec->systemCodecInfo;
        if (sysCodecInfo.payloadType == payloadType)
            return accountCodec;
    }

    for (const auto& accountCodec : video_codec_list_) {
        auto sysCodecInfo = accountCodec->systemCodecInfo;
        if (sysCodecInfo.payloadType == payloadType)
            return accountCodec;
    }
    return nullptr;
120
}
121

Guillaume Roguez's avatar
Guillaume Roguez committed
122
123
124
static void
randomFill(std::vector<uint8_t>& dest)
{
125
    std::uniform_int_distribution<int> rand_byte{ 0, std::numeric_limits<uint8_t>::max() };
126
    random_device rdev;
Guillaume Roguez's avatar
Guillaume Roguez committed
127
128
129
130
    std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev)));
}


Eloi Bail's avatar
Eloi Bail committed
131
132
void
Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp)
133
{
134
    activeLocalSession_ = sdp;
135
136
}

Eloi Bail's avatar
Eloi Bail committed
137
138
void
Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp)
139
{
140
    if (!sdp) {
Adrien Béraud's avatar
Adrien Béraud committed
141
        JAMI_ERR("Remote sdp is NULL");
Rafaël Carré's avatar
Rafaël Carré committed
142
143
144
        return;
    }

145
    activeRemoteSession_ = sdp;
146
147
}

Guillaume Roguez's avatar
Guillaume Roguez committed
148
149
pjmedia_sdp_attr *
Sdp::generateSdesAttribute()
150
{
Guillaume Roguez's avatar
Guillaume Roguez committed
151
152
    static constexpr const unsigned cryptoSuite = 0;
    std::vector<uint8_t> keyAndSalt;
Adrien Béraud's avatar
Adrien Béraud committed
153
154
    keyAndSalt.resize(jami::CryptoSuites[cryptoSuite].masterKeyLength / 8
                    + jami::CryptoSuites[cryptoSuite].masterSaltLength/ 8);
Guillaume Roguez's avatar
Guillaume Roguez committed
155
156
    // generate keys
    randomFill(keyAndSalt);
Eloi Bail's avatar
Eloi Bail committed
157

Guillaume Roguez's avatar
Guillaume Roguez committed
158
159
    std::string tag = "1";
    std::string crypto_attr = tag + " "
Adrien Béraud's avatar
Adrien Béraud committed
160
                            + jami::CryptoSuites[cryptoSuite].name
Guillaume Roguez's avatar
Guillaume Roguez committed
161
162
163
164
                            + " inline:" + base64::encode(keyAndSalt);
    pj_str_t val { (char*) crypto_attr.c_str(),
                    static_cast<pj_ssize_t>(crypto_attr.size()) };
    return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val);
165
166
}

Guillaume Roguez's avatar
Guillaume Roguez committed
167
pjmedia_sdp_media *
168
Sdp::setMediaDescriptorLines(bool audio, bool holding, sip_utils::KeyExchangeProtocol kx)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
169
{
170
    pjmedia_sdp_media *med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media);
171

172
    med->desc.media = audio ? pj_str((char*) "audio") : pj_str((char*) "video");
173
    med->desc.port_count = 1;
174
    med->desc.port = audio ? localAudioDataPort_ : localVideoDataPort_;
Guillaume Roguez's avatar
Guillaume Roguez committed
175

176
    // in case of sdes, media are tagged as "RTP/SAVP", RTP/AVP elsewhere
Guillaume Roguez's avatar
Guillaume Roguez committed
177
178
179
    med->desc.transport = pj_str(kx == sip_utils::KeyExchangeProtocol::NONE ?
        (char*) "RTP/AVP" :
        (char*) "RTP/SAVP");
180

Guillaume Roguez's avatar
Guillaume Roguez committed
181
    unsigned dynamic_payload = 96;
182

183
    med->desc.fmt_count = audio ? audio_codec_list_.size() : video_codec_list_.size();
184
    for (unsigned i = 0; i < med->desc.fmt_count; ++i) {
Guillaume Roguez's avatar
Guillaume Roguez committed
185
186
187
188
189
190
        pjmedia_sdp_rtpmap rtpmap;
        rtpmap.param.slen = 0;

        std::string channels; // must have the lifetime of rtpmap
        std::string enc_name;
        unsigned payload;
Tristan Matthews's avatar
Tristan Matthews committed
191
192

        if (audio) {
193
194
195
196
197
            auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(audio_codec_list_[i]);
            payload = accountAudioCodec->payloadType;
            enc_name = accountAudioCodec->systemCodecInfo.name;

            if (accountAudioCodec->audioformat.nb_channels > 1) {
198
                channels = std::to_string(accountAudioCodec->audioformat.nb_channels);
Guillaume Roguez's avatar
Guillaume Roguez committed
199
200
201
                rtpmap.param.ptr = (char *) channels.c_str();
                rtpmap.param.slen = strlen(channels.c_str()); // don't include NULL terminator
            }
202
203
            // G722 requires G722/8000 media description even though it's @ 16000 Hz
            // See http://tools.ietf.org/html/rfc3551#section-4.5.2
204
            if (accountAudioCodec->isPCMG722())
Guillaume Roguez's avatar
Guillaume Roguez committed
205
                rtpmap.clock_rate = 8000;
Eloi Bail's avatar
Eloi Bail committed
206
            else
207
208
                rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate;

Tristan Matthews's avatar
Tristan Matthews committed
209
        } else {
210
            // FIXME: get this key from header
Guillaume Roguez's avatar
Guillaume Roguez committed
211
            payload = dynamic_payload++;
212
            enc_name = video_codec_list_[i]->systemCodecInfo.name;
Guillaume Roguez's avatar
Guillaume Roguez committed
213
            rtpmap.clock_rate = 90000;
Tristan Matthews's avatar
Tristan Matthews committed
214
        }
215

Tristan Matthews's avatar
Tristan Matthews committed
216
217
        std::ostringstream s;
        s << payload;
218
        pj_strdup2(memPool_.get(), &med->desc.fmt[i], s.str().c_str());
219
220
221

        // Add a rtpmap field for each codec
        // We could add one only for dynamic payloads because the codecs with static RTP payloads
Guillaume Roguez's avatar
Guillaume Roguez committed
222
        // are entirely defined in the RFC 3351
223
        rtpmap.pt = med->desc.fmt[i];
224
        rtpmap.enc_name = pj_str((char*) enc_name.c_str());
225

Rafaël Carré's avatar
Rafaël Carré committed
226
        pjmedia_sdp_attr *attr;
227
        pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr);
228
        med->attr[med->attr_count++] = attr;
229

Adrien Béraud's avatar
Adrien Béraud committed
230
#ifdef ENABLE_VIDEO
231
232
233
        if (enc_name == "H264") {
            // FIXME: this should not be hardcoded, it will determine what profile and level
            // our peer will send us
234
235
236
237
238
            const auto accountVideoCodec = std::static_pointer_cast<AccountVideoCodecInfo>(video_codec_list_[i]);
            const auto profileLevelID = accountVideoCodec->parameters.empty() ?
                libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID :
                accountVideoCodec->parameters;
            std::ostringstream os;
Guillaume Roguez's avatar
Guillaume Roguez committed
239
            os << "fmtp:" << payload << " " << profileLevelID;
240
            med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), os.str().c_str(), NULL);
241
        }
242
#endif
243
    }
244

245
    if (audio) {
246
        setTelephoneEventRtpmap(med);
247
248
        addRTCPAttribute(med); // video has its own RTCP
    }
249

250
    med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), holding ? (audio ? "sendonly" : "inactive") : "sendrecv", NULL);
Guillaume Roguez's avatar
Guillaume Roguez committed
251
252
253

    if (kx == sip_utils::KeyExchangeProtocol::SDES) {
        if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS)
254
            throw SdpException("Could not add sdes attribute to media");
Guillaume Roguez's avatar
Guillaume Roguez committed
255
    }
Guillaume Roguez's avatar
Guillaume Roguez committed
256

Rafaël Carré's avatar
Rafaël Carré committed
257
    return med;
258
}
259

260
261
262

void Sdp::addRTCPAttribute(pjmedia_sdp_media *med)
{
Adrien Béraud's avatar
Adrien Béraud committed
263
264
    IpAddr outputAddr = publishedIpAddr_;
    outputAddr.setPort(localAudioControlPort_);
265
    pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), outputAddr.pjPtr());
266
267
268
269
    if (attr)
        pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr);
}

270
void
Adrien Béraud's avatar
Adrien Béraud committed
271
Sdp::setPublishedIP(const std::string &addr, pj_uint16_t addr_type)
272
{
Adrien Béraud's avatar
Adrien Béraud committed
273
274
    publishedIpAddr_ = addr;
    publishedIpAddrType_ = addr_type;
275
    if (localSession_) {
Adrien Béraud's avatar
Adrien Béraud committed
276
        if (addr_type == pj_AF_INET6())
277
278
279
            localSession_->origin.addr_type = pj_str((char*) "IP6");
        else
            localSession_->origin.addr_type = pj_str((char*) "IP4");
Adrien Béraud's avatar
Adrien Béraud committed
280
        localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str());
281
        localSession_->conn->addr = localSession_->origin.addr;
282
        if (pjmedia_sdp_validate(localSession_) != PJ_SUCCESS)
Adrien Béraud's avatar
Adrien Béraud committed
283
            JAMI_ERR("Could not validate SDP");
284
285
286
    }
}

Adrien Béraud's avatar
Adrien Béraud committed
287
void
Adrien Béraud's avatar
Adrien Béraud committed
288
Sdp::setPublishedIP(const IpAddr& ip_addr)
Adrien Béraud's avatar
Adrien Béraud committed
289
{
Adrien Béraud's avatar
Adrien Béraud committed
290
    setPublishedIP(ip_addr, ip_addr.getFamily());
Adrien Béraud's avatar
Adrien Béraud committed
291
292
}

293
294
void Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media *med)
{
295
296
297
    std::ostringstream s;
    s << telephoneEventPayload_;
    ++med->desc.fmt_count;
298
    pj_strdup2(memPool_.get(), &med->desc.fmt[med->desc.fmt_count - 1], s.str().c_str());
299

300
    pjmedia_sdp_attr *attr_rtpmap = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
301
302
    attr_rtpmap->name = pj_str((char *) "rtpmap");
    attr_rtpmap->value = pj_str((char *) "101 telephone-event/8000");
303
304
305

    med->attr[med->attr_count++] = attr_rtpmap;

306
    pjmedia_sdp_attr *attr_fmtp = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr)));
307
308
    attr_fmtp->name = pj_str((char *) "fmtp");
    attr_fmtp->value = pj_str((char *) "101 0-15");
309
310
311
312

    med->attr[med->attr_count++] = attr_fmtp;
}

313
void Sdp::setLocalMediaVideoCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedCodecs)
314
{
Adrien Béraud's avatar
Adrien Béraud committed
315
#ifdef ENABLE_VIDEO
316
    video_codec_list_ = selectedCodecs;
317
#else
Eloi Bail's avatar
Eloi Bail committed
318
    (void) selectedCodecs;
319
#endif
320
}
321

322
void Sdp::setLocalMediaAudioCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedCodecs)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
323
{
324
    audio_codec_list_ = selectedCodecs;
325
326
}

327
328
void
Sdp::printSession(const pjmedia_sdp_session *session, const char* header)
329
{
330
    static constexpr size_t BUF_SZ = 4095;
331
    sip_utils::register_thread();
332
333
334
335
336
337
338
    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> tmpPool_(
        pj_pool_create(&getSIPVoIPLink()->getCachingPool()->factory, "printSdp", BUF_SZ, BUF_SZ, nullptr),
        pj_pool_release
    );

    auto cloned_session = pjmedia_sdp_session_clone(tmpPool_.get(), session);
    if (!cloned_session) {
Adrien Béraud's avatar
Adrien Béraud committed
339
        JAMI_ERR("Could not clone SDP for printing");
340
341
342
343
344
345
346
347
        return;
    }

    // Filter-out sensible data like SRTP master key.
    for (unsigned i = 0; i < cloned_session->media_count; ++i) {
        pjmedia_sdp_media_remove_all_attr(cloned_session->media[i], "crypto");
    }

348
349
    std::array<char, BUF_SZ+1> buffer;
    auto size = pjmedia_sdp_print(cloned_session, buffer.data(), BUF_SZ);
350
    if (size < 0) {
Adrien Béraud's avatar
Adrien Béraud committed
351
        JAMI_ERR("%sSDP too big for dump", header);
352
353
        return;
    }
354
    buffer[size] = '\0';
Adrien Béraud's avatar
Adrien Béraud committed
355
    JAMI_DBG("%s%s", header, &buffer[0]);
Tristan Matthews's avatar
Tristan Matthews committed
356
357
}

358
359
int Sdp::createLocalSession(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
                            const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
360
361
                            sip_utils::KeyExchangeProtocol security,
                            bool holding)
362
{
363
364
    setLocalMediaAudioCapabilities(selectedAudioCodecs);
    setLocalMediaVideoCapabilities(selectedVideoCodecs);
365

366
367
    localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session);
    localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn);
368

369
    /* Initialize the fields of the struct */
Rafaël Carré's avatar
Rafaël Carré committed
370
371
    localSession_->origin.version = 0;
    pj_time_val tv;
372
    pj_gettimeofday(&tv);
373

374
    localSession_->origin.user = pj_str(pj_gethostname()->ptr);
Rafaël Carré's avatar
Rafaël Carré committed
375
376
    // Use Network Time Protocol format timestamp to ensure uniqueness.
    localSession_->origin.id = tv.sec + 2208988800UL;
377
    localSession_->origin.net_type = pj_str((char*) "IN");
Adrien Béraud's avatar
Adrien Béraud committed
378
    if (publishedIpAddrType_ == pj_AF_INET6())
379
380
381
        localSession_->origin.addr_type = pj_str((char*) "IP6");
    else
        localSession_->origin.addr_type = pj_str((char*) "IP4");
Adrien Béraud's avatar
Adrien Béraud committed
382
    localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
383
    localSession_->name = pj_str((char*) PACKAGE_NAME);
Rafaël Carré's avatar
Rafaël Carré committed
384
385
386
387
388
389
390
391
392
393
394
    localSession_->conn->net_type = localSession_->origin.net_type;
    localSession_->conn->addr_type = localSession_->origin.addr_type;
    localSession_->conn->addr = localSession_->origin.addr;

    // RFC 3264: An offer/answer model session description protocol
    // As the session is created and destroyed through an external signaling mean (SIP), the line
    // should have a value of "0 0".
    localSession_->time.start = 0;
    localSession_->time.stop = 0;

    // For DTMF RTP events
Guillaume Roguez's avatar
Guillaume Roguez committed
395
    constexpr bool audio = true;
396
    localSession_->media_count = 1;
397
    localSession_->media[0] = setMediaDescriptorLines(audio, holding, security);
398
    if (not selectedVideoCodecs.empty()) {
399
        localSession_->media[1] = setMediaDescriptorLines(!audio, holding, security);
400
401
        ++localSession_->media_count;
    }
402

403
    printSession(localSession_, "SDP: Local SDP Session:\n");
404

405
    return pjmedia_sdp_validate(localSession_);
406
}
407

408
bool
409
410
Sdp::createOffer(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
                 const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
411
412
                 sip_utils::KeyExchangeProtocol security,
                 bool holding)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
413
{
414
    if (createLocalSession(selectedAudioCodecs, selectedVideoCodecs, security, holding) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
415
        JAMI_ERR("Failed to create initial offer");
416
417
418
        return false;
    }

419
    if (pjmedia_sdp_neg_create_w_local_offer(memPool_.get(), localSession_, &negotiator_) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
420
        JAMI_ERR("Failed to create an initial SDP negotiator");
421
        return false;
422
    }
Eloi Bail's avatar
Eloi Bail committed
423

424
    return true;
425
426
}

Guillaume Roguez's avatar
Guillaume Roguez committed
427
void Sdp::receiveOffer(const pjmedia_sdp_session* remote,
428
429
                       const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs,
                       const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs,
430
431
                       sip_utils::KeyExchangeProtocol kx,
                       bool holding)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
432
{
433
    if (!remote) {
Adrien Béraud's avatar
Adrien Béraud committed
434
        JAMI_ERR("Remote session is NULL");
435
436
        return;
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
437

438
    printSession(remote, "Remote SDP Session:\n");
439

Guillaume Roguez's avatar
Guillaume Roguez committed
440
    if (not localSession_ and createLocalSession(selectedAudioCodecs,
441
                                                 selectedVideoCodecs, kx, holding) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
442
        JAMI_ERR("Failed to create initial offer");
Tristan Matthews's avatar
Tristan Matthews committed
443
444
        return;
    }
445

446
    remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote);
447

448
    if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(), localSession_,
449
            remoteSession_, &negotiator_) != PJ_SUCCESS)
Adrien Béraud's avatar
Adrien Béraud committed
450
        JAMI_ERR("Failed to initialize negotiator");
451
}
452

Rafaël Carré's avatar
Rafaël Carré committed
453
void Sdp::startNegotiation()
454
{
455
    if (negotiator_ == NULL) {
Adrien Béraud's avatar
Adrien Béraud committed
456
        JAMI_ERR("Can't start negotiation with invalid negotiator");
457
458
459
        return;
    }

460
461
    const pjmedia_sdp_session *active_local;
    const pjmedia_sdp_session *active_remote;
Emmanuel Milou's avatar
Emmanuel Milou committed
462

463
    if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
Adrien Béraud's avatar
Adrien Béraud committed
464
        JAMI_WARN("Negotiator not in right state for negotiation");
465
466
        return;
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
467

468
    if (pjmedia_sdp_neg_negotiate(memPool_.get(), negotiator_, 0) != PJ_SUCCESS)
Rafaël Carré's avatar
Rafaël Carré committed
469
        return;
470

Rafaël Carré's avatar
Rafaël Carré committed
471
    if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS)
Adrien Béraud's avatar
Adrien Béraud committed
472
        JAMI_ERR("Could not retrieve local active session");
473
474
    else
        setActiveLocalSdpSession(active_local);
475

Rafaël Carré's avatar
Rafaël Carré committed
476
    if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS)
Adrien Béraud's avatar
Adrien Béraud committed
477
        JAMI_ERR("Could not retrieve remote active session");
478
479
    else
        setActiveRemoteSdpSession(active_remote);
480
}
481

482

Guillaume Roguez's avatar
Guillaume Roguez committed
483
std::string
484
Sdp::getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep)
Guillaume Roguez's avatar
Guillaume Roguez committed
485
{
Adrien Béraud's avatar
Adrien Béraud committed
486
    sip_utils::register_thread();
Guillaume Roguez's avatar
Guillaume Roguez committed
487
488
489
    static constexpr size_t BUF_SZ = 4096;
    std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> tmpPool_(
        pj_pool_create(&getSIPVoIPLink()->getCachingPool()->factory,
490
                           "tmpSdp", BUF_SZ, BUF_SZ, nullptr),
Guillaume Roguez's avatar
Guillaume Roguez committed
491
492
493
494
        pj_pool_release
    );
    auto cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session);
    if (!cloned) {
Adrien Béraud's avatar
Adrien Béraud committed
495
        JAMI_ERR("Could not clone SDP");
496
        return "";
497
498
    }

499
    // deactivate non-video media
Guillaume Roguez's avatar
Guillaume Roguez committed
500
501
502
503
    bool hasKeep = false;
    for (unsigned i = 0; i < cloned->media_count; i++)
        if (i != media_keep) {
            if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS)
Adrien Béraud's avatar
Adrien Béraud committed
504
                JAMI_ERR("Could not deactivate media");
505
        } else {
Guillaume Roguez's avatar
Guillaume Roguez committed
506
            hasKeep = true;
507
508
        }

Guillaume Roguez's avatar
Guillaume Roguez committed
509
    if (not hasKeep) {
Adrien Béraud's avatar
Adrien Béraud committed
510
        JAMI_DBG("No media to keep present in SDP");
511
512
513
        return "";
    }

Guillaume Roguez's avatar
Guillaume Roguez committed
514
515
516
517
518
519
520
521
522
    // Leaking medias will be dropped with tmpPool_
    for (unsigned i = 0; i < cloned->media_count; i++)
        if (cloned->media[i]->desc.port == 0) {
            std::move(cloned->media+i+1,
                      cloned->media+cloned->media_count,
                      cloned->media+i);
            cloned->media_count--;
            i--;
        }
523

Adrien Béraud's avatar
Adrien Béraud committed
524
525
    for (unsigned i = 0; i < cloned->media_count; i++) {
        auto media = cloned->media[i];
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550

        // filter other codecs
        for (unsigned c=0; c<media->desc.fmt_count; c++) {
            auto& pt = media->desc.fmt[c];
            if (pj_strtoul(&pt) == pt_keep)
                continue;

            while (auto attr = pjmedia_sdp_attr_find2(media->attr_count,
                                                      media->attr,
                                                      "rtpmap", &pt))
                pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);

            while (auto attr = pjmedia_sdp_attr_find2(media->attr_count,
                                                      media->attr,
                                                      "fmt", &pt))
                pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr);

            std::move(media->desc.fmt+c+1,
                      media->desc.fmt+media->desc.fmt_count,
                      media->desc.fmt+c);
            media->desc.fmt_count--;
            c--;
        }

        // we handle crypto ourselfs, don't tell libav about it
551
        pjmedia_sdp_media_remove_all_attr(media, "crypto");
Adrien Béraud's avatar
Adrien Béraud committed
552
553
    }

Guillaume Roguez's avatar
Guillaume Roguez committed
554
555
556
    char buffer[BUF_SZ];
    size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer));
    string sessionStr(buffer, std::min(size, sizeof(buffer)));
557

558
    return sessionStr;
559
}
560

Guillaume Roguez's avatar
Guillaume Roguez committed
561
std::vector<MediaDescription>
Adrien Béraud's avatar
Adrien Béraud committed
562
Sdp::getMediaSlots(const pjmedia_sdp_session* session, bool remote) const
Guillaume Roguez's avatar
Guillaume Roguez committed
563
{
564
565
    static constexpr pj_str_t STR_RTPMAP { (char*) "rtpmap", 6 };
    static constexpr pj_str_t STR_FMTP { (char*) "fmtp", 4 };
566

Guillaume Roguez's avatar
Guillaume Roguez committed
567
568
569
570
571
572
573
574
575
576
577
    std::vector<MediaDescription> ret;
    for (unsigned i = 0; i < session->media_count; i++) {
        auto media = session->media[i];
        ret.emplace_back(MediaDescription());
        MediaDescription& descr = ret.back();
        if (!pj_stricmp2(&media->desc.media, "audio"))
            descr.type = MEDIA_AUDIO;
        else if (!pj_stricmp2(&media->desc.media, "video"))
            descr.type = MEDIA_VIDEO;
        else
            continue;
578

Guillaume Roguez's avatar
Guillaume Roguez committed
579
580
581
        descr.enabled = media->desc.port;
        if (!descr.enabled)
            continue;
582

Guillaume Roguez's avatar
Guillaume Roguez committed
583
584
585
        // get connection info
        pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn;
        if (not conn) {
Adrien Béraud's avatar
Adrien Béraud committed
586
            JAMI_ERR("Could not find connection information for media");
Guillaume Roguez's avatar
Guillaume Roguez committed
587
588
589
590
            continue;
        }
        descr.addr = std::string(conn->addr.ptr, conn->addr.slen);
        descr.addr.setPort(media->desc.port);
591

592
        descr.holding = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "sendonly", nullptr)
Guillaume Roguez's avatar
Guillaume Roguez committed
593
                     || pjmedia_sdp_attr_find2(media->attr_count, media->attr, "inactive", nullptr);
594

Guillaume Roguez's avatar
Guillaume Roguez committed
595
596
597
598
        // get codecs infos
        for (unsigned j = 0; j<media->desc.fmt_count; j++) {
            const auto rtpMapAttribute = pjmedia_sdp_media_find_attr(media, &STR_RTPMAP, &media->desc.fmt[j]);
            if (!rtpMapAttribute) {
Adrien Béraud's avatar
Adrien Béraud committed
599
                JAMI_ERR("Could not find rtpmap attribute");
Guillaume Roguez's avatar
Guillaume Roguez committed
600
601
602
603
604
605
606
                descr.enabled = false;
                continue;
            }
            pjmedia_sdp_rtpmap rtpmap;
            if (pjmedia_sdp_attr_get_rtpmap(rtpMapAttribute, &rtpmap) != PJ_SUCCESS ||
                rtpmap.enc_name.slen == 0)
            {
Adrien Béraud's avatar
Adrien Béraud committed
607
                JAMI_ERR("Could not find payload type %.*s in SDP",
608
                        (int)media->desc.fmt[j].slen, media->desc.fmt[j].ptr);
Guillaume Roguez's avatar
Guillaume Roguez committed
609
610
611
612
                descr.enabled = false;
                continue;
            }
            const std::string codec_raw(rtpmap.enc_name.ptr, rtpmap.enc_name.slen);
613
614
            descr.rtp_clockrate = rtpmap.clock_rate;
            descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate);
Guillaume Roguez's avatar
Guillaume Roguez committed
615
            if (not descr.codec) {
Adrien Béraud's avatar
Adrien Béraud committed
616
                JAMI_ERR("Could not find codec %s", codec_raw.c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
617
618
619
                descr.enabled = false;
                continue;
            }
620
            descr.payload_type = pj_strtoul(&rtpmap.pt);
621
622
623
624
625
626
627
628
            if (descr.type == MEDIA_VIDEO) {
                const auto fmtpAttr = pjmedia_sdp_media_find_attr(media, &STR_FMTP, &media->desc.fmt[j]);
                //descr.bitrate = getOutgoingVideoField(codec, "bitrate");
                if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) {
                    const auto& v = fmtpAttr->value;
                    descr.parameters = std::string(v.ptr, v.ptr + v.slen);
                }
            }
629
            // for now, just keep the first codec only
630
            descr.enabled = true;
Guillaume Roguez's avatar
Guillaume Roguez committed
631
            break;
632
633
        }

Adrien Béraud's avatar
Adrien Béraud committed
634
        if (not remote)
635
            descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type);
Adrien Béraud's avatar
Adrien Béraud committed
636
637
638
639
640
641
642
643
644

        // get crypto info
        std::vector<std::string> crypto;
        for (unsigned j = 0; j < media->attr_count; j++) {
            const auto attribute = media->attr[j];
            if (pj_stricmp2(&attribute->name, "crypto") == 0)
                crypto.emplace_back(attribute->value.ptr, attribute->value.slen);
        }
        descr.crypto = sdesNego_.negotiate(crypto);
645
    }
Guillaume Roguez's avatar
Guillaume Roguez committed
646
    return ret;
647
648
}

Guillaume Roguez's avatar
Guillaume Roguez committed
649
650
std::vector<Sdp::MediaSlot>
Sdp::getMediaSlots() const
651
{
Guillaume Roguez's avatar
Guillaume Roguez committed
652
653
654
655
656
657
658
659
    auto loc = getMediaSlots(activeLocalSession_, false);
    auto rem = getMediaSlots(activeRemoteSession_, true);
    size_t slot_n = std::min(loc.size(), rem.size());
    std::vector<MediaSlot> s;
    s.reserve(slot_n);
    for (decltype(slot_n) i=0; i<slot_n; i++)
        s.emplace_back(std::move(loc[i]), std::move(rem[i]));
    return s;
660
}
661

662
663
664
void
Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands)
{
665
    if (media_index >= localSession_->media_count) {
Adrien Béraud's avatar
Adrien Béraud committed
666
        JAMI_ERR("addIceCandidates failed: cannot access media#%u (may be deactivated)", media_index);
667
668
669
        return;
    }

670
671
672
673
    auto media = localSession_->media[media_index];

    for (const auto &item : cands) {
        pj_str_t val = { (char*) item.c_str(), static_cast<pj_ssize_t>(item.size()) };
674
        pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val);
675
676
677
678
679
680
681
682
683

        if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS)
            throw SdpException("Could not add ICE candidates attribute to media");
    }
}

std::vector<std::string>
Sdp::getIceCandidates(unsigned media_index) const
{
684
    auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_;
685
    auto localSession = activeLocalSession_ ? activeLocalSession_ : localSession_;
686
    if (not session) {
Adrien Béraud's avatar
Adrien Béraud committed
687
        JAMI_ERR("getIceCandidates failed: no remote session");
688
689
        return {};
    }
690
    if (media_index >= session->media_count || media_index >= localSession->media_count) {
Adrien Béraud's avatar
Adrien Béraud committed
691
        JAMI_ERR("getIceCandidates failed: cannot access media#%u (may be deactivated)", media_index);
692
693
        return {};
    }
694
    auto media = session->media[media_index];
695
696
    auto localMedia = localSession->media[media_index];
    if (media->desc.port == 0 || localMedia->desc.port == 0) {
Adrien Béraud's avatar
Adrien Béraud committed
697
        JAMI_ERR("getIceCandidates failed: media#%u is disabled", media_index);
698
699
700
        return {};
    }

701
702
703
704
705
706
707
708
709
710
711
712
    std::vector<std::string> candidates;

    for (unsigned i=0; i < media->attr_count; i++) {
        pjmedia_sdp_attr *attribute = media->attr[i];
        if (pj_stricmp2(&attribute->name, "candidate") == 0)
            candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen));
    }

    return candidates;
}

void
Guillaume Roguez's avatar
Guillaume Roguez committed
713
Sdp::addIceAttributes(const IceTransport::Attribute&& ice_attrs)
714
715
716
717
718
{
    pj_str_t value;
    pjmedia_sdp_attr *attr;

    value = { (char*)ice_attrs.ufrag.c_str(), static_cast<pj_ssize_t>(ice_attrs.ufrag.size()) };
719
    attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value);
720
721
722
723
724

    if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
        throw SdpException("Could not add ICE.ufrag attribute to local SDP");

    value = { (char*)ice_attrs.pwd.c_str(), static_cast<pj_ssize_t>(ice_attrs.pwd.size()) };
725
    attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value);
726
727
728
729
730

    if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS)
        throw SdpException("Could not add ICE.pwd attribute to local SDP");
}

Guillaume Roguez's avatar
Guillaume Roguez committed
731
IceTransport::Attribute
732
733
Sdp::getIceAttributes() const
{
Guillaume Roguez's avatar
Guillaume Roguez committed
734
    IceTransport::Attribute ice_attrs;
735
736
737
    if (auto session = (activeRemoteSession_ ? activeRemoteSession_ : remoteSession_))
        return getIceAttributes(session);
    return {};
738
}
739

740
741
742
743
IceTransport::Attribute
Sdp::getIceAttributes(const pjmedia_sdp_session* session)
{
    IceTransport::Attribute ice_attrs;
744
745
746
747
748
749
750
751
752
753
    for (unsigned i=0; i < session->attr_count; i++) {
        pjmedia_sdp_attr *attribute = session->attr[i];
        if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0)
            ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen);
        else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0)
            ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen);
    }
    return ice_attrs;
}

754
755
void
Sdp::clearIce()
756
{
757
758
    clearIce(localSession_);
    clearIce(remoteSession_);
759
760
}

761
762
void
Sdp::clearIce(pjmedia_sdp_session* session)
763
{
764
    if (not session)
Tristan Matthews's avatar
Tristan Matthews committed
765
        return;
766
767
768
769
770
771
772
    pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag");
    pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-pwd");
    pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate");
    for (unsigned i=0; i < session->media_count; i++) {
        auto media = session->media[i];
        pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate");
    }
773
774
}

Adrien Béraud's avatar
Adrien Béraud committed
775
} // namespace jami