description.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988
  1. /**
  2. * Copyright (c) 2019-2020 Paul-Louis Ageneau
  3. * Copyright (c) 2020 Staz Modrzynski
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. */
  19. #include "description.hpp"
  20. #include <algorithm>
  21. #include <array>
  22. #include <cctype>
  23. #include <chrono>
  24. #include <iostream>
  25. #include <random>
  26. #include <sstream>
  27. #include <unordered_map>
  28. using std::chrono::system_clock;
  29. namespace {
  30. using std::string;
  31. using std::string_view;
  32. inline bool match_prefix(string_view str, string_view prefix) {
  33. return str.size() >= prefix.size() &&
  34. std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
  35. }
  36. inline void trim_begin(string &str) {
  37. str.erase(str.begin(),
  38. std::find_if(str.begin(), str.end(), [](char c) { return !std::isspace(c); }));
  39. }
  40. inline void trim_end(string &str) {
  41. str.erase(
  42. std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
  43. str.end());
  44. }
  45. inline std::pair<string_view, string_view> parse_pair(string_view attr) {
  46. string_view key, value;
  47. if (size_t separator = attr.find(':'); separator != string::npos) {
  48. key = attr.substr(0, separator);
  49. value = attr.substr(separator + 1);
  50. } else {
  51. key = attr;
  52. }
  53. return std::make_pair(std::move(key), std::move(value));
  54. }
  55. template <typename T> T to_integer(string_view s) {
  56. const string str(s);
  57. try {
  58. return std::is_signed<T>::value ? T(std::stol(str)) : T(std::stoul(str));
  59. } catch (...) {
  60. throw std::invalid_argument("Invalid integer \"" + str + "\" in description");
  61. }
  62. }
  63. inline bool is_sha256_fingerprint(string_view f) {
  64. if (f.size() != 32 * 3 - 1)
  65. return false;
  66. for (size_t i = 0; i < f.size(); ++i) {
  67. if (i % 3 == 2) {
  68. if (f[i] != ':')
  69. return false;
  70. } else {
  71. if (!std::isxdigit(f[i]))
  72. return false;
  73. }
  74. }
  75. return true;
  76. }
  77. } // namespace
  78. namespace rtc {
  79. Description::Description(const string &sdp, Type type, Role role)
  80. : mType(Type::Unspec), mRole(role) {
  81. hintType(type);
  82. int index = -1;
  83. shared_ptr<Entry> current;
  84. std::istringstream ss(sdp);
  85. while (ss) {
  86. string line;
  87. std::getline(ss, line);
  88. trim_end(line);
  89. if (line.empty())
  90. continue;
  91. if (match_prefix(line, "m=")) { // Media description line (aka m-line)
  92. current = createEntry(line.substr(2), std::to_string(++index), Direction::Unknown);
  93. } else if (match_prefix(line, "o=")) { // Origin line
  94. std::istringstream origin(line.substr(2));
  95. origin >> mUsername >> mSessionId;
  96. } else if (match_prefix(line, "a=")) { // Attribute line
  97. string attr = line.substr(2);
  98. auto [key, value] = parse_pair(attr);
  99. if (key == "setup") {
  100. if (value == "active")
  101. mRole = Role::Active;
  102. else if (value == "passive")
  103. mRole = Role::Passive;
  104. else
  105. mRole = Role::ActPass;
  106. } else if (key == "fingerprint") {
  107. if (match_prefix(value, "sha-256 ")) {
  108. string fingerprint{value.substr(8)};
  109. trim_begin(fingerprint);
  110. setFingerprint(std::move(fingerprint));
  111. } else {
  112. PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
  113. }
  114. } else if (key == "ice-ufrag") {
  115. mIceUfrag = value;
  116. } else if (key == "ice-pwd") {
  117. mIcePwd = value;
  118. } else if (key == "candidate") {
  119. addCandidate(Candidate(attr, bundleMid()));
  120. } else if (key == "end-of-candidates") {
  121. mEnded = true;
  122. } else if (current) {
  123. current->parseSdpLine(std::move(line));
  124. }
  125. } else if (current) {
  126. current->parseSdpLine(std::move(line));
  127. }
  128. }
  129. if (mUsername.empty())
  130. mUsername = "rtc";
  131. if (mSessionId.empty()) {
  132. auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
  133. std::default_random_engine generator(seed);
  134. std::uniform_int_distribution<uint32_t> uniform;
  135. mSessionId = std::to_string(uniform(generator));
  136. }
  137. }
  138. Description::Description(const string &sdp, string typeString)
  139. : Description(sdp, !typeString.empty() ? stringToType(typeString) : Type::Unspec,
  140. Role::ActPass) {}
  141. Description::Type Description::type() const { return mType; }
  142. string Description::typeString() const { return typeToString(mType); }
  143. Description::Role Description::role() const { return mRole; }
  144. string Description::bundleMid() const {
  145. // Get the mid of the first media
  146. return !mEntries.empty() ? mEntries[0]->mid() : "0";
  147. }
  148. optional<string> Description::iceUfrag() const { return mIceUfrag; }
  149. optional<string> Description::icePwd() const { return mIcePwd; }
  150. optional<string> Description::fingerprint() const { return mFingerprint; }
  151. bool Description::ended() const { return mEnded; }
  152. void Description::hintType(Type type) {
  153. if (mType == Type::Unspec) {
  154. mType = type;
  155. if (mType == Type::Answer && mRole == Role::ActPass)
  156. mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
  157. }
  158. }
  159. void Description::setFingerprint(string fingerprint) {
  160. if (!is_sha256_fingerprint(fingerprint))
  161. throw std::invalid_argument("Invalid SHA256 fingerprint \"" + fingerprint + "\"");
  162. std::transform(fingerprint.begin(), fingerprint.end(), fingerprint.begin(),
  163. [](char c) { return char(std::toupper(c)); });
  164. mFingerprint.emplace(std::move(fingerprint));
  165. }
  166. bool Description::hasCandidate(const Candidate &candidate) const {
  167. for (const Candidate &other : mCandidates)
  168. if (candidate == other)
  169. return true;
  170. return false;
  171. }
  172. void Description::addCandidate(Candidate candidate) {
  173. candidate.hintMid(bundleMid());
  174. for (const Candidate &other : mCandidates)
  175. if (candidate == other)
  176. return;
  177. mCandidates.emplace_back(std::move(candidate));
  178. }
  179. void Description::addCandidates(std::vector<Candidate> candidates) {
  180. for (Candidate candidate : candidates)
  181. addCandidate(std::move(candidate));
  182. }
  183. void Description::endCandidates() { mEnded = true; }
  184. std::vector<Candidate> Description::extractCandidates() {
  185. std::vector<Candidate> result;
  186. std::swap(mCandidates, result);
  187. mEnded = false;
  188. return result;
  189. }
  190. Description::operator string() const { return generateSdp("\r\n"); }
  191. string Description::generateSdp(string_view eol) const {
  192. std::ostringstream sdp;
  193. // Header
  194. sdp << "v=0" << eol;
  195. sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
  196. sdp << "s=-" << eol;
  197. sdp << "t=0 0" << eol;
  198. // Bundle (RFC8843 Negotiating Media Multiplexing Using the Session Description Protocol)
  199. // https://tools.ietf.org/html/rfc8843
  200. sdp << "a=group:BUNDLE";
  201. for (const auto &entry : mEntries)
  202. sdp << ' ' << entry->mid();
  203. sdp << eol;
  204. // Lip-sync
  205. std::ostringstream lsGroup;
  206. for (const auto &entry : mEntries)
  207. if (entry != mApplication)
  208. lsGroup << ' ' << entry->mid();
  209. if (!lsGroup.str().empty())
  210. sdp << "a=group:LS" << lsGroup.str() << eol;
  211. // Session-level attributes
  212. sdp << "a=msid-semantic:WMS *" << eol;
  213. sdp << "a=setup:" << mRole << eol;
  214. if (mIceUfrag)
  215. sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
  216. if (mIcePwd)
  217. sdp << "a=ice-pwd:" << *mIcePwd << eol;
  218. if (!mEnded)
  219. sdp << "a=ice-options:trickle" << eol;
  220. if (mFingerprint)
  221. sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
  222. auto cand = defaultCandidate();
  223. const string addr = cand && cand->isResolved()
  224. ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
  225. " " + *cand->address())
  226. : "IP4 0.0.0.0";
  227. const string port = std::to_string(
  228. cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
  229. // Entries
  230. bool first = true;
  231. for (const auto &entry : mEntries) {
  232. sdp << entry->generateSdp(eol, addr, port);
  233. if (std::exchange(first, false)) {
  234. // Candidates
  235. for (const auto &candidate : mCandidates)
  236. sdp << string(candidate) << eol;
  237. if (mEnded)
  238. sdp << "a=end-of-candidates" << eol;
  239. }
  240. }
  241. return sdp.str();
  242. }
  243. string Description::generateApplicationSdp(string_view eol) const {
  244. std::ostringstream sdp;
  245. // Header
  246. sdp << "v=0" << eol;
  247. sdp << "o=" << mUsername << " " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
  248. sdp << "s=-" << eol;
  249. sdp << "t=0 0" << eol;
  250. auto cand = defaultCandidate();
  251. const string addr = cand && cand->isResolved()
  252. ? (string(cand->family() == Candidate::Family::Ipv6 ? "IP6" : "IP4") +
  253. " " + *cand->address())
  254. : "IP4 0.0.0.0";
  255. const string port = std::to_string(
  256. cand && cand->isResolved() ? *cand->port() : 9); // Port 9 is the discard protocol
  257. // Application
  258. auto app = mApplication ? mApplication : std::make_shared<Application>();
  259. sdp << app->generateSdp(eol, addr, port);
  260. // Session-level attributes
  261. sdp << "a=msid-semantic:WMS *" << eol;
  262. sdp << "a=setup:" << mRole << eol;
  263. if (mIceUfrag)
  264. sdp << "a=ice-ufrag:" << *mIceUfrag << eol;
  265. if (mIcePwd)
  266. sdp << "a=ice-pwd:" << *mIcePwd << eol;
  267. if (!mEnded)
  268. sdp << "a=ice-options:trickle" << eol;
  269. if (mFingerprint)
  270. sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
  271. // Candidates
  272. for (const auto &candidate : mCandidates)
  273. sdp << string(candidate) << eol;
  274. if (mEnded)
  275. sdp << "a=end-of-candidates" << eol;
  276. return sdp.str();
  277. }
  278. optional<Candidate> Description::defaultCandidate() const {
  279. // Return the first host candidate with highest priority, favoring IPv4
  280. optional<Candidate> result;
  281. for (const auto &c : mCandidates) {
  282. if (c.type() == Candidate::Type::Host) {
  283. if (!result ||
  284. (result->family() == Candidate::Family::Ipv6 &&
  285. c.family() == Candidate::Family::Ipv4) ||
  286. (result->family() == c.family() && result->priority() < c.priority()))
  287. result.emplace(c);
  288. }
  289. }
  290. return result;
  291. }
  292. shared_ptr<Description::Entry> Description::createEntry(string mline, string mid, Direction dir) {
  293. string type = mline.substr(0, mline.find(' '));
  294. if (type == "application") {
  295. removeApplication();
  296. mApplication = std::make_shared<Application>(std::move(mid));
  297. mEntries.emplace_back(mApplication);
  298. return mApplication;
  299. } else {
  300. auto media = std::make_shared<Media>(std::move(mline), std::move(mid), dir);
  301. mEntries.emplace_back(media);
  302. return media;
  303. }
  304. }
  305. void Description::removeApplication() {
  306. if (!mApplication)
  307. return;
  308. auto it = std::find(mEntries.begin(), mEntries.end(), mApplication);
  309. if (it != mEntries.end())
  310. mEntries.erase(it);
  311. mApplication.reset();
  312. }
  313. bool Description::hasApplication() const { return mApplication != nullptr; }
  314. bool Description::hasAudioOrVideo() const {
  315. for (auto entry : mEntries)
  316. if (entry != mApplication)
  317. return true;
  318. return false;
  319. }
  320. bool Description::hasMid(string_view mid) const {
  321. for (const auto &entry : mEntries)
  322. if (entry->mid() == mid)
  323. return true;
  324. return false;
  325. }
  326. int Description::addMedia(Media media) {
  327. mEntries.emplace_back(std::make_shared<Media>(std::move(media)));
  328. return int(mEntries.size()) - 1;
  329. }
  330. int Description::addMedia(Application application) {
  331. removeApplication();
  332. mApplication = std::make_shared<Application>(std::move(application));
  333. mEntries.emplace_back(mApplication);
  334. return int(mEntries.size()) - 1;
  335. }
  336. int Description::addApplication(string mid) { return addMedia(Application(std::move(mid))); }
  337. Description::Application *Description::application() { return mApplication.get(); }
  338. int Description::addVideo(string mid, Direction dir) {
  339. return addMedia(Video(std::move(mid), dir));
  340. }
  341. int Description::addAudio(string mid, Direction dir) {
  342. return addMedia(Audio(std::move(mid), dir));
  343. }
  344. variant<Description::Media *, Description::Application *> Description::media(unsigned int index) {
  345. if (index >= mEntries.size())
  346. throw std::out_of_range("Media index out of range");
  347. const auto &entry = mEntries[index];
  348. if (entry == mApplication) {
  349. auto result = dynamic_cast<Application *>(entry.get());
  350. if (!result)
  351. throw std::logic_error("Bad type of application in description");
  352. return result;
  353. } else {
  354. auto result = dynamic_cast<Media *>(entry.get());
  355. if (!result)
  356. throw std::logic_error("Bad type of media in description");
  357. return result;
  358. }
  359. }
  360. variant<const Description::Media *, const Description::Application *>
  361. Description::media(unsigned int index) const {
  362. if (index >= mEntries.size())
  363. throw std::out_of_range("Media index out of range");
  364. const auto &entry = mEntries[index];
  365. if (entry == mApplication) {
  366. auto result = dynamic_cast<Application *>(entry.get());
  367. if (!result)
  368. throw std::logic_error("Bad type of application in description");
  369. return result;
  370. } else {
  371. auto result = dynamic_cast<Media *>(entry.get());
  372. if (!result)
  373. throw std::logic_error("Bad type of media in description");
  374. return result;
  375. }
  376. }
  377. unsigned int Description::mediaCount() const { return unsigned(mEntries.size()); }
  378. Description::Entry::Entry(const string &mline, string mid, Direction dir)
  379. : mMid(std::move(mid)), mDirection(dir) {
  380. unsigned int port;
  381. std::istringstream ss(mline);
  382. ss >> mType;
  383. ss >> port; // ignored
  384. ss >> mDescription;
  385. }
  386. void Description::Entry::setDirection(Direction dir) { mDirection = dir; }
  387. Description::Entry::operator string() const { return generateSdp("\r\n", "IP4 0.0.0.0", "9"); }
  388. string Description::Entry::generateSdp(string_view eol, string_view addr, string_view port) const {
  389. std::ostringstream sdp;
  390. sdp << "m=" << type() << ' ' << port << ' ' << description() << eol;
  391. sdp << "c=IN " << addr << eol;
  392. sdp << generateSdpLines(eol);
  393. return sdp.str();
  394. }
  395. string Description::Entry::generateSdpLines(string_view eol) const {
  396. std::ostringstream sdp;
  397. sdp << "a=bundle-only" << eol;
  398. sdp << "a=mid:" << mMid << eol;
  399. switch (mDirection) {
  400. case Direction::SendOnly:
  401. sdp << "a=sendonly" << eol;
  402. break;
  403. case Direction::RecvOnly:
  404. sdp << "a=recvonly" << eol;
  405. break;
  406. case Direction::SendRecv:
  407. sdp << "a=sendrecv" << eol;
  408. break;
  409. case Direction::Inactive:
  410. sdp << "a=inactive" << eol;
  411. break;
  412. default:
  413. // Ignore
  414. break;
  415. }
  416. for (const auto &attr : mAttributes) {
  417. if (attr.find("extmap") == string::npos && attr.find("rtcp-rsize") == string::npos)
  418. sdp << "a=" << attr << eol;
  419. }
  420. return sdp.str();
  421. }
  422. void Description::Entry::parseSdpLine(string_view line) {
  423. if (match_prefix(line, "a=")) {
  424. string_view attr = line.substr(2);
  425. auto [key, value] = parse_pair(attr);
  426. if (key == "mid")
  427. mMid = value;
  428. else if (attr == "sendonly")
  429. mDirection = Direction::SendOnly;
  430. else if (attr == "recvonly")
  431. mDirection = Direction::RecvOnly;
  432. else if (key == "sendrecv")
  433. mDirection = Direction::SendRecv;
  434. else if (key == "inactive")
  435. mDirection = Direction::Inactive;
  436. else if (key == "bundle-only") {
  437. // always added
  438. } else
  439. mAttributes.emplace_back(line.substr(2));
  440. }
  441. }
  442. std::vector<string>::iterator Description::Entry::beginAttributes() { return mAttributes.begin(); }
  443. std::vector<string>::iterator Description::Entry::endAttributes() { return mAttributes.end(); }
  444. std::vector<string>::iterator
  445. Description::Entry::removeAttribute(std::vector<string>::iterator it) {
  446. return mAttributes.erase(it);
  447. }
  448. void Description::Media::addSSRC(uint32_t ssrc, optional<string> name, optional<string> msid,
  449. optional<string> trackID) {
  450. if (name)
  451. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " cname:" + *name);
  452. else
  453. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc));
  454. if (msid)
  455. mAttributes.emplace_back("ssrc:" + std::to_string(ssrc) + " msid:" + *msid + " " +
  456. trackID.value_or(*msid));
  457. mSsrcs.emplace_back(ssrc);
  458. }
  459. void Description::Media::replaceSSRC(uint32_t oldSSRC, uint32_t ssrc, optional<string> name,
  460. optional<string> msid, optional<string> trackID) {
  461. auto it = mAttributes.begin();
  462. while (it != mAttributes.end()) {
  463. if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
  464. it = mAttributes.erase(it);
  465. } else
  466. it++;
  467. }
  468. addSSRC(ssrc, std::move(name), std::move(msid), std::move(trackID));
  469. }
  470. void Description::Media::removeSSRC(uint32_t oldSSRC) {
  471. auto it = mAttributes.begin();
  472. while (it != mAttributes.end()) {
  473. if (it->find("ssrc:" + std::to_string(oldSSRC)) == 0) {
  474. it = mAttributes.erase(it);
  475. } else
  476. it++;
  477. }
  478. }
  479. bool Description::Media::hasSSRC(uint32_t ssrc) {
  480. return std::find(mSsrcs.begin(), mSsrcs.end(), ssrc) != mSsrcs.end();
  481. }
  482. Description::Application::Application(string mid)
  483. : Entry("application 9 UDP/DTLS/SCTP", std::move(mid), Direction::SendRecv) {}
  484. string Description::Application::description() const {
  485. return Entry::description() + " webrtc-datachannel";
  486. }
  487. Description::Application Description::Application::reciprocate() const {
  488. Application reciprocated(*this);
  489. reciprocated.mMaxMessageSize.reset();
  490. return reciprocated;
  491. }
  492. string Description::Application::generateSdpLines(string_view eol) const {
  493. std::ostringstream sdp;
  494. sdp << Entry::generateSdpLines(eol);
  495. if (mSctpPort)
  496. sdp << "a=sctp-port:" << *mSctpPort << eol;
  497. if (mMaxMessageSize)
  498. sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
  499. return sdp.str();
  500. }
  501. void Description::Application::parseSdpLine(string_view line) {
  502. if (match_prefix(line, "a=")) {
  503. string_view attr = line.substr(2);
  504. auto [key, value] = parse_pair(attr);
  505. if (key == "sctp-port") {
  506. mSctpPort = to_integer<uint16_t>(value);
  507. } else if (key == "max-message-size") {
  508. mMaxMessageSize = to_integer<size_t>(value);
  509. } else {
  510. Entry::parseSdpLine(line);
  511. }
  512. } else {
  513. Entry::parseSdpLine(line);
  514. }
  515. }
  516. Description::Media::Media(const string &sdp) : Entry(sdp, "", Direction::Unknown) {
  517. std::istringstream ss(sdp);
  518. while (ss) {
  519. string line;
  520. std::getline(ss, line);
  521. trim_end(line);
  522. if (line.empty())
  523. continue;
  524. parseSdpLine(line);
  525. }
  526. if (mid().empty())
  527. throw std::invalid_argument("Missing mid in media SDP");
  528. }
  529. Description::Media::Media(const string &mline, string mid, Direction dir)
  530. : Entry(mline, std::move(mid), dir) {}
  531. string Description::Media::description() const {
  532. std::ostringstream desc;
  533. desc << Entry::description();
  534. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
  535. desc << ' ' << it->first;
  536. return desc.str();
  537. }
  538. Description::Media Description::Media::reciprocate() const {
  539. Media reciprocated(*this);
  540. // Invert direction
  541. switch (direction()) {
  542. case Direction::RecvOnly:
  543. reciprocated.setDirection(Direction::SendOnly);
  544. break;
  545. case Direction::SendOnly:
  546. reciprocated.setDirection(Direction::RecvOnly);
  547. break;
  548. default:
  549. // We are good
  550. break;
  551. }
  552. return reciprocated;
  553. }
  554. Description::Media::RTPMap &Description::Media::getFormat(int fmt) {
  555. auto it = mRtpMap.find(fmt);
  556. if (it != mRtpMap.end())
  557. return it->second;
  558. throw std::invalid_argument("m-line index is out of bounds");
  559. }
  560. Description::Media::RTPMap &Description::Media::getFormat(const string &fmt) {
  561. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it)
  562. if (it->second.format == fmt)
  563. return it->second;
  564. throw std::invalid_argument("format was not found");
  565. }
  566. void Description::Media::removeFormat(const string &fmt) {
  567. auto it = mRtpMap.begin();
  568. std::vector<int> remed;
  569. // Remove the actual formats
  570. while (it != mRtpMap.end()) {
  571. if (it->second.format == fmt) {
  572. remed.emplace_back(it->first);
  573. it = mRtpMap.erase(it);
  574. } else {
  575. it++;
  576. }
  577. }
  578. // Remove any other rtpmaps that depend on the formats we just removed
  579. it = mRtpMap.begin();
  580. while (it != mRtpMap.end()) {
  581. auto it2 = it->second.fmtps.begin();
  582. bool rem = false;
  583. while (it2 != it->second.fmtps.end()) {
  584. if (it2->find("apt=") == 0) {
  585. for (auto remid : remed) {
  586. if (it2->find(std::to_string(remid)) != string::npos) {
  587. std::cout << *it2 << ' ' << remid << std::endl;
  588. it = mRtpMap.erase(it);
  589. rem = true;
  590. break;
  591. }
  592. }
  593. break;
  594. }
  595. it2++;
  596. }
  597. if (!rem)
  598. it++;
  599. }
  600. }
  601. void Description::Video::addVideoCodec(int payloadType, string codec, optional<string> profile) {
  602. RTPMap map(std::to_string(payloadType) + ' ' + codec + "/90000");
  603. map.addFB("nack");
  604. map.addFB("nack pli");
  605. // map.addFB("ccm fir");
  606. map.addFB("goog-remb");
  607. if (profile)
  608. map.fmtps.emplace_back(*profile);
  609. addRTPMap(map);
  610. /* TODO
  611. * TIL that Firefox does not properly support the negotiation of RTX! It works, but doesn't
  612. * negotiate the SSRC so we have no idea what SSRC is RTX going to be. Three solutions: One) we
  613. * don't negotitate it and (maybe) break RTX support with Edge. Two) we do negotiate it and
  614. * rebuild the original packet before we send it distribute it to each track. Three) we complain
  615. * to mozilla. This one probably won't do much.
  616. */
  617. // RTX Packets
  618. // RTPMap rtx(std::to_string(payloadType+1) + " rtx/90000");
  619. // // TODO rtx-time is how long can a request be stashed for before needing to resend it.
  620. // Needs to be parameterized rtx.addAttribute("apt=" + std::to_string(payloadType) +
  621. // ";rtx-time=3000"); addRTPMap(rtx);
  622. }
  623. void Description::Audio::addAudioCodec(int payloadType, string codec, optional<string> profile) {
  624. // TODO This 48000/2 should be parameterized
  625. RTPMap map(std::to_string(payloadType) + ' ' + codec + "/48000/2");
  626. if (profile)
  627. map.fmtps.emplace_back(*profile);
  628. addRTPMap(map);
  629. }
  630. void Description::Media::addRTXCodec(unsigned int payloadType, unsigned int originalPayloadType,
  631. unsigned int clockRate) {
  632. RTPMap map(std::to_string(payloadType) + " RTX/" + std::to_string(clockRate));
  633. map.fmtps.emplace_back("apt=" + std::to_string(originalPayloadType));
  634. addRTPMap(map);
  635. }
  636. void Description::Video::addH264Codec(int pt, optional<string> profile) {
  637. addVideoCodec(pt, "H264", profile);
  638. }
  639. void Description::Video::addVP8Codec(int payloadType) {
  640. addVideoCodec(payloadType, "VP8", nullopt);
  641. }
  642. void Description::Video::addVP9Codec(int payloadType) {
  643. addVideoCodec(payloadType, "VP9", nullopt);
  644. }
  645. void Description::Media::setBitrate(int bitrate) { mBas = bitrate; }
  646. int Description::Media::getBitrate() const { return mBas; }
  647. bool Description::Media::hasPayloadType(int payloadType) const {
  648. return mRtpMap.find(payloadType) != mRtpMap.end();
  649. }
  650. string Description::Media::generateSdpLines(string_view eol) const {
  651. std::ostringstream sdp;
  652. if (mBas >= 0)
  653. sdp << "b=AS:" << mBas << eol;
  654. sdp << Entry::generateSdpLines(eol);
  655. sdp << "a=rtcp-mux" << eol;
  656. for (auto it = mRtpMap.begin(); it != mRtpMap.end(); ++it) {
  657. auto &map = it->second;
  658. // Create the a=rtpmap
  659. sdp << "a=rtpmap:" << map.pt << ' ' << map.format << '/' << map.clockRate;
  660. if (!map.encParams.empty())
  661. sdp << '/' << map.encParams;
  662. sdp << eol;
  663. for (const auto &val : map.rtcpFbs) {
  664. if (val != "transport-cc")
  665. sdp << "a=rtcp-fb:" << map.pt << ' ' << val << eol;
  666. }
  667. for (const auto &val : map.fmtps)
  668. sdp << "a=fmtp:" << map.pt << ' ' << val << eol;
  669. }
  670. return sdp.str();
  671. }
  672. void Description::Media::parseSdpLine(string_view line) {
  673. if (match_prefix(line, "a=")) {
  674. string_view attr = line.substr(2);
  675. auto [key, value] = parse_pair(attr);
  676. if (key == "rtpmap") {
  677. auto pt = Description::Media::RTPMap::parsePT(value);
  678. auto it = mRtpMap.find(pt);
  679. if (it == mRtpMap.end()) {
  680. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap(value))).first;
  681. } else {
  682. it->second.setMLine(value);
  683. }
  684. } else if (key == "rtcp-fb") {
  685. size_t p = value.find(' ');
  686. int pt = to_integer<int>(value.substr(0, p));
  687. auto it = mRtpMap.find(pt);
  688. if (it == mRtpMap.end()) {
  689. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
  690. }
  691. it->second.rtcpFbs.emplace_back(value.substr(p + 1));
  692. } else if (key == "fmtp") {
  693. size_t p = value.find(' ');
  694. int pt = to_integer<int>(value.substr(0, p));
  695. auto it = mRtpMap.find(pt);
  696. if (it == mRtpMap.end())
  697. it = mRtpMap.insert(std::make_pair(pt, Description::Media::RTPMap())).first;
  698. it->second.fmtps.emplace_back(value.substr(p + 1));
  699. } else if (key == "rtcp-mux") {
  700. // always added
  701. } else if (key == "ssrc") {
  702. mSsrcs.emplace_back(to_integer<uint32_t>(value));
  703. } else {
  704. Entry::parseSdpLine(line);
  705. }
  706. } else if (match_prefix(line, "b=AS")) {
  707. mBas = to_integer<int>(line.substr(line.find(':') + 1));
  708. } else {
  709. Entry::parseSdpLine(line);
  710. }
  711. }
  712. void Description::Media::addRTPMap(const Description::Media::RTPMap &map) {
  713. mRtpMap.emplace(map.pt, map);
  714. }
  715. std::vector<uint32_t> Description::Media::getSSRCs() { return mSsrcs; }
  716. std::map<int, Description::Media::RTPMap>::iterator Description::Media::beginMaps() {
  717. return mRtpMap.begin();
  718. }
  719. std::map<int, Description::Media::RTPMap>::iterator Description::Media::endMaps() {
  720. return mRtpMap.end();
  721. }
  722. std::map<int, Description::Media::RTPMap>::iterator
  723. Description::Media::removeMap(std::map<int, Description::Media::RTPMap>::iterator iterator) {
  724. return mRtpMap.erase(iterator);
  725. }
  726. Description::Media::RTPMap::RTPMap(string_view mline) { setMLine(mline); }
  727. void Description::Media::RTPMap::removeFB(const string &str) {
  728. auto it = rtcpFbs.begin();
  729. while (it != rtcpFbs.end()) {
  730. if (it->find(str) != string::npos) {
  731. it = rtcpFbs.erase(it);
  732. } else
  733. it++;
  734. }
  735. }
  736. void Description::Media::RTPMap::addFB(const string &str) { rtcpFbs.emplace_back(str); }
  737. int Description::Media::RTPMap::parsePT(string_view view) {
  738. size_t p = view.find(' ');
  739. return to_integer<int>(view.substr(0, p));
  740. }
  741. void Description::Media::RTPMap::setMLine(string_view mline) {
  742. size_t p = mline.find(' ');
  743. this->pt = to_integer<int>(mline.substr(0, p));
  744. string_view line = mline.substr(p + 1);
  745. size_t spl = line.find('/');
  746. this->format = line.substr(0, spl);
  747. line = line.substr(spl + 1);
  748. spl = line.find('/');
  749. if (spl == string::npos) {
  750. spl = line.find(' ');
  751. }
  752. if (spl == string::npos)
  753. this->clockRate = to_integer<int>(line);
  754. else {
  755. this->clockRate = to_integer<int>(line.substr(0, spl));
  756. this->encParams = line.substr(spl + 1);
  757. }
  758. }
  759. Description::Audio::Audio(string mid, Direction dir)
  760. : Media("audio 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
  761. void Description::Audio::addOpusCodec(int payloadType, optional<string> profile) {
  762. addAudioCodec(payloadType, "OPUS", profile);
  763. }
  764. Description::Video::Video(string mid, Direction dir)
  765. : Media("video 9 UDP/TLS/RTP/SAVPF", std::move(mid), dir) {}
  766. Description::Type Description::stringToType(const string &typeString) {
  767. using TypeMap_t = std::unordered_map<string, Type>;
  768. static const TypeMap_t TypeMap = {{"unspec", Type::Unspec},
  769. {"offer", Type::Offer},
  770. {"answer", Type::Answer},
  771. {"pranswer", Type::Pranswer},
  772. {"rollback", Type::Rollback}};
  773. auto it = TypeMap.find(typeString);
  774. return it != TypeMap.end() ? it->second : Type::Unspec;
  775. }
  776. string Description::typeToString(Type type) {
  777. switch (type) {
  778. case Type::Unspec:
  779. return "unspec";
  780. case Type::Offer:
  781. return "offer";
  782. case Type::Answer:
  783. return "answer";
  784. case Type::Pranswer:
  785. return "pranswer";
  786. case Type::Rollback:
  787. return "rollback";
  788. default:
  789. return "unknown";
  790. }
  791. }
  792. } // namespace rtc
  793. std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
  794. return out << std::string(description);
  795. }
  796. std::ostream &operator<<(std::ostream &out, rtc::Description::Type type) {
  797. return out << rtc::Description::typeToString(type);
  798. }
  799. std::ostream &operator<<(std::ostream &out, rtc::Description::Role role) {
  800. using Role = rtc::Description::Role;
  801. const char *str;
  802. // Used for SDP generation, do not change
  803. switch (role) {
  804. case Role::Active:
  805. str = "active";
  806. break;
  807. case Role::Passive:
  808. str = "passive";
  809. break;
  810. default:
  811. str = "actpass";
  812. break;
  813. }
  814. return out << str;
  815. }