/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 |