Coverage Report

Created: 2026-09-14 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/deps/libdatachannel/src/description.cpp
Line
Count
Source
1
/**
2
 * Copyright (c) 2019-2020 Paul-Louis Ageneau
3
 * Copyright (c) 2020 Staz Modrzynski
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
 */
9
10
#include "description.hpp"
11
12
#include "impl/internals.hpp"
13
#include "impl/utils.hpp"
14
15
#include <algorithm>
16
#include <array>
17
#include <cctype>
18
#include <chrono>
19
#include <iostream>
20
#include <random>
21
#include <sstream>
22
#include <unordered_map>
23
24
using std::chrono::system_clock;
25
26
namespace {
27
28
using std::string;
29
using std::string_view;
30
31
10.3M
inline bool match_prefix(string_view str, string_view prefix) {
32
10.3M
  return str.size() >= prefix.size() &&
33
10.2M
         std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
34
10.3M
}
35
36
2.94M
inline void trim_end(string &str) {
37
2.94M
  str.erase(
38
3.02M
      std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
39
2.94M
      str.end());
40
2.94M
}
41
42
0
inline string get_first_line(const string &str) {
43
0
  string line;
44
0
  std::istringstream ss(str);
45
0
  std::getline(ss, line);
46
0
  return line;
47
0
}
48
49
4.18M
inline std::pair<string_view, string_view> parse_pair(string_view attr) {
50
4.18M
  string_view key, value;
51
4.18M
  if (size_t separator = attr.find(':'); separator != string::npos) {
52
816k
    key = attr.substr(0, separator);
53
816k
    value = attr.substr(separator + 1);
54
3.36M
  } else {
55
3.36M
    key = attr;
56
3.36M
  }
57
4.18M
  return std::make_pair(std::move(key), std::move(value));
58
4.18M
}
59
60
386k
template <typename T> T to_integer(string_view s) {
61
386k
  const string str(s);
62
386k
  try {
63
386k
    return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
64
386k
  } catch (...) {
65
166
    throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
66
166
  }
67
386k
}
description.cpp:int (anonymous namespace)::to_integer<int>(std::__1::basic_string_view<char, std::__1::char_traits<char> >)
Line
Count
Source
60
366k
template <typename T> T to_integer(string_view s) {
61
366k
  const string str(s);
62
366k
  try {
63
366k
    return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
64
366k
  } catch (...) {
65
143
    throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
66
143
  }
67
366k
}
description.cpp:unsigned short (anonymous namespace)::to_integer<unsigned short>(std::__1::basic_string_view<char, std::__1::char_traits<char> >)
Line
Count
Source
60
792
template <typename T> T to_integer(string_view s) {
61
792
  const string str(s);
62
792
  try {
63
792
    return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
64
792
  } catch (...) {
65
5
    throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
66
5
  }
67
792
}
description.cpp:unsigned long (anonymous namespace)::to_integer<unsigned long>(std::__1::basic_string_view<char, std::__1::char_traits<char> >)
Line
Count
Source
60
853
template <typename T> T to_integer(string_view s) {
61
853
  const string str(s);
62
853
  try {
63
853
    return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
64
853
  } catch (...) {
65
4
    throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
66
4
  }
67
853
}
description.cpp:unsigned int (anonymous namespace)::to_integer<unsigned int>(std::__1::basic_string_view<char, std::__1::char_traits<char> >)
Line
Count
Source
60
18.5k
template <typename T> T to_integer(string_view s) {
61
18.5k
  const string str(s);
62
18.5k
  try {
63
18.5k
    return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
64
18.5k
  } catch (...) {
65
14
    throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
66
14
  }
67
18.5k
}
68
69
} // namespace
70
71
namespace rtc {
72
73
namespace utils = impl::utils;
74
75
Description::Description(const string &sdp, Type type, Role role)
76
8.47k
    : mType(Type::Unspec), mRole(role) {
77
8.47k
  hintType(type);
78
79
8.47k
  int index = -1;
80
8.47k
  shared_ptr<Entry> current;
81
8.47k
  std::istringstream ss(sdp);
82
2.95M
  while (ss) {
83
2.94M
    string line;
84
2.94M
    std::getline(ss, line);
85
2.94M
    trim_end(line);
86
2.94M
    if (line.empty())
87
26.3k
      continue;
88
89
2.92M
    if (match_prefix(line, "m=")) { // Media description line (aka m-line)
90
748k
      current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
91
92
2.17M
    } else if (match_prefix(line, "o=")) { // Origin line
93
2.90k
      std::istringstream origin(line.substr(2));
94
2.90k
      origin >> mUsername >> mSessionId;
95
96
2.17M
    } else if (match_prefix(line, "a=")) { // Attribute line
97
2.08M
      string attr = line.substr(2);
98
2.08M
      auto [key, value] = parse_pair(attr);
99
100
2.08M
      if (key == "setup") {
101
2.40k
        if (value == "active")
102
680
          mRole = Role::Active;
103
1.72k
        else if (value == "passive")
104
447
          mRole = Role::Passive;
105
1.28k
        else
106
1.28k
          mRole = Role::ActPass;
107
108
2.08M
      } else if (key == "fingerprint") {
109
        // RFC 8122: The fingerprint attribute may be either a session-level or a
110
        // media-level SDP attribute. If it is a session-level attribute, it applies to all
111
        // TLS sessions for which no media-level fingerprint attribute is defined.
112
9.90k
        if (!mFingerprint || index == 0) { // first media overrides session-level
113
9.50k
          auto fingerprintExploded = utils::explode(string(value), ' ');
114
9.50k
          if (fingerprintExploded.size() != 2) {
115
3.42k
            PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
116
3.42k
            continue;
117
3.42k
          }
118
119
6.08k
          auto first = fingerprintExploded.at(0);
120
6.08k
          std::transform(first.begin(), first.end(), first.begin(),
121
2.36M
                         [](char c) { return char(std::tolower(c)); });
122
123
6.08k
          std::optional<CertificateFingerprint::Algorithm> fingerprintAlgorithm;
124
125
6.08k
          for (auto a : std::array<CertificateFingerprint::Algorithm, 5>{
126
6.08k
                   CertificateFingerprint::Algorithm::Sha1,
127
6.08k
                   CertificateFingerprint::Algorithm::Sha224,
128
6.08k
                   CertificateFingerprint::Algorithm::Sha256,
129
6.08k
                   CertificateFingerprint::Algorithm::Sha384,
130
25.1k
                   CertificateFingerprint::Algorithm::Sha512}) {
131
25.1k
            if (first == CertificateFingerprint::AlgorithmIdentifier(a)) {
132
1.77k
              fingerprintAlgorithm = a;
133
1.77k
              break;
134
1.77k
            }
135
25.1k
          }
136
137
6.08k
          if (fingerprintAlgorithm.has_value()) {
138
1.77k
            setFingerprint(CertificateFingerprint{
139
1.77k
                fingerprintAlgorithm.value(), std::move(fingerprintExploded.at(1))});
140
4.31k
          } else {
141
4.31k
            PLOG_WARNING << "Unknown certificate fingerprint algorithm: " << first;
142
4.31k
          }
143
6.08k
        }
144
2.07M
      } else if (key == "ice-ufrag") {
145
        // RFC 8839: The "ice-pwd" and "ice-ufrag" attributes can appear at either the
146
        // session-level or media-level. When present in both, the value in the media-level
147
        // takes precedence.
148
3.31k
        if (!mIceUfrag || index == 0) // media-level for first media overrides session-level
149
608
          mIceUfrag = value;
150
2.06M
      } else if (key == "ice-pwd") {
151
        // RFC 8839: The "ice-pwd" and "ice-ufrag" attributes can appear at either the
152
        // session-level or media-level. When present in both, the value in the media-level
153
        // takes precedence.
154
1.31k
        if (!mIcePwd || index == 0) // media-level for first media overrides session-level
155
547
          mIcePwd = value;
156
2.06M
      } else if (key == "ice-options") {
157
        // RFC 8839: The "ice-options" attribute is a session-level and media-level
158
        // attribute.
159
1.11k
        if (mIceOptions.empty())
160
707
          mIceOptions = utils::explode(string(value), ',');
161
2.06M
      } else if (key == "candidate") {
162
17.0k
        addCandidate(Candidate(attr, bundleMid()));
163
2.05M
      } else if (key == "end-of-candidates") {
164
479
        mEnded = true;
165
2.04M
      } else if (current) {
166
1.22M
        current->parseSdpLine(std::move(line));
167
1.22M
      } else {
168
823k
        mAttributes.emplace_back(attr);
169
823k
      }
170
171
2.08M
    } else if (current) {
172
78.5k
      current->parseSdpLine(std::move(line));
173
78.5k
    }
174
2.92M
  }
175
176
8.47k
  if (mUsername.empty())
177
6.58k
    mUsername = "rtc";
178
179
8.47k
  if (mSessionId.empty()) {
180
6.67k
    auto uniform = std::bind(std::uniform_int_distribution<uint32_t>(), utils::random_engine());
181
6.67k
    mSessionId = std::to_string(uniform());
182
6.67k
  }
183
8.47k
}
184
185
Description::Description(const string &sdp, string typeString)
186
8.47k
    : Description(sdp, !typeString.empty() ? stringToType(typeString) : Type::Unspec,
187
8.47k
                  Role::ActPass) {}
188
189
0
Description::Type Description::type() const { return mType; }
190
191
0
string Description::typeString() const { return typeToString(mType); }
192
193
0
Description::Role Description::role() const { return mRole; }
194
195
33.8k
string Description::bundleMid() const {
196
  // Get the mid of the first non-removed media
197
33.8k
  for (const auto &entry : mEntries)
198
14.1k
    if (!entry->isRemoved())
199
7.82k
      return entry->mid();
200
201
26.0k
  return "0";
202
33.8k
}
203
204
0
optional<string> Description::iceUfrag() const { return mIceUfrag; }
205
206
0
std::vector<string> Description::iceOptions() const { return mIceOptions; }
207
208
0
optional<string> Description::icePwd() const { return mIcePwd; }
209
210
0
optional<CertificateFingerprint> Description::fingerprint() const { return mFingerprint; }
211
212
0
bool Description::ended() const { return mEnded; }
213
214
8.47k
void Description::hintType(Type type) {
215
8.47k
  if (mType == Type::Unspec)
216
8.47k
    mType = type;
217
8.47k
}
218
219
0
void Description::addIceOption(string option) {
220
0
  if (std::find(mIceOptions.begin(), mIceOptions.end(), option) == mIceOptions.end())
221
0
    mIceOptions.emplace_back(std::move(option));
222
0
}
223
224
0
void Description::removeIceOption(const string &option) {
225
0
  mIceOptions.erase(std::remove(mIceOptions.begin(), mIceOptions.end(), option),
226
0
                    mIceOptions.end());
227
0
}
228
229
0
void Description::setIceAttribute(string ufrag, string pwd) {
230
0
  mIceUfrag = std::move(ufrag);
231
0
  mIcePwd = std::move(pwd);
232
0
}
233
234
1.77k
void Description::setFingerprint(CertificateFingerprint f) {
235
1.77k
  if (!f.isValid())
236
152
    throw std::invalid_argument("Invalid " +
237
152
                                CertificateFingerprint::AlgorithmIdentifier(f.algorithm) +
238
152
                                " fingerprint \"" + f.value + "\"");
239
240
1.62k
  std::transform(f.value.begin(), f.value.end(), f.value.begin(),
241
134k
                 [](char c) { return char(std::toupper(c)); });
242
1.62k
  mFingerprint = std::move(f);
243
1.62k
}
244
245
0
std::vector<string> Description::Entry::attributes() const { return mAttributes; }
246
247
0
void Description::Entry::addAttribute(string attr) {
248
0
  if (std::find(mAttributes.begin(), mAttributes.end(), attr) == mAttributes.end())
249
0
    mAttributes.emplace_back(std::move(attr));
250
0
}
251
252
0
void Description::Entry::removeAttribute(const string &attr) {
253
0
  mAttributes.erase(
254
0
      std::remove_if(mAttributes.begin(), mAttributes.end(),
255
0
                     [&](const auto &a) { return a == attr || parse_pair(a).first == attr; }),
256
0
      mAttributes.end());
257
0
}
258
259
0
std::vector<Candidate> Description::candidates() const { return mCandidates; }
260
261
0
std::vector<Candidate> Description::extractCandidates() {
262
0
  std::vector<Candidate> result;
263
0
  std::swap(mCandidates, result);
264
0
  mEnded = false;
265
0
  return result;
266
0
}
267
268
16.7k
bool Description::hasCandidate(const Candidate &candidate) const {
269
16.7k
  return std::find(mCandidates.begin(), mCandidates.end(), candidate) != mCandidates.end();
270
16.7k
}
271
272
16.7k
void Description::addCandidate(Candidate candidate) {
273
16.7k
  candidate.hintMid(bundleMid());
274
275
16.7k
  if (!hasCandidate(candidate))
276
6.36k
    mCandidates.emplace_back(std::move(candidate));
277
16.7k
}
278
279
0
void Description::addCandidates(std::vector<Candidate> candidates) {
280
0
  for (Candidate candidate : candidates)
281
0
    addCandidate(std::move(candidate));
282
0
}
283
284
0
void Description::endCandidates() { mEnded = true; }
285
286
0
Description::operator string() const { return generateSdp("\r\n"); }
287
288
0
string Description::generateSdp(string_view eol) const {
289
0
  std::ostringstream sdp;
290
291
  // Header
292
0
  sdp << "v=0" << eol;
293
0
  sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
294
0
  sdp << "s=-" << eol;
295
0
  sdp << "t=0 0" << eol;
296
297
  // BUNDLE (RFC 8843 Negotiating Media Multiplexing Using the Session Description Protocol)
298
  // https://www.rfc-editor.org/rfc/rfc8843.html
299
0
  std::ostringstream bundleGroup;
300
0
  for (const auto &entry : mEntries)
301
0
    if (!entry->isRemoved())
302
0
      bundleGroup << ' ' << entry->mid();
303
304
0
  if (!bundleGroup.str().empty())
305
0
    sdp << "a=group:BUNDLE" << bundleGroup.str() << eol;
306
307
  // Lip-sync
308
0
  std::ostringstream lsGroup;
309
0
  for (const auto &entry : mEntries)
310
0
    if (!entry->isRemoved() && entry != mApplication)
311
0
      lsGroup << ' ' << entry->mid();
312
313
0
  if (!lsGroup.str().empty())
314
0
    sdp << "a=group:LS" << lsGroup.str() << eol;
315
316
  // Session-level attributes
317
0
  sdp << "a=msid-semantic:WMS *" << eol;
318
0
  if (!mIceOptions.empty())
319
0
    sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
320
0
  if (mFingerprint)
321
0
    sdp << "a=fingerprint:"
322
0
        << CertificateFingerprint::AlgorithmIdentifier(mFingerprint->algorithm) << " "
323
0
        << mFingerprint->value << eol;
324
325
0
  for (const auto &attr : mAttributes)
326
0
    sdp << "a=" << attr << eol;
327
328
0
  auto cand = defaultCandidate();
329
0
  const string addr = cand && cand->isResolved()
330
0
                          ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
331
0
                             " " + *cand->address())
332
0
                          : "IP4 0.0.0.0";
333
0
  const uint16_t port =
334
0
      cand && cand->isResolved() ? *cand->port() : 9; // Port 9 is the discard protocol
335
336
  // Entries
337
0
  bool first = true;
338
0
  for (const auto &entry : mEntries) {
339
0
    sdp << entry->generateSdp(eol, addr, port);
340
341
    // RFC 8829: Attributes that SDP permits to be at either the session level or the media level
342
    // SHOULD generally be at the media level even if they are identical.
343
0
    sdp << "a=setup:" << mRole << eol;
344
0
    if (mIceUfrag)
345
0
      sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
346
0
    if (mIcePwd)
347
0
      sdp << "a=ice-pwd:" << *mIcePwd << eol;
348
349
0
    if (!entry->isRemoved() && std::exchange(first, false)) {
350
      // Candidates
351
0
      for (const auto &candidate : mCandidates)
352
0
        sdp << string(candidate) << eol;
353
354
0
      if (mEnded)
355
0
        sdp << "a=end-of-candidates" << eol;
356
0
    }
357
0
  }
358
359
0
  return sdp.str();
360
0
}
361
362
0
string Description::generateApplicationSdp(string_view eol) const {
363
0
  std::ostringstream sdp;
364
365
  // Header
366
0
  sdp << "v=0" << eol;
367
0
  sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
368
0
  sdp << "s=-" << eol;
369
0
  sdp << "t=0 0" << eol;
370
371
0
  auto cand = defaultCandidate();
372
0
  const string addr = cand && cand->isResolved()
373
0
                          ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
374
0
                             " " + *cand->address())
375
0
                          : "IP4 0.0.0.0";
376
0
  const uint16_t port =
377
0
      cand && cand->isResolved() ? *cand->port() : 9; // Port 9 is the discard protocol
378
379
  // Session-level attributes
380
0
  sdp << "a=msid-semantic:WMS *" << eol;
381
0
  if (!mIceOptions.empty())
382
0
    sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
383
384
0
  for (const auto &attr : mAttributes)
385
0
    sdp << "a=" << attr << eol;
386
387
  // Application
388
0
  auto app = mApplication ? mApplication : std::make_shared<Application>();
389
0
  sdp << app->generateSdp(eol, addr, port);
390
391
  // Media-level attributes
392
0
  sdp << "a=setup:" << mRole << eol;
393
0
  if (mIceUfrag)
394
0
    sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
395
0
  if (mIcePwd)
396
0
    sdp << "a=ice-pwd:" << *mIcePwd << eol;
397
0
  if (mFingerprint)
398
0
    sdp << "a=fingerprint:"
399
0
        << CertificateFingerprint::AlgorithmIdentifier(mFingerprint->algorithm) << " "
400
0
        << mFingerprint->value << eol;
401
402
  // Candidates
403
0
  for (const auto &candidate : mCandidates)
404
0
    sdp << string(candidate) << eol;
405
406
0
  if (mEnded)
407
0
    sdp << "a=end-of-candidates" << eol;
408
409
0
  return sdp.str();
410
0
}
411
412
0
optional<Candidate> Description::defaultCandidate() const {
413
  // Return the first host candidate with highest priority, favoring IPv4
414
0
  optional<Candidate> result;
415
0
  for (const auto &c : mCandidates) {
416
0
    if (c.type() == Candidate::Type::Host) {
417
0
      if (!result ||
418
0
          (result->family() == Candidate::Family::Ipv6 &&
419
0
           c.family() == Candidate::Family::Ipv4) ||
420
0
          (result->family() == c.family() && result->priority() < c.priority()))
421
0
        result.emplace(c);
422
0
    }
423
0
  }
424
0
  return result;
425
0
}
426
427
748k
shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
428
748k
  string type = mline.substr(0, mline.find(' '));
429
748k
  if (type == "application") {
430
2.74k
    removeApplication();
431
2.74k
    mApplication = std::make_shared<Application>(mline, std::move(mid));
432
2.74k
    mEntries.emplace_back(mApplication);
433
2.74k
    return mApplication;
434
745k
  } else {
435
745k
    auto media = std::make_shared<Media>(std::move(mline), std::move(mid), dir);
436
745k
    mEntries.emplace_back(media);
437
745k
    return media;
438
745k
  }
439
748k
}
440
441
2.74k
void Description::removeApplication() {
442
2.74k
  if (!mApplication)
443
571
    return;
444
445
2.17k
  auto it = std::find(mEntries.begin(), mEntries.end(), mApplication);
446
2.17k
  if (it != mEntries.end())
447
2.17k
    mEntries.erase(it);
448
449
2.17k
  mApplication.reset();
450
2.17k
}
451
452
0
bool Description::hasApplication() const { return mApplication && !mApplication->isRemoved(); }
453
454
0
bool Description::hasAudioOrVideo() const {
455
0
  for (auto entry : mEntries)
456
0
    if (entry != mApplication && !entry->isRemoved())
457
0
      return true;
458
459
0
  return false;
460
0
}
461
462
0
bool Description::hasMid(string_view mid) const {
463
0
  for (const auto &entry : mEntries)
464
0
    if (entry->mid() == mid)
465
0
      return true;
466
467
0
  return false;
468
0
}
469
470
0
int Description::addMedia(Media media) {
471
0
  mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
472
0
  return int(mEntries.size()) - 1;
473
0
}
474
475
0
int Description::addMedia(Application application) {
476
0
  removeApplication();
477
0
  mApplication = std::make_shared<Application>(std::move(application));
478
0
  mEntries.emplace_back(mApplication);
479
0
  return int(mEntries.size()) - 1;
480
0
}
481
482
0
int Description::addApplication(string mid) { return addMedia(Application(std::move(mid))); }
483
484
0
const Description::Application *Description::application() const { return mApplication.get(); }
485
486
0
Description::Application *Description::application() { return mApplication.get(); }
487
488
0
int Description::addVideo(string mid, Direction dir) {
489
0
  return addMedia(Video(std::move(mid), dir));
490
0
}
491
492
0
int Description::addAudio(string mid, Direction dir) {
493
0
  return addMedia(Audio(std::move(mid), dir));
494
0
}
495
496
0
void Description::clearMedia() {
497
0
  mEntries.clear();
498
0
  mApplication.reset();
499
0
}
500
501
0
variant<Description::Media *, Description::Application *> Description::media(int index) {
502
0
  if (index < 0 || index >= int(mEntries.size()))
503
0
    throw std::out_of_range("Media index out of range");
504
505
0
  const auto &entry = mEntries[index];
506
0
  if (entry == mApplication) {
507
0
    auto result = dynamic_cast<Application *>(entry.get());
508
0
    if (!result)
509
0
      throw std::logic_error("Bad type of application in description");
510
511
0
    return result;
512
513
0
  } else {
514
0
    auto result = dynamic_cast<Media *>(entry.get());
515
0
    if (!result)
516
0
      throw std::logic_error("Bad type of media in description");
517
518
0
    return result;
519
0
  }
520
0
}
521
522
0
variant<const Description::Media *, const Description::Application *> Description::media(int index) const {
523
0
  if (index < 0 || index >= int(mEntries.size()))
524
0
    throw std::out_of_range("Media index out of range");
525
526
0
  const auto &entry = mEntries[index];
527
0
  if (entry == mApplication) {
528
0
    auto result = dynamic_cast<Application *>(entry.get());
529
0
    if (!result)
530
0
      throw std::logic_error("Bad type of application in description");
531
532
0
    return result;
533
534
0
  } else {
535
0
    auto result = dynamic_cast<Media *>(entry.get());
536
0
    if (!result)
537
0
      throw std::logic_error("Bad type of media in description");
538
539
0
    return result;
540
0
  }
541
0
}
542
543
0
int Description::mediaCount() const { return int(mEntries.size()); }
544
545
0
string Description::sessionId() const { return mSessionId; }
546
547
Description::Entry::Entry(const string &mline, string mid, Direction dir)
548
748k
    : mMid(std::move(mid)), mDirection(dir) {
549
550
748k
  uint16_t port = 0;
551
748k
  std::istringstream ss(match_prefix(mline, "m=") ? mline.substr(2) : mline);
552
748k
  ss >> mType;
553
748k
  ss >> port;
554
748k
  ss >> mProtocol;
555
748k
  ss >> std::ws;
556
748k
  std::getline(ss, mDescription);
557
558
748k
  if (mType.empty() || mProtocol.empty())
559
402
    throw std::invalid_argument("Invalid media description line");
560
561
  // RFC 3264: Existing media streams are removed by creating a new SDP with the port number for
562
  // that stream set to zero.
563
  // RFC 8843: If the offerer assigns a zero port value to a bundled "m=" section, but does not
564
  // include an SDP 'bundle-only' attribute in the "m=" section, it is an indication that the
565
  // offerer wants to disable the "m=" section.
566
747k
  mIsRemoved = (port == 0);
567
747k
}
568
569
0
string Description::Entry::type() const { return mType; }
570
571
0
string Description::Entry::protocol() const { return mProtocol; }
572
573
745k
string Description::Entry::description() const { return mDescription; }
574
575
7.82k
string Description::Entry::mid() const { return mMid; }
576
577
0
Description::Direction Description::Entry::direction() const { return mDirection; }
578
579
0
void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
580
581
14.1k
bool Description::Entry::isRemoved() const { return mIsRemoved; }
582
583
0
void Description::Entry::markRemoved() { mIsRemoved = true; }
584
585
0
std::vector<string> Description::attributes() const { return mAttributes; }
586
587
0
void Description::addAttribute(string attr) {
588
0
  if (std::find(mAttributes.begin(), mAttributes.end(), attr) == mAttributes.end())
589
0
    mAttributes.emplace_back(std::move(attr));
590
0
}
591
592
0
void Description::Entry::addRid(string rid) { mRids.emplace_back(rid); }
593
594
0
void Description::removeAttribute(const string &attr) {
595
0
  mAttributes.erase(
596
0
      std::remove_if(mAttributes.begin(), mAttributes.end(),
597
0
                     [&](const auto &a) { return a == attr || parse_pair(a).first == attr; }),
598
0
      mAttributes.end());
599
0
}
600
601
0
std::vector<int> Description::Entry::extIds() {
602
0
  std::vector<int> result;
603
0
  for (auto it = mExtMaps.begin(); it != mExtMaps.end(); ++it)
604
0
    result.push_back(it->first);
605
606
0
  return result;
607
0
}
608
609
0
Description::Entry::ExtMap *Description::Entry::extMap(int id) {
610
0
  auto it = mExtMaps.find(id);
611
0
  if (it == mExtMaps.end())
612
0
    throw std::invalid_argument("extmap not found");
613
614
0
  return &it->second;
615
0
}
616
617
0
const Description::Entry::ExtMap *Description::Entry::extMap(int id) const {
618
0
  auto it = mExtMaps.find(id);
619
0
  if (it == mExtMaps.end())
620
0
    throw std::invalid_argument("extmap not found");
621
622
0
  return &it->second;
623
0
}
624
625
0
void Description::Entry::addExtMap(ExtMap map) {
626
0
  auto id = map.id;
627
0
  mExtMaps.emplace(id, std::move(map));
628
0
}
629
630
0
void Description::Entry::removeExtMap(int id) { mExtMaps.erase(id); }
631
632
0
Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", 9); }
633
634
0
string Description::Entry::generateSdp(string_view eol, string_view addr, uint16_t port) const {
635
0
  std::ostringstream sdp;
636
  // RFC 3264: Existing media streams are removed by creating a new SDP with the port number for
637
  // that stream set to zero. [...] A stream that is offered with a port of zero MUST be marked
638
  // with port zero in the answer.
639
0
  sdp << "m=" << type() << ' ' << (mIsRemoved ? 0 : port) << ' ' << protocol() << ' '
640
0
      << description() << eol;
641
0
  sdp << "c=IN " << addr << eol;
642
0
  sdp << generateSdpLines(eol);
643
644
0
  return sdp.str();
645
0
}
646
647
0
string Description::Entry::generateSdpLines(string_view eol) const {
648
0
  std::ostringstream sdp;
649
0
  sdp << "a=mid:" << mMid << eol;
650
651
0
  for (auto it = mExtMaps.begin(); it != mExtMaps.end(); ++it) {
652
0
    auto &map = it->second;
653
654
0
    sdp << "a=extmap:" << map.id;
655
0
    if (map.direction != Direction::Unknown)
656
0
      sdp << '/' << map.direction;
657
658
0
    sdp << ' ' << map.uri;
659
0
    if (!map.attributes.empty())
660
0
      sdp << ' ' << map.attributes;
661
662
0
    sdp << eol;
663
0
  }
664
665
0
  if (mDirection != Direction::Unknown)
666
0
    sdp << "a=" << mDirection << eol;
667
668
0
  for (const auto &attr : mAttributes) {
669
0
    if (mRids.size() != 0 && match_prefix(attr, "ssrc:")) {
670
0
      continue;
671
0
    }
672
673
0
    sdp << "a=" << attr << eol;
674
0
  }
675
676
0
  for (const auto &rid : mRids) {
677
0
    sdp << "a=rid:" << rid << " send" << eol;
678
0
  }
679
680
0
  if (mRids.size() != 0) {
681
0
    sdp << "a=simulcast:send ";
682
683
0
    bool first = true;
684
0
    for (const auto &rid : mRids) {
685
0
      if (first) {
686
0
        first = false;
687
0
      } else {
688
0
        sdp << ";";
689
0
      }
690
691
0
      sdp << rid;
692
0
    }
693
694
0
    sdp << eol;
695
0
  }
696
697
0
  return sdp.str();
698
0
}
699
700
947k
void Description::Entry::parseSdpLine(string_view line) {
701
947k
  if (match_prefix(line, "a=")) {
702
869k
    string_view attr = line.substr(2);
703
869k
    auto [key, value] = parse_pair(attr);
704
705
869k
    if (key == "mid") {
706
1.05k
      mMid = value;
707
868k
    } else if (key == "extmap") {
708
8.82k
      auto id = Description::Media::ExtMap::parseId(value);
709
8.82k
      auto it = mExtMaps.find(id);
710
8.82k
      if (it == mExtMaps.end())
711
5.54k
        it = mExtMaps.insert(std::make_pair(id, Description::Media::ExtMap(value))).first;
712
3.28k
      else
713
3.28k
        it->second.setDescription(value);
714
715
859k
    } else if (attr == "sendonly")
716
408
      mDirection = Direction::SendOnly;
717
859k
    else if (attr == "recvonly")
718
462
      mDirection = Direction::RecvOnly;
719
858k
    else if (key == "sendrecv")
720
549
      mDirection = Direction::SendRecv;
721
858k
    else if (key == "inactive")
722
544
      mDirection = Direction::Inactive;
723
857k
    else if (key == "bundle-only") {
724
      // RFC 8843: When an offerer generates a subsequent offer, in which it wants to disable
725
      // a bundled "m=" section from a BUNDLE group, the offerer [...] MUST NOT assign an SDP
726
      // 'bundle-only' attribute to the "m=" section.
727
404
      mIsRemoved = false;
728
857k
    } else {
729
857k
      mAttributes.emplace_back(attr);
730
857k
    }
731
869k
  }
732
947k
}
733
734
8.82k
int Description::Entry::ExtMap::parseId(string_view description) {
735
8.82k
  size_t p = description.find(' ');
736
8.82k
  return to_integer<int>(description.substr(0, p));
737
8.82k
}
738
739
0
Description::Entry::ExtMap::ExtMap(int id, string uri, Direction direction) {
740
0
  this->id = id;
741
0
  this->uri = std::move(uri);
742
0
  this->direction = direction;
743
0
}
744
745
5.54k
Description::Entry::ExtMap::ExtMap(string_view description) { setDescription(description); }
746
747
8.81k
void Description::Entry::ExtMap::setDescription(string_view description) {
748
8.81k
  const size_t uriStart = description.find(' ');
749
8.81k
  if (uriStart == string::npos)
750
174
    throw std::invalid_argument("Invalid description for extmap");
751
752
8.64k
  const string_view idAndDirection = description.substr(0, uriStart);
753
8.64k
  const size_t idSplit = idAndDirection.find('/');
754
8.64k
  if (idSplit == string::npos) {
755
6.77k
    this->id = to_integer<int>(idAndDirection);
756
6.77k
  } else {
757
1.86k
    this->id = to_integer<int>(idAndDirection.substr(0, idSplit));
758
759
1.86k
    const string_view directionStr = idAndDirection.substr(idSplit + 1);
760
1.86k
    if (directionStr == "sendonly")
761
412
      this->direction = Direction::SendOnly;
762
1.45k
    else if (directionStr == "recvonly")
763
430
      this->direction = Direction::RecvOnly;
764
1.02k
    else if (directionStr == "sendrecv")
765
395
      this->direction = Direction::SendRecv;
766
627
    else if (directionStr == "inactive")
767
416
      this->direction = Direction::Inactive;
768
211
    else
769
211
      throw std::invalid_argument("Invalid direction for extmap");
770
1.86k
  }
771
772
8.42k
  const string_view uriAndAttributes = description.substr(uriStart + 1);
773
8.42k
  const size_t attributeSplit = uriAndAttributes.find(' ');
774
775
8.42k
  if (attributeSplit == string::npos)
776
6.73k
    this->uri = uriAndAttributes;
777
1.69k
  else {
778
1.69k
    this->uri = uriAndAttributes.substr(0, attributeSplit);
779
1.69k
    this->attributes = uriAndAttributes.substr(attributeSplit + 1);
780
1.69k
  }
781
8.42k
}
782
783
void Description::Media::addSSRC(uint32_t ssrc, optional<string> name, optional<string> msid,
784
0
                                 optional<string> trackId) {
785
0
  if (name) {
786
0
    mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + *name);
787
0
    mCNameMap.emplace(ssrc, *name);
788
0
  } else {
789
0
    mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
790
0
  }
791
792
0
  if (msid) {
793
0
    mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " msid:" + *msid + " " +
794
0
                             trackId.value_or(*msid));
795
0
    mAttributes.emplace_back("msid:" + *msid + " " + trackId.value_or(*msid));
796
0
  }
797
798
0
  mSsrcs.emplace_back(ssrc);
799
0
}
800
801
0
void Description::Media::removeSSRC(uint32_t ssrc) {
802
0
  string prefix = "ssrc:" + std::to_string(ssrc);
803
0
  mAttributes.erase(std::remove_if(mAttributes.begin(), mAttributes.end(),
804
0
                                   [&](const auto &a) { return match_prefix(a, prefix); }),
805
0
                    mAttributes.end());
806
807
0
  mSsrcs.erase(std::remove(mSsrcs.begin(), mSsrcs.end(), ssrc), mSsrcs.end());
808
0
}
809
810
void Description::Media::replaceSSRC(uint32_t old, uint32_t ssrc, optional<string> name,
811
0
                                     optional<string> msid, optional<string> trackID) {
812
0
  removeSSRC(old);
813
0
  addSSRC(ssrc, std::move(name), std::move(msid), std::move(trackID));
814
0
}
815
816
18.5k
bool Description::Media::hasSSRC(uint32_t ssrc) const {
817
18.5k
  return std::find(mSsrcs.begin(), mSsrcs.end(), ssrc) != mSsrcs.end();
818
18.5k
}
819
820
0
void Description::Media::clearSSRCs() {
821
0
  auto it = mAttributes.begin();
822
0
  while (it != mAttributes.end()) {
823
0
    if (match_prefix(*it, "ssrc:"))
824
0
      it = mAttributes.erase(it);
825
0
    else
826
0
      ++it;
827
0
  }
828
829
0
  mSsrcs.clear();
830
0
  mCNameMap.clear();
831
0
}
832
833
0
std::vector<uint32_t> Description::Media::getSSRCs() const { return mSsrcs; }
834
835
0
optional<string> Description::Media::getCNameForSsrc(uint32_t ssrc) const {
836
0
  auto it = mCNameMap.find(ssrc);
837
0
  if (it != mCNameMap.end()) {
838
0
    return it->second;
839
0
  }
840
0
  return nullopt;
841
0
}
842
843
Description::Application::Application(string mid)
844
0
    : Entry("application 9 UDP/DTLS/SCTP webrtc-datachannel", std::move(mid), Direction::SendRecv) {
845
0
}
846
847
Description::Application::Application(const string &mline, string mid)
848
2.74k
    : Entry(mline, std::move(mid), Direction::SendRecv) {}
849
850
0
Description::Application Description::Application::reciprocate() const {
851
0
  Application reciprocated(*this);
852
853
0
  reciprocated.mMaxMessageSize.reset();
854
855
0
  return reciprocated;
856
0
}
857
858
0
void Description::Application::setSctpPort(uint16_t port) { mSctpPort = port; }
859
860
0
void Description::Application::hintSctpPort(uint16_t port) { mSctpPort = mSctpPort.value_or(port); }
861
862
0
void Description::Application::setMaxMessageSize(size_t size) { mMaxMessageSize = size; }
863
864
0
optional<uint16_t> Description::Application::sctpPort() const { return mSctpPort; }
865
866
0
optional<size_t> Description::Application::maxMessageSize() const { return mMaxMessageSize; }
867
868
0
string Description::Application::generateSdpLines(string_view eol) const {
869
0
  std::ostringstream sdp;
870
0
  sdp << Entry::generateSdpLines(eol);
871
872
0
  if (mSctpPort)
873
0
    sdp << "a=sctp-port:" << *mSctpPort << eol;
874
875
0
  if (mMaxMessageSize)
876
0
    sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
877
878
0
  return sdp.str();
879
0
}
880
881
25.3k
void Description::Application::parseSdpLine(string_view line) {
882
25.3k
  if (match_prefix(line, "a=")) {
883
21.1k
    string_view attr = line.substr(2);
884
21.1k
    auto [key, value] = parse_pair(attr);
885
886
21.1k
    if (key == "sctp-port") {
887
792
      mSctpPort = to_integer<uint16_t>(value);
888
20.3k
    } else if (key == "max-message-size") {
889
853
      mMaxMessageSize = to_integer<size_t>(value);
890
19.4k
    } else {
891
19.4k
      Entry::parseSdpLine(line);
892
19.4k
    }
893
21.1k
  } else {
894
4.27k
    Entry::parseSdpLine(line);
895
4.27k
  }
896
25.3k
}
897
898
Description::Media::Media(const string &mline, string mid, Direction dir)
899
745k
    : Entry(mline, std::move(mid), dir) {
900
745k
  std::istringstream ss(Entry::description());
901
745k
  int payloadType;
902
1.29M
  while (ss >> payloadType)
903
549k
    mOrderedPayloadTypes.push_back(payloadType);
904
745k
}
905
906
0
Description::Media::Media(const string &sdp) : Media(get_first_line(sdp), "", Direction::Unknown) {
907
0
  string line;
908
0
  std::istringstream ss(sdp);
909
0
  std::getline(ss, line); // discard first line
910
0
  while (ss) {
911
0
    std::getline(ss, line);
912
0
    trim_end(line);
913
0
    if (line.empty())
914
0
      continue;
915
916
0
    parseSdpLine(line);
917
0
  }
918
919
0
  if (mid().empty())
920
0
    throw std::invalid_argument("Missing mid in media description");
921
0
}
922
923
0
string Description::Media::description() const {
924
0
  std::ostringstream ss;
925
0
  for (auto it = mOrderedPayloadTypes.begin(); it != mOrderedPayloadTypes.end(); ++it) {
926
0
    if (it != mOrderedPayloadTypes.begin())
927
0
      ss << ' ';
928
929
0
    ss << *it;
930
0
  }
931
932
0
  return ss.str();
933
0
}
934
935
0
Description::Media Description::Media::reciprocate() const {
936
0
  Media reciprocated(*this);
937
938
  // Invert direction
939
0
  switch (reciprocated.direction()) {
940
0
  case Direction::RecvOnly:
941
0
    reciprocated.setDirection(Direction::SendOnly);
942
0
    break;
943
0
  case Direction::SendOnly:
944
0
    reciprocated.setDirection(Direction::RecvOnly);
945
0
    break;
946
0
  default:
947
    // We are good
948
0
    break;
949
0
  }
950
951
  // Invert directions of extmap
952
0
  auto &extMaps = reciprocated.mExtMaps;
953
0
  for (auto it = extMaps.begin(); it != extMaps.end(); ++it) {
954
0
    auto &map = it->second;
955
0
    switch (map.direction) {
956
0
    case Direction::RecvOnly:
957
0
      map.direction = Direction::SendOnly;
958
0
      break;
959
0
    case Direction::SendOnly:
960
0
      map.direction = Direction::RecvOnly;
961
0
      break;
962
0
    default:
963
      // We are good
964
0
      break;
965
0
    }
966
0
  }
967
968
  // Clear sent SSRCs
969
0
  reciprocated.clearSSRCs();
970
971
  // Remove rtcp-rsize attribute as Reduced-Size RTCP is not supported (see RFC 5506)
972
0
  reciprocated.removeAttribute("rtcp-rsize");
973
974
0
  return reciprocated;
975
0
}
976
977
0
int Description::Media::bitrate() const { return mBas; }
978
979
0
void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
980
981
0
bool Description::Media::hasPayloadType(int payloadType) const {
982
0
  return mRtpMaps.find(payloadType) != mRtpMaps.end();
983
0
}
984
985
0
std::vector<int> Description::Media::payloadTypes() const { return mOrderedPayloadTypes; }
986
987
0
Description::Media::RtpMap *Description::Media::rtpMap(int payloadType) {
988
0
  auto it = mRtpMaps.find(payloadType);
989
0
  if (it == mRtpMaps.end())
990
0
    throw std::invalid_argument("rtpmap not found");
991
992
0
  return &it->second;
993
0
}
994
995
0
const Description::Media::RtpMap *Description::Media::rtpMap(int payloadType) const {
996
0
  auto it = mRtpMaps.find(payloadType);
997
0
  if (it == mRtpMaps.end())
998
0
    throw std::invalid_argument("rtpmap not found");
999
1000
0
  return &it->second;
1001
0
}
1002
1003
0
void Description::Media::addRtpMap(RtpMap map) {
1004
0
  int payloadType = map.payloadType;
1005
0
  if (std::find(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), payloadType) ==
1006
0
      mOrderedPayloadTypes.end())
1007
0
    mOrderedPayloadTypes.push_back(payloadType);
1008
1009
0
  mRtpMaps.emplace(payloadType, std::move(map));
1010
0
}
1011
1012
0
void Description::Media::removeRtpMap(int payloadType) {
1013
  // Remove the actual format
1014
0
  mOrderedPayloadTypes.erase(
1015
0
      std::remove(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), payloadType),
1016
0
      mOrderedPayloadTypes.end());
1017
0
  mRtpMaps.erase(payloadType);
1018
1019
  // Remove any other rtpmaps that depend on the format we just removed
1020
0
  auto it = mRtpMaps.begin();
1021
0
  while (it != mRtpMaps.end()) {
1022
0
    const auto &fmtps = it->second.fmtps;
1023
0
    if (std::find(fmtps.begin(), fmtps.end(), "apt=" + std::to_string(payloadType)) !=
1024
0
        fmtps.end()) {
1025
0
      mOrderedPayloadTypes.erase(
1026
0
          std::remove(mOrderedPayloadTypes.begin(), mOrderedPayloadTypes.end(), it->first),
1027
0
          mOrderedPayloadTypes.end());
1028
0
      it = mRtpMaps.erase(it);
1029
0
    } else {
1030
0
      ++it;
1031
0
    }
1032
0
  }
1033
0
}
1034
1035
0
void Description::Media::removeFormat(const string &format) {
1036
0
  std::vector<int> payloadTypes;
1037
0
  for (const auto &it : mRtpMaps) {
1038
0
    if (it.second.format == format)
1039
0
      payloadTypes.push_back(it.first);
1040
0
  }
1041
0
  for (int pt : payloadTypes)
1042
0
    removeRtpMap(pt);
1043
0
}
1044
1045
0
void Description::Media::addRtxCodec(int payloadType, int origPayloadType, unsigned int clockRate) {
1046
0
  RtpMap rtp(std::to_string(payloadType) + " RTX/" + std::to_string(clockRate));
1047
0
  rtp.fmtps.emplace_back("apt=" + std::to_string(origPayloadType));
1048
0
  addRtpMap(rtp);
1049
0
}
1050
1051
0
string Description::Media::generateSdpLines(string_view eol) const {
1052
0
  std::ostringstream sdp;
1053
0
  if (mBas >= 0)
1054
0
    sdp << "b=AS:" << mBas << eol;
1055
1056
0
  sdp << Entry::generateSdpLines(eol);
1057
0
  sdp << "a=rtcp-mux" << eol;
1058
1059
0
  for (auto it = mRtpMaps.begin(); it != mRtpMaps.end(); ++it) {
1060
0
    auto &map = it->second;
1061
1062
    // Create the a=rtpmap
1063
0
    sdp << "a=rtpmap:" << map.payloadType << ' ' << map.format << '/' << map.clockRate;
1064
0
    if (!map.encParams.empty())
1065
0
      sdp << '/' << map.encParams;
1066
1067
0
    sdp << eol;
1068
1069
0
    for (const auto &val : map.rtcpFbs)
1070
0
      sdp << "a=rtcp-fb:" << map.payloadType << ' ' << val << eol;
1071
1072
0
    for (const auto &val : map.fmtps)
1073
0
      sdp << "a=fmtp:" << map.payloadType << ' ' << val << eol;
1074
0
  }
1075
1076
0
  return sdp.str();
1077
0
}
1078
1079
1.27M
void Description::Media::parseSdpLine(string_view line) {
1080
1.27M
  if (match_prefix(line, "a=")) {
1081
1.20M
    string_view attr = line.substr(2);
1082
1.20M
    auto [key, value] = parse_pair(attr);
1083
1084
1.20M
    if (key == "rtpmap") {
1085
6.50k
      auto pt = Description::Media::RtpMap::parsePayloadType(value);
1086
6.50k
      auto it = mRtpMaps.find(pt);
1087
6.50k
      if (it == mRtpMaps.end())
1088
3.80k
        it = mRtpMaps.insert(std::make_pair(pt, Description::Media::RtpMap(value))).first;
1089
2.70k
      else
1090
2.70k
        it->second.setDescription(value);
1091
1092
1.19M
    } else if (key == "rtcp-fb") {
1093
7.05k
      size_t p = value.find(' ');
1094
7.05k
      int pt = to_integer<int>(value.substr(0, p));
1095
7.05k
      auto it = mRtpMaps.find(pt);
1096
7.05k
      if (it == mRtpMaps.end())
1097
4.67k
        it = mRtpMaps.insert(std::make_pair(pt, Description::Media::RtpMap(pt))).first;
1098
1099
7.05k
      it->second.rtcpFbs.emplace_back(value.substr(p + 1));
1100
1101
1.19M
    } else if (key == "fmtp") {
1102
322k
      size_t p = value.find(' ');
1103
322k
      int pt = to_integer<int>(value.substr(0, p));
1104
322k
      auto it = mRtpMaps.find(pt);
1105
322k
      if (it == mRtpMaps.end())
1106
27.9k
        it = mRtpMaps.insert(std::make_pair(pt, Description::Media::RtpMap(pt))).first;
1107
1108
322k
      it->second.fmtps.emplace_back(value.substr(p + 1));
1109
1110
868k
    } else if (key == "rtcp-mux") {
1111
      // always added
1112
1113
868k
    } else if (key == "ssrc") {
1114
18.5k
      auto ssrc = to_integer<uint32_t>(value);
1115
18.5k
      if (!hasSSRC(ssrc))
1116
14.8k
        mSsrcs.emplace_back(ssrc);
1117
1118
18.5k
      auto cnamePos = value.find("cname:");
1119
18.5k
      if (cnamePos != string::npos) {
1120
5.19k
        auto cname = value.substr(cnamePos + 6);
1121
5.19k
        mCNameMap.emplace(ssrc, cname);
1122
5.19k
      }
1123
18.5k
      mAttributes.emplace_back(attr);
1124
1125
849k
    } else {
1126
849k
      Entry::parseSdpLine(line);
1127
849k
    }
1128
1129
1.20M
  } else if (match_prefix(line, "b=AS")) {
1130
0
    mBas = to_integer<int>(line.substr(line.find(':') + 1));
1131
74.2k
  } else {
1132
74.2k
    Entry::parseSdpLine(line);
1133
74.2k
  }
1134
1.27M
}
1135
1136
32.6k
Description::Media::RtpMap::RtpMap(int payloadType) {
1137
32.6k
  this->payloadType = payloadType;
1138
32.6k
  this->clockRate = 0;
1139
32.6k
}
1140
1141
6.50k
int Description::Media::RtpMap::parsePayloadType(string_view mline) {
1142
6.50k
  size_t p = mline.find(' ');
1143
6.50k
  return to_integer<int>(mline.substr(0, p));
1144
6.50k
}
1145
1146
3.80k
Description::Media::RtpMap::RtpMap(string_view description) { setDescription(description); }
1147
1148
6.49k
void Description::Media::RtpMap::setDescription(string_view description) {
1149
6.49k
  size_t p = description.find(' ');
1150
6.49k
  if (p == string::npos)
1151
236
    throw std::invalid_argument("Invalid format description for rtpmap");
1152
1153
6.26k
  this->payloadType = to_integer<int>(description.substr(0, p));
1154
1155
6.26k
  string_view line = description.substr(p + 1);
1156
6.26k
  size_t spl = line.find('/');
1157
6.26k
  if (spl == string::npos)
1158
82
    throw std::invalid_argument("Invalid format description for rtpmap");
1159
1160
6.17k
  this->format = line.substr(0, spl);
1161
1162
6.17k
  line = line.substr(spl + 1);
1163
6.17k
  spl = line.find('/');
1164
6.17k
  if (spl == string::npos) {
1165
4.86k
    spl = line.find(' ');
1166
4.86k
  }
1167
6.17k
  if (spl == string::npos)
1168
3.73k
    this->clockRate = to_integer<int>(line);
1169
2.44k
  else {
1170
2.44k
    this->clockRate = to_integer<int>(line.substr(0, spl));
1171
2.44k
    this->encParams = line.substr(spl + 1);
1172
2.44k
  }
1173
6.17k
}
1174
1175
0
void Description::Media::RtpMap::addFeedback(string fb) {
1176
0
  if (std::find(rtcpFbs.begin(), rtcpFbs.end(), fb) == rtcpFbs.end())
1177
0
    rtcpFbs.emplace_back(std::move(fb));
1178
0
}
1179
1180
0
void Description::Media::RtpMap::removeFeedback(const string &str) {
1181
0
  auto it = rtcpFbs.begin();
1182
0
  while (it != rtcpFbs.end()) {
1183
0
    if (it->find(str) != string::npos)
1184
0
      it = rtcpFbs.erase(it);
1185
0
    else
1186
0
      it++;
1187
0
  }
1188
0
}
1189
1190
0
void Description::Media::RtpMap::addParameter(string p) {
1191
0
  if (std::find(fmtps.begin(), fmtps.end(), p) == fmtps.end())
1192
0
    fmtps.emplace_back(std::move(p));
1193
0
}
1194
1195
0
void Description::Media::RtpMap::removeParameter(const string &str) {
1196
0
  fmtps.erase(std::remove_if(fmtps.begin(), fmtps.end(),
1197
0
                             [&](const auto &p) { return p.find(str) != string::npos; }),
1198
0
              fmtps.end());
1199
0
}
1200
1201
Description::Audio::Audio(string mid, Direction dir)
1202
0
    : Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
1203
1204
0
void Description::Audio::addAudioCodec(int payloadType, string codec, optional<string> profile) {
1205
0
  if (codec.find('/') == string::npos) {
1206
0
    if (codec == "PCMA" || codec == "PCMU" || codec == "G722")
1207
0
      codec += "/8000/1";
1208
0
    else
1209
0
      codec += "/48000/2";
1210
0
  }
1211
1212
0
  RtpMap map(std::to_string(payloadType) + ' ' + codec);
1213
1214
0
  if (profile)
1215
0
    map.fmtps.emplace_back(*profile);
1216
1217
0
  addRtpMap(map);
1218
0
}
1219
1220
0
void Description::Audio::addOpusCodec(int payloadType, optional<string> profile) {
1221
0
  addAudioCodec(payloadType, "opus", profile);
1222
0
}
1223
1224
0
void Description::Audio::addPCMACodec(int payloadType, optional<string> profile) {
1225
0
  addAudioCodec(payloadType, "PCMA", profile);
1226
0
}
1227
1228
0
void Description::Audio::addPCMUCodec(int payloadType, optional<string> profile) {
1229
0
  addAudioCodec(payloadType, "PCMU", profile);
1230
0
}
1231
1232
0
void Description::Audio::addG722Codec(int payloadType, optional<string> profile) {
1233
0
  addAudioCodec(payloadType, "G722", profile);
1234
0
}
1235
1236
0
void Description::Audio::addAACCodec(int payloadType, optional<string> profile) {
1237
0
  if (profile) {
1238
0
    addAudioCodec(payloadType, "MP4A-LATM", profile);
1239
0
  } else {
1240
0
    addAudioCodec(payloadType, "MP4A-LATM", "cpresent=1");
1241
0
  }
1242
0
}
1243
1244
Description::Video::Video(string mid, Direction dir)
1245
0
    : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
1246
1247
0
void Description::Video::addVideoCodec(int payloadType, string codec, optional<string> profile) {
1248
0
  if (codec.find('/') == string::npos)
1249
0
    codec += "/90000";
1250
1251
0
  RtpMap map(std::to_string(payloadType) + ' ' + codec);
1252
1253
0
  map.addFeedback("nack");
1254
0
  map.addFeedback("nack pli");
1255
  // map.addFB("ccm fir");
1256
0
  map.addFeedback("goog-remb");
1257
1258
0
  if (profile)
1259
0
    map.fmtps.emplace_back(*profile);
1260
1261
0
  addRtpMap(map);
1262
1263
  /* TODO
1264
   *  TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
1265
   * negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
1266
   * don't negotitate it and (maybe) break RTX support with Edge. Two) we do negotiate it and
1267
   * rebuild the original packet before we send it distribute it to each track. Three) we complain
1268
   * to mozilla. This one probably won't do much.
1269
   */
1270
  // RTX Packets
1271
  // Format rtx(std::to_string(payloadType+1) + " rtx/90000");
1272
  // // TODO rtx-time is how long can a request be stashed for before needing to resend it.
1273
  // Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
1274
  // ";rtx-time=3000"); addFormat(rtx);
1275
0
}
1276
1277
0
void Description::Video::addH264Codec(int payloadType, optional<string> profile) {
1278
0
  addVideoCodec(payloadType, "H264", profile);
1279
0
}
1280
1281
0
void Description::Video::addH265Codec(int payloadType, optional<string> profile) {
1282
0
  addVideoCodec(payloadType, "H265", profile);
1283
0
}
1284
1285
0
void Description::Video::addVP8Codec(int payloadType, optional<string> profile) {
1286
0
  addVideoCodec(payloadType, "VP8", profile);
1287
0
}
1288
1289
0
void Description::Video::addVP9Codec(int payloadType, optional<string> profile) {
1290
0
  addVideoCodec(payloadType, "VP9", profile);
1291
0
}
1292
1293
0
void Description::Video::addAV1Codec(int payloadType, optional<string> profile) {
1294
0
  addVideoCodec(payloadType, "AV1", profile);
1295
0
}
1296
1297
8.47k
Description::Type Description::stringToType(const string &typeString) {
1298
8.47k
  using TypeMap_t = std::unordered_map<string, Type>;
1299
8.47k
  static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
1300
8.47k
                                    {"offer", Type::Offer},
1301
8.47k
                                    {"answer", Type::Answer},
1302
8.47k
                                    {"pranswer", Type::Pranswer},
1303
8.47k
                                    {"rollback", Type::Rollback}};
1304
8.47k
  auto it = TypeMap.find(typeString);
1305
8.47k
  return it != TypeMap.end() ? it->second : Type::Unspec;
1306
8.47k
}
1307
1308
0
string Description::typeToString(Type type) {
1309
0
  switch (type) {
1310
0
  case Type::Unspec:
1311
0
    return "unspec";
1312
0
  case Type::Offer:
1313
0
    return "offer";
1314
0
  case Type::Answer:
1315
0
    return "answer";
1316
0
  case Type::Pranswer:
1317
0
    return "pranswer";
1318
0
  case Type::Rollback:
1319
0
    return "rollback";
1320
0
  default:
1321
0
    return "unknown";
1322
0
  }
1323
0
}
1324
1325
size_t
1326
1.77k
CertificateFingerprint::AlgorithmSize(CertificateFingerprint::Algorithm fingerprintAlgorithm) {
1327
1.77k
  switch (fingerprintAlgorithm) {
1328
688
  case CertificateFingerprint::Algorithm::Sha1:
1329
688
    return 20;
1330
639
  case CertificateFingerprint::Algorithm::Sha224:
1331
639
    return 28;
1332
210
  case CertificateFingerprint::Algorithm::Sha256:
1333
210
    return 32;
1334
225
  case CertificateFingerprint::Algorithm::Sha384:
1335
225
    return 48;
1336
12
  case CertificateFingerprint::Algorithm::Sha512:
1337
12
    return 64;
1338
0
  default:
1339
0
    return 0;
1340
1.77k
  }
1341
1.77k
}
1342
1343
std::string CertificateFingerprint::AlgorithmIdentifier(
1344
25.2k
    CertificateFingerprint::Algorithm fingerprintAlgorithm) {
1345
25.2k
  switch (fingerprintAlgorithm) {
1346
6.15k
  case CertificateFingerprint::Algorithm::Sha1:
1347
6.15k
    return "sha-1";
1348
5.43k
  case CertificateFingerprint::Algorithm::Sha224:
1349
5.43k
    return "sha-224";
1350
4.77k
  case CertificateFingerprint::Algorithm::Sha256:
1351
4.77k
    return "sha-256";
1352
4.56k
  case CertificateFingerprint::Algorithm::Sha384:
1353
4.56k
    return "sha-384";
1354
4.33k
  case CertificateFingerprint::Algorithm::Sha512:
1355
4.33k
    return "sha-512";
1356
0
  default:
1357
0
    return "unknown";
1358
25.2k
  }
1359
25.2k
}
1360
1361
1.77k
bool CertificateFingerprint::isValid() const {
1362
1.77k
  size_t expectedSize = AlgorithmSize(this->algorithm);
1363
1.77k
  if (expectedSize == 0 || this->value.size() != expectedSize * 3 - 1) {
1364
112
    return false;
1365
112
  }
1366
1367
137k
  for (size_t i = 0; i < this->value.size(); ++i) {
1368
135k
    if (i % 3 == 2) {
1369
44.1k
      if (this->value[i] != ':')
1370
27
        return false;
1371
91.5k
    } else {
1372
91.5k
      if (!std::isxdigit(this->value[i]))
1373
13
        return false;
1374
91.5k
    }
1375
135k
  }
1376
1.62k
  return true;
1377
1.66k
}
1378
1379
0
std::ostream &operator<<(std::ostream &out, const Description &description) {
1380
0
  return out << string(description);
1381
0
}
1382
1383
0
std::ostream &operator<<(std::ostream &out, Description::Type type) {
1384
0
  return out << Description::typeToString(type);
1385
0
}
1386
1387
0
std::ostream &operator<<(std::ostream &out, Description::Role role) {
1388
0
  using Role = Description::Role;
1389
  // Used for SDP generation, do not change
1390
0
  switch (role) {
1391
0
  case Role::Active:
1392
0
    out << "active";
1393
0
    break;
1394
0
  case Role::Passive:
1395
0
    out << "passive";
1396
0
    break;
1397
0
  default:
1398
0
    out << "actpass";
1399
0
    break;
1400
0
  }
1401
0
  return out;
1402
0
}
1403
1404
0
std::ostream &operator<<(std::ostream &out, const Description::Direction &direction) {
1405
  // Used for SDP generation, do not change
1406
0
  switch (direction) {
1407
0
  case Description::Direction::RecvOnly:
1408
0
    out << "recvonly";
1409
0
    break;
1410
0
  case Description::Direction::SendOnly:
1411
0
    out << "sendonly";
1412
0
    break;
1413
0
  case Description::Direction::SendRecv:
1414
0
    out << "sendrecv";
1415
0
    break;
1416
0
  case Description::Direction::Inactive:
1417
0
    out << "inactive";
1418
0
    break;
1419
0
  case Description::Direction::Unknown:
1420
0
  default:
1421
0
    out << "unknown";
1422
0
    break;
1423
0
  }
1424
0
  return out;
1425
0
}
1426
1427
} // namespace rtc