diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/media/webrtc/sdp/SdpAttribute.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/sdp/SdpAttribute.cpp')
-rw-r--r-- | dom/media/webrtc/sdp/SdpAttribute.cpp | 1562 |
1 files changed, 1562 insertions, 0 deletions
diff --git a/dom/media/webrtc/sdp/SdpAttribute.cpp b/dom/media/webrtc/sdp/SdpAttribute.cpp new file mode 100644 index 0000000000..cacb25e6b1 --- /dev/null +++ b/dom/media/webrtc/sdp/SdpAttribute.cpp @@ -0,0 +1,1562 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "sdp/SdpAttribute.h" +#include "sdp/SdpHelper.h" +#include <iomanip> +#include <bitset> + +#ifdef CRLF +# undef CRLF +#endif +#define CRLF "\r\n" + +namespace mozilla { + +static unsigned char PeekChar(std::istream& is, std::string* error) { + int next = is.peek(); + if (next == EOF) { + *error = "Truncated"; + return 0; + } + + return next; +} + +static std::string ParseToken(std::istream& is, const std::string& delims, + std::string* error) { + std::string token; + while (is) { + unsigned char c = PeekChar(is, error); + if (!c || (delims.find(c) != std::string::npos)) { + break; + } + token.push_back(std::tolower(is.get())); + } + return token; +} + +static bool SkipChar(std::istream& is, unsigned char c, std::string* error) { + if (PeekChar(is, error) != c) { + *error = "Expected \'"; + error->push_back(c); + error->push_back('\''); + return false; + } + + is.get(); + return true; +} + +void SdpConnectionAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mValue << CRLF; +} + +void SdpDirectionAttribute::Serialize(std::ostream& os) const { + os << "a=" << mValue << CRLF; +} + +void SdpDtlsMessageAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mRole << " " << mValue << CRLF; +} + +bool SdpDtlsMessageAttribute::Parse(std::istream& is, std::string* error) { + std::string roleToken = ParseToken(is, " ", error); + if (roleToken == "server") { + mRole = kServer; + } else if (roleToken == "client") { + mRole = kClient; + } else { + *error = "Invalid dtls-message role; must be either client or server"; + return false; + } + + is >> std::ws; + + std::string s(std::istreambuf_iterator<char>(is), {}); + mValue = s; + + return true; +} + +void SdpExtmapAttributeList::Serialize(std::ostream& os) const { + for (auto i = mExtmaps.begin(); i != mExtmaps.end(); ++i) { + os << "a=" << mType << ":" << i->entry; + if (i->direction_specified) { + os << "/" << i->direction; + } + os << " " << i->extensionname; + if (i->extensionattributes.length()) { + os << " " << i->extensionattributes; + } + os << CRLF; + } +} + +void SdpFingerprintAttributeList::Serialize(std::ostream& os) const { + for (auto i = mFingerprints.begin(); i != mFingerprints.end(); ++i) { + os << "a=" << mType << ":" << i->hashFunc << " " + << FormatFingerprint(i->fingerprint) << CRLF; + } +} + +// Format the fingerprint in RFC 4572 Section 5 attribute format +std::string SdpFingerprintAttributeList::FormatFingerprint( + const std::vector<uint8_t>& fp) { + if (fp.empty()) { + MOZ_ASSERT(false, "Cannot format an empty fingerprint."); + return ""; + } + + std::ostringstream os; + for (auto i = fp.begin(); i != fp.end(); ++i) { + os << ":" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') + << static_cast<uint32_t>(*i); + } + return os.str().substr(1); +} + +static uint8_t FromUppercaseHex(char ch) { + if ((ch >= '0') && (ch <= '9')) { + return ch - '0'; + } + if ((ch >= 'A') && (ch <= 'F')) { + return ch - 'A' + 10; + } + return 16; // invalid +} + +// Parse the fingerprint from RFC 4572 Section 5 attribute format +std::vector<uint8_t> SdpFingerprintAttributeList::ParseFingerprint( + const std::string& str) { + size_t targetSize = (str.length() + 1) / 3; + std::vector<uint8_t> fp(targetSize); + size_t fpIndex = 0; + + if (str.length() % 3 != 2) { + fp.clear(); + return fp; + } + + for (size_t i = 0; i < str.length(); i += 3) { + uint8_t high = FromUppercaseHex(str[i]); + uint8_t low = FromUppercaseHex(str[i + 1]); + if (high > 0xf || low > 0xf || + (i + 2 < str.length() && str[i + 2] != ':')) { + fp.clear(); // error + return fp; + } + fp[fpIndex++] = high << 4 | low; + } + return fp; +} + +bool SdpFmtpAttributeList::operator==(const SdpFmtpAttributeList& other) const { + return mFmtps == other.mFmtps; +} + +void SdpFmtpAttributeList::Serialize(std::ostream& os) const { + for (auto i = mFmtps.begin(); i != mFmtps.end(); ++i) { + if (i->parameters) { + os << "a=" << mType << ":" << i->format << " "; + i->parameters->Serialize(os); + os << CRLF; + } + } +} + +void SdpGroupAttributeList::Serialize(std::ostream& os) const { + for (auto i = mGroups.begin(); i != mGroups.end(); ++i) { + os << "a=" << mType << ":" << i->semantics; + for (auto j = i->tags.begin(); j != i->tags.end(); ++j) { + os << " " << (*j); + } + os << CRLF; + } +} + +// We're just using an SdpStringAttribute for this right now +#if 0 +void SdpIdentityAttribute::Serialize(std::ostream& os) const +{ + os << "a=" << mType << ":" << mAssertion; + for (auto i = mExtensions.begin(); i != mExtensions.end(); i++) { + os << (i == mExtensions.begin() ? " " : ";") << (*i); + } + os << CRLF; +} +#endif + +// Class to help with omitting a leading delimiter for the first item in a list +class SkipFirstDelimiter { + public: + explicit SkipFirstDelimiter(const std::string& delim) + : mDelim(delim), mFirst(true) {} + + std::ostream& print(std::ostream& os) { + if (!mFirst) { + os << mDelim; + } + mFirst = false; + return os; + } + + private: + std::string mDelim; + bool mFirst; +}; + +static std::ostream& operator<<(std::ostream& os, SkipFirstDelimiter& delim) { + return delim.print(os); +} + +void SdpImageattrAttributeList::XYRange::Serialize(std::ostream& os) const { + if (discreteValues.empty()) { + os << "[" << min << ":"; + if (step != 1) { + os << step << ":"; + } + os << max << "]"; + } else if (discreteValues.size() == 1) { + os << discreteValues.front(); + } else { + os << "["; + SkipFirstDelimiter comma(","); + for (auto value : discreteValues) { + os << comma << value; + } + os << "]"; + } +} + +template <typename T> +bool GetUnsigned(std::istream& is, T min, T max, T* value, std::string* error) { + if (PeekChar(is, error) == '-') { + *error = "Value is less than 0"; + return false; + } + + is >> std::noskipws >> *value; + + if (is.fail()) { + *error = "Malformed"; + return false; + } + + if (*value < min) { + *error = "Value too small"; + return false; + } + + if (*value > max) { + *error = "Value too large"; + return false; + } + + return true; +} + +static bool GetXYValue(std::istream& is, uint32_t* value, std::string* error) { + return GetUnsigned<uint32_t>(is, 1, 999999, value, error); +} + +bool SdpImageattrAttributeList::XYRange::ParseDiscreteValues( + std::istream& is, std::string* error) { + do { + uint32_t value; + if (!GetXYValue(is, &value, error)) { + return false; + } + discreteValues.push_back(value); + } while (SkipChar(is, ',', error)); + + return SkipChar(is, ']', error); +} + +bool SdpImageattrAttributeList::XYRange::ParseAfterMin(std::istream& is, + std::string* error) { + // We have already parsed "[320:", and now expect another uint + uint32_t value; + if (!GetXYValue(is, &value, error)) { + return false; + } + + if (SkipChar(is, ':', error)) { + // Range with step eg [320:16:640] + step = value; + // Now |value| should be the max + if (!GetXYValue(is, &value, error)) { + return false; + } + } + + max = value; + if (min >= max) { + *error = "Min is not smaller than max"; + return false; + } + + return SkipChar(is, ']', error); +} + +bool SdpImageattrAttributeList::XYRange::ParseAfterBracket(std::istream& is, + std::string* error) { + // Either a range, or a list of discrete values + // [320:640], [320:16:640], or [320,640] + uint32_t value; + if (!GetXYValue(is, &value, error)) { + return false; + } + + if (SkipChar(is, ':', error)) { + // Range - [640:480] or [640:16:480] + min = value; + return ParseAfterMin(is, error); + } + + if (SkipChar(is, ',', error)) { + discreteValues.push_back(value); + return ParseDiscreteValues(is, error); + } + + *error = "Expected \':\' or \',\'"; + return false; +} + +bool SdpImageattrAttributeList::XYRange::Parse(std::istream& is, + std::string* error) { + if (SkipChar(is, '[', error)) { + return ParseAfterBracket(is, error); + } + + // Single discrete value + uint32_t value; + if (!GetXYValue(is, &value, error)) { + return false; + } + discreteValues.push_back(value); + + return true; +} + +static bool GetSPValue(std::istream& is, float* value, std::string* error) { + return GetUnsigned<float>(is, 0.1f, 9.9999f, value, error); +} + +static bool GetQValue(std::istream& is, float* value, std::string* error) { + return GetUnsigned<float>(is, 0.0f, 1.0f, value, error); +} + +bool SdpImageattrAttributeList::SRange::ParseDiscreteValues( + std::istream& is, std::string* error) { + do { + float value; + if (!GetSPValue(is, &value, error)) { + return false; + } + discreteValues.push_back(value); + } while (SkipChar(is, ',', error)); + + return SkipChar(is, ']', error); +} + +bool SdpImageattrAttributeList::SRange::ParseAfterMin(std::istream& is, + std::string* error) { + if (!GetSPValue(is, &max, error)) { + return false; + } + + if (min >= max) { + *error = "Min is not smaller than max"; + return false; + } + + return SkipChar(is, ']', error); +} + +bool SdpImageattrAttributeList::SRange::ParseAfterBracket(std::istream& is, + std::string* error) { + // Either a range, or a list of discrete values + float value; + if (!GetSPValue(is, &value, error)) { + return false; + } + + if (SkipChar(is, '-', error)) { + min = value; + return ParseAfterMin(is, error); + } + + if (SkipChar(is, ',', error)) { + discreteValues.push_back(value); + return ParseDiscreteValues(is, error); + } + + *error = "Expected either \'-\' or \',\'"; + return false; +} + +bool SdpImageattrAttributeList::SRange::Parse(std::istream& is, + std::string* error) { + if (SkipChar(is, '[', error)) { + return ParseAfterBracket(is, error); + } + + // Single discrete value + float value; + if (!GetSPValue(is, &value, error)) { + return false; + } + discreteValues.push_back(value); + return true; +} + +bool SdpImageattrAttributeList::PRange::Parse(std::istream& is, + std::string* error) { + if (!SkipChar(is, '[', error)) { + return false; + } + + if (!GetSPValue(is, &min, error)) { + return false; + } + + if (!SkipChar(is, '-', error)) { + return false; + } + + if (!GetSPValue(is, &max, error)) { + return false; + } + + if (min >= max) { + *error = "min must be smaller than max"; + return false; + } + + if (!SkipChar(is, ']', error)) { + return false; + } + return true; +} + +void SdpImageattrAttributeList::SRange::Serialize(std::ostream& os) const { + os << std::setprecision(4) << std::fixed; + if (discreteValues.empty()) { + os << "[" << min << "-" << max << "]"; + } else if (discreteValues.size() == 1) { + os << discreteValues.front(); + } else { + os << "["; + SkipFirstDelimiter comma(","); + for (auto value : discreteValues) { + os << comma << value; + } + os << "]"; + } +} + +void SdpImageattrAttributeList::PRange::Serialize(std::ostream& os) const { + os << std::setprecision(4) << std::fixed; + os << "[" << min << "-" << max << "]"; +} + +static std::string ParseKey(std::istream& is, std::string* error) { + std::string token = ParseToken(is, "=", error); + if (!SkipChar(is, '=', error)) { + return ""; + } + return token; +} + +static bool SkipBraces(std::istream& is, std::string* error) { + if (PeekChar(is, error) != '[') { + *error = "Expected \'[\'"; + return false; + } + + size_t braceCount = 0; + do { + switch (PeekChar(is, error)) { + case '[': + ++braceCount; + break; + case ']': + --braceCount; + break; + default: + break; + } + is.get(); + } while (braceCount && is); + + if (!is) { + *error = "Expected closing brace"; + return false; + } + + return true; +} + +// Assumptions: +// 1. If the value contains '[' or ']', they are balanced. +// 2. The value contains no ',' outside of brackets. +static bool SkipValue(std::istream& is, std::string* error) { + while (is) { + switch (PeekChar(is, error)) { + case ',': + case ']': + return true; + case '[': + if (!SkipBraces(is, error)) { + return false; + } + break; + default: + is.get(); + } + } + + *error = "No closing \']\' on set"; + return false; +} + +bool SdpImageattrAttributeList::Set::Parse(std::istream& is, + std::string* error) { + if (!SkipChar(is, '[', error)) { + return false; + } + + if (ParseKey(is, error) != "x") { + *error = "Expected x="; + return false; + } + + if (!xRange.Parse(is, error)) { + return false; + } + + if (!SkipChar(is, ',', error)) { + return false; + } + + if (ParseKey(is, error) != "y") { + *error = "Expected y="; + return false; + } + + if (!yRange.Parse(is, error)) { + return false; + } + + qValue = 0.5f; // default + + bool gotSar = false; + bool gotPar = false; + bool gotQ = false; + + while (SkipChar(is, ',', error)) { + std::string key = ParseKey(is, error); + if (key.empty()) { + *error = "Expected key-value"; + return false; + } + + if (key == "sar") { + if (gotSar) { + *error = "Extra sar parameter"; + return false; + } + gotSar = true; + if (!sRange.Parse(is, error)) { + return false; + } + } else if (key == "par") { + if (gotPar) { + *error = "Extra par parameter"; + return false; + } + gotPar = true; + if (!pRange.Parse(is, error)) { + return false; + } + } else if (key == "q") { + if (gotQ) { + *error = "Extra q parameter"; + return false; + } + gotQ = true; + if (!GetQValue(is, &qValue, error)) { + return false; + } + } else { + if (!SkipValue(is, error)) { + return false; + } + } + } + + return SkipChar(is, ']', error); +} + +void SdpImageattrAttributeList::Set::Serialize(std::ostream& os) const { + os << "[x="; + xRange.Serialize(os); + os << ",y="; + yRange.Serialize(os); + if (sRange.IsSet()) { + os << ",sar="; + sRange.Serialize(os); + } + if (pRange.IsSet()) { + os << ",par="; + pRange.Serialize(os); + } + if (qValue >= 0) { + os << std::setprecision(2) << std::fixed << ",q=" << qValue; + } + os << "]"; +} + +bool SdpImageattrAttributeList::Imageattr::ParseSets(std::istream& is, + std::string* error) { + std::string type = ParseToken(is, " \t", error); + + bool* isAll = nullptr; + std::vector<Set>* sets = nullptr; + + if (type == "send") { + isAll = &sendAll; + sets = &sendSets; + } else if (type == "recv") { + isAll = &recvAll; + sets = &recvSets; + } else { + *error = "Unknown type, must be either send or recv"; + return false; + } + + if (*isAll || !sets->empty()) { + *error = "Multiple send or recv set lists"; + return false; + } + + is >> std::ws; + if (SkipChar(is, '*', error)) { + *isAll = true; + return true; + } + + do { + Set set; + if (!set.Parse(is, error)) { + return false; + } + + sets->push_back(set); + is >> std::ws; + } while (PeekChar(is, error) == '['); + + return true; +} + +bool SdpImageattrAttributeList::Imageattr::Parse(std::istream& is, + std::string* error) { + if (!SkipChar(is, '*', error)) { + uint16_t value; + if (!GetUnsigned<uint16_t>(is, 0, UINT16_MAX, &value, error)) { + return false; + } + pt = Some(value); + } + + is >> std::ws; + if (!ParseSets(is, error)) { + return false; + } + + // There might be a second one + is >> std::ws; + if (is.eof()) { + return true; + } + + if (!ParseSets(is, error)) { + return false; + } + + is >> std::ws; + if (!is.eof()) { + *error = "Trailing characters"; + return false; + } + + return true; +} + +void SdpImageattrAttributeList::Imageattr::Serialize(std::ostream& os) const { + if (pt.isSome()) { + os << *pt; + } else { + os << "*"; + } + + if (sendAll) { + os << " send *"; + } else if (!sendSets.empty()) { + os << " send"; + for (auto& set : sendSets) { + os << " "; + set.Serialize(os); + } + } + + if (recvAll) { + os << " recv *"; + } else if (!recvSets.empty()) { + os << " recv"; + for (auto& set : recvSets) { + os << " "; + set.Serialize(os); + } + } +} + +void SdpImageattrAttributeList::Serialize(std::ostream& os) const { + for (auto& imageattr : mImageattrs) { + os << "a=" << mType << ":"; + imageattr.Serialize(os); + os << CRLF; + } +} + +bool SdpImageattrAttributeList::PushEntry(const std::string& raw, + std::string* error, + size_t* errorPos) { + std::istringstream is(raw); + + Imageattr imageattr; + if (!imageattr.Parse(is, error)) { + is.clear(); + *errorPos = is.tellg(); + return false; + } + + mImageattrs.push_back(imageattr); + return true; +} + +void SdpMsidAttributeList::Serialize(std::ostream& os) const { + for (auto i = mMsids.begin(); i != mMsids.end(); ++i) { + os << "a=" << mType << ":" << i->identifier; + if (i->appdata.length()) { + os << " " << i->appdata; + } + os << CRLF; + } +} + +void SdpMsidSemanticAttributeList::Serialize(std::ostream& os) const { + for (auto i = mMsidSemantics.begin(); i != mMsidSemantics.end(); ++i) { + os << "a=" << mType << ":" << i->semantic; + for (auto j = i->msids.begin(); j != i->msids.end(); ++j) { + os << " " << *j; + } + os << CRLF; + } +} + +void SdpRemoteCandidatesAttribute::Serialize(std::ostream& os) const { + if (mCandidates.empty()) { + return; + } + + os << "a=" << mType; + for (auto i = mCandidates.begin(); i != mCandidates.end(); i++) { + os << (i == mCandidates.begin() ? ":" : " ") << i->id << " " << i->address + << " " << i->port; + } + os << CRLF; +} + +// Remove this function. See Bug 1469702 +bool SdpRidAttributeList::Rid::ParseParameters(std::istream& is, + std::string* error) { + if (!PeekChar(is, error)) { + // No parameters + return true; + } + + do { + is >> std::ws; + std::string key = ParseKey(is, error); + if (key.empty()) { + return false; // Illegal trailing cruft + } + + // This allows pt= to appear anywhere, instead of only at the beginning, but + // this ends up being significantly less code. + if (key == "pt") { + if (!ParseFormats(is, error)) { + return false; + } + } else if (key == "max-width") { + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxWidth, + error)) { + return false; + } + } else if (key == "max-height") { + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxHeight, + error)) { + return false; + } + } else if (key == "max-fps") { + uint32_t maxFps; + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &maxFps, error)) { + return false; + } + constraints.maxFps = Some(maxFps); + } else if (key == "max-fs") { + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxFs, + error)) { + return false; + } + } else if (key == "max-br") { + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxBr, + error)) { + return false; + } + } else if (key == "max-pps") { + if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxPps, + error)) { + return false; + } + } else if (key == "depend") { + if (!ParseDepend(is, error)) { + return false; + } + } else { + (void)ParseToken(is, ";", error); + } + } while (SkipChar(is, ';', error)); + return true; +} + +// Remove this function. See Bug 1469702 +bool SdpRidAttributeList::Rid::ParseDepend(std::istream& is, + std::string* error) { + do { + std::string id = ParseToken(is, ",;", error); + if (id.empty()) { + return false; + } + dependIds.push_back(id); + } while (SkipChar(is, ',', error)); + + return true; +} + +// Remove this function. See Bug 1469702 +bool SdpRidAttributeList::Rid::ParseFormats(std::istream& is, + std::string* error) { + do { + uint16_t fmt; + if (!GetUnsigned<uint16_t>(is, 0, 127, &fmt, error)) { + return false; + } + formats.push_back(fmt); + } while (SkipChar(is, ',', error)); + + return true; +} + +void SdpRidAttributeList::Rid::SerializeParameters(std::ostream& os) const { + if (!HasParameters()) { + return; + } + + os << " "; + + SkipFirstDelimiter semic(";"); + + if (!formats.empty()) { + os << semic << "pt="; + SkipFirstDelimiter comma(","); + for (uint16_t fmt : formats) { + os << comma << fmt; + } + } + + if (constraints.maxWidth) { + os << semic << "max-width=" << constraints.maxWidth; + } + + if (constraints.maxHeight) { + os << semic << "max-height=" << constraints.maxHeight; + } + + if (constraints.maxFps) { + os << semic << "max-fps=" << constraints.maxFps; + } + + if (constraints.maxFs) { + os << semic << "max-fs=" << constraints.maxFs; + } + + if (constraints.maxBr) { + os << semic << "max-br=" << constraints.maxBr; + } + + if (constraints.maxPps) { + os << semic << "max-pps=" << constraints.maxPps; + } + + if (!dependIds.empty()) { + os << semic << "depend="; + SkipFirstDelimiter comma(","); + for (const std::string& id : dependIds) { + os << comma << id; + } + } +} + +// Remove this function. See Bug 1469702 +bool SdpRidAttributeList::Rid::Parse(std::istream& is, std::string* error) { + id = ParseToken(is, " ", error); + if (!CheckRidValidity(id, error)) { + return false; + } + + is >> std::ws; + std::string directionToken = ParseToken(is, " ", error); + if (directionToken == "send") { + direction = sdp::kSend; + } else if (directionToken == "recv") { + direction = sdp::kRecv; + } else { + *error = "Invalid direction, must be either send or recv"; + return false; + } + + return ParseParameters(is, error); +} + +static std::bitset<256> GetAllowedRidCharacters() { + // From RFC 8851: + // rid-id = 1*(alpha-numeric / "-" / "_") + std::bitset<256> result; + for (unsigned char c = 'a'; c <= 'z'; ++c) { + result.set(c); + } + for (unsigned char c = 'A'; c <= 'Z'; ++c) { + result.set(c); + } + for (unsigned char c = '0'; c <= '9'; ++c) { + result.set(c); + } + // NOTE: RFC 8851 says these are allowed, but RFC 8852 says they are not + // https://www.rfc-editor.org/errata/eid7132 + // result.set('-'); + // result.set('_'); + return result; +} + +/* static */ +bool SdpRidAttributeList::CheckRidValidity(const std::string& aRid, + std::string* aError) { + if (aRid.empty()) { + *aError = "Rid must be non-empty (according to RFC 8851)"; + return false; + } + + // We need to check against a maximum length, but that is nowhere + // specified in webrtc-pc right now. + if (aRid.size() > 255) { + *aError = "Rid can be at most 255 characters long (according to RFC 8852)"; + return false; + } + + // TODO: Right now, if the rid is longer than kMaxRidLength, we don't treat it + // as a parse error, since the grammar does not have this restriction. + // Instead, our JSEP code ignores rids that exceed this limit. However, there + // is a possibility that the IETF grammar (in RFC 8852) will change the limit + // from 255 to 16, in which case we will need to revise this code. + + static const std::bitset<256> allowed = GetAllowedRidCharacters(); + for (unsigned char c : aRid) { + if (!allowed[c]) { + *aError = + "Rid can contain only alphanumeric characters (according to RFC " + "8852)"; + return false; + } + } + + return true; +} + +// This can be overridden if necessary +size_t SdpRidAttributeList::kMaxRidLength = 255; + +void SdpRidAttributeList::Rid::Serialize(std::ostream& os) const { + os << id << " " << direction; + SerializeParameters(os); +} + +bool SdpRidAttributeList::Rid::HasFormat(const std::string& format) const { + if (formats.empty()) { + return true; + } + + uint16_t formatAsInt; + if (!SdpHelper::GetPtAsInt(format, &formatAsInt)) { + return false; + } + + return (std::find(formats.begin(), formats.end(), formatAsInt) != + formats.end()); +} + +void SdpRidAttributeList::Serialize(std::ostream& os) const { + for (const Rid& rid : mRids) { + os << "a=" << mType << ":"; + rid.Serialize(os); + os << CRLF; + } +} + +// Remove this function. See Bug 1469702 +bool SdpRidAttributeList::PushEntry(const std::string& raw, std::string* error, + size_t* errorPos) { + std::istringstream is(raw); + + Rid rid; + if (!rid.Parse(is, error)) { + is.clear(); + *errorPos = is.tellg(); + return false; + } + + mRids.push_back(rid); + return true; +} + +void SdpRidAttributeList::PushEntry(const std::string& id, sdp::Direction dir, + const std::vector<uint16_t>& formats, + const EncodingConstraints& constraints, + const std::vector<std::string>& dependIds) { + SdpRidAttributeList::Rid rid; + + rid.id = id; + rid.direction = dir; + rid.formats = formats; + rid.constraints = constraints; + rid.dependIds = dependIds; + + mRids.push_back(std::move(rid)); +} + +void SdpRtcpAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mPort; + if (!mAddress.empty()) { + os << " " << mNetType << " " << mAddrType << " " << mAddress; + } + os << CRLF; +} + +const char* SdpRtcpFbAttributeList::pli = "pli"; +const char* SdpRtcpFbAttributeList::sli = "sli"; +const char* SdpRtcpFbAttributeList::rpsi = "rpsi"; +const char* SdpRtcpFbAttributeList::app = "app"; + +const char* SdpRtcpFbAttributeList::fir = "fir"; +const char* SdpRtcpFbAttributeList::tmmbr = "tmmbr"; +const char* SdpRtcpFbAttributeList::tstr = "tstr"; +const char* SdpRtcpFbAttributeList::vbcm = "vbcm"; + +void SdpRtcpFbAttributeList::Serialize(std::ostream& os) const { + for (auto i = mFeedbacks.begin(); i != mFeedbacks.end(); ++i) { + os << "a=" << mType << ":" << i->pt << " " << i->type; + if (i->parameter.length()) { + os << " " << i->parameter; + if (i->extra.length()) { + os << " " << i->extra; + } + } + os << CRLF; + } +} + +static bool ShouldSerializeChannels(SdpRtpmapAttributeList::CodecType type) { + switch (type) { + case SdpRtpmapAttributeList::kOpus: + case SdpRtpmapAttributeList::kG722: + return true; + case SdpRtpmapAttributeList::kPCMU: + case SdpRtpmapAttributeList::kPCMA: + case SdpRtpmapAttributeList::kVP8: + case SdpRtpmapAttributeList::kVP9: + case SdpRtpmapAttributeList::kiLBC: + case SdpRtpmapAttributeList::kiSAC: + case SdpRtpmapAttributeList::kH264: + case SdpRtpmapAttributeList::kRed: + case SdpRtpmapAttributeList::kUlpfec: + case SdpRtpmapAttributeList::kTelephoneEvent: + case SdpRtpmapAttributeList::kRtx: + return false; + case SdpRtpmapAttributeList::kOtherCodec: + return true; + } + MOZ_CRASH(); +} + +void SdpRtpmapAttributeList::Serialize(std::ostream& os) const { + for (auto i = mRtpmaps.begin(); i != mRtpmaps.end(); ++i) { + os << "a=" << mType << ":" << i->pt << " " << i->name << "/" << i->clock; + if (i->channels && ShouldSerializeChannels(i->codec)) { + os << "/" << i->channels; + } + os << CRLF; + } +} + +void SdpSctpmapAttributeList::Serialize(std::ostream& os) const { + for (auto i = mSctpmaps.begin(); i != mSctpmaps.end(); ++i) { + os << "a=" << mType << ":" << i->pt << " " << i->name << " " << i->streams + << CRLF; + } +} + +void SdpSetupAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mRole << CRLF; +} + +void SdpSimulcastAttribute::Version::Serialize(std::ostream& os) const { + SkipFirstDelimiter comma(","); + for (const Encoding& choice : choices) { + os << comma; + if (choice.paused) { + os << '~'; + } + os << choice.rid; + } +} + +bool SdpSimulcastAttribute::Version::Parse(std::istream& is, + std::string* error) { + do { + bool paused = SkipChar(is, '~', error); + std::string value = ParseToken(is, ",; ", error); + if (value.empty()) { + *error = "Missing rid"; + return false; + } + if (!SdpRidAttributeList::CheckRidValidity(value, error)) { + return false; + } + choices.push_back(Encoding(value, paused)); + } while (SkipChar(is, ',', error)); + + return true; +} + +void SdpSimulcastAttribute::Versions::Serialize(std::ostream& os) const { + SkipFirstDelimiter semic(";"); + for (const Version& version : *this) { + if (!version.IsSet()) { + continue; + } + os << semic; + version.Serialize(os); + } +} + +bool SdpSimulcastAttribute::Versions::Parse(std::istream& is, + std::string* error) { + do { + Version version; + if (!version.Parse(is, error)) { + return false; + } + push_back(version); + } while (SkipChar(is, ';', error)); + + return true; +} + +void SdpSimulcastAttribute::Serialize(std::ostream& os) const { + MOZ_ASSERT(sendVersions.IsSet() || recvVersions.IsSet()); + + os << "a=" << mType << ":"; + + if (sendVersions.IsSet()) { + os << "send "; + sendVersions.Serialize(os); + } + + if (recvVersions.IsSet()) { + if (sendVersions.IsSet()) { + os << " "; + } + os << "recv "; + recvVersions.Serialize(os); + } + + os << CRLF; +} + +bool SdpSimulcastAttribute::Parse(std::istream& is, std::string* error) { + bool gotRecv = false; + bool gotSend = false; + + while (true) { + is >> std::ws; + std::string token = ParseToken(is, " \t", error); + if (token.empty()) { + break; + } + + if (token == "send") { + if (gotSend) { + *error = "Already got a send list"; + return false; + } + gotSend = true; + + is >> std::ws; + if (!sendVersions.Parse(is, error)) { + return false; + } + } else if (token == "recv") { + if (gotRecv) { + *error = "Already got a recv list"; + return false; + } + gotRecv = true; + + is >> std::ws; + if (!recvVersions.Parse(is, error)) { + return false; + } + } else { + *error = "Type must be either 'send' or 'recv'"; + return false; + } + } + + if (!gotSend && !gotRecv) { + *error = "Empty simulcast attribute"; + return false; + } + + return true; +} + +void SdpSsrcAttributeList::Serialize(std::ostream& os) const { + for (auto i = mSsrcs.begin(); i != mSsrcs.end(); ++i) { + os << "a=" << mType << ":" << i->ssrc << " " << i->attribute << CRLF; + } +} + +void SdpSsrcGroupAttributeList::Serialize(std::ostream& os) const { + for (auto i = mSsrcGroups.begin(); i != mSsrcGroups.end(); ++i) { + os << "a=" << mType << ":" << i->semantics; + for (auto j = i->ssrcs.begin(); j != i->ssrcs.end(); ++j) { + os << " " << (*j); + } + os << CRLF; + } +} + +void SdpMultiStringAttribute::Serialize(std::ostream& os) const { + for (auto i = mValues.begin(); i != mValues.end(); ++i) { + os << "a=" << mType << ":" << *i << CRLF; + } +} + +void SdpOptionsAttribute::Serialize(std::ostream& os) const { + if (mValues.empty()) { + return; + } + + os << "a=" << mType << ":"; + + for (auto i = mValues.begin(); i != mValues.end(); ++i) { + if (i != mValues.begin()) { + os << " "; + } + os << *i; + } + os << CRLF; +} + +void SdpOptionsAttribute::Load(const std::string& value) { + size_t start = 0; + size_t end = value.find(' '); + while (end != std::string::npos) { + PushEntry(value.substr(start, end)); + start = end + 1; + end = value.find(' ', start); + } + PushEntry(value.substr(start)); +} + +void SdpFlagAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << CRLF; +} + +void SdpStringAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mValue << CRLF; +} + +void SdpNumberAttribute::Serialize(std::ostream& os) const { + os << "a=" << mType << ":" << mValue << CRLF; +} + +bool SdpAttribute::IsAllowedAtMediaLevel(AttributeType type) { + switch (type) { + case kBundleOnlyAttribute: + return true; + case kCandidateAttribute: + return true; + case kConnectionAttribute: + return true; + case kDirectionAttribute: + return true; + case kDtlsMessageAttribute: + return false; + case kEndOfCandidatesAttribute: + return true; + case kExtmapAttribute: + return true; + case kFingerprintAttribute: + return true; + case kFmtpAttribute: + return true; + case kGroupAttribute: + return false; + case kIceLiteAttribute: + return false; + case kIceMismatchAttribute: + return true; + // RFC 5245 says this is session-level only, but + // draft-ietf-mmusic-ice-sip-sdp-03 updates this to allow at the media + // level. + case kIceOptionsAttribute: + return true; + case kIcePwdAttribute: + return true; + case kIceUfragAttribute: + return true; + case kIdentityAttribute: + return false; + case kImageattrAttribute: + return true; + case kLabelAttribute: + return true; + case kMaxptimeAttribute: + return true; + case kMidAttribute: + return true; + case kMsidAttribute: + return true; + case kMsidSemanticAttribute: + return false; + case kPtimeAttribute: + return true; + case kRemoteCandidatesAttribute: + return true; + case kRidAttribute: + return true; + case kRtcpAttribute: + return true; + case kRtcpFbAttribute: + return true; + case kRtcpMuxAttribute: + return true; + case kRtcpRsizeAttribute: + return true; + case kRtpmapAttribute: + return true; + case kSctpmapAttribute: + return true; + case kSetupAttribute: + return true; + case kSimulcastAttribute: + return true; + case kSsrcAttribute: + return true; + case kSsrcGroupAttribute: + return true; + case kSctpPortAttribute: + return true; + case kMaxMessageSizeAttribute: + return true; + } + MOZ_CRASH("Unknown attribute type"); +} + +bool SdpAttribute::IsAllowedAtSessionLevel(AttributeType type) { + switch (type) { + case kBundleOnlyAttribute: + return false; + case kCandidateAttribute: + return false; + case kConnectionAttribute: + return true; + case kDirectionAttribute: + return true; + case kDtlsMessageAttribute: + return true; + case kEndOfCandidatesAttribute: + return true; + case kExtmapAttribute: + return true; + case kFingerprintAttribute: + return true; + case kFmtpAttribute: + return false; + case kGroupAttribute: + return true; + case kIceLiteAttribute: + return true; + case kIceMismatchAttribute: + return false; + case kIceOptionsAttribute: + return true; + case kIcePwdAttribute: + return true; + case kIceUfragAttribute: + return true; + case kIdentityAttribute: + return true; + case kImageattrAttribute: + return false; + case kLabelAttribute: + return false; + case kMaxptimeAttribute: + return false; + case kMidAttribute: + return false; + case kMsidSemanticAttribute: + return true; + case kMsidAttribute: + return false; + case kPtimeAttribute: + return false; + case kRemoteCandidatesAttribute: + return false; + case kRidAttribute: + return false; + case kRtcpAttribute: + return false; + case kRtcpFbAttribute: + return false; + case kRtcpMuxAttribute: + return false; + case kRtcpRsizeAttribute: + return false; + case kRtpmapAttribute: + return false; + case kSctpmapAttribute: + return false; + case kSetupAttribute: + return true; + case kSimulcastAttribute: + return false; + case kSsrcAttribute: + return false; + case kSsrcGroupAttribute: + return false; + case kSctpPortAttribute: + return false; + case kMaxMessageSizeAttribute: + return false; + } + MOZ_CRASH("Unknown attribute type"); +} + +const std::string SdpAttribute::GetAttributeTypeString(AttributeType type) { + switch (type) { + case kBundleOnlyAttribute: + return "bundle-only"; + case kCandidateAttribute: + return "candidate"; + case kConnectionAttribute: + return "connection"; + case kDtlsMessageAttribute: + return "dtls-message"; + case kEndOfCandidatesAttribute: + return "end-of-candidates"; + case kExtmapAttribute: + return "extmap"; + case kFingerprintAttribute: + return "fingerprint"; + case kFmtpAttribute: + return "fmtp"; + case kGroupAttribute: + return "group"; + case kIceLiteAttribute: + return "ice-lite"; + case kIceMismatchAttribute: + return "ice-mismatch"; + case kIceOptionsAttribute: + return "ice-options"; + case kIcePwdAttribute: + return "ice-pwd"; + case kIceUfragAttribute: + return "ice-ufrag"; + case kIdentityAttribute: + return "identity"; + case kImageattrAttribute: + return "imageattr"; + case kLabelAttribute: + return "label"; + case kMaxptimeAttribute: + return "maxptime"; + case kMidAttribute: + return "mid"; + case kMsidAttribute: + return "msid"; + case kMsidSemanticAttribute: + return "msid-semantic"; + case kPtimeAttribute: + return "ptime"; + case kRemoteCandidatesAttribute: + return "remote-candidates"; + case kRidAttribute: + return "rid"; + case kRtcpAttribute: + return "rtcp"; + case kRtcpFbAttribute: + return "rtcp-fb"; + case kRtcpMuxAttribute: + return "rtcp-mux"; + case kRtcpRsizeAttribute: + return "rtcp-rsize"; + case kRtpmapAttribute: + return "rtpmap"; + case kSctpmapAttribute: + return "sctpmap"; + case kSetupAttribute: + return "setup"; + case kSimulcastAttribute: + return "simulcast"; + case kSsrcAttribute: + return "ssrc"; + case kSsrcGroupAttribute: + return "ssrc-group"; + case kSctpPortAttribute: + return "sctp-port"; + case kMaxMessageSizeAttribute: + return "max-message-size"; + case kDirectionAttribute: + MOZ_CRASH("kDirectionAttribute not valid here"); + } + MOZ_CRASH("Unknown attribute type"); +} + +} // namespace mozilla |