From 3c30078b23d831352511ffc4f86a182a049012a4 Mon Sep 17 00:00:00 2001 From: "Byron Campen [:bwc]" Date: Wed, 19 Nov 2014 16:11:06 -0800 Subject: [PATCH] Bug 1091242 - Part 1: SDP wrapper code around sipcc sdp impl. See https://github.com/unicorn-wg/gecko-dev/tree/multistream_rebase for more history. r=jesup --- media/webrtc/signaling/src/sdp/Sdp.h | 195 ++ .../webrtc/signaling/src/sdp/SdpAttribute.cpp | 588 ++++++ media/webrtc/signaling/src/sdp/SdpAttribute.h | 1315 +++++++++++++ .../signaling/src/sdp/SdpAttributeList.h | 90 + media/webrtc/signaling/src/sdp/SdpEnum.h | 53 + .../webrtc/signaling/src/sdp/SdpErrorHolder.h | 50 + .../signaling/src/sdp/SdpMediaSection.h | 284 +++ media/webrtc/signaling/src/sdp/SipccSdp.cpp | 186 ++ media/webrtc/signaling/src/sdp/SipccSdp.h | 88 + .../src/sdp/SipccSdpAttributeList.cpp | 1116 +++++++++++ .../signaling/src/sdp/SipccSdpAttributeList.h | 133 ++ .../src/sdp/SipccSdpMediaSection.cpp | 332 ++++ .../signaling/src/sdp/SipccSdpMediaSection.h | 93 + .../signaling/src/sdp/SipccSdpParser.cpp | 83 + .../webrtc/signaling/src/sdp/SipccSdpParser.h | 35 + media/webrtc/signaling/test/sdp_unittests.cpp | 1717 ++++++++++++++++- 16 files changed, 6300 insertions(+), 58 deletions(-) create mode 100644 media/webrtc/signaling/src/sdp/Sdp.h create mode 100644 media/webrtc/signaling/src/sdp/SdpAttribute.cpp create mode 100644 media/webrtc/signaling/src/sdp/SdpAttribute.h create mode 100644 media/webrtc/signaling/src/sdp/SdpAttributeList.h create mode 100644 media/webrtc/signaling/src/sdp/SdpEnum.h create mode 100644 media/webrtc/signaling/src/sdp/SdpErrorHolder.h create mode 100644 media/webrtc/signaling/src/sdp/SdpMediaSection.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdp.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdp.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpParser.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpParser.h diff --git a/media/webrtc/signaling/src/sdp/Sdp.h b/media/webrtc/signaling/src/sdp/Sdp.h new file mode 100644 index 000000000000..3b00e5ab1b0d --- /dev/null +++ b/media/webrtc/signaling/src/sdp/Sdp.h @@ -0,0 +1,195 @@ +/* -*- 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/. */ + +/* + + ,-----. ,--. ,--. + ' .--./ ,--,--.,--.,--.,-' '-.`--' ,---. ,--,--, + | | ' ,-. || || |'-. .-',--.| .-. || ` + ' '--'\\ '-' |' '' ' | | | |' '-' '| || | + `-----' `--`--' `----' `--' `--' `---' `--''--' + + :+o+- + -dNNNNNd. + yNNNNNNNs + :mNNNNNm- + `/sso/``-://- + .:+sydNNNNNNms: `://` + `-/+shmNNNNNNNNNNNNNNNms- :mNNNm/ + `-/oydmNNNNNNNNNNNNNNNNNNNNNNNNdo- +NNNNNN+ + .shmNNNNNNNNNNNmdyo/:dNNNNNNNNNNNNNNNNdo. `sNNNNNm+ + hNNNNNNNNmhs+:-` .dNNNNNNNNNNNNNNNNNNNNh+-` `hNNNNNm: + -yddyo/:. -dNNNNm::ymNNNNNNNNNNNNNNNmdy+/dNNNNNd. + :mNNNNd. `/ymNNNNNNNNNNNNNNNNNNNNNNh` + +NNNNNh` `+hNNNNNNNNNNNNNNNNNNNs + sNNNNNy` .yNNNNNm`-/oymNNNm+ + `yNNNNNo oNNNNNm` `-. + .dNNNNm/ oNNNNNm` + oNNNNm: +NNNNNm` + `+yho. +NNNNNm` + +NNNNNNs. + `yNNNNNNmy- + -smNNNNNNh: + .smNNNNNNh/ + `omNNNNNNd: + `+dNNNNNd + ````......```` /hmdy- + `.:/+osyhddmNNMMMMMMMMMMMMMMMMMMMMNNmddhyso+/:.` + `-+shmNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmhs+-` + -smMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMds- + hMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMh + yMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMs + .ohNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNh+. + ./oydmMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMmhyo:. + `.:/+osyyhddmmNNMMMMMMMMMMMMMMNNmmddhyyso+/:.` + + ,--------.,--. ,--. ,--. + '--. .--'| ,---. `--' ,---. | | ,---. + | | | .-. |,--.( .-' | |( .-' + | | | | | || |.-' `) | |.-' `) + `--' `--' `--'`--'`----' `--'`----' + ,--. + ,---. ,------. ,------. ,--. | | + ' .-' | .-. \ | .--. ' ,--,--.,--.--.,-' '-. ,--,--.| | + `. `-. | | \ :| '--' |' ,-. || .--''-. .-'' ,-. || | + .-' || '--' /| | --' \ '-' || | | | \ '-' |`--' + `-----' `-------' `--' `--`--'`--' `--' `--`--'.--. + '__' +*/ + +#ifndef _SDP_H_ +#define _SDP_H_ + +#include +#include +#include +#include "mozilla/UniquePtr.h" +#include "mozilla/Maybe.h" +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/sdp/SdpAttributeList.h" +#include "signaling/src/sdp/SdpEnum.h" + +namespace mozilla +{ + +class SdpOrigin; +class SdpEncryptionKey; +class SdpMediaSection; + +/** + * Base class for an SDP + */ +class Sdp +{ +public: + Sdp(){}; + virtual ~Sdp(){}; + + virtual const SdpOrigin& GetOrigin() const = 0; + // Note: connection information is always retrieved from media sections + virtual uint32_t GetBandwidth(const std::string& type) const = 0; + + virtual const SdpAttributeList& GetAttributeList() const = 0; + virtual SdpAttributeList& GetAttributeList() = 0; + + virtual size_t GetMediaSectionCount() const = 0; + virtual const SdpMediaSection& GetMediaSection(size_t level) const = 0; + virtual SdpMediaSection& GetMediaSection(size_t level) = 0; + + virtual SdpMediaSection& AddMediaSection(SdpMediaSection::MediaType media, + SdpDirectionAttribute::Direction dir, + uint16_t port, + SdpMediaSection::Protocol proto, + sdp::AddrType addrType, + const std::string& addr) = 0; + + virtual void Serialize(std::ostream&) const = 0; + + std::string ToString() const; +}; + +inline std::ostream& operator<<(std::ostream& os, const Sdp& sdp) +{ + sdp.Serialize(os); + return os; +} + +inline std::string +Sdp::ToString() const +{ + std::stringstream s; + s << *this; + return s.str(); +} + +class SdpOrigin +{ +public: + SdpOrigin(const std::string& username, uint64_t sessId, uint64_t sessVer, + sdp::AddrType addrType, const std::string& addr) + : mUsername(username), + mSessionId(sessId), + mSessionVersion(sessVer), + mAddrType(addrType), + mAddress(addr) + { + } + + const std::string& + GetUsername() const + { + return mUsername; + } + + uint64_t + GetSessionId() const + { + return mSessionId; + } + + uint64_t + GetSessionVersion() const + { + return mSessionVersion; + } + + const sdp::AddrType + GetAddrType() const + { + return mAddrType; + } + + const std::string& + GetAddress() const + { + return mAddress; + } + + void + Serialize(std::ostream& os) const + { + sdp::NetType netType = sdp::kInternet; + os << "o=" << mUsername << " " << mSessionId << " " << mSessionVersion + << " " << netType << " " << mAddrType << " " << mAddress << "\r\n"; + } + +private: + std::string mUsername; + uint64_t mSessionId; + uint64_t mSessionVersion; + sdp::AddrType mAddrType; + std::string mAddress; +}; + +inline std::ostream& operator<<(std::ostream& os, const SdpOrigin& origin) +{ + origin.Serialize(os); + return os; +} + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SdpAttribute.cpp b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp new file mode 100644 index 000000000000..2694a33f83f8 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpAttribute.cpp @@ -0,0 +1,588 @@ +/* -*- 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 "signaling/src/sdp/SdpAttribute.h" + +#include + +#ifdef CRLF +#undef CRLF +#endif +#define CRLF "\r\n" + +namespace mozilla +{ + +void +SdpConnectionAttribute::Serialize(std::ostream& os) const +{ + os << "a=" << mType << ":" << mValue << CRLF; +} + +void +SdpDirectionAttribute::Serialize(std::ostream& os) const +{ + os << "a=" << mValue << CRLF; +} + +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& 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(*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 +SdpFingerprintAttributeList::ParseFingerprint(const std::string& str) +{ + size_t targetSize = (str.length() + 1) / 3; + std::vector 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; +} + +void +SdpFmtpAttributeList::Serialize(std::ostream& os) const +{ + for (auto i = mFmtps.begin(); i != mFmtps.end(); ++i) { + os << "a=" << mType << ":" << i->format << " "; + if (i->parameters) { + i->parameters->Serialize(os); + } else { + os << i->parameters_string; + } + 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 + +void +SdpImageattrAttributeList::Serialize(std::ostream& os) const +{ + MOZ_ASSERT(false, "Serializer not yet implemented"); +} + +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 +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; +} + +void +SdpRtcpAttribute::Serialize(std::ostream& os) const +{ + os << "a=" << mType << ":" << mPort; + if (mNetType != sdp::kNetTypeNone && mAddrType != sdp::kAddrTypeNone) { + 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: + 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; + if (i->streams) { + os << " " << i->streams; + } + os << CRLF; + } +} + +void +SdpSetupAttribute::Serialize(std::ostream& os) const +{ + os << "a=" << mType << ":" << mRole << CRLF; +} + +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 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 kInactiveAttribute: + 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 kRecvonlyAttribute: + return true; + case kRemoteCandidatesAttribute: + 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 kSendonlyAttribute: + return true; + case kSendrecvAttribute: + return true; + case kSetupAttribute: + return true; + case kSsrcAttribute: + return true; + case kSsrcGroupAttribute: + 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 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 kInactiveAttribute: + return true; + 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 kRecvonlyAttribute: + return true; + case kRemoteCandidatesAttribute: + 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 kSendonlyAttribute: + return true; + case kSendrecvAttribute: + return true; + case kSetupAttribute: + return true; + case kSsrcAttribute: + return false; + case kSsrcGroupAttribute: + 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 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 kInactiveAttribute: + return "inactive"; + 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 kRecvonlyAttribute: + return "recvonly"; + case kRemoteCandidatesAttribute: + return "remote-candidates"; + 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 kSendonlyAttribute: + return "sendonly"; + case kSendrecvAttribute: + return "sendrecv"; + case kSetupAttribute: + return "setup"; + case kSsrcAttribute: + return "ssrc"; + case kSsrcGroupAttribute: + return "ssrc-group"; + case kDirectionAttribute: + MOZ_CRASH("kDirectionAttribute not valid here"); + } + MOZ_CRASH("Unknown attribute type"); +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SdpAttribute.h b/media/webrtc/signaling/src/sdp/SdpAttribute.h new file mode 100644 index 000000000000..aeb5577076c7 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpAttribute.h @@ -0,0 +1,1315 @@ +/* -*- 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/. */ + +#ifndef _SDPATTRIBUTE_H_ +#define _SDPATTRIBUTE_H_ + +#include +#include +#include +#include +#include +#include + +#include "mozilla/UniquePtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" + +#include "signaling/src/sdp/SdpEnum.h" + +namespace mozilla +{ + +/** + * Base class for SDP attributes +*/ +class SdpAttribute +{ +public: + enum AttributeType { + kFirstAttribute = 0, + kBundleOnlyAttribute = 0, + kCandidateAttribute, + kConnectionAttribute, + kDirectionAttribute, + kEndOfCandidatesAttribute, + kExtmapAttribute, + kFingerprintAttribute, + kFmtpAttribute, + kGroupAttribute, + kIceLiteAttribute, + kIceMismatchAttribute, + kIceOptionsAttribute, + kIcePwdAttribute, + kIceUfragAttribute, + kIdentityAttribute, + kImageattrAttribute, + kInactiveAttribute, + kLabelAttribute, + kMaxptimeAttribute, + kMidAttribute, + kMsidAttribute, + kMsidSemanticAttribute, + kPtimeAttribute, + kRecvonlyAttribute, + kRemoteCandidatesAttribute, + kRtcpAttribute, + kRtcpFbAttribute, + kRtcpMuxAttribute, + kRtcpRsizeAttribute, + kRtpmapAttribute, + kSctpmapAttribute, + kSendonlyAttribute, + kSendrecvAttribute, + kSetupAttribute, + kSsrcAttribute, + kSsrcGroupAttribute, + kLastAttribute = kSsrcGroupAttribute + }; + + explicit SdpAttribute(AttributeType type) : mType(type) {} + virtual ~SdpAttribute() {} + + AttributeType + GetType() const + { + return mType; + } + + virtual void Serialize(std::ostream&) const = 0; + + static bool IsAllowedAtSessionLevel(AttributeType type); + static bool IsAllowedAtMediaLevel(AttributeType type); + static const std::string GetAttributeTypeString(AttributeType type); + +protected: + AttributeType mType; +}; + +inline std::ostream& operator<<(std::ostream& os, const SdpAttribute& attr) +{ + attr.Serialize(os); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, + const SdpAttribute::AttributeType type) +{ + os << SdpAttribute::GetAttributeTypeString(type); + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=candidate, RFC5245 +//------------------------------------------------------------------------- +// +// candidate-attribute = "candidate" ":" foundation SP component-id SP +// transport SP +// priority SP +// connection-address SP ;from RFC 4566 +// port ;port from RFC 4566 +// SP cand-type +// [SP rel-addr] +// [SP rel-port] +// *(SP extension-att-name SP +// extension-att-value) +// foundation = 1*32ice-char +// component-id = 1*5DIGIT +// transport = "UDP" / transport-extension +// transport-extension = token ; from RFC 3261 +// priority = 1*10DIGIT +// cand-type = "typ" SP candidate-types +// candidate-types = "host" / "srflx" / "prflx" / "relay" / token +// rel-addr = "raddr" SP connection-address +// rel-port = "rport" SP port +// extension-att-name = byte-string ;from RFC 4566 +// extension-att-value = byte-string +// ice-char = ALPHA / DIGIT / "+" / "/" + +// We use a SdpMultiStringAttribute for candidates + +/////////////////////////////////////////////////////////////////////////// +// a=connection, RFC4145 +//------------------------------------------------------------------------- +// connection-attr = "a=connection:" conn-value +// conn-value = "new" / "existing" +class SdpConnectionAttribute : public SdpAttribute +{ +public: + enum ConnValue { kNew, kExisting }; + + explicit SdpConnectionAttribute(SdpConnectionAttribute::ConnValue value) + : SdpAttribute(kConnectionAttribute), mValue(value) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + ConnValue mValue; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpConnectionAttribute::ConnValue c) +{ + switch (c) { + case SdpConnectionAttribute::kNew: + os << "new"; + break; + case SdpConnectionAttribute::kExisting: + os << "existing"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=sendrecv / a=sendonly / a=recvonly / a=inactive, RFC 4566 +//------------------------------------------------------------------------- +class SdpDirectionAttribute : public SdpAttribute +{ +public: + static const unsigned kSendFlag = 1; + static const unsigned kRecvFlag = 1 << 1; + + enum Direction { + kInactive = 0, + kSendonly = kSendFlag, + kRecvonly = kRecvFlag, + kSendrecv = kSendFlag | kRecvFlag + }; + + explicit SdpDirectionAttribute(Direction value) + : SdpAttribute(kDirectionAttribute), mValue(value) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + Direction mValue; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpDirectionAttribute::Direction d) +{ + switch (d) { + case SdpDirectionAttribute::kSendonly: + os << "sendonly"; + break; + case SdpDirectionAttribute::kRecvonly: + os << "recvonly"; + break; + case SdpDirectionAttribute::kSendrecv: + os << "sendrecv"; + break; + case SdpDirectionAttribute::kInactive: + os << "inactive"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=extmap, RFC5285 +//------------------------------------------------------------------------- +// RFC5285 +// extmap = mapentry SP extensionname [SP extensionattributes] +// +// extensionname = URI +// +// direction = "sendonly" / "recvonly" / "sendrecv" / "inactive" +// +// mapentry = "extmap:" 1*5DIGIT ["/" direction] +// +// extensionattributes = byte-string +// +// URI = +// +// byte-string = +// +// SP = +// +// DIGIT = +class SdpExtmapAttributeList : public SdpAttribute +{ +public: + SdpExtmapAttributeList() : SdpAttribute(kExtmapAttribute) {} + + struct Extmap { + uint16_t entry; + SdpDirectionAttribute::Direction direction; + bool direction_specified; + std::string extensionname; + std::string extensionattributes; + }; + + void + PushEntry(uint16_t entry, SdpDirectionAttribute::Direction direction, + bool direction_specified, const std::string& extensionname, + const std::string& extensionattributes = "") + { + mExtmaps.push_back({ entry, direction, direction_specified, extensionname, + extensionattributes }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mExtmaps; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=fingerprint, RFC4572 +//------------------------------------------------------------------------- +// fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint +// +// hash-func = "sha-1" / "sha-224" / "sha-256" / +// "sha-384" / "sha-512" / +// "md5" / "md2" / token +// ; Additional hash functions can only come +// ; from updates to RFC 3279 +// +// fingerprint = 2UHEX *(":" 2UHEX) +// ; Each byte in upper-case hex, separated +// ; by colons. +// +// UHEX = DIGIT / %x41-46 ; A-F uppercase +class SdpFingerprintAttributeList : public SdpAttribute +{ +public: + SdpFingerprintAttributeList() : SdpAttribute(kFingerprintAttribute) {} + + enum HashAlgorithm { + kSha1, + kSha224, + kSha256, + kSha384, + kSha512, + kMd5, + kMd2, + kUnknownAlgorithm + }; + + struct Fingerprint { + HashAlgorithm hashFunc; + std::vector fingerprint; + }; + + // For use by application programmers. Enforces that it's a known and + // non-crazy algorithm. + void + PushEntry(const std::string& algorithm_str, + const std::vector& fingerprint, + bool enforcePlausible = true) + { + SdpFingerprintAttributeList::HashAlgorithm algorithm = + SdpFingerprintAttributeList::kUnknownAlgorithm; + + if (algorithm_str == "sha-1") { + algorithm = SdpFingerprintAttributeList::kSha1; + } else if (algorithm_str == "sha-224") { + algorithm = SdpFingerprintAttributeList::kSha224; + } else if (algorithm_str == "sha-256") { + algorithm = SdpFingerprintAttributeList::kSha256; + } else if (algorithm_str == "sha-384") { + algorithm = SdpFingerprintAttributeList::kSha384; + } else if (algorithm_str == "sha-512") { + algorithm = SdpFingerprintAttributeList::kSha512; + } else if (algorithm_str == "md5") { + algorithm = SdpFingerprintAttributeList::kMd5; + } else if (algorithm_str == "md2") { + algorithm = SdpFingerprintAttributeList::kMd2; + } + + if ((algorithm == SdpFingerprintAttributeList::kUnknownAlgorithm) || + fingerprint.empty()) { + if (enforcePlausible) { + MOZ_ASSERT(false, "Unknown fingerprint algorithm"); + } else { + return; + } + } + + PushEntry(algorithm, fingerprint); + } + + void + PushEntry(HashAlgorithm hashFunc, const std::vector& fingerprint) + { + mFingerprints.push_back({ hashFunc, fingerprint }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mFingerprints; + + static std::string FormatFingerprint(const std::vector& fp); + static std::vector ParseFingerprint(const std::string& str); +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpFingerprintAttributeList::HashAlgorithm a) +{ + switch (a) { + case SdpFingerprintAttributeList::kSha1: + os << "sha-1"; + break; + case SdpFingerprintAttributeList::kSha224: + os << "sha-224"; + break; + case SdpFingerprintAttributeList::kSha256: + os << "sha-256"; + break; + case SdpFingerprintAttributeList::kSha384: + os << "sha-384"; + break; + case SdpFingerprintAttributeList::kSha512: + os << "sha-512"; + break; + case SdpFingerprintAttributeList::kMd5: + os << "md5"; + break; + case SdpFingerprintAttributeList::kMd2: + os << "md2"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=group, RFC5888 +//------------------------------------------------------------------------- +// group-attribute = "a=group:" semantics +// *(SP identification-tag) +// semantics = "LS" / "FID" / semantics-extension +// semantics-extension = token +// identification-tag = token +class SdpGroupAttributeList : public SdpAttribute +{ +public: + SdpGroupAttributeList() : SdpAttribute(kGroupAttribute) {} + + enum Semantics { + kLs, // RFC5888 + kFid, // RFC5888 + kSrf, // RFC3524 + kAnat, // RFC4091 + kFec, // RFC5956 + kFecFr, // RFC5956 + kCs, // draft-mehta-rmt-flute-sdp-05 + kDdp, // RFC5583 + kDup, // RFC7104 + kBundle // draft-ietf-mmusic-bundle + }; + + struct Group { + Semantics semantics; + std::vector tags; + }; + + void + PushEntry(Semantics semantics, const std::vector& tags) + { + mGroups.push_back({ semantics, tags }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mGroups; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpGroupAttributeList::Semantics s) +{ + switch (s) { + case SdpGroupAttributeList::kLs: + os << "LS"; + break; + case SdpGroupAttributeList::kFid: + os << "FID"; + break; + case SdpGroupAttributeList::kSrf: + os << "SRF"; + break; + case SdpGroupAttributeList::kAnat: + os << "ANAT"; + break; + case SdpGroupAttributeList::kFec: + os << "FEC"; + break; + case SdpGroupAttributeList::kFecFr: + os << "FEC-FR"; + break; + case SdpGroupAttributeList::kCs: + os << "CS"; + break; + case SdpGroupAttributeList::kDdp: + os << "DDP"; + break; + case SdpGroupAttributeList::kDup: + os << "DUP"; + break; + case SdpGroupAttributeList::kBundle: + os << "BUNDLE"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=identity, draft-ietf-rtcweb-security-arch +//------------------------------------------------------------------------- +// identity-attribute = "identity:" identity-assertion +// [ SP identity-extension +// *(";" [ SP ] identity-extension) ] +// identity-assertion = base64 +// base64 = 1*(ALPHA / DIGIT / "+" / "/" / "=" ) +// identity-extension = extension-att-name [ "=" extension-att-value ] +// extension-att-name = token +// extension-att-value = 1*(%x01-09 / %x0b-0c / %x0e-3a / %x3c-ff) +// ; byte-string from [RFC4566] omitting ";" + +// We're just using an SdpStringAttribute for this right now +#if 0 +class SdpIdentityAttribute : public SdpAttribute +{ +public: + explicit SdpIdentityAttribute(const std::string &assertion, + const std::vector &extensions = + std::vector()) : + SdpAttribute(kIdentityAttribute), + mAssertion(assertion), + mExtensions(extensions) {} + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::string mAssertion; + std::vector mExtensions; +} +#endif + +/////////////////////////////////////////////////////////////////////////// +// a=imageattr, RFC6236 +//------------------------------------------------------------------------- +// image-attr = "imageattr:" PT 1*2( 1*WSP ( "send" / "recv" ) +// 1*WSP attr-list ) +// PT = 1*DIGIT / "*" +// attr-list = ( set *(1*WSP set) ) / "*" +// ; WSP and DIGIT defined in [RFC5234] +// +// set= "[" "x=" xyrange "," "y=" xyrange *( "," key-value ) "]" +// ; x is the horizontal image size range (pixel count) +// ; y is the vertical image size range (pixel count) +// +// key-value = ( "sar=" srange ) +// / ( "par=" prange ) +// / ( "q=" qvalue ) +// ; Key-value MAY be extended with other keyword +// ; parameters. +// ; At most, one instance each of sar, par, or q +// ; is allowed in a set. +// ; +// ; sar (sample aspect ratio) is the sample aspect ratio +// ; associated with the set (optional, MAY be ignored) +// ; par (picture aspect ratio) is the allowed +// ; ratio between the display's x and y physical +// ; size (optional) +// ; q (optional, range [0.0..1.0], default value 0.5) +// ; is the preference for the given set, +// ; a higher value means a higher preference +// +// onetonine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" +// ; Digit between 1 and 9 +// xyvalue = onetonine *5DIGIT +// ; Digit between 1 and 9 that is +// ; followed by 0 to 5 other digits +// step = xyvalue +// xyrange = ( "[" xyvalue ":" [ step ":" ] xyvalue "]" ) +// ; Range between a lower and an upper value +// ; with an optional step, default step = 1 +// ; The rightmost occurrence of xyvalue MUST have a +// ; higher value than the leftmost occurrence. +// / ( "[" xyvalue 1*( "," xyvalue ) "]" ) +// ; Discrete values separated by ',' +// / ( xyvalue ) +// ; A single value +// spvalue = ( "0" "." onetonine *3DIGIT ) +// ; Values between 0.1000 and 0.9999 +// / ( onetonine "." 1*4DIGIT ) +// ; Values between 1.0000 and 9.9999 +// srange = ( "[" spvalue 1*( "," spvalue ) "]" ) +// ; Discrete values separated by ','. +// ; Each occurrence of spvalue MUST be +// ; greater than the previous occurrence. +// / ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// / ( spvalue ) +// ; A single value +// +// prange = ( "[" spvalue "-" spvalue "]" ) +// ; Range between a lower and an upper level (inclusive) +// ; The second occurrence of spvalue MUST have a higher +// ; value than the first +// +// qvalue = ( "0" "." 1*2DIGIT ) +// / ( "1" "." 1*2("0") ) +// ; Values between 0.00 and 1.00 +// +// XXX TBD -- We don't use this yet, and it's a project unto itself. +// + +class SdpImageattrAttributeList : public SdpAttribute +{ +public: + SdpImageattrAttributeList() : SdpAttribute(kImageattrAttribute) {} + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=msid, draft-ietf-mmusic-msid +//------------------------------------------------------------------------- +// msid-attr = "msid:" identifier [ SP appdata ] +// identifier = 1*64token-char ; see RFC 4566 +// appdata = 1*64token-char ; see RFC 4566 +class SdpMsidAttributeList : public SdpAttribute +{ +public: + SdpMsidAttributeList() : SdpAttribute(kMsidAttribute) {} + + struct Msid { + std::string identifier; + std::string appdata; + }; + + void + PushEntry(const std::string& identifier, const std::string& appdata = "") + { + mMsids.push_back({ identifier, appdata }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mMsids; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=remote-candiate, RFC5245 +//------------------------------------------------------------------------- +// remote-candidate-att = "remote-candidates" ":" remote-candidate +// 0*(SP remote-candidate) +// remote-candidate = component-ID SP connection-address SP port +class SdpRemoteCandidatesAttribute : public SdpAttribute +{ +public: + struct Candidate { + std::string id; + std::string address; + uint16_t port; + }; + + explicit SdpRemoteCandidatesAttribute( + const std::vector& candidates) + : SdpAttribute(kRemoteCandidatesAttribute), mCandidates(candidates) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mCandidates; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp, RFC3605 +//------------------------------------------------------------------------- +// rtcp-attribute = "a=rtcp:" port [nettype space addrtype space +// connection-address] CRLF +class SdpRtcpAttribute : public SdpAttribute +{ +public: + explicit SdpRtcpAttribute(uint16_t port, + sdp::NetType netType = sdp::kNetTypeNone, + sdp::AddrType addrType = sdp::kAddrTypeNone, + const std::string& address = "") + : SdpAttribute(kRtcpAttribute), + mPort(port), + mNetType(netType), + mAddrType(addrType), + mAddress(address) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + uint16_t mPort; + sdp::NetType mNetType; + sdp::AddrType mAddrType; + std::string mAddress; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=rtcp-fb, RFC4585 +//------------------------------------------------------------------------- +// rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF +// +// rtcp-fb-pt = "*" ; wildcard: applies to all formats +// / fmt ; as defined in SDP spec +// +// rtcp-fb-val = "ack" rtcp-fb-ack-param +// / "nack" rtcp-fb-nack-param +// / "trr-int" SP 1*DIGIT +// / rtcp-fb-id rtcp-fb-param +// +// rtcp-fb-id = 1*(alpha-numeric / "-" / "_") +// +// rtcp-fb-param = SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-ack-param = SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +// rtcp-fb-nack-param = SP "pli" +// / SP "sli" +// / SP "rpsi" +// / SP "app" [SP byte-string] +// / SP token [SP byte-string] +// / ; empty +// +class SdpRtcpFbAttributeList : public SdpAttribute +{ +public: + SdpRtcpFbAttributeList() : SdpAttribute(kRtcpFbAttribute) {} + + enum Type { kAck, kApp, kCcm, kNack, kTrrInt }; + + static const char* pli; + static const char* sli; + static const char* rpsi; + static const char* app; + + static const char* fir; + static const char* tmmbr; + static const char* tstr; + static const char* vbcm; + + struct Feedback { + std::string pt; + Type type; + std::string parameter; + std::string extra; + }; + + void + PushEntry(const std::string& pt, Type type, const std::string& parameter = "", + const std::string& extra = "") + { + mFeedbacks.push_back({ pt, type, parameter, extra }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mFeedbacks; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpRtcpFbAttributeList::Type type) +{ + switch (type) { + case SdpRtcpFbAttributeList::kAck: + os << "ack"; + break; + case SdpRtcpFbAttributeList::kApp: + os << "app"; + break; + case SdpRtcpFbAttributeList::kCcm: + os << "ccm"; + break; + case SdpRtcpFbAttributeList::kNack: + os << "nack"; + break; + case SdpRtcpFbAttributeList::kTrrInt: + os << "trr-int"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=rtpmap, RFC4566 +//------------------------------------------------------------------------- +// a=rtpmap: / [/] +class SdpRtpmapAttributeList : public SdpAttribute +{ +public: + SdpRtpmapAttributeList() : SdpAttribute(kRtpmapAttribute) {} + + // Minimal set to get going + enum CodecType { + kOpus, + kG722, + kPCMU, + kPCMA, + kVP8, + kVP9, + kiLBC, + kiSAC, + kH264, + kOtherCodec + }; + + struct Rtpmap { + std::string pt; + CodecType codec; + std::string name; + uint32_t clock; + // Technically, this could mean something else in the future. + // In practice, that's probably not going to happen. + uint32_t channels; + }; + + void + PushEntry(const std::string& pt, CodecType codec, const std::string& name, + uint32_t clock, uint32_t channels = 0) + { + mRtpmaps.push_back({ pt, codec, name, clock, channels }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + bool + HasEntry(const std::string& pt) const + { + for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) { + if (it->pt == pt) { + return true; + } + } + return false; + } + + const Rtpmap& + GetEntry(const std::string& pt) const + { + for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) { + if (it->pt == pt) { + return *it; + } + } + MOZ_CRASH(); + } + + std::vector mRtpmaps; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpRtpmapAttributeList::CodecType c) +{ + switch (c) { + case SdpRtpmapAttributeList::kOpus: + os << "opus"; + break; + case SdpRtpmapAttributeList::kG722: + os << "G722"; + break; + case SdpRtpmapAttributeList::kPCMU: + os << "PCMU"; + break; + case SdpRtpmapAttributeList::kPCMA: + os << "PCMA"; + break; + case SdpRtpmapAttributeList::kVP8: + os << "VP8"; + break; + case SdpRtpmapAttributeList::kVP9: + os << "VP9"; + break; + case SdpRtpmapAttributeList::kiLBC: + os << "iLBC"; + break; + case SdpRtpmapAttributeList::kiSAC: + os << "iSAC"; + break; + case SdpRtpmapAttributeList::kH264: + os << "H264"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=fmtp, RFC4566, RFC5576 +//------------------------------------------------------------------------- +// a=fmtp: +// +class SdpFmtpAttributeList : public SdpAttribute +{ +public: + SdpFmtpAttributeList() : SdpAttribute(kFmtpAttribute) {} + + // Base class for format parameters + class Parameters + { + public: + explicit Parameters(SdpRtpmapAttributeList::CodecType aCodec) + : codec_type(aCodec) + { + } + + virtual ~Parameters() {} + virtual Parameters* Clone() const = 0; + virtual void Serialize(std::ostream& os) const = 0; + + SdpRtpmapAttributeList::CodecType codec_type; + }; + + class H264Parameters : public Parameters + { + public: + H264Parameters() + : Parameters(SdpRtpmapAttributeList::kH264), + packetization_mode(0), + level_asymmetry_allowed(false), + profile_level_id(0), + max_mbps(0), + max_fs(0), + max_cpb(0), + max_dpb(0), + max_br(0) + { + memset(sprop_parameter_sets, 0, sizeof(sprop_parameter_sets)); + } + + virtual Parameters* + Clone() const MOZ_OVERRIDE + { + return new H264Parameters(*this); + } + + virtual void + Serialize(std::ostream& os) const MOZ_OVERRIDE + { + // Note: don't move this, since having an unconditional param up top + // lets us avoid a whole bunch of conditional streaming of ';' below + os << "profile-level-id=" << std::hex << std::setfill('0') << std::setw(6) + << profile_level_id << std::dec << std::setfill(' '); + + os << ";level-asymmetry-allowed=" << (level_asymmetry_allowed ? 1 : 0); + + if (strlen(sprop_parameter_sets)) { + os << ";sprop-parameter-sets=" << sprop_parameter_sets; + } + + if (packetization_mode != 0) { + os << ";packetization-mode=" << packetization_mode; + } + + if (max_mbps != 0) { + os << ";max-mbps=" << max_mbps; + } + + if (max_fs != 0) { + os << ";max-fs=" << max_fs; + } + + if (max_cpb != 0) { + os << ";max-cpb=" << max_cpb; + } + + if (max_dpb != 0) { + os << ";max-dpb=" << max_dpb; + } + + if (max_br != 0) { + os << ";max-br=" << max_br; + } + } + + static const size_t max_sprop_len = 128; + char sprop_parameter_sets[max_sprop_len]; + unsigned int packetization_mode; + bool level_asymmetry_allowed; + unsigned int profile_level_id; + unsigned int max_mbps; + unsigned int max_fs; + unsigned int max_cpb; + unsigned int max_dpb; + unsigned int max_br; + }; + + class VP8Parameters : public Parameters + { + public: + VP8Parameters() + : Parameters(SdpRtpmapAttributeList::kVP8), max_fs(0), max_fr(0) + { + } + + virtual Parameters* + Clone() const MOZ_OVERRIDE + { + return new VP8Parameters(*this); + } + + virtual void + Serialize(std::ostream& os) const MOZ_OVERRIDE + { + // draft-ietf-payload-vp8-11 says these are mandatory, upper layer + // needs to ensure they're set properly. + os << "max-fs=" << max_fs; + os << ";max-fr=" << max_fr; + } + + unsigned int max_fs; + unsigned int max_fr; + }; + + class Fmtp + { + public: + Fmtp(const std::string& aFormat, const std::string& aParametersString, + UniquePtr aParameters) + : format(aFormat), + parameters_string(aParametersString), + parameters(Move(aParameters)) + { + } + + // TODO: Rip all of this out when we have move semantics in the stl. + Fmtp(const Fmtp& orig) { *this = orig; } + + Fmtp& operator=(const Fmtp& rhs) + { + if (this != &rhs) { + format = rhs.format; + parameters_string = rhs.parameters_string; + parameters.reset(rhs.parameters ? rhs.parameters->Clone() : nullptr); + } + return *this; + } + + // The contract around these is as follows: + // * |format| and |parameters_string| are always set + // * |parameters| is only set if we recognized the media type and had + // a subclass of Parameters to represent that type of parameters + // * |parameters| is a best-effort representation; it might be missing + // stuff + // * if |parameters| is set, it determines the serialized form, + // otherwise |parameters_string| is used + // * Parameters::codec_type tells you the concrete class, eg + // kH264 -> H264Parameters + std::string format; + std::string parameters_string; + UniquePtr parameters; + }; + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + void + PushEntry(const std::string& format, const std::string& parameters_string, + UniquePtr parameters) + { + mFmtps.push_back(Fmtp(format, parameters_string, Move(parameters))); + } + + std::vector mFmtps; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=sctpmap, draft-ietf-mmusic-sctp-sdp-05 +//------------------------------------------------------------------------- +// sctpmap-attr = "a=sctpmap:" sctpmap-number media-subtypes +// [streams] +// sctpmap-number = 1*DIGIT +// protocol = labelstring +// labelstring = text +// text = byte-string +// streams = 1*DIGIT +// +// We're going to pretend that there are spaces where they make sense. +// +// (draft-06 is not backward compatabile and draft-07 replaced sctpmap's with +// fmtp maps - we should carefully choose when to upgrade) +class SdpSctpmapAttributeList : public SdpAttribute +{ +public: + SdpSctpmapAttributeList() : SdpAttribute(kSctpmapAttribute) {} + + struct Sctpmap { + std::string pt; + std::string name; + uint32_t streams; + }; + + void + PushEntry(const std::string& pt, const std::string& name, + uint32_t streams = 0) + { + mSctpmaps.push_back({ pt, name, streams }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + bool + HasEntry(const std::string& pt) const + { + for (auto it = mSctpmaps.begin(); it != mSctpmaps.end(); ++it) { + if (it->pt == pt) { + return true; + } + } + return false; + } + + const Sctpmap& + GetEntry(const std::string& pt) const + { + for (auto it = mSctpmaps.begin(); it != mSctpmaps.end(); ++it) { + if (it->pt == pt) { + return *it; + } + } + MOZ_CRASH(); + } + + std::vector mSctpmaps; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=setup, RFC4145 +//------------------------------------------------------------------------- +// setup-attr = "a=setup:" role +// role = "active" / "passive" / "actpass" / "holdconn" +class SdpSetupAttribute : public SdpAttribute +{ +public: + enum Role { kActive, kPassive, kActpass, kHoldconn }; + + explicit SdpSetupAttribute(Role role) + : SdpAttribute(kSetupAttribute), mRole(role) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + Role mRole; +}; + +inline std::ostream& operator<<(std::ostream& os, SdpSetupAttribute::Role r) +{ + switch (r) { + case SdpSetupAttribute::kActive: + os << "active"; + break; + case SdpSetupAttribute::kPassive: + os << "passive"; + break; + case SdpSetupAttribute::kActpass: + os << "actpass"; + break; + case SdpSetupAttribute::kHoldconn: + os << "holdconn"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc, RFC5576 +//------------------------------------------------------------------------- +// ssrc-attr = "ssrc:" ssrc-id SP attribute +// ; The base definition of "attribute" is in RFC 4566. +// ; (It is the content of "a=" lines.) +// +// ssrc-id = integer ; 0 .. 2**32 - 1 +//------------------------------------------------------------------------- +// TODO -- In the future, it might be nice if we ran a parse on the +// attribute section of this so that we could interpret it semantically. +// For WebRTC, the key use case for a=ssrc is assocaiting SSRCs with +// media sections, and we're not really going to care about the attribute +// itself. So we're just going to store it as a string for the time being. +// Issue 187. +class SdpSsrcAttributeList : public SdpAttribute +{ +public: + SdpSsrcAttributeList() : SdpAttribute(kSsrcAttribute) {} + + struct Ssrc { + uint32_t ssrc; + std::string attribute; + }; + + void + PushEntry(uint32_t ssrc, const std::string& attribute) + { + mSsrcs.push_back({ ssrc, attribute }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mSsrcs; +}; + +/////////////////////////////////////////////////////////////////////////// +// a=ssrc-group, RFC5576 +//------------------------------------------------------------------------- +// ssrc-group-attr = "ssrc-group:" semantics *(SP ssrc-id) +// +// semantics = "FEC" / "FID" / token +// +// ssrc-id = integer ; 0 .. 2**32 - 1 +class SdpSsrcGroupAttributeList : public SdpAttribute +{ +public: + enum Semantics { + kFec, // RFC5576 + kFid, // RFC5576 + kFecFr, // RFC5956 + kDup // RFC7104 + }; + + struct SsrcGroup { + Semantics semantics; + std::vector ssrcs; + }; + + SdpSsrcGroupAttributeList() : SdpAttribute(kSsrcGroupAttribute) {} + + void + PushEntry(Semantics semantics, const std::vector& ssrcs) + { + mSsrcGroups.push_back({ semantics, ssrcs }); + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::vector mSsrcGroups; +}; + +inline std::ostream& operator<<(std::ostream& os, + SdpSsrcGroupAttributeList::Semantics s) +{ + switch (s) { + case SdpSsrcGroupAttributeList::kFec: + os << "FEC"; + break; + case SdpSsrcGroupAttributeList::kFid: + os << "FID"; + break; + case SdpSsrcGroupAttributeList::kFecFr: + os << "FEC-FR"; + break; + case SdpSsrcGroupAttributeList::kDup: + os << "DUP"; + break; + default: + MOZ_ASSERT(false); + os << "?"; + } + return os; +} + +/////////////////////////////////////////////////////////////////////////// +class SdpMultiStringAttribute : public SdpAttribute +{ +public: + explicit SdpMultiStringAttribute(AttributeType type) : SdpAttribute(type) {} + + void + PushEntry(const std::string& entry) + { + mValues.push_back(entry); + } + + virtual void Serialize(std::ostream& os) const; + + std::vector mValues; +}; + +// otherwise identical to SdpMultiStringAttribute, this is used for +// ice-options and other places where the value is serialized onto +// a single line with space separating tokens +class SdpOptionsAttribute : public SdpAttribute +{ +public: + explicit SdpOptionsAttribute(AttributeType type) : SdpAttribute(type) {} + + void + PushEntry(const std::string& entry) + { + mValues.push_back(entry); + } + + void Load(const std::string& value); + + virtual void Serialize(std::ostream& os) const; + + std::vector mValues; +}; + +// Used for attributes that take no value (eg; a=ice-lite) +class SdpFlagAttribute : public SdpAttribute +{ +public: + explicit SdpFlagAttribute(AttributeType type) : SdpAttribute(type) {} + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; +}; + +// Used for any other kind of single-valued attribute not otherwise specialized +class SdpStringAttribute : public SdpAttribute +{ +public: + explicit SdpStringAttribute(AttributeType type, const std::string& value) + : SdpAttribute(type), mValue(value) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + std::string mValue; +}; + +// Used for any purely (non-negative) numeric attribute +class SdpNumberAttribute : public SdpAttribute +{ +public: + explicit SdpNumberAttribute(AttributeType type, uint32_t value = 0) + : SdpAttribute(type), mValue(value) + { + } + + virtual void Serialize(std::ostream& os) const MOZ_OVERRIDE; + + uint32_t mValue; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SdpAttributeList.h b/media/webrtc/signaling/src/sdp/SdpAttributeList.h new file mode 100644 index 000000000000..4343efb9650e --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpAttributeList.h @@ -0,0 +1,90 @@ +/* -*- 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/. */ + +#ifndef _SDPATTRIBUTELIST_H_ +#define _SDPATTRIBUTELIST_H_ + +#include "mozilla/UniquePtr.h" +#include "mozilla/Attributes.h" + +#include "signaling/src/sdp/SdpAttribute.h" + +namespace mozilla +{ + +class SdpAttributeList +{ +public: + typedef SdpAttribute::AttributeType AttributeType; + + // Avoid default params on virtual functions + bool + HasAttribute(AttributeType type) const + { + return HasAttribute(type, true); + } + + const SdpAttribute* + GetAttribute(AttributeType type) const + { + return GetAttribute(type, true); + } + + virtual bool HasAttribute(AttributeType type, bool sessionFallback) const = 0; + virtual const SdpAttribute* GetAttribute(AttributeType type, + bool sessionFallback) const = 0; + // The setter takes an attribute of any type, and takes ownership + virtual void SetAttribute(SdpAttribute* attr) = 0; + virtual void RemoveAttribute(AttributeType type) = 0; + virtual void Clear() = 0; + + virtual const SdpConnectionAttribute& GetConnection() const = 0; + virtual const SdpOptionsAttribute& GetIceOptions() const = 0; + virtual const SdpRtcpAttribute& GetRtcp() const = 0; + virtual const SdpRemoteCandidatesAttribute& GetRemoteCandidates() const = 0; + virtual const SdpSetupAttribute& GetSetup() const = 0; + + // These attributes can appear multiple times, so the returned + // classes actually represent a collection of values. + virtual const std::vector& GetCandidate() const = 0; + virtual const SdpExtmapAttributeList& GetExtmap() const = 0; + virtual const SdpFingerprintAttributeList& GetFingerprint() const = 0; + virtual const SdpFmtpAttributeList& GetFmtp() const = 0; + virtual const SdpGroupAttributeList& GetGroup() const = 0; + virtual const SdpImageattrAttributeList& GetImageattr() const = 0; + virtual const SdpMsidAttributeList& GetMsid() const = 0; + virtual const SdpRtcpFbAttributeList& GetRtcpFb() const = 0; + virtual const SdpRtpmapAttributeList& GetRtpmap() const = 0; + virtual const SdpSctpmapAttributeList& GetSctpmap() const = 0; + virtual const SdpSsrcAttributeList& GetSsrc() const = 0; + virtual const SdpSsrcGroupAttributeList& GetSsrcGroup() const = 0; + + // These attributes are effectively simple types, so we'll make life + // easy by just returning their value. + virtual const std::string& GetIcePwd() const = 0; + virtual const std::string& GetIceUfrag() const = 0; + virtual const std::string& GetIdentity() const = 0; + virtual const std::string& GetLabel() const = 0; + virtual unsigned int GetMaxptime() const = 0; + virtual const std::string& GetMid() const = 0; + virtual const std::string& GetMsidSemantic() const = 0; + virtual unsigned int GetPtime() const = 0; + + // This is "special", because it's multiple things + virtual SdpDirectionAttribute::Direction GetDirection() const = 0; + + virtual void Serialize(std::ostream&) const = 0; +}; + +inline std::ostream& operator<<(std::ostream& os, const SdpAttributeList& al) +{ + al.Serialize(os); + return os; +} + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SdpEnum.h b/media/webrtc/signaling/src/sdp/SdpEnum.h new file mode 100644 index 000000000000..0a90b30b15d2 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpEnum.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef _SDPENUM_H_ +#define _SDPENUM_H_ + +#include + +#include "mozilla/Assertions.h" + +namespace mozilla +{ +namespace sdp +{ + +enum NetType { kNetTypeNone, kInternet }; + +inline std::ostream& operator<<(std::ostream& os, sdp::NetType t) +{ + switch (t) { + case sdp::kNetTypeNone: + MOZ_ASSERT(false); + return os << "NONE"; + case sdp::kInternet: + return os << "IN"; + } + MOZ_CRASH("Unknown NetType"); +} + +enum AddrType { kAddrTypeNone, kIPv4, kIPv6 }; + +inline std::ostream& operator<<(std::ostream& os, sdp::AddrType t) +{ + switch (t) { + case sdp::kAddrTypeNone: + MOZ_ASSERT(false); + return os << "NONE"; + case sdp::kIPv4: + return os << "IP4"; + case sdp::kIPv6: + return os << "IP6"; + } + MOZ_CRASH("Unknown AddrType"); +} + +} // namespace sdp + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SdpErrorHolder.h b/media/webrtc/signaling/src/sdp/SdpErrorHolder.h new file mode 100644 index 000000000000..556fcefb68f7 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpErrorHolder.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef _SDPERRORHOLDER_H_ +#define _SDPERRORHOLDER_H_ + +#include +#include + +namespace mozilla +{ + +class SdpErrorHolder +{ +public: + SdpErrorHolder() {} + virtual ~SdpErrorHolder() {} + + void + AddParseError(size_t line, const std::string& message) + { + mErrors.push_back(std::make_pair(line, message)); + } + + void + ClearParseErrors() + { + mErrors.clear(); + } + + /** + * Returns a reference to the list of parse errors. + * This gets cleared out when you call Parse. + */ + const std::vector >& + GetParseErrors() const + { + return mErrors; + } + +private: + std::vector > mErrors; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SdpMediaSection.h b/media/webrtc/signaling/src/sdp/SdpMediaSection.h new file mode 100644 index 000000000000..440cb180b77e --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h @@ -0,0 +1,284 @@ +/* -*- 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/. */ + +#ifndef _SDPMEDIASECTION_H_ +#define _SDPMEDIASECTION_H_ + +#include "mozilla/Maybe.h" +#include "signaling/src/sdp/SdpEnum.h" +#include "signaling/src/sdp/SdpAttributeList.h" +#include +#include + +#include "signaling/src/sdp/SdpEnum.h" + +namespace mozilla +{ + +class SdpAttributeList; + +class SdpConnection; + +class SdpMediaSection +{ +public: + enum MediaType { kAudio, kVideo, kText, kApplication, kMessage }; + + enum Protocol { + kRtpAvp, // RTP/AVP [RFC4566] + kUdp, // udp [RFC4566] + kVat, // vat [historic] + kRtp, // rtp [historic] + kUdptl, // udptl [ITU-T] + kTcp, // TCP [RFC4145] + kRtpAvpf, // RTP/AVPF [RFC4585] + kTcpRtpAvp, // TCP/RTP/AVP [RFC4571] + kRtpSavp, // RTP/SAVP [RFC3711] + kTcpBfcp, // TCP/BFCP [RFC4583] + kTcpTlsBfcp, // TCP/TLS/BFCP [RFC4583] + kTcpTls, // TCP/TLS [RFC4572] + kFluteUdp, // FLUTE/UDP [RFC-mehta-rmt-flute-sdp-05] + kTcpMsrp, // TCP/MSRP [RFC4975] + kTcpTlsMsrp, // TCP/TLS/MSRP [RFC4975] + kDccp, // DCCP [RFC5762] + kDccpRtpAvp, // DCCP/RTP/AVP [RFC5762] + kDccpRtpSavp, // DCCP/RTP/SAVP [RFC5762] + kDccpRtpAvpf, // DCCP/RTP/AVPF [RFC5762] + kDccpRtpSavpf, // DCCP/RTP/SAVPF [RFC5762] + kRtpSavpf, // RTP/SAVPF [RFC5124] + kUdpTlsRtpSavp, // UDP/TLS/RTP/SAVP [RFC5764] + kTcpTlsRtpSavp, // TCP/TLS/RTP/SAVP [JSEP-TBD] + kDccpTlsRtpSavp, // DCCP/TLS/RTP/SAVP [RFC5764] + kUdpTlsRtpSavpf, // UDP/TLS/RTP/SAVPF [RFC5764] + kTcpTlsRtpSavpf, // TCP/TLS/RTP/SAVPF [JSEP-TBD] + kDccpTlsRtpSavpf, // DCCP/TLS/RTP/SAVPF [RFC5764] + kUdpMbmsFecRtpAvp, // UDP/MBMS-FEC/RTP/AVP [RFC6064] + kUdpMbmsFecRtpSavp, // UDP/MBMS-FEC/RTP/SAVP [RFC6064] + kUdpMbmsRepair, // UDP/MBMS-REPAIR [RFC6064] + kFecUdp, // FEC/UDP [RFC6364] + kUdpFec, // UDP/FEC [RFC6364] + kTcpMrcpv2, // TCP/MRCPv2 [RFC6787] + kTcpTlsMrcpv2, // TCP/TLS/MRCPv2 [RFC6787] + kPstn, // PSTN [RFC7195] + kUdpTlsUdptl, // UDP/TLS/UDPTL [RFC7345] + kSctp, // SCTP [draft-ietf-mmusic-sctp-sdp-07] + kSctpDtls, // SCTP/DTLS [draft-ietf-mmusic-sctp-sdp-07] + kDtlsSctp // DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-07] + }; + + explicit SdpMediaSection(size_t level) : mLevel(level) {} + + virtual MediaType GetMediaType() const = 0; + virtual unsigned int GetPort() const = 0; + virtual void SetPort(unsigned int port) = 0; + virtual unsigned int GetPortCount() const = 0; + virtual Protocol GetProtocol() const = 0; + virtual const SdpConnection& GetConnection() const = 0; + virtual SdpConnection& GetConnection() = 0; + virtual uint32_t GetBandwidth(const std::string& type) const = 0; + virtual const std::vector& GetFormats() const = 0; + + virtual const SdpAttributeList& GetAttributeList() const = 0; + virtual SdpAttributeList& GetAttributeList() = 0; + + virtual SdpDirectionAttribute GetDirectionAttribute() const = 0; + + virtual void Serialize(std::ostream&) const = 0; + + virtual void AddCodec(const std::string& pt, const std::string& name, + uint32_t clockrate, uint16_t channels) = 0; + + virtual void AddDataChannel(const std::string& pt, const std::string& name, + uint16_t streams) = 0; + + size_t + GetLevel() const + { + return mLevel; + } + +private: + size_t mLevel; +}; + +inline std::ostream& operator<<(std::ostream& os, const SdpMediaSection& ms) +{ + ms.Serialize(os); + return os; +} + +inline std::ostream& operator<<(std::ostream& os, SdpMediaSection::MediaType t) +{ + switch (t) { + case SdpMediaSection::kAudio: + return os << "audio"; + case SdpMediaSection::kVideo: + return os << "video"; + case SdpMediaSection::kText: + return os << "text"; + case SdpMediaSection::kApplication: + return os << "application"; + case SdpMediaSection::kMessage: + return os << "message"; + } + MOZ_ASSERT(false, "Unknown MediaType"); + return os << "?"; +} + +inline std::ostream& operator<<(std::ostream& os, SdpMediaSection::Protocol p) +{ + switch (p) { + case SdpMediaSection::kRtpAvp: + return os << "RTP/AVP"; + case SdpMediaSection::kUdp: + return os << "udp"; + case SdpMediaSection::kVat: + return os << "vat"; + case SdpMediaSection::kRtp: + return os << "rtp"; + case SdpMediaSection::kUdptl: + return os << "udptl"; + case SdpMediaSection::kTcp: + return os << "TCP"; + case SdpMediaSection::kRtpAvpf: + return os << "RTP/AVPF"; + case SdpMediaSection::kTcpRtpAvp: + return os << "TCP/RTP/AVP"; + case SdpMediaSection::kRtpSavp: + return os << "RTP/SAVP"; + case SdpMediaSection::kTcpBfcp: + return os << "TCP/BFCP"; + case SdpMediaSection::kTcpTlsBfcp: + return os << "TCP/TLS/BFCP"; + case SdpMediaSection::kTcpTls: + return os << "TCP/TLS"; + case SdpMediaSection::kFluteUdp: + return os << "FLUTE/UDP"; + case SdpMediaSection::kTcpMsrp: + return os << "TCP/MSRP"; + case SdpMediaSection::kTcpTlsMsrp: + return os << "TCP/TLS/MSRP"; + case SdpMediaSection::kDccp: + return os << "DCCP"; + case SdpMediaSection::kDccpRtpAvp: + return os << "DCCP/RTP/AVP"; + case SdpMediaSection::kDccpRtpSavp: + return os << "DCCP/RTP/SAVP"; + case SdpMediaSection::kDccpRtpAvpf: + return os << "DCCP/RTP/AVPF"; + case SdpMediaSection::kDccpRtpSavpf: + return os << "DCCP/RTP/SAVPF"; + case SdpMediaSection::kRtpSavpf: + return os << "RTP/SAVPF"; + case SdpMediaSection::kUdpTlsRtpSavp: + return os << "UDP/TLS/RTP/SAVP"; + case SdpMediaSection::kTcpTlsRtpSavp: + return os << "TCP/TLS/RTP/SAVP"; + case SdpMediaSection::kDccpTlsRtpSavp: + return os << "DCCP/TLS/RTP/SAVP"; + case SdpMediaSection::kUdpTlsRtpSavpf: + return os << "UDP/TLS/RTP/SAVPF"; + case SdpMediaSection::kTcpTlsRtpSavpf: + return os << "TCP/TLS/RTP/SAVPF"; + case SdpMediaSection::kDccpTlsRtpSavpf: + return os << "DCCP/TLS/RTP/SAVPF"; + case SdpMediaSection::kUdpMbmsFecRtpAvp: + return os << "UDP/MBMS-FEC/RTP/AVP"; + case SdpMediaSection::kUdpMbmsFecRtpSavp: + return os << "UDP/MBMS-FEC/RTP/SAVP"; + case SdpMediaSection::kUdpMbmsRepair: + return os << "UDP/MBMS-REPAIR"; + case SdpMediaSection::kFecUdp: + return os << "FEC/UDP"; + case SdpMediaSection::kUdpFec: + return os << "UDP/FEC"; + case SdpMediaSection::kTcpMrcpv2: + return os << "TCP/MRCPv2"; + case SdpMediaSection::kTcpTlsMrcpv2: + return os << "TCP/TLS/MRCPv2"; + case SdpMediaSection::kPstn: + return os << "PSTN"; + case SdpMediaSection::kUdpTlsUdptl: + return os << "UDP/TLS/UDPTL"; + case SdpMediaSection::kSctp: + return os << "SCTP"; + case SdpMediaSection::kSctpDtls: + return os << "SCTP/DTLS"; + case SdpMediaSection::kDtlsSctp: + return os << "DTLS/SCTP"; + } + MOZ_ASSERT(false, "Unknown Protocol"); + return os << "?"; +} + +class SdpConnection +{ +public: + SdpConnection(sdp::AddrType addrType, std::string addr, uint8_t ttl = 0, + uint32_t count = 0) + : mAddrType(addrType), mAddr(addr), mTtl(ttl), mCount(count) + { + } + ~SdpConnection() {} + + sdp::AddrType + GetAddrType() const + { + return mAddrType; + } + const std::string& + GetAddress() const + { + return mAddr; + } + void + SetAddress(const std::string& address) + { + mAddr = address; + } + uint8_t + GetTtl() const + { + return mTtl; + } + uint32_t + GetCount() const + { + return mCount; + } + + void + Serialize(std::ostream& os) const + { + sdp::NetType netType = sdp::kInternet; + + os << "c=" << netType << " " << mAddrType << " " << mAddr; + + if (mTtl) { + os << "/" << static_cast(mTtl); + if (mCount) { + os << "/" << mCount; + } + } + os << "\r\n"; + } + +private: + sdp::AddrType mAddrType; + std::string mAddr; + uint8_t mTtl; // 0-255; 0 when unset + uint32_t mCount; // 0 when unset +}; + +inline std::ostream& operator<<(std::ostream& os, const SdpConnection& c) +{ + c.Serialize(os); + return os; +} + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SipccSdp.cpp b/media/webrtc/signaling/src/sdp/SipccSdp.cpp new file mode 100644 index 000000000000..282910bf6462 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdp.cpp @@ -0,0 +1,186 @@ +/* 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 "signaling/src/sdp/SipccSdp.h" + +#include +#include "mozilla/Assertions.h" +#include "signaling/src/sdp/SdpErrorHolder.h" + +#ifdef CRLF +#undef CRLF +#endif +#define CRLF "\r\n" + +namespace mozilla +{ + +SipccSdp::~SipccSdp() +{ + for (auto i = mMediaSections.begin(); i != mMediaSections.end(); ++i) { + delete *i; + } +} + +const SdpOrigin& +SipccSdp::GetOrigin() const +{ + return mOrigin; +} + +uint32_t +SipccSdp::GetBandwidth(const std::string& type) const +{ + auto found = mBandwidths.find(type); + if (found == mBandwidths.end()) { + return 0; + } + return found->second; +} + +const SdpMediaSection& +SipccSdp::GetMediaSection(size_t level) const +{ + if (level > mMediaSections.size()) { + MOZ_CRASH(); + } + return *mMediaSections[level]; +} + +SdpMediaSection& +SipccSdp::GetMediaSection(size_t level) +{ + if (level > mMediaSections.size()) { + MOZ_CRASH(); + } + return *mMediaSections[level]; +} + +SdpMediaSection& +SipccSdp::AddMediaSection(SdpMediaSection::MediaType mediaType, + SdpDirectionAttribute::Direction dir, uint16_t port, + SdpMediaSection::Protocol protocol, + sdp::AddrType addrType, const std::string& addr) +{ + size_t level = mMediaSections.size(); + SipccSdpMediaSection* media = + new SipccSdpMediaSection(level, &mAttributeList); + media->mMediaType = mediaType; + media->mPort = port; + media->mPortCount = 0; + media->mProtocol = protocol; + media->mConnection = MakeUnique(addrType, addr); + media->GetAttributeList().SetAttribute(new SdpDirectionAttribute(dir)); + mMediaSections.push_back(media); + return *media; +} + +bool +SipccSdp::LoadOrigin(sdp_t* sdp, SdpErrorHolder& errorHolder) +{ + std::string username = sdp_get_owner_username(sdp); + uint64_t sessId = strtoul(sdp_get_owner_sessionid(sdp), nullptr, 10); + uint64_t sessVer = strtoul(sdp_get_owner_version(sdp), nullptr, 10); + + sdp_nettype_e type = sdp_get_owner_network_type(sdp); + if (type != SDP_NT_INTERNET) { + errorHolder.AddParseError(2, "Unsupported network type"); + return false; + } + + sdp::AddrType addrType; + switch (sdp_get_owner_address_type(sdp)) { + case SDP_AT_IP4: + addrType = sdp::kIPv4; + break; + case SDP_AT_IP6: + addrType = sdp::kIPv6; + break; + default: + errorHolder.AddParseError(2, "Unsupported address type"); + return false; + } + + std::string address = sdp_get_owner_address(sdp); + mOrigin = SdpOrigin(username, sessId, sessVer, addrType, address); + return true; +} + +bool +SipccSdp::Load(sdp_t* sdp, SdpErrorHolder& errorHolder) +{ + // Believe it or not, SDP_SESSION_LEVEL is 0xFFFF + if (!mAttributeList.Load(sdp, SDP_SESSION_LEVEL, errorHolder)) { + return false; + } + + if (!LoadOrigin(sdp, errorHolder)) { + return false; + } + + if (!mBandwidths.Load(sdp, SDP_SESSION_LEVEL, errorHolder)) { + return false; + } + + for (int i = 0; i < sdp_get_num_media_lines(sdp); ++i) { + // note that we pass a "level" here that is one higher + // sipcc counts media sections from 1, using 0xFFFF as the "session" + UniquePtr section( + new SipccSdpMediaSection(i, &mAttributeList)); + if (!section->Load(sdp, i + 1, errorHolder)) { + return false; + } + mMediaSections.push_back(section.release()); + } + return true; +} + +void +SipccSdp::Serialize(std::ostream& os) const +{ + os << "v=0" << CRLF << mOrigin << "s=-" << CRLF; + + // We don't support creating i=, u=, e=, p= + // We don't generate c= at the session level (only in media) + + mBandwidths.Serialize(os); + os << "t=0 0" << CRLF; + + // We don't support r= or z= + + // attributes + os << mAttributeList; + + // media sections + for (auto i = mMediaSections.begin(); i != mMediaSections.end(); ++i) { + os << (**i); + } +} + +bool +SipccSdpBandwidths::Load(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + size_t count = sdp_get_num_bw_lines(sdp, level); + for (size_t i = 1; i <= count; ++i) { + sdp_bw_modifier_e bwtype = sdp_get_bw_modifier(sdp, level, i); + uint32_t bandwidth = sdp_get_bw_value(sdp, level, i); + if (bwtype != SDP_BW_MODIFIER_UNSUPPORTED) { + const char* typeName = sdp_get_bw_modifier_name(bwtype); + (*this)[typeName] = bandwidth; + } + } + + return true; +} + +void +SipccSdpBandwidths::Serialize(std::ostream& os) const +{ + for (auto i = begin(); i != end(); ++i) { + os << "b=" << i->first << ":" << i->second << CRLF; + } +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SipccSdp.h b/media/webrtc/signaling/src/sdp/SipccSdp.h new file mode 100644 index 000000000000..e3819919d691 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdp.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#ifndef _SIPCCSDP_H_ +#define _SIPCCSDP_H_ + +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" + +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SipccSdpMediaSection.h" +#include "signaling/src/sdp/SipccSdpAttributeList.h" +extern "C" { +#include "signaling/src/sdp/sipcc/sdp.h" +} + +namespace mozilla +{ + +class SipccSdpParser; +class SdpErrorHolder; + +class SipccSdp MOZ_FINAL : public Sdp +{ + friend class SipccSdpParser; + +public: + explicit SipccSdp(const SdpOrigin& origin) + : mOrigin(origin), mAttributeList(nullptr) + { + } + ~SipccSdp(); + + virtual const SdpOrigin& GetOrigin() const MOZ_OVERRIDE; + + // Note: connection information is always retrieved from media sections + virtual uint32_t GetBandwidth(const std::string& type) const MOZ_OVERRIDE; + + virtual size_t + GetMediaSectionCount() const MOZ_OVERRIDE + { + return mMediaSections.size(); + } + + virtual const SdpAttributeList& + GetAttributeList() const MOZ_OVERRIDE + { + return mAttributeList; + } + + virtual SdpAttributeList& + GetAttributeList() MOZ_OVERRIDE + { + return mAttributeList; + } + + virtual const SdpMediaSection& GetMediaSection(size_t level) const + MOZ_OVERRIDE; + + virtual SdpMediaSection& GetMediaSection(size_t level) MOZ_OVERRIDE; + + virtual SdpMediaSection& AddMediaSection( + SdpMediaSection::MediaType media, SdpDirectionAttribute::Direction dir, + uint16_t port, SdpMediaSection::Protocol proto, sdp::AddrType addrType, + const std::string& addr) MOZ_OVERRIDE; + + virtual void Serialize(std::ostream&) const MOZ_OVERRIDE; + +private: + SipccSdp() : mOrigin("", 0, 0, sdp::kIPv4, ""), mAttributeList(nullptr) {} + + bool Load(sdp_t* sdp, SdpErrorHolder& errorHolder); + bool LoadOrigin(sdp_t* sdp, SdpErrorHolder& errorHolder); + + SdpOrigin mOrigin; + SipccSdpBandwidths mBandwidths; + SipccSdpAttributeList mAttributeList; + std::vector mMediaSections; +}; + +} // namespace mozilla + +#endif // _sdp_h_ diff --git a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp new file mode 100644 index 000000000000..7fb309765f79 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp @@ -0,0 +1,1116 @@ +/* -*- 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 "signaling/src/sdp/SipccSdpAttributeList.h" + +#include +#include "mozilla/Assertions.h" +#include "signaling/src/sdp/SdpErrorHolder.h" + +extern "C" { +#include "signaling/src/sdp/sipcc/sdp_private.h" +} + +namespace mozilla +{ + +/* static */ const std::string SipccSdpAttributeList::kEmptyString = ""; + +SipccSdpAttributeList::SipccSdpAttributeList( + const SipccSdpAttributeList* sessionLevel) + : mSessionLevel(sessionLevel) +{ + memset(&mAttributes, 0, sizeof(mAttributes)); +} + +SipccSdpAttributeList::~SipccSdpAttributeList() +{ + for (size_t i = 0; i < kNumAttributeTypes; ++i) { + delete mAttributes[i]; + } +} + +bool +SipccSdpAttributeList::HasAttribute(AttributeType type, + bool sessionFallback) const +{ + return !!GetAttribute(type, sessionFallback); +} + +const SdpAttribute* +SipccSdpAttributeList::GetAttribute(AttributeType type, + bool sessionFallback) const +{ + const SdpAttribute* value = mAttributes[static_cast(type)]; + // Only do fallback when the attribute can appear at both the media and + // session level + if (!value && !AtSessionLevel() && sessionFallback && + SdpAttribute::IsAllowedAtSessionLevel(type) && + SdpAttribute::IsAllowedAtMediaLevel(type)) { + return mSessionLevel->GetAttribute(type, false); + } + return value; +} + +void +SipccSdpAttributeList::RemoveAttribute(AttributeType type) +{ + delete mAttributes[static_cast(type)]; + mAttributes[static_cast(type)] = nullptr; +} + +void +SipccSdpAttributeList::Clear() +{ + for (size_t i = 0; i < kNumAttributeTypes; ++i) { + RemoveAttribute(static_cast(i)); + } +} + +void +SipccSdpAttributeList::SetAttribute(SdpAttribute* attr) +{ + if (!IsAllowedHere(attr->GetType())) { + MOZ_ASSERT(false, "This type of attribute is not allowed here"); + return; + } + RemoveAttribute(attr->GetType()); + mAttributes[attr->GetType()] = attr; +} + +void +SipccSdpAttributeList::LoadSimpleString(sdp_t* sdp, uint16_t level, + sdp_attr_e attr, + AttributeType targetType, + SdpErrorHolder& errorHolder) +{ + const char* value = sdp_attr_get_simple_string(sdp, attr, level, 0, 1); + if (value) { + if (!IsAllowedHere(targetType)) { + uint32_t lineNumber = sdp_attr_line_number(sdp, attr, level, 0, 1); + WarnAboutMisplacedAttribute(targetType, lineNumber, errorHolder); + } else { + SetAttribute(new SdpStringAttribute(targetType, std::string(value))); + } + } +} + +void +SipccSdpAttributeList::LoadSimpleStrings(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + LoadSimpleString(sdp, level, SDP_ATTR_MID, SdpAttribute::kMidAttribute, + errorHolder); + LoadSimpleString(sdp, level, SDP_ATTR_LABEL, SdpAttribute::kLabelAttribute, + errorHolder); + LoadSimpleString(sdp, level, SDP_ATTR_IDENTITY, + SdpAttribute::kIdentityAttribute, errorHolder); + LoadSimpleString(sdp, level, SDP_ATTR_MSID_SEMANTIC, + SdpAttribute::kMsidSemanticAttribute, errorHolder); +} + +void +SipccSdpAttributeList::LoadSimpleNumber(sdp_t* sdp, uint16_t level, + sdp_attr_e attr, + AttributeType targetType, + SdpErrorHolder& errorHolder) +{ + if (sdp_attr_valid(sdp, attr, level, 0, 1)) { + if (!IsAllowedHere(targetType)) { + uint32_t lineNumber = sdp_attr_line_number(sdp, attr, level, 0, 1); + WarnAboutMisplacedAttribute(targetType, lineNumber, errorHolder); + } else { + uint32_t value = sdp_attr_get_simple_u32(sdp, attr, level, 0, 1); + SetAttribute(new SdpNumberAttribute(targetType, value)); + } + } +} + +void +SipccSdpAttributeList::LoadSimpleNumbers(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + LoadSimpleNumber(sdp, level, SDP_ATTR_PTIME, SdpAttribute::kPtimeAttribute, + errorHolder); + LoadSimpleNumber(sdp, level, SDP_ATTR_MAXPTIME, + SdpAttribute::kMaxptimeAttribute, errorHolder); +} + +void +SipccSdpAttributeList::LoadFlags(sdp_t* sdp, uint16_t level) +{ + if (AtSessionLevel()) { + if (sdp_attr_valid(sdp, SDP_ATTR_ICE_LITE, level, 0, 1)) { + SetAttribute(new SdpFlagAttribute(SdpAttribute::kIceLiteAttribute)); + } + } else { // media-level + if (sdp_attr_valid(sdp, SDP_ATTR_RTCP_MUX, level, 0, 1)) { + SetAttribute(new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); + } + if (sdp_attr_valid(sdp, SDP_ATTR_END_OF_CANDIDATES, level, 0, 1)) { + SetAttribute( + new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute)); + } + if (sdp_attr_valid(sdp, SDP_ATTR_BUNDLE_ONLY, level, 0, 1)) { + SetAttribute(new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute)); + } + } +} + +static void +ConvertDirection(sdp_direction_e sipcc_direction, + SdpDirectionAttribute::Direction* dir_outparam) +{ + switch (sipcc_direction) { + case SDP_DIRECTION_SENDRECV: + *dir_outparam = SdpDirectionAttribute::kSendrecv; + return; + case SDP_DIRECTION_SENDONLY: + *dir_outparam = SdpDirectionAttribute::kSendonly; + return; + case SDP_DIRECTION_RECVONLY: + *dir_outparam = SdpDirectionAttribute::kRecvonly; + return; + case SDP_DIRECTION_INACTIVE: + *dir_outparam = SdpDirectionAttribute::kInactive; + return; + case SDP_MAX_QOS_DIRECTIONS: + // Nothing actually sets this value. + // Fall through to MOZ_CRASH below. + { + } + } + + MOZ_CRASH("Invalid direction from sipcc; this is probably corruption"); +} + +void +SipccSdpAttributeList::LoadDirection(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + SdpDirectionAttribute::Direction dir; + ConvertDirection(sdp_get_media_direction(sdp, level, 0), &dir); + SetAttribute(new SdpDirectionAttribute(dir)); +} + +void +SipccSdpAttributeList::LoadIceAttributes(sdp_t* sdp, uint16_t level) +{ + char* value; + sdp_result_e sdpres = + sdp_attr_get_ice_attribute(sdp, level, 0, SDP_ATTR_ICE_UFRAG, 1, &value); + if (sdpres == SDP_SUCCESS) { + SetAttribute(new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, + std::string(value))); + } + sdpres = + sdp_attr_get_ice_attribute(sdp, level, 0, SDP_ATTR_ICE_PWD, 1, &value); + if (sdpres == SDP_SUCCESS) { + SetAttribute(new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, + std::string(value))); + } + + const char* iceOptVal = + sdp_attr_get_simple_string(sdp, SDP_ATTR_ICE_OPTIONS, level, 0, 1); + if (iceOptVal) { + auto* iceOptions = + new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute); + iceOptions->Load(iceOptVal); + SetAttribute(iceOptions); + } +} + +bool +SipccSdpAttributeList::LoadFingerprint(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + char* value; + UniquePtr fingerprintAttrs; + + for (uint16_t i = 1; i < UINT16_MAX; ++i) { + sdp_result_e result = sdp_attr_get_dtls_fingerprint_attribute( + sdp, level, 0, SDP_ATTR_DTLS_FINGERPRINT, i, &value); + + if (result != SDP_SUCCESS) { + break; + } + + std::string fingerprintAttr(value); + uint32_t lineNumber = + sdp_attr_line_number(sdp, SDP_ATTR_DTLS_FINGERPRINT, level, 0, i); + + // sipcc does not expose parse code for this + size_t start = fingerprintAttr.find_first_not_of(" \t"); + if (start == std::string::npos) { + errorHolder.AddParseError(lineNumber, "Empty fingerprint attribute"); + return false; + } + + size_t end = fingerprintAttr.find_first_of(" \t", start); + if (end == std::string::npos) { + // One token, no trailing ws + errorHolder.AddParseError(lineNumber, + "Only one token in fingerprint attribute"); + return false; + } + + std::string algorithmToken(fingerprintAttr.substr(start, end - start)); + + start = fingerprintAttr.find_first_not_of(" \t", end); + if (start == std::string::npos) { + // One token, trailing ws + errorHolder.AddParseError(lineNumber, + "Only one token in fingerprint attribute"); + return false; + } + + std::string fingerprintToken(fingerprintAttr.substr(start)); + + std::vector fingerprint = + SdpFingerprintAttributeList::ParseFingerprint(fingerprintToken); + if (fingerprint.size() == 0) { + errorHolder.AddParseError(lineNumber, "Malformed fingerprint token"); + return false; + } + + if (!fingerprintAttrs) { + fingerprintAttrs.reset(new SdpFingerprintAttributeList); + } + + // Don't assert on unknown algorithm, just skip + fingerprintAttrs->PushEntry(algorithmToken, fingerprint, false); + } + + if (fingerprintAttrs) { + SetAttribute(fingerprintAttrs.release()); + } + + return true; +} + +void +SipccSdpAttributeList::LoadCandidate(sdp_t* sdp, uint16_t level) +{ + char* value; + auto candidates = + MakeUnique(SdpAttribute::kCandidateAttribute); + + for (uint16_t i = 1; i < UINT16_MAX; ++i) { + sdp_result_e result = sdp_attr_get_ice_attribute( + sdp, level, 0, SDP_ATTR_ICE_CANDIDATE, i, &value); + + if (result != SDP_SUCCESS) { + break; + } + + candidates->mValues.push_back(value); + } + + if (!candidates->mValues.empty()) { + SetAttribute(candidates.release()); + } +} + +bool +SipccSdpAttributeList::LoadSctpmap(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + auto sctpmap = MakeUnique(); + for (uint16_t i = 0; i < UINT16_MAX; ++i) { + sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_SCTPMAP, i + 1); + + if (!attr) { + break; + } + + // Yeah, this is a little weird, but for now we'll just store this as a + // payload type. + uint16_t payloadType = attr->attr.sctpmap.port; + uint16_t streams = attr->attr.sctpmap.streams; + const char* name = attr->attr.sctpmap.protocol; + + std::ostringstream osPayloadType; + osPayloadType << payloadType; + sctpmap->PushEntry(osPayloadType.str(), name, streams); + } + + if (!sctpmap->mSctpmaps.empty()) { + SetAttribute(sctpmap.release()); + } + + return true; +} + +SdpRtpmapAttributeList::CodecType +SipccSdpAttributeList::GetCodecType(rtp_ptype type) +{ + switch (type) { + case RTP_PCMU: + return SdpRtpmapAttributeList::kPCMU; + case RTP_PCMA: + return SdpRtpmapAttributeList::kPCMA; + case RTP_G722: + return SdpRtpmapAttributeList::kG722; + case RTP_H264_P0: + case RTP_H264_P1: + return SdpRtpmapAttributeList::kH264; + case RTP_OPUS: + return SdpRtpmapAttributeList::kOpus; + case RTP_VP8: + return SdpRtpmapAttributeList::kVP8; + case RTP_NONE: + // Happens when sipcc doesn't know how to translate to the enum + case RTP_CELP: + case RTP_G726: + case RTP_GSM: + case RTP_G723: + case RTP_DVI4: + case RTP_DVI4_II: + case RTP_LPC: + case RTP_G728: + case RTP_G729: + case RTP_JPEG: + case RTP_NV: + case RTP_H261: + case RTP_AVT: + case RTP_L16: + case RTP_H263: + case RTP_ILBC: + case RTP_I420: + return SdpRtpmapAttributeList::kOtherCodec; + } + MOZ_CRASH("Invalid codec type from sipcc. Probably corruption."); +} + +bool +SipccSdpAttributeList::LoadRtpmap(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + auto rtpmap = MakeUnique(); + uint16_t count; + sdp_result_e result = + sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_RTPMAP, &count); + if (result != SDP_SUCCESS) { + MOZ_ASSERT(false, "Unable to get rtpmap size"); + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Unable to get rtpmap size"); + return false; + } + for (uint16_t i = 0; i < count; ++i) { + uint16_t pt = sdp_attr_get_rtpmap_payload_type(sdp, level, 0, i + 1); + const char* ccName = sdp_attr_get_rtpmap_encname(sdp, level, 0, i + 1); + + if (!ccName) { + // Probably no rtpmap attribute for a pt in an m-line + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "No rtpmap attribute for payload type"); + continue; + } + + std::string name(ccName); + + SdpRtpmapAttributeList::CodecType codec = + GetCodecType(sdp_get_known_payload_type(sdp, level, pt)); + + uint32_t clock = sdp_attr_get_rtpmap_clockrate(sdp, level, 0, i + 1); + uint16_t channels = 0; + + // sipcc gives us a channels value of "1" for video + if (sdp_get_media_type(sdp, level) == SDP_MEDIA_AUDIO) { + channels = sdp_attr_get_rtpmap_num_chan(sdp, level, 0, i + 1); + } + + std::ostringstream osPayloadType; + osPayloadType << pt; + rtpmap->PushEntry(osPayloadType.str(), codec, name, clock, channels); + } + + if (!rtpmap->mRtpmaps.empty()) { + SetAttribute(rtpmap.release()); + } + + return true; +} + +void +SipccSdpAttributeList::LoadSetup(sdp_t* sdp, uint16_t level) +{ + sdp_setup_type_e setupType; + auto sdpres = sdp_attr_get_setup_attribute(sdp, level, 0, 1, &setupType); + + if (sdpres != SDP_SUCCESS) { + return; + } + + switch (setupType) { + case SDP_SETUP_ACTIVE: + SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kActive)); + return; + case SDP_SETUP_PASSIVE: + SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kPassive)); + return; + case SDP_SETUP_ACTPASS: + SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kActpass)); + return; + case SDP_SETUP_HOLDCONN: + SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kHoldconn)); + return; + case SDP_SETUP_UNKNOWN: + return; + case SDP_SETUP_NOT_FOUND: + case SDP_MAX_SETUP: + // There is no code that will set these. + // Fall through to MOZ_CRASH() below. + { + } + } + + MOZ_CRASH("Invalid setup type from sipcc. This is probably corruption."); +} + +bool +SipccSdpAttributeList::LoadGroups(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + uint16_t attrCount = 0; + if (sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_GROUP, &attrCount) != + SDP_SUCCESS) { + MOZ_ASSERT(false, "Could not get count of group attributes"); + errorHolder.AddParseError(0, "Could not get count of group attributes"); + return false; + } + + UniquePtr groups = MakeUnique(); + for (uint16_t attr = 1; attr <= attrCount; ++attr) { + SdpGroupAttributeList::Semantics semantics; + std::vector tags; + + switch (sdp_get_group_attr(sdp, level, 0, attr)) { + case SDP_GROUP_ATTR_FID: + semantics = SdpGroupAttributeList::kFid; + break; + case SDP_GROUP_ATTR_LS: + semantics = SdpGroupAttributeList::kLs; + break; + case SDP_GROUP_ATTR_ANAT: + semantics = SdpGroupAttributeList::kAnat; + break; + case SDP_GROUP_ATTR_BUNDLE: + semantics = SdpGroupAttributeList::kBundle; + break; + default: + continue; + } + + uint16_t idCount = sdp_get_group_num_id(sdp, level, 0, attr); + for (uint16_t id = 1; id <= idCount; ++id) { + const char* idStr = sdp_get_group_id(sdp, level, 0, attr, id); + if (!idStr) { + std::ostringstream os; + os << "bad a=group identifier at " << (attr - 1) << ", " << (id - 1); + errorHolder.AddParseError(0, os.str()); + return false; + } + tags.push_back(std::string(idStr)); + } + groups->PushEntry(semantics, tags); + } + + if (!groups->mGroups.empty()) { + SetAttribute(groups.release()); + } + + return true; +} + +void +SipccSdpAttributeList::LoadFmtp(sdp_t* sdp, uint16_t level) +{ + auto fmtps = MakeUnique(); + + for (uint16_t i = 1; i < UINT16_MAX; ++i) { + sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_FMTP, i); + + if (!attr) { + break; + } + + sdp_fmtp_t* fmtp = &(attr->attr.fmtp); + + // Get the payload type + std::stringstream osPayloadType; + // payload_num is the number in the fmtp attribute, verbatim + osPayloadType << fmtp->payload_num; + + // Get the serialized form of the parameters + flex_string fs; + flex_string_init(&fs); + + // Very lame, but we need direct access so we can get the serialized form + sdp_result_e sdpres = sdp_build_attr_fmtp_params(sdp, fmtp, &fs); + + if (sdpres != SDP_SUCCESS) { + flex_string_free(&fs); + continue; + } + + std::string paramsString(fs.buffer); + flex_string_free(&fs); + + // Get parsed form of parameters, if supported + UniquePtr parameters; + + rtp_ptype codec = sdp_get_known_payload_type(sdp, level, fmtp->payload_num); + + switch (codec) { + case RTP_H264_P0: + case RTP_H264_P1: { + SdpFmtpAttributeList::H264Parameters* h264Parameters( + new SdpFmtpAttributeList::H264Parameters); + + sstrncpy(h264Parameters->sprop_parameter_sets, fmtp->parameter_sets, + sizeof(h264Parameters->sprop_parameter_sets)); + + h264Parameters->level_asymmetry_allowed = + !!(fmtp->level_asymmetry_allowed); + + h264Parameters->packetization_mode = fmtp->packetization_mode; +// Copied from VcmSIPCCBinding +#ifdef _WIN32 + sscanf_s(fmtp->profile_level_id, "%x", + &h264Parameters->profile_level_id, sizeof(unsigned*)); +#else + sscanf(fmtp->profile_level_id, "%xu", + &h264Parameters->profile_level_id); +#endif + h264Parameters->max_mbps = fmtp->max_mbps; + h264Parameters->max_fs = fmtp->max_fs; + h264Parameters->max_cpb = fmtp->max_cpb; + h264Parameters->max_dpb = fmtp->max_dpb; + h264Parameters->max_br = fmtp->max_br; + + parameters.reset(h264Parameters); + } break; + case RTP_VP8: { + SdpFmtpAttributeList::VP8Parameters* vp8Parameters( + new SdpFmtpAttributeList::VP8Parameters); + + vp8Parameters->max_fs = fmtp->max_fs; + vp8Parameters->max_fr = fmtp->max_fr; + + parameters.reset(vp8Parameters); + } break; + default: { + } + } + + fmtps->PushEntry(osPayloadType.str(), paramsString, Move(parameters)); + } + + if (!fmtps->mFmtps.empty()) { + SetAttribute(fmtps.release()); + } +} + +void +SipccSdpAttributeList::LoadMsids(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + uint16_t attrCount = 0; + if (sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_MSID, &attrCount) != + SDP_SUCCESS) { + MOZ_ASSERT(false, "Unable to get count of msid attributes"); + errorHolder.AddParseError(0, "Unable to get count of msid attributes"); + return; + } + auto msids = MakeUnique(); + for (uint16_t i = 1; i <= attrCount; ++i) { + uint32_t lineNumber = sdp_attr_line_number(sdp, SDP_ATTR_MSID, level, 0, i); + + const char* identifier = sdp_attr_get_msid_identifier(sdp, level, 0, i); + if (!identifier) { + errorHolder.AddParseError(lineNumber, "msid attribute with bad identity"); + continue; + } + + const char* appdata = sdp_attr_get_msid_appdata(sdp, level, 0, i); + if (!appdata) { + errorHolder.AddParseError(lineNumber, "msid attribute with bad appdata"); + continue; + } + + msids->PushEntry(identifier, appdata); + } + + if (!msids->mMsids.empty()) { + SetAttribute(msids.release()); + } +} + +void +SipccSdpAttributeList::LoadExtmap(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + auto extmaps = MakeUnique(); + + for (uint16_t i = 1; i < UINT16_MAX; ++i) { + sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_EXTMAP, i); + + if (!attr) { + break; + } + + sdp_extmap_t* extmap = &(attr->attr.extmap); + + SdpDirectionAttribute::Direction dir = SdpDirectionAttribute::kSendrecv; + + if (extmap->media_direction_specified) { + ConvertDirection(extmap->media_direction, &dir); + } + + extmaps->PushEntry(extmap->id, dir, extmap->media_direction_specified, + extmap->uri, extmap->extension_attributes); + } + + if (!extmaps->mExtmaps.empty()) { + if (!AtSessionLevel() && + mSessionLevel->HasAttribute(SdpAttribute::kExtmapAttribute)) { + uint32_t lineNumber = + sdp_attr_line_number(sdp, SDP_ATTR_EXTMAP, level, 0, 1); + errorHolder.AddParseError( + lineNumber, "extmap attributes in both session and media level"); + } + SetAttribute(extmaps.release()); + } +} + +void +SipccSdpAttributeList::LoadRtcpFb(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + auto rtcpfbs = MakeUnique(); + + for (uint16_t i = 1; i < UINT16_MAX; ++i) { + sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_RTCP_FB, i); + + if (!attr) { + break; + } + + sdp_fmtp_fb_t* rtcpfb = &attr->attr.rtcp_fb; + + SdpRtcpFbAttributeList::Type type; + std::string parameter; + + // Set type and parameter + switch (rtcpfb->feedback_type) { + case SDP_RTCP_FB_ACK: + type = SdpRtcpFbAttributeList::kAck; + switch (rtcpfb->param.ack) { + // TODO: sipcc doesn't seem to support ack with no following token. + // Issue 189. + case SDP_RTCP_FB_ACK_RPSI: + parameter = SdpRtcpFbAttributeList::rpsi; + break; + case SDP_RTCP_FB_ACK_APP: + parameter = SdpRtcpFbAttributeList::app; + break; + default: + // Type we don't care about, ignore. + continue; + } + break; + case SDP_RTCP_FB_CCM: + type = SdpRtcpFbAttributeList::kCcm; + switch (rtcpfb->param.ccm) { + case SDP_RTCP_FB_CCM_FIR: + parameter = SdpRtcpFbAttributeList::fir; + break; + case SDP_RTCP_FB_CCM_TMMBR: + parameter = SdpRtcpFbAttributeList::tmmbr; + break; + case SDP_RTCP_FB_CCM_TSTR: + parameter = SdpRtcpFbAttributeList::tstr; + break; + case SDP_RTCP_FB_CCM_VBCM: + parameter = SdpRtcpFbAttributeList::vbcm; + break; + default: + // Type we don't care about, ignore. + continue; + } + break; + case SDP_RTCP_FB_NACK: + type = SdpRtcpFbAttributeList::kNack; + switch (rtcpfb->param.nack) { + case SDP_RTCP_FB_NACK_BASIC: + break; + case SDP_RTCP_FB_NACK_SLI: + parameter = SdpRtcpFbAttributeList::sli; + break; + case SDP_RTCP_FB_NACK_PLI: + parameter = SdpRtcpFbAttributeList::pli; + break; + case SDP_RTCP_FB_NACK_RPSI: + parameter = SdpRtcpFbAttributeList::rpsi; + break; + case SDP_RTCP_FB_NACK_APP: + parameter = SdpRtcpFbAttributeList::app; + break; + default: + // Type we don't care about, ignore. + continue; + } + break; + case SDP_RTCP_FB_TRR_INT: { + type = SdpRtcpFbAttributeList::kTrrInt; + std::ostringstream os; + os << rtcpfb->param.trr_int; + parameter = os.str(); + } break; + default: + // Type we don't care about, ignore. + continue; + } + + std::stringstream osPayloadType; + osPayloadType << rtcpfb->payload_num; + + std::string pt(osPayloadType.str()); + std::string extra(rtcpfb->extra); + + rtcpfbs->PushEntry(pt, type, parameter, extra); + } + + if (!rtcpfbs->mFeedbacks.empty()) { + SetAttribute(rtcpfbs.release()); + } +} + +bool +SipccSdpAttributeList::Load(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + + LoadSimpleStrings(sdp, level, errorHolder); + LoadSimpleNumbers(sdp, level, errorHolder); + LoadFlags(sdp, level); + LoadDirection(sdp, level, errorHolder); + + if (AtSessionLevel()) { + if (!LoadGroups(sdp, level, errorHolder)) { + return false; + } + } else { + sdp_media_e mtype = sdp_get_media_type(sdp, level); + if (mtype == SDP_MEDIA_APPLICATION) { + if (!LoadSctpmap(sdp, level, errorHolder)) { + return false; + } + } else { + if (!LoadRtpmap(sdp, level, errorHolder)) { + return false; + } + } + LoadCandidate(sdp, level); + LoadFmtp(sdp, level); + LoadMsids(sdp, level, errorHolder); + LoadRtcpFb(sdp, level, errorHolder); + } + + LoadIceAttributes(sdp, level); + if (!LoadFingerprint(sdp, level, errorHolder)) { + return false; + } + LoadSetup(sdp, level); + LoadExtmap(sdp, level, errorHolder); + + return true; +} + +bool +SipccSdpAttributeList::IsAllowedHere(SdpAttribute::AttributeType type) const +{ + if (AtSessionLevel() && !SdpAttribute::IsAllowedAtSessionLevel(type)) { + return false; + } + + if (!AtSessionLevel() && !SdpAttribute::IsAllowedAtMediaLevel(type)) { + return false; + } + + return true; +} + +void +SipccSdpAttributeList::WarnAboutMisplacedAttribute( + SdpAttribute::AttributeType type, uint32_t lineNumber, + SdpErrorHolder& errorHolder) +{ + std::string warning = SdpAttribute::GetAttributeTypeString(type) + + (AtSessionLevel() ? " at session level. Ignoring." + : " at media level. Ignoring."); + errorHolder.AddParseError(lineNumber, warning); +} + +const std::vector& +SipccSdpAttributeList::GetCandidate() const +{ + if (!HasAttribute(SdpAttribute::kCandidateAttribute)) { + MOZ_CRASH(); + } + + return static_cast( + GetAttribute(SdpAttribute::kCandidateAttribute))->mValues; +} + +const SdpConnectionAttribute& +SipccSdpAttributeList::GetConnection() const +{ + if (!HasAttribute(SdpAttribute::kConnectionAttribute)) { + MOZ_CRASH(); + } + + return *static_cast( + GetAttribute(SdpAttribute::kConnectionAttribute)); +} + +SdpDirectionAttribute::Direction +SipccSdpAttributeList::GetDirection() const +{ + if (!HasAttribute(SdpAttribute::kDirectionAttribute)) { + MOZ_CRASH(); + } + + const SdpAttribute* attr = GetAttribute(SdpAttribute::kDirectionAttribute); + return static_cast(attr)->mValue; +} + +const SdpExtmapAttributeList& +SipccSdpAttributeList::GetExtmap() const +{ + if (!HasAttribute(SdpAttribute::kExtmapAttribute)) { + MOZ_CRASH(); + } + + return *static_cast( + GetAttribute(SdpAttribute::kExtmapAttribute)); +} + +const SdpFingerprintAttributeList& +SipccSdpAttributeList::GetFingerprint() const +{ + if (!HasAttribute(SdpAttribute::kFingerprintAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kFingerprintAttribute); + return *static_cast(attr); +} + +const SdpFmtpAttributeList& +SipccSdpAttributeList::GetFmtp() const +{ + if (!HasAttribute(SdpAttribute::kFmtpAttribute)) { + MOZ_CRASH(); + } + + return *static_cast( + GetAttribute(SdpAttribute::kFmtpAttribute)); +} + +const SdpGroupAttributeList& +SipccSdpAttributeList::GetGroup() const +{ + if (!HasAttribute(SdpAttribute::kGroupAttribute)) { + MOZ_CRASH(); + } + + return *static_cast( + GetAttribute(SdpAttribute::kGroupAttribute)); +} + +const SdpOptionsAttribute& +SipccSdpAttributeList::GetIceOptions() const +{ + if (!HasAttribute(SdpAttribute::kIceOptionsAttribute)) { + MOZ_CRASH(); + } + + const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceOptionsAttribute); + return *static_cast(attr); +} + +const std::string& +SipccSdpAttributeList::GetIcePwd() const +{ + if (!HasAttribute(SdpAttribute::kIcePwdAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kIcePwdAttribute); + return static_cast(attr)->mValue; +} + +const std::string& +SipccSdpAttributeList::GetIceUfrag() const +{ + if (!HasAttribute(SdpAttribute::kIceUfragAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceUfragAttribute); + return static_cast(attr)->mValue; +} + +const std::string& +SipccSdpAttributeList::GetIdentity() const +{ + if (!HasAttribute(SdpAttribute::kIdentityAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kIdentityAttribute); + return static_cast(attr)->mValue; +} + +const SdpImageattrAttributeList& +SipccSdpAttributeList::GetImageattr() const +{ + MOZ_CRASH("Not yet implemented."); +} + +const std::string& +SipccSdpAttributeList::GetLabel() const +{ + if (!HasAttribute(SdpAttribute::kLabelAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kLabelAttribute); + return static_cast(attr)->mValue; +} + +uint32_t +SipccSdpAttributeList::GetMaxptime() const +{ + if (!HasAttribute(SdpAttribute::kMaxptimeAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kMaxptimeAttribute); + return static_cast(attr)->mValue; +} + +const std::string& +SipccSdpAttributeList::GetMid() const +{ + if (!HasAttribute(SdpAttribute::kMidAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kMidAttribute); + return static_cast(attr)->mValue; +} + +const SdpMsidAttributeList& +SipccSdpAttributeList::GetMsid() const +{ + if (!HasAttribute(SdpAttribute::kMsidAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidAttribute); + return *static_cast(attr); +} + +const std::string& +SipccSdpAttributeList::GetMsidSemantic() const +{ + if (!HasAttribute(SdpAttribute::kMsidSemanticAttribute)) { + return kEmptyString; + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidSemanticAttribute); + return static_cast(attr)->mValue; +} + +uint32_t +SipccSdpAttributeList::GetPtime() const +{ + if (!HasAttribute(SdpAttribute::kPtimeAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kPtimeAttribute); + return static_cast(attr)->mValue; +} + +const SdpRtcpAttribute& +SipccSdpAttributeList::GetRtcp() const +{ + MOZ_CRASH("Not yet implemented"); +} + +const SdpRtcpFbAttributeList& +SipccSdpAttributeList::GetRtcpFb() const +{ + if (!HasAttribute(SdpAttribute::kRtcpFbAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtcpFbAttribute); + return *static_cast(attr); +} + +const SdpRemoteCandidatesAttribute& +SipccSdpAttributeList::GetRemoteCandidates() const +{ + MOZ_CRASH("Not yet implemented"); +} + +const SdpRtpmapAttributeList& +SipccSdpAttributeList::GetRtpmap() const +{ + if (!HasAttribute(SdpAttribute::kRtpmapAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtpmapAttribute); + return *static_cast(attr); +} + +const SdpSctpmapAttributeList& +SipccSdpAttributeList::GetSctpmap() const +{ + if (!HasAttribute(SdpAttribute::kSctpmapAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpmapAttribute); + return *static_cast(attr); +} + +const SdpSetupAttribute& +SipccSdpAttributeList::GetSetup() const +{ + if (!HasAttribute(SdpAttribute::kSetupAttribute)) { + MOZ_CRASH(); + } + const SdpAttribute* attr = GetAttribute(SdpAttribute::kSetupAttribute); + return *static_cast(attr); +} + +const SdpSsrcAttributeList& +SipccSdpAttributeList::GetSsrc() const +{ + MOZ_CRASH("Not yet implemented"); +} + +const SdpSsrcGroupAttributeList& +SipccSdpAttributeList::GetSsrcGroup() const +{ + MOZ_CRASH("Not yet implemented"); +} + +void +SipccSdpAttributeList::Serialize(std::ostream& os) const +{ + for (size_t i = 0; i < kNumAttributeTypes; ++i) { + if (mAttributes[i]) { + os << *mAttributes[i]; + } + } +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h new file mode 100644 index 000000000000..bf5db765fe3d --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h @@ -0,0 +1,133 @@ +/* -*- 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/. */ + +#ifndef _SIPCCSDPATTRIBUTELIST_H_ +#define _SIPCCSDPATTRIBUTELIST_H_ + +#include "signaling/src/sdp/SdpAttributeList.h" + +extern "C" { +#include "signaling/src/sdp/sipcc/sdp.h" +} + +namespace mozilla +{ + +class SipccSdp; +class SipccSdpMediaSection; +class SdpErrorHolder; + +class SipccSdpAttributeList : public SdpAttributeList +{ + friend class SipccSdpMediaSection; + friend class SipccSdp; + +public: + // Make sure we don't hide the default arg thunks + using SdpAttributeList::HasAttribute; + using SdpAttributeList::GetAttribute; + + virtual bool HasAttribute(AttributeType type, + bool sessionFallback) const MOZ_OVERRIDE; + virtual const SdpAttribute* GetAttribute( + AttributeType type, bool sessionFallback) const MOZ_OVERRIDE; + virtual void SetAttribute(SdpAttribute* attr) MOZ_OVERRIDE; + virtual void RemoveAttribute(AttributeType type) MOZ_OVERRIDE; + virtual void Clear() MOZ_OVERRIDE; + + virtual const SdpConnectionAttribute& GetConnection() const MOZ_OVERRIDE; + virtual const SdpFingerprintAttributeList& GetFingerprint() const + MOZ_OVERRIDE; + virtual const SdpGroupAttributeList& GetGroup() const MOZ_OVERRIDE; + virtual const SdpOptionsAttribute& GetIceOptions() const MOZ_OVERRIDE; + virtual const SdpRtcpAttribute& GetRtcp() const MOZ_OVERRIDE; + virtual const SdpRemoteCandidatesAttribute& GetRemoteCandidates() const + MOZ_OVERRIDE; + virtual const SdpSetupAttribute& GetSetup() const MOZ_OVERRIDE; + virtual const SdpSsrcAttributeList& GetSsrc() const MOZ_OVERRIDE; + virtual const SdpSsrcGroupAttributeList& GetSsrcGroup() const MOZ_OVERRIDE; + + // These attributes can appear multiple times, so the returned + // classes actually represent a collection of values. + virtual const std::vector& GetCandidate() const MOZ_OVERRIDE; + virtual const SdpExtmapAttributeList& GetExtmap() const MOZ_OVERRIDE; + virtual const SdpFmtpAttributeList& GetFmtp() const MOZ_OVERRIDE; + virtual const SdpImageattrAttributeList& GetImageattr() const MOZ_OVERRIDE; + virtual const SdpMsidAttributeList& GetMsid() const MOZ_OVERRIDE; + virtual const SdpRtcpFbAttributeList& GetRtcpFb() const MOZ_OVERRIDE; + virtual const SdpRtpmapAttributeList& GetRtpmap() const MOZ_OVERRIDE; + virtual const SdpSctpmapAttributeList& GetSctpmap() const MOZ_OVERRIDE; + + // These attributes are effectively simple types, so we'll make life + // easy by just returning their value. + virtual const std::string& GetIcePwd() const MOZ_OVERRIDE; + virtual const std::string& GetIceUfrag() const MOZ_OVERRIDE; + virtual const std::string& GetIdentity() const MOZ_OVERRIDE; + virtual const std::string& GetLabel() const MOZ_OVERRIDE; + virtual unsigned int GetMaxptime() const MOZ_OVERRIDE; + virtual const std::string& GetMid() const MOZ_OVERRIDE; + virtual const std::string& GetMsidSemantic() const MOZ_OVERRIDE; + virtual unsigned int GetPtime() const MOZ_OVERRIDE; + + virtual SdpDirectionAttribute::Direction GetDirection() const MOZ_OVERRIDE; + + virtual void Serialize(std::ostream&) const MOZ_OVERRIDE; + + virtual ~SipccSdpAttributeList(); + +private: + static const std::string kEmptyString; + static const size_t kNumAttributeTypes = SdpAttribute::kLastAttribute + 1; + + // Pass a session-level attribute list if constructing a media-level one, + // otherwise pass nullptr + explicit SipccSdpAttributeList(const SipccSdpAttributeList* sessionLevel); + + bool Load(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadSimpleStrings(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder); + void LoadSimpleString(sdp_t* sdp, uint16_t level, sdp_attr_e attr, + AttributeType targetType, SdpErrorHolder& errorHolder); + void LoadSimpleNumbers(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder); + void LoadSimpleNumber(sdp_t* sdp, uint16_t level, sdp_attr_e attr, + AttributeType targetType, SdpErrorHolder& errorHolder); + void LoadFlags(sdp_t* sdp, uint16_t level); + void LoadDirection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + bool LoadRtpmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + bool LoadSctpmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadIceAttributes(sdp_t* sdp, uint16_t level); + bool LoadFingerprint(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadCandidate(sdp_t* sdp, uint16_t level); + void LoadSetup(sdp_t* sdp, uint16_t level); + bool LoadGroups(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadFmtp(sdp_t* sdp, uint16_t level); + void LoadMsids(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadExtmap(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadRtcpFb(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + static SdpRtpmapAttributeList::CodecType GetCodecType(rtp_ptype type); + + bool + AtSessionLevel() const + { + return !mSessionLevel; + } + bool IsAllowedHere(SdpAttribute::AttributeType type) const; + void WarnAboutMisplacedAttribute(SdpAttribute::AttributeType type, + uint32_t lineNumber, + SdpErrorHolder& errorHolder); + + const SipccSdpAttributeList* mSessionLevel; + + SdpAttribute* mAttributes[kNumAttributeTypes]; + + SipccSdpAttributeList(const SipccSdpAttributeList& orig) MOZ_DELETE; + SipccSdpAttributeList& operator=(const SipccSdpAttributeList& rhs) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp new file mode 100644 index 000000000000..354e22b66f66 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp @@ -0,0 +1,332 @@ +/* -*- 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 "signaling/src/sdp/SipccSdpMediaSection.h" + +#include +#include "signaling/src/sdp/SdpErrorHolder.h" + +#ifdef CRLF +#undef CRLF +#endif +#define CRLF "\r\n" + +namespace mozilla +{ + +unsigned int +SipccSdpMediaSection::GetPort() const +{ + return mPort; +} + +void +SipccSdpMediaSection::SetPort(unsigned int port) +{ + mPort = port; +} + +unsigned int +SipccSdpMediaSection::GetPortCount() const +{ + return mPortCount; +} + +SdpMediaSection::Protocol +SipccSdpMediaSection::GetProtocol() const +{ + return mProtocol; +} + +const SdpConnection& +SipccSdpMediaSection::GetConnection() const +{ + return *mConnection; +} + +SdpConnection& +SipccSdpMediaSection::GetConnection() +{ + return *mConnection; +} + +uint32_t +SipccSdpMediaSection::GetBandwidth(const std::string& type) const +{ + auto found = mBandwidths.find(type); + if (found == mBandwidths.end()) { + return 0; + } + return found->second; +} + +const std::vector& +SipccSdpMediaSection::GetFormats() const +{ + return mFormats; +} + +const SdpAttributeList& +SipccSdpMediaSection::GetAttributeList() const +{ + return mAttributeList; +} + +SdpAttributeList& +SipccSdpMediaSection::GetAttributeList() +{ + return mAttributeList; +} + +SdpDirectionAttribute +SipccSdpMediaSection::GetDirectionAttribute() const +{ + return SdpDirectionAttribute(mAttributeList.GetDirection()); +} + +bool +SipccSdpMediaSection::Load(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + switch (sdp_get_media_type(sdp, level)) { + case SDP_MEDIA_AUDIO: + mMediaType = kAudio; + break; + case SDP_MEDIA_VIDEO: + mMediaType = kVideo; + break; + case SDP_MEDIA_APPLICATION: + mMediaType = kApplication; + break; + case SDP_MEDIA_TEXT: + mMediaType = kText; + break; + + default: + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Unsupported media section type"); + return false; + } + + mPort = sdp_get_media_portnum(sdp, level); + int32_t pc = sdp_get_media_portcount(sdp, level); + if (pc == SDP_INVALID_VALUE) { + // SDP_INVALID_VALUE (ie; -2) is used when there is no port count. :( + mPortCount = 0; + } else if (pc > static_cast(UINT16_MAX) || pc < 0) { + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Invalid port count"); + return false; + } else { + mPortCount = pc; + } + + if (!LoadProtocol(sdp, level, errorHolder)) { + return false; + } + LoadFormats(sdp, level); + + if (!mAttributeList.Load(sdp, level, errorHolder)) { + return false; + } + + if (!mBandwidths.Load(sdp, level, errorHolder)) { + return false; + } + + return LoadConnection(sdp, level, errorHolder); +} + +bool +SipccSdpMediaSection::LoadProtocol(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + switch (sdp_get_media_transport(sdp, level)) { + case SDP_TRANSPORT_RTPAVP: + mProtocol = kRtpAvp; + break; + case SDP_TRANSPORT_RTPSAVP: + mProtocol = kRtpSavp; + break; + case SDP_TRANSPORT_RTPAVPF: + mProtocol = kRtpAvpf; + break; + case SDP_TRANSPORT_RTPSAVPF: + mProtocol = kRtpSavpf; + break; + case SDP_TRANSPORT_UDPTLSRTPSAVP: + mProtocol = kUdpTlsRtpSavp; + break; + case SDP_TRANSPORT_UDPTLSRTPSAVPF: + mProtocol = kUdpTlsRtpSavpf; + break; + case SDP_TRANSPORT_TCPTLSRTPSAVP: + mProtocol = kTcpTlsRtpSavp; + break; + case SDP_TRANSPORT_TCPTLSRTPSAVPF: + mProtocol = kTcpTlsRtpSavpf; + break; + case SDP_TRANSPORT_DTLSSCTP: + mProtocol = kDtlsSctp; + break; + + default: + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Unsupported media transport type"); + return false; + } + return true; +} + +void +SipccSdpMediaSection::LoadFormats(sdp_t* sdp, uint16_t level) +{ + sdp_media_e mtype = sdp_get_media_type(sdp, level); + + if (mtype == SDP_MEDIA_APPLICATION) { + uint32_t ptype = sdp_get_media_sctp_port(sdp, level); + std::ostringstream osPayloadType; + osPayloadType << ptype; + mFormats.push_back(osPayloadType.str()); + } else if (mtype == SDP_MEDIA_AUDIO || mtype == SDP_MEDIA_VIDEO) { + uint16_t count = sdp_get_media_num_payload_types(sdp, level); + for (uint16_t i = 0; i < count; ++i) { + sdp_payload_ind_e indicator; // we ignore this, which is fine + uint32_t ptype = + sdp_get_media_payload_type(sdp, level, i + 1, &indicator); + + std::ostringstream osPayloadType; + // sipcc stores payload types in a funny way. When sipcc and the SDP it + // parsed differ on what payload type number should be used for a given + // codec, sipcc's value goes in the lower byte, and the SDP's value in + // the upper byte. When they do not differ, only the lower byte is used. + // We want what was in the SDP, verbatim. + osPayloadType << GET_DYN_PAYLOAD_TYPE_VALUE(ptype); + mFormats.push_back(osPayloadType.str()); + } + } +} + +bool +SipccSdpMediaSection::LoadConnection(sdp_t* sdp, uint16_t level, + SdpErrorHolder& errorHolder) +{ + if (!sdp_connection_valid(sdp, level)) { + level = SDP_SESSION_LEVEL; + if (!sdp_connection_valid(sdp, level)) { + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Missing c= line"); + return false; + } + } + + sdp_nettype_e type = sdp_get_conn_nettype(sdp, level); + if (type != SDP_NT_INTERNET) { + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Unsupported network type"); + return false; + } + + sdp::AddrType addrType; + switch (sdp_get_conn_addrtype(sdp, level)) { + case SDP_AT_IP4: + addrType = sdp::kIPv4; + break; + case SDP_AT_IP6: + addrType = sdp::kIPv6; + break; + default: + errorHolder.AddParseError(sdp_get_media_line_number(sdp, level), + "Unsupported address type"); + return false; + } + + std::string address = sdp_get_conn_address(sdp, level); + int16_t ttl = static_cast(sdp_get_mcast_ttl(sdp, level)); + if (ttl < 0) { + ttl = 0; + } + int32_t numAddr = + static_cast(sdp_get_mcast_num_of_addresses(sdp, level)); + if (numAddr < 0) { + numAddr = 0; + } + mConnection = MakeUnique(addrType, address, ttl, numAddr); + return true; +} + +void +SipccSdpMediaSection::AddCodec(const std::string& pt, const std::string& name, + uint32_t clockrate, uint16_t channels) +{ + mFormats.push_back(pt); + + SdpRtpmapAttributeList* rtpmap = new SdpRtpmapAttributeList(); + if (mAttributeList.HasAttribute(SdpAttribute::kRtpmapAttribute)) { + const SdpRtpmapAttributeList& old = mAttributeList.GetRtpmap(); + for (auto it = old.mRtpmaps.begin(); it != old.mRtpmaps.end(); ++it) { + rtpmap->mRtpmaps.push_back(*it); + } + } + SdpRtpmapAttributeList::CodecType codec = SdpRtpmapAttributeList::kOtherCodec; + if (name == "opus") { + codec = SdpRtpmapAttributeList::kOpus; + } else if (name == "G722") { + codec = SdpRtpmapAttributeList::kG722; + } else if (name == "PCMU") { + codec = SdpRtpmapAttributeList::kPCMU; + } else if (name == "PCMA") { + codec = SdpRtpmapAttributeList::kPCMA; + } else if (name == "VP8") { + codec = SdpRtpmapAttributeList::kVP8; + } else if (name == "H264") { + codec = SdpRtpmapAttributeList::kH264; + } + + rtpmap->PushEntry(pt, codec, name, clockrate, channels); + mAttributeList.SetAttribute(rtpmap); +} + +void +SipccSdpMediaSection::AddDataChannel(const std::string& pt, + const std::string& name, uint16_t streams) +{ + // Only one allowed, for now. This may change as the specs (and deployments) + // evolve. + mFormats.clear(); + mFormats.push_back(pt); + SdpSctpmapAttributeList* sctpmap = new SdpSctpmapAttributeList(); + sctpmap->PushEntry(pt, name, streams); + mAttributeList.SetAttribute(sctpmap); +} + +void +SipccSdpMediaSection::Serialize(std::ostream& os) const +{ + os << "m=" << mMediaType << " " << mPort; + if (mPortCount) { + os << "/" << mPortCount; + } + os << " " << mProtocol; + for (auto i = mFormats.begin(); i != mFormats.end(); ++i) { + os << " " << (*i); + } + os << CRLF; + + // We dont do i= + + if (mConnection) { + os << *mConnection; + } + + mBandwidths.Serialize(os); + + // We dont do k= because they're evil + + os << mAttributeList; +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h new file mode 100644 index 000000000000..ca43591bb0c4 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef _SIPCCSDPMEDIASECTION_H_ +#define _SIPCCSDPMEDIASECTION_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/sdp/SipccSdpAttributeList.h" + +#include + +extern "C" { +#include "signaling/src/sdp/sipcc/sdp.h" +} + +namespace mozilla +{ + +class SipccSdp; +class SdpErrorHolder; + +class SipccSdpBandwidths MOZ_FINAL : public std::map +{ +public: + bool Load(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void Serialize(std::ostream& os) const; +}; + +class SipccSdpMediaSection MOZ_FINAL : public SdpMediaSection +{ + friend class SipccSdp; + +public: + ~SipccSdpMediaSection() {} + + virtual MediaType + GetMediaType() const MOZ_OVERRIDE + { + return mMediaType; + } + + virtual unsigned int GetPort() const MOZ_OVERRIDE; + virtual void SetPort(unsigned int port) MOZ_OVERRIDE; + virtual unsigned int GetPortCount() const MOZ_OVERRIDE; + virtual Protocol GetProtocol() const MOZ_OVERRIDE; + virtual const SdpConnection& GetConnection() const MOZ_OVERRIDE; + virtual SdpConnection& GetConnection() MOZ_OVERRIDE; + virtual uint32_t GetBandwidth(const std::string& type) const MOZ_OVERRIDE; + virtual const std::vector& GetFormats() const MOZ_OVERRIDE; + + virtual const SdpAttributeList& GetAttributeList() const MOZ_OVERRIDE; + virtual SdpAttributeList& GetAttributeList() MOZ_OVERRIDE; + virtual SdpDirectionAttribute GetDirectionAttribute() const MOZ_OVERRIDE; + + virtual void AddCodec(const std::string& pt, const std::string& name, + uint32_t clockrate, uint16_t channels) MOZ_OVERRIDE; + + virtual void AddDataChannel(const std::string& pt, const std::string& name, + uint16_t streams) MOZ_OVERRIDE; + + virtual void Serialize(std::ostream&) const MOZ_OVERRIDE; + +private: + SipccSdpMediaSection(size_t level, const SipccSdpAttributeList* sessionLevel) + : SdpMediaSection(level), mAttributeList(sessionLevel) + { + } + + bool Load(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + bool LoadConnection(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + bool LoadProtocol(sdp_t* sdp, uint16_t level, SdpErrorHolder& errorHolder); + void LoadFormats(sdp_t* sdp, uint16_t level); + + // the following values are cached on first get + MediaType mMediaType; + uint16_t mPort; + uint16_t mPortCount; + Protocol mProtocol; + std::vector mFormats; + + UniquePtr mConnection; + SipccSdpBandwidths mBandwidths; + + SipccSdpAttributeList mAttributeList; +}; +} + +#endif diff --git a/media/webrtc/signaling/src/sdp/SipccSdpParser.cpp b/media/webrtc/signaling/src/sdp/SipccSdpParser.cpp new file mode 100644 index 000000000000..04fea305c916 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpParser.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "signaling/src/sdp/SipccSdpParser.h" +#include "signaling/src/sdp/SipccSdp.h" + +#include +extern "C" { +#include "signaling/src/sdp/sipcc/sdp.h" +} + +namespace mozilla +{ + +extern "C" { + +void +sipcc_sdp_parser_error_handler(void *context, uint32_t line, + const char *message) +{ + SdpErrorHolder *errorHolder = static_cast(context); + std::string err(message); + errorHolder->AddParseError(line, err); +} + +} // extern "C" + +UniquePtr +SipccSdpParser::Parse(const std::string &sdpText) +{ + ClearParseErrors(); + + sdp_conf_options_t *sipcc_config = sdp_init_config(); + if (!sipcc_config) { + return UniquePtr(); + } + + sdp_nettype_supported(sipcc_config, SDP_NT_INTERNET, true); + sdp_addrtype_supported(sipcc_config, SDP_AT_IP4, true); + sdp_addrtype_supported(sipcc_config, SDP_AT_IP6, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPAVP, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPAVPF, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPSAVP, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPSAVPF, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_UDPTLSRTPSAVP, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_UDPTLSRTPSAVPF, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_TCPTLSRTPSAVP, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_TCPTLSRTPSAVPF, true); + sdp_transport_supported(sipcc_config, SDP_TRANSPORT_DTLSSCTP, true); + sdp_require_session_name(sipcc_config, false); + + sdp_config_set_error_handler(sipcc_config, &sipcc_sdp_parser_error_handler, + this); + + // Takes ownership of |sipcc_config| iff it succeeds + sdp_t *sdp = sdp_init_description(sipcc_config); + if (!sdp) { + sdp_free_config(sipcc_config); + return UniquePtr(); + } + + const char *rawString = sdpText.c_str(); + sdp_result_e sdpres = sdp_parse(sdp, rawString, sdpText.length()); + if (sdpres != SDP_SUCCESS) { + sdp_free_description(sdp); + return UniquePtr(); + } + + UniquePtr sipccSdp(new SipccSdp); + + bool success = sipccSdp->Load(sdp, *this); + sdp_free_description(sdp); + if (!success) { + return UniquePtr(); + } + + return UniquePtr(Move(sipccSdp)); +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/sdp/SipccSdpParser.h b/media/webrtc/signaling/src/sdp/SipccSdpParser.h new file mode 100644 index 000000000000..e188dc4bc2e4 --- /dev/null +++ b/media/webrtc/signaling/src/sdp/SipccSdpParser.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef _SIPCCSDPPARSER_H_ +#define _SIPCCSDPPARSER_H_ + +#include + +#include "mozilla/UniquePtr.h" + +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SdpErrorHolder.h" + +namespace mozilla +{ + +class SipccSdpParser MOZ_FINAL : public SdpErrorHolder +{ +public: + SipccSdpParser() {} + virtual ~SipccSdpParser() {} + + /** + * This parses the provided text into an SDP object. + * This returns a nullptr-valued pointer if things go poorly. + */ + UniquePtr Parse(const std::string& sdpText); +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/test/sdp_unittests.cpp b/media/webrtc/signaling/test/sdp_unittests.cpp index eaa03b74889e..676368c86d36 100644 --- a/media/webrtc/signaling/test/sdp_unittests.cpp +++ b/media/webrtc/signaling/test/sdp_unittests.cpp @@ -1,3 +1,5 @@ +/* -*- 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/. */ @@ -7,6 +9,7 @@ #include "CSFLog.h" #include +#include #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" @@ -26,11 +29,22 @@ MtransportTestUtils *test_utils; nsCOMPtr gThread; +#include "signaling/src/sdp/SipccSdpParser.h" +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/sdp/SdpAttribute.h" + extern "C" { -#include "sdp.h" -#include "sdp_private.h" +#include "signaling/src/sdp/sipcc/sdp.h" +#include "signaling/src/sdp/sipcc/sdp_private.h" } +#ifdef CRLF +#undef CRLF +#endif +#define CRLF "\r\n" + +using namespace mozilla; + namespace test { static bool SetupGlobalThread() { @@ -51,32 +65,10 @@ static bool SetupGlobalThread() { class SdpTest : public ::testing::Test { public: SdpTest() : sdp_ptr_(nullptr) { - sdp_media_e supported_media[] = { - SDP_MEDIA_AUDIO, - SDP_MEDIA_VIDEO, - SDP_MEDIA_APPLICATION, - SDP_MEDIA_DATA, - SDP_MEDIA_CONTROL, - SDP_MEDIA_NAS_RADIUS, - SDP_MEDIA_NAS_TACACS, - SDP_MEDIA_NAS_DIAMETER, - SDP_MEDIA_NAS_L2TP, - SDP_MEDIA_NAS_LOGIN, - SDP_MEDIA_NAS_NONE, - SDP_MEDIA_IMAGE, - }; + } - config_p_ = sdp_init_config(); - unsigned int i; - for (i = 0; i < sizeof(supported_media) / sizeof(sdp_media_e); i++) { - sdp_media_supported(config_p_, supported_media[i], true); - } - sdp_nettype_supported(config_p_, SDP_NT_INTERNET, true); - sdp_addrtype_supported(config_p_, SDP_AT_IP4, true); - sdp_addrtype_supported(config_p_, SDP_AT_IP6, true); - sdp_transport_supported(config_p_, SDP_TRANSPORT_RTPSAVPF, true); - sdp_transport_supported(config_p_, SDP_TRANSPORT_UDPTL, true); - sdp_require_session_name(config_p_, false); + ~SdpTest() { + sdp_free_description(sdp_ptr_); } static void SetUpTestCase() { @@ -96,13 +88,44 @@ class SdpTest : public ::testing::Test { if (!sdp_ptr_) { sdp_free_description(sdp_ptr_); } - sdp_ptr_ = sdp_init_description("BogusPeerConnectionId", config_p_); + + sdp_media_e supported_media[] = { + SDP_MEDIA_AUDIO, + SDP_MEDIA_VIDEO, + SDP_MEDIA_APPLICATION, + SDP_MEDIA_DATA, + SDP_MEDIA_CONTROL, + SDP_MEDIA_NAS_RADIUS, + SDP_MEDIA_NAS_TACACS, + SDP_MEDIA_NAS_DIAMETER, + SDP_MEDIA_NAS_L2TP, + SDP_MEDIA_NAS_LOGIN, + SDP_MEDIA_NAS_NONE, + SDP_MEDIA_IMAGE, + }; + + sdp_conf_options_t *config_p = sdp_init_config(); + unsigned int i; + for (i = 0; i < sizeof(supported_media) / sizeof(sdp_media_e); i++) { + sdp_media_supported(config_p, supported_media[i], true); + } + sdp_nettype_supported(config_p, SDP_NT_INTERNET, true); + sdp_addrtype_supported(config_p, SDP_AT_IP4, true); + sdp_addrtype_supported(config_p, SDP_AT_IP6, true); + sdp_transport_supported(config_p, SDP_TRANSPORT_RTPSAVPF, true); + sdp_transport_supported(config_p, SDP_TRANSPORT_UDPTL, true); + sdp_require_session_name(config_p, false); + + sdp_ptr_ = sdp_init_description(config_p); + if (!sdp_ptr_) { + sdp_free_config(config_p); + } } void ParseSdp(const std::string &sdp_str) { - char *bufp = const_cast(sdp_str.data()); + const char *buf = sdp_str.data(); ResetSdp(); - ASSERT_EQ(sdp_parse(sdp_ptr_, &bufp, sdp_str.size()), SDP_SUCCESS); + ASSERT_EQ(sdp_parse(sdp_ptr_, buf, sdp_str.size()), SDP_SUCCESS); } void InitLocalSdp() { @@ -152,9 +175,9 @@ class SdpTest : public ::testing::Test { return final_level_; } - u16 AddNewRtcpFbAck(int level, sdp_rtcp_fb_ack_type_e type, - u16 payload = SDP_ALL_PAYLOADS) { - u16 inst_num = 0; + uint16_t AddNewRtcpFbAck(int level, sdp_rtcp_fb_ack_type_e type, + uint16_t payload = SDP_ALL_PAYLOADS) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_rtcp_fb_ack(sdp_ptr_, level, payload, inst_num, @@ -162,9 +185,9 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewRtcpFbNack(int level, sdp_rtcp_fb_nack_type_e type, - u16 payload = SDP_ALL_PAYLOADS) { - u16 inst_num = 0; + uint16_t AddNewRtcpFbNack(int level, sdp_rtcp_fb_nack_type_e type, + uint16_t payload = SDP_ALL_PAYLOADS) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_rtcp_fb_nack(sdp_ptr_, level, payload, inst_num, @@ -172,9 +195,9 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewRtcpFbTrrInt(int level, u32 interval, - u16 payload = SDP_ALL_PAYLOADS) { - u16 inst_num = 0; + uint16_t AddNewRtcpFbTrrInt(int level, uint32_t interval, + uint16_t payload = SDP_ALL_PAYLOADS) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_rtcp_fb_trr_int(sdp_ptr_, level, payload, inst_num, @@ -182,17 +205,17 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewRtcpFbCcm(int level, sdp_rtcp_fb_ccm_type_e type, - u16 payload = SDP_ALL_PAYLOADS) { - u16 inst_num = 0; + uint16_t AddNewRtcpFbCcm(int level, sdp_rtcp_fb_ccm_type_e type, + uint16_t payload = SDP_ALL_PAYLOADS) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_RTCP_FB, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_rtcp_fb_ccm(sdp_ptr_, level, payload, inst_num, type), SDP_SUCCESS); return inst_num; } - u16 AddNewExtMap(int level, const char* uri) { - u16 inst_num = 0; + uint16_t AddNewExtMap(int level, const char* uri) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_EXTMAP, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_extmap(sdp_ptr_, level, inst_num, @@ -200,8 +223,8 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewFmtpMaxFs(int level, u32 max_fs) { - u16 inst_num = 0; + uint16_t AddNewFmtpMaxFs(int level, uint32_t max_fs) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, @@ -211,8 +234,8 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewFmtpMaxFr(int level, u32 max_fr) { - u16 inst_num = 0; + uint16_t AddNewFmtpMaxFr(int level, uint32_t max_fr) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, @@ -222,8 +245,8 @@ class SdpTest : public ::testing::Test { return inst_num; } - u16 AddNewFmtpMaxFsFr(int level, u32 max_fs, u32 max_fr) { - u16 inst_num = 0; + uint16_t AddNewFmtpMaxFsFr(int level, uint32_t max_fs, uint32_t max_fr) { + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, level, 0, SDP_ATTR_FMTP, &inst_num), SDP_SUCCESS); EXPECT_EQ(sdp_attr_set_fmtp_payload_type(sdp_ptr_, level, 0, inst_num, @@ -237,7 +260,6 @@ class SdpTest : public ::testing::Test { protected: int final_level_; - void *config_p_; sdp_t *sdp_ptr_; }; @@ -245,9 +267,9 @@ static const std::string kVideoSdp = "v=0\r\n" "o=- 137331303 2 IN IP4 127.0.0.1\r\n" "s=SIP Call\r\n" + "c=IN IP4 198.51.100.7\r\n" "t=0 0\r\n" "m=video 56436 RTP/SAVPF 120\r\n" - "c=IN IP4 198.51.100.7\r\n" "a=rtpmap:120 VP8/90000\r\n"; TEST_F(SdpTest, parseRtcpFbAckRpsi) { @@ -752,13 +774,13 @@ TEST_F(SdpTest, parseExtMap) { } TEST_F(SdpTest, parseFmtpMaxFs) { - u32 val = 0; + uint32_t val = 0; ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n"); ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS); ASSERT_EQ(val, 300U); } TEST_F(SdpTest, parseFmtpMaxFr) { - u32 val = 0; + uint32_t val = 0; ParseSdp(kVideoSdp + "a=fmtp:120 max-fs=300;max-fr=30\r\n"); ASSERT_EQ(sdp_attr_get_fmtp_max_fr(sdp_ptr_, 1, 0, 1, &val), SDP_SUCCESS); ASSERT_EQ(val, 30U); @@ -802,19 +824,19 @@ static const std::string kBrokenFmtp = "a=fmtp:120 max-fs=300;max\0fr=30"; TEST_F(SdpTest, parseBrokenFmtp) { - u32 val = 0; - char *buf = const_cast(kBrokenFmtp.data()); + uint32_t val = 0; + const char *buf = kBrokenFmtp.data(); ResetSdp(); /* We need to manually invoke the parser here to be able to specify the length * of the string beyond the \0 in last line of the string. */ - ASSERT_EQ(sdp_parse(sdp_ptr_, &buf, 165), SDP_SUCCESS); + ASSERT_EQ(sdp_parse(sdp_ptr_, buf, 165), SDP_SUCCESS); ASSERT_EQ(sdp_attr_get_fmtp_max_fs(sdp_ptr_, 1, 0, 1, &val), SDP_INVALID_PARAMETER); } TEST_F(SdpTest, addIceLite) { InitLocalSdp(); - u16 inst_num = 0; + uint16_t inst_num = 0; EXPECT_EQ(sdp_add_new_attr(sdp_ptr_, SDP_SESSION_LEVEL, 0, SDP_ATTR_ICE_LITE, &inst_num), SDP_SUCCESS); std::string body = SerializeSdp(); @@ -833,6 +855,1585 @@ TEST_F(SdpTest, parseIceLite) { SDP_SESSION_LEVEL, 0)); } +class NewSdpTest : public ::testing::Test, + public ::testing::WithParamInterface { + public: + NewSdpTest() {} + + void ParseSdp(const std::string &sdp, bool expectSuccess = true) { + mSdp = mozilla::Move(mParser.Parse(sdp)); + + // Are we configured to do a parse and serialize before actually + // running the test? + if (GetParam()) { + std::stringstream os; + + if (expectSuccess) { + ASSERT_TRUE(mSdp) << "Parse failed on first pass: " + << GetParseErrors(); + } + + if (mSdp) { + // Serialize and re-parse + mSdp->Serialize(os); + mSdp = mozilla::Move(mParser.Parse(os.str())); + + // Whether we expected the parse to work or not, it should + // succeed the second time if it succeeded the first. + ASSERT_TRUE(mSdp) << "Parse failed on second pass, SDP was: " + << std::endl << os.str() << std::endl + << "Errors were: " << GetParseErrors(); + + // Serialize again and compare + std::stringstream os2; + mSdp->Serialize(os2); + ASSERT_EQ(os.str(), os2.str()); + } + } + + if (expectSuccess) { + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(0U, mParser.GetParseErrors().size()) + << "Got unexpected parse errors/warnings: " + << GetParseErrors(); + } + } + + // For streaming parse errors + std::string GetParseErrors() const { + std::stringstream output; + for (auto e = mParser.GetParseErrors().begin(); + e != mParser.GetParseErrors().end(); + ++e) { + output << e->first << ": " << e->second << std::endl; + } + return output.str(); + } + + void CheckRtpmap(const std::string& expected_pt, + SdpRtpmapAttributeList::CodecType codec, + const std::string& name, + uint32_t clock, + uint16_t channels, + const std::string& search_pt, + const SdpRtpmapAttributeList& rtpmaps) const { + ASSERT_TRUE(rtpmaps.HasEntry(search_pt)); + auto attr = rtpmaps.GetEntry(search_pt); + ASSERT_EQ(expected_pt, attr.pt); + ASSERT_EQ(codec, attr.codec); + ASSERT_EQ(name, attr.name); + ASSERT_EQ(clock, attr.clock); + ASSERT_EQ(channels, attr.channels); + } + + void CheckSctpmap(const std::string& expected_pt, + const std::string& name, + uint16_t streams, + const std::string& search_pt, + const SdpSctpmapAttributeList& sctpmaps) const { + ASSERT_TRUE(sctpmaps.HasEntry(search_pt)); + auto attr = sctpmaps.GetEntry(search_pt); + ASSERT_EQ(expected_pt, search_pt); + ASSERT_EQ(expected_pt, attr.pt); + ASSERT_EQ(name, attr.name); + ASSERT_EQ(streams, attr.streams); + } + + void CheckRtcpFb(const SdpRtcpFbAttributeList::Feedback& feedback, + const std::string& pt, + SdpRtcpFbAttributeList::Type type, + const std::string& first_parameter, + const std::string& extra = "") const { + ASSERT_EQ(pt, feedback.pt); + ASSERT_EQ(type, feedback.type); + ASSERT_EQ(first_parameter, feedback.parameter); + ASSERT_EQ(extra, feedback.extra); + } + + void CheckSerialize(const std::string& expected, + const SdpAttribute& attr) const { + std::stringstream str; + attr.Serialize(str); + ASSERT_EQ(expected, str.str()); + } + + SipccSdpParser mParser; + mozilla::UniquePtr mSdp; +}; // class NewSdpTest + +TEST_P(NewSdpTest, CreateDestroy) { +} + +TEST_P(NewSdpTest, ParseEmpty) { + ParseSdp("", false); + ASSERT_FALSE(mSdp); + ASSERT_NE(0U, mParser.GetParseErrors().size()) + << "Expected at least one parse error."; +} + +const std::string kBadSdp = "This is SDPARTA!!!!"; + +TEST_P(NewSdpTest, ParseGarbage) { + ParseSdp(kBadSdp, false); + ASSERT_FALSE(mSdp); + ASSERT_NE(0U, mParser.GetParseErrors().size()) + << "Expected at least one parse error."; +} + +TEST_P(NewSdpTest, ParseGarbageTwice) { + ParseSdp(kBadSdp, false); + ASSERT_FALSE(mSdp); + size_t errorCount = mParser.GetParseErrors().size(); + ASSERT_NE(0U, errorCount) + << "Expected at least one parse error."; + ParseSdp(kBadSdp, false); + ASSERT_FALSE(mSdp); + ASSERT_EQ(errorCount, mParser.GetParseErrors().size()) + << "Expected same error count for same SDP."; +} + +TEST_P(NewSdpTest, ParseMinimal) { + ParseSdp(kVideoSdp); + ASSERT_EQ(0U, mParser.GetParseErrors().size()) << + "Got parse errors: " << GetParseErrors(); +} + +TEST_P(NewSdpTest, CheckOriginGetUsername) { + ParseSdp(kVideoSdp); + ASSERT_EQ("-", mSdp->GetOrigin().GetUsername()) + << "Wrong username in origin"; +} + +TEST_P(NewSdpTest, CheckOriginGetSessionId) { + ParseSdp(kVideoSdp); + ASSERT_EQ(137331303U, mSdp->GetOrigin().GetSessionId()) + << "Wrong session id in origin"; +} + +TEST_P(NewSdpTest, CheckOriginGetSessionVersion) { + ParseSdp(kVideoSdp); + ASSERT_EQ(2U , mSdp->GetOrigin().GetSessionVersion()) + << "Wrong version in origin"; +} + +TEST_P(NewSdpTest, CheckOriginGetAddrType) { + ParseSdp(kVideoSdp); + ASSERT_EQ(sdp::kIPv4, mSdp->GetOrigin().GetAddrType()) + << "Wrong address type in origin"; +} + +TEST_P(NewSdpTest, CheckOriginGetAddress) { + ParseSdp(kVideoSdp); + ASSERT_EQ("127.0.0.1" , mSdp->GetOrigin().GetAddress()) + << "Wrong address in origin"; +} + +TEST_P(NewSdpTest, CheckGetMissingBandwidth) { + ParseSdp(kVideoSdp); + ASSERT_EQ(0U, mSdp->GetBandwidth("CT")) + << "Wrong bandwidth in session"; +} + +TEST_P(NewSdpTest, CheckGetBandwidth) { + ParseSdp("v=0" CRLF + "o=- 137331303 2 IN IP4 127.0.0.1" CRLF + "s=SIP Call" CRLF + "c=IN IP4 198.51.100.7" CRLF + "b=CT:5000" CRLF + "t=0 0" CRLF + "m=video 56436 RTP/SAVPF 120" CRLF + "a=rtpmap:120 VP8/90000" CRLF + ); + ASSERT_EQ(5000U, mSdp->GetBandwidth("CT")) + << "Wrong bandwidth in session"; +} + +TEST_P(NewSdpTest, CheckGetMediaSectionsCount) { + ParseSdp(kVideoSdp); + ASSERT_EQ(1U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetMediaType) { + ParseSdp(kVideoSdp); + ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(0).GetMediaType()) + << "Wrong type for first media section"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetProtocol) { + ParseSdp(kVideoSdp); + ASSERT_EQ(SdpMediaSection::kRtpSavpf, mSdp->GetMediaSection(0).GetProtocol()) + << "Wrong protocol for video"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetFormats) { + ParseSdp(kVideoSdp); + auto video_formats = mSdp->GetMediaSection(0).GetFormats(); + ASSERT_EQ(1U, video_formats.size()) << "Wrong number of formats for video"; + ASSERT_EQ("120", video_formats[0]); +} + +TEST_P(NewSdpTest, CheckMediaSectionGetPort) { + ParseSdp(kVideoSdp); + ASSERT_EQ(56436U, mSdp->GetMediaSection(0).GetPort()) + << "Wrong port number in media section"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetMissingPortCount) { + ParseSdp(kVideoSdp); + ASSERT_EQ(0U, mSdp->GetMediaSection(0).GetPortCount()) + << "Wrong port count in media section"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetPortCount) { + ParseSdp(kVideoSdp + + "m=audio 12345/2 RTP/SAVPF 0" CRLF + "a=rtpmap:0 PCMU/8000" CRLF + ); + ASSERT_EQ(2U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; + ASSERT_EQ(2U, mSdp->GetMediaSection(1).GetPortCount()) + << "Wrong port count in media section"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetMissingBandwidth) { + ParseSdp(kVideoSdp); + ASSERT_EQ(0U, mSdp->GetMediaSection(0).GetBandwidth("CT")) + << "Wrong bandwidth in media section"; +} + +TEST_P(NewSdpTest, CheckMediaSectionGetBandwidth) { + ParseSdp("v=0\r\n" + "o=- 137331303 2 IN IP4 127.0.0.1\r\n" + "c=IN IP4 198.51.100.7\r\n" + "t=0 0\r\n" + "m=video 56436 RTP/SAVPF 120\r\n" + "b=CT:1000\r\n" + "a=rtpmap:120 VP8/90000\r\n"); + ASSERT_EQ(1000U, mSdp->GetMediaSection(0).GetBandwidth("CT")) + << "Wrong bandwidth in media section"; +} + + +// SDP from a basic A/V apprtc call FFX/FFX +const std::string kBasicAudioVideoOffer = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ice-ufrag:4a799b2e" CRLF +"a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF +"a=ice-lite" CRLF +"a=ice-options:trickle foo" CRLF +"a=msid-semantic:WMS plus" CRLF +"a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF +"a=identity:blahblahblah foo;bar" CRLF +"a=group:BUNDLE first second" CRLF +"a=group:BUNDLE third" CRLF +"a=group:LS first third" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=mid:first" CRLF +"a=rtpmap:109 opus/48000/2" CRLF +"a=ptime:20" CRLF +"a=maxptime:20" CRLF +"a=rtpmap:9 G722/8000" CRLF +"a=rtpmap:0 PCMU/8000" CRLF +"a=rtpmap:8 PCMA/8000" CRLF +"a=rtpmap:101 telephone-event/8000" CRLF +"a=fmtp:101 0-15" CRLF +"a=ice-ufrag:00000000" CRLF +"a=ice-pwd:0000000000000000000000000000000" CRLF +"a=sendonly" CRLF +"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF +"a=setup:actpass" CRLF +"a=rtcp-mux" CRLF +"a=msid:track stream" CRLF +"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF +"a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453" CRLF +"a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761" CRLF +"a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF +"a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF +"a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF +"a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF +"a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF +"a=end-of-candidates" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP6 ::1" CRLF +"a=mid:second" CRLF +"a=rtpmap:120 VP8/90000" CRLF +"a=fmtp:120 max-fs=3600;max-fr=30" CRLF +"a=recvonly" CRLF +"a=rtcp-fb:120 nack" CRLF +"a=rtcp-fb:120 nack pli" CRLF +"a=rtcp-fb:120 ccm fir" CRLF +"a=setup:active" CRLF +"a=rtcp-mux" CRLF +"a=msid:tracka streama" CRLF +"a=msid:trackb streamb" CRLF +"a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF +"a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF +"a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378" CRLF +"a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941" CRLF +"a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800" CRLF +"a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530" CRLF +"a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935" CRLF +"a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026" CRLF +"a=end-of-candidates" CRLF +"m=audio 9 RTP/SAVPF 0" CRLF +"a=mid:third" CRLF +"a=rtpmap:0 PCMU/8000" CRLF +"a=ice-lite" CRLF +"a=ice-options:foo bar" CRLF +"a=msid:noappdata" CRLF +"a=bundle-only" CRLF; + +TEST_P(NewSdpTest, BasicAudioVideoSdpParse) { + ParseSdp(kBasicAudioVideoOffer); +} + +TEST_P(NewSdpTest, CheckIceUfrag) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIceUfragAttribute)); + auto ice_ufrag = mSdp->GetAttributeList().GetIceUfrag(); + ASSERT_EQ("4a799b2e", ice_ufrag) << "Wrong ice-ufrag value"; + + ice_ufrag = mSdp->GetMediaSection(0) + .GetAttributeList().GetIceUfrag(); + ASSERT_EQ("00000000", ice_ufrag) << "ice-ufrag isn't overridden"; + + ice_ufrag = mSdp->GetMediaSection(1) + .GetAttributeList().GetIceUfrag(); + ASSERT_EQ("4a799b2e", ice_ufrag) << "ice-ufrag isn't carried to m-section"; +} + +TEST_P(NewSdpTest, CheckIcePwd) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIcePwdAttribute)); + auto ice_pwd = mSdp->GetAttributeList().GetIcePwd(); + ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd) << "Wrong ice-pwd value"; + + ice_pwd = mSdp->GetMediaSection(0) + .GetAttributeList().GetIcePwd(); + ASSERT_EQ("0000000000000000000000000000000", ice_pwd) + << "ice-pwd isn't overridden"; + + ice_pwd = mSdp->GetMediaSection(1) + .GetAttributeList().GetIcePwd(); + ASSERT_EQ("e4cc12a910f106a0a744719425510e17", ice_pwd) + << "ice-pwd isn't carried to m-section"; +} + +TEST_P(NewSdpTest, CheckIceOptions) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIceOptionsAttribute)); + auto ice_options = mSdp->GetAttributeList().GetIceOptions(); + ASSERT_EQ(2U, ice_options.mValues.size()) << "Wrong ice-options size"; + ASSERT_EQ("trickle", ice_options.mValues[0]) << "Wrong ice-options value"; + ASSERT_EQ("foo", ice_options.mValues[1]) << "Wrong ice-options value"; + + ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kIceOptionsAttribute)); + auto ice_options_media_level = + mSdp->GetMediaSection(2).GetAttributeList().GetIceOptions(); + ASSERT_EQ(2U, ice_options_media_level.mValues.size()) << "Wrong ice-options size"; + ASSERT_EQ("foo", ice_options_media_level.mValues[0]) << "Wrong ice-options value"; + ASSERT_EQ("bar", ice_options_media_level.mValues[1]) << "Wrong ice-options value"; +} + +TEST_P(NewSdpTest, CheckFingerprint) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kFingerprintAttribute)); + auto fingerprints = mSdp->GetAttributeList().GetFingerprint(); + ASSERT_EQ(1U, fingerprints.mFingerprints.size()); + ASSERT_EQ(SdpFingerprintAttributeList::kSha256, + fingerprints.mFingerprints[0].hashFunc) + << "Wrong hash function"; + ASSERT_EQ("DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:" + "3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C", + SdpFingerprintAttributeList::FormatFingerprint( + fingerprints.mFingerprints[0].fingerprint)) + << "Wrong fingerprint"; + ASSERT_EQ(0xdfU, fingerprints.mFingerprints[0].fingerprint[0]) + << "first fingerprint element is iffy"; +} + +TEST_P(NewSdpTest, CheckIdentity) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIdentityAttribute)); + auto identity = mSdp->GetAttributeList().GetIdentity(); + ASSERT_EQ("blahblahblah", identity) << "Wrong identity assertion"; +} + +TEST_P(NewSdpTest, CheckNumberOfMediaSections) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; +} + +TEST_P(NewSdpTest, CheckMlines) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(0).GetMediaType()) + << "Wrong type for first media section"; + ASSERT_EQ(SdpMediaSection::kRtpSavpf, + mSdp->GetMediaSection(0).GetProtocol()) + << "Wrong protocol for audio"; + auto audio_formats = mSdp->GetMediaSection(0).GetFormats(); + ASSERT_EQ(5U, audio_formats.size()) << "Wrong number of formats for audio"; + ASSERT_EQ("109", audio_formats[0]); + ASSERT_EQ("9", audio_formats[1]); + ASSERT_EQ("0", audio_formats[2]); + ASSERT_EQ("8", audio_formats[3]); + ASSERT_EQ("101", audio_formats[4]); + + ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType()) + << "Wrong type for second media section"; + ASSERT_EQ(SdpMediaSection::kRtpSavpf, + mSdp->GetMediaSection(1).GetProtocol()) + << "Wrong protocol for video"; + auto video_formats = mSdp->GetMediaSection(1).GetFormats(); + ASSERT_EQ(1U, video_formats.size()) << "Wrong number of formats for video"; + ASSERT_EQ("120", video_formats[0]); + + ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(2).GetMediaType()) + << "Wrong type for third media section"; +} + +TEST_P(NewSdpTest, CheckSetup) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)); + ASSERT_EQ(SdpSetupAttribute::kActpass, + mSdp->GetMediaSection(0).GetAttributeList().GetSetup().mRole); + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)); + ASSERT_EQ(SdpSetupAttribute::kActive, + mSdp->GetMediaSection(1).GetAttributeList().GetSetup().mRole); + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)); +} + +TEST_P(NewSdpTest, CheckRtpmap) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; + + const SdpMediaSection& audiosec = mSdp->GetMediaSection(0); + const SdpRtpmapAttributeList& rtpmap = audiosec.GetAttributeList().GetRtpmap(); + ASSERT_EQ(5U, rtpmap.mRtpmaps.size()) + << "Wrong number of rtpmap attributes for audio"; + + // Need to know name of type + CheckRtpmap("109", + SdpRtpmapAttributeList::kOpus, + "opus", + 48000, + 2, + audiosec.GetFormats()[0], + rtpmap); + + CheckRtpmap("9", + SdpRtpmapAttributeList::kG722, + "G722", + 8000, + 1, + audiosec.GetFormats()[1], + rtpmap); + + CheckRtpmap("0", + SdpRtpmapAttributeList::kPCMU, + "PCMU", + 8000, + 1, + audiosec.GetFormats()[2], + rtpmap); + + CheckRtpmap("8", + SdpRtpmapAttributeList::kPCMA, + "PCMA", + 8000, + 1, + audiosec.GetFormats()[3], + rtpmap); + + CheckRtpmap("101", + SdpRtpmapAttributeList::kOtherCodec, + "telephone-event", + 8000, + 1, + audiosec.GetFormats()[4], + rtpmap); + + const SdpMediaSection& videosec = mSdp->GetMediaSection(1); + CheckRtpmap("120", + SdpRtpmapAttributeList::kVP8, + "VP8", + 90000, + 0, + videosec.GetFormats()[0], + videosec.GetAttributeList().GetRtpmap()); +} + +const std::string kH264AudioVideoOffer = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ice-ufrag:4a799b2e" CRLF +"a=ice-pwd:e4cc12a910f106a0a744719425510e17" CRLF +"a=ice-lite" CRLF +"a=msid-semantic:WMS plus" CRLF +"a=fingerprint:sha-256 DF:2E:AC:8A:FD:0A:8E:99:BF:5D:E8:3C:E7:FA:FB:08:3B:3C:54:1D:D7:D4:05:77:A0:72:9B:14:08:6D:0F:4C" CRLF +"a=group:BUNDLE first second" CRLF +"a=group:BUNDLE third" CRLF +"a=group:LS first third" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=mid:first" CRLF +"a=rtpmap:109 opus/48000/2" CRLF +"a=ptime:20" CRLF +"a=maxptime:20" CRLF +"a=rtpmap:9 G722/8000" CRLF +"a=rtpmap:0 PCMU/8000" CRLF +"a=rtpmap:8 PCMA/8000" CRLF +"a=rtpmap:101 telephone-event/8000" CRLF +"a=fmtp:101 0-15" CRLF +"a=ice-ufrag:00000000" CRLF +"a=ice-pwd:0000000000000000000000000000000" CRLF +"a=sendonly" CRLF +"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF +"a=setup:actpass" CRLF +"a=rtcp-mux" CRLF +"a=msid:track stream" CRLF +"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF +"a=candidate:2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453" CRLF +"a=candidate:3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761" CRLF +"a=candidate:6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858" CRLF +"a=candidate:3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454" CRLF +"a=candidate:2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428" CRLF +"a=candidate:6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340" CRLF +"a=candidate:0 2 UDP 2130379006 10.0.0.36 55428 typ host" CRLF +"m=video 9 RTP/SAVPF 97 98 120" CRLF +"c=IN IP6 ::1" CRLF +"a=mid:second" CRLF +"a=rtpmap:97 H264/90000" CRLF +"a=fmtp:97 profile-level-id=42a01e" CRLF +"a=rtpmap:98 H264/90000" CRLF +"a=fmtp:98 PROFILE=0;LEVEL=0;profile-level-id=42a00d;packetization-mode=1;level-asymmetry-allowed=1;max-mbps=42000;max-fs=1400;max-cpb=1000;max-dpb=1000;max-br=180000;parameter-add=1;usedtx=0;stereo=0;useinbandfec=0;cbr=0" CRLF +"a=rtpmap:120 VP8/90000" CRLF +"a=fmtp:120 max-fs=3601;max-fr=31" CRLF +"a=recvonly" CRLF +"a=setup:active" CRLF +"a=rtcp-mux" CRLF +"a=msid:tracka streama" CRLF +"a=msid:trackb streamb" CRLF +"a=candidate:0 1 UDP 2130379007 10.0.0.36 59530 typ host" CRLF +"a=candidate:0 2 UDP 2130379006 10.0.0.36 64378 typ host" CRLF +"a=candidate:2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378" CRLF +"a=candidate:6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941" CRLF +"a=candidate:6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800" CRLF +"a=candidate:2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530" CRLF +"a=candidate:3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935" CRLF +"a=candidate:3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026" CRLF +"m=audio 9 RTP/SAVPF 0" CRLF +"a=mid:third" CRLF +"a=rtpmap:0 PCMU/8000" CRLF +"a=ice-lite" CRLF +"a=msid:noappdata" CRLF; + +TEST_P(NewSdpTest, CheckFormatParameters) { + ParseSdp(kH264AudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kFmtpAttribute)); + auto audio_format_params = + mSdp->GetMediaSection(0).GetAttributeList().GetFmtp().mFmtps; + ASSERT_EQ(1U, audio_format_params.size()); + ASSERT_EQ("101", audio_format_params[0].format); + ASSERT_EQ("0-15", audio_format_params[0].parameters_string); + + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kFmtpAttribute)); + auto video_format_params = + mSdp->GetMediaSection(1).GetAttributeList().GetFmtp().mFmtps; + ASSERT_EQ(3U, video_format_params.size()); + ASSERT_EQ("97", video_format_params[0].format); + ASSERT_TRUE(video_format_params[0].parameters); + ASSERT_EQ(SdpRtpmapAttributeList::kH264, + video_format_params[0].parameters->codec_type); + const SdpFmtpAttributeList::H264Parameters *h264_parameters( + static_cast( + video_format_params[0].parameters.get())); + ASSERT_EQ((uint32_t)0x42a01e, h264_parameters->profile_level_id); + ASSERT_EQ(0U, h264_parameters->packetization_mode); + ASSERT_EQ(false, h264_parameters->level_asymmetry_allowed); + ASSERT_EQ(0U, h264_parameters->max_mbps); + ASSERT_EQ(0U, h264_parameters->max_fs); + ASSERT_EQ(0U, h264_parameters->max_cpb); + ASSERT_EQ(0U, h264_parameters->max_dpb); + ASSERT_EQ(0U, h264_parameters->max_br); + + ASSERT_EQ("98", video_format_params[1].format); + ASSERT_TRUE(video_format_params[1].parameters); + ASSERT_EQ(SdpRtpmapAttributeList::kH264, + video_format_params[1].parameters->codec_type); + h264_parameters = + static_cast( + video_format_params[1].parameters.get()); + ASSERT_EQ((uint32_t)0x42a00d, h264_parameters->profile_level_id); + ASSERT_EQ(1U, h264_parameters->packetization_mode); + ASSERT_EQ(true, h264_parameters->level_asymmetry_allowed); + ASSERT_EQ(42000U, h264_parameters->max_mbps); + ASSERT_EQ(1400U, h264_parameters->max_fs); + ASSERT_EQ(1000U, h264_parameters->max_cpb); + ASSERT_EQ(1000U, h264_parameters->max_dpb); + ASSERT_EQ(180000U, h264_parameters->max_br); + + ASSERT_EQ("120", video_format_params[2].format); + ASSERT_TRUE(video_format_params[2].parameters); + ASSERT_EQ(SdpRtpmapAttributeList::kVP8, + video_format_params[2].parameters->codec_type); + const SdpFmtpAttributeList::VP8Parameters *vp8_parameters = + static_cast( + video_format_params[2].parameters.get()); + ASSERT_EQ(3601U, vp8_parameters->max_fs); + ASSERT_EQ(31U, vp8_parameters->max_fr); + + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kFmtpAttribute)); +} + +TEST_P(NewSdpTest, CheckPtime) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_EQ(20U, mSdp->GetMediaSection(0).GetAttributeList().GetPtime()); + ASSERT_FALSE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kPtimeAttribute)); +} + +TEST_P(NewSdpTest, CheckFlags) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)); + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)); + ASSERT_FALSE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)); + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)); + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpMuxAttribute)); + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpMuxAttribute)); + + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)); + ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)); + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kEndOfCandidatesAttribute)); + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kEndOfCandidatesAttribute)); + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kEndOfCandidatesAttribute)); +} + +TEST_P(NewSdpTest, CheckConnectionLines) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; + + const SdpConnection& conn1 = mSdp->GetMediaSection(0).GetConnection(); + ASSERT_EQ(sdp::kIPv4, conn1.GetAddrType()); + ASSERT_EQ("0.0.0.0", conn1.GetAddress()); + ASSERT_EQ(0U, conn1.GetTtl()); + ASSERT_EQ(0U, conn1.GetCount()); + + const SdpConnection& conn2 = mSdp->GetMediaSection(1).GetConnection(); + ASSERT_EQ(sdp::kIPv6, conn2.GetAddrType()); + ASSERT_EQ("::1", conn2.GetAddress()); + ASSERT_EQ(0U, conn2.GetTtl()); + ASSERT_EQ(0U, conn2.GetCount()); + + // tests that we can fall through to session level as appropriate + const SdpConnection& conn3 = mSdp->GetMediaSection(2).GetConnection(); + ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType()); + ASSERT_EQ("224.0.0.1", conn3.GetAddress()); + ASSERT_EQ(100U, conn3.GetTtl()); + ASSERT_EQ(12U, conn3.GetCount()); +} + +TEST_P(NewSdpTest, CheckDirections) { + ParseSdp(kBasicAudioVideoOffer); + + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(SdpDirectionAttribute::kSendonly, + mSdp->GetMediaSection(0).GetAttributeList().GetDirection()); + ASSERT_EQ(SdpDirectionAttribute::kRecvonly, + mSdp->GetMediaSection(1).GetAttributeList().GetDirection()); + ASSERT_EQ(SdpDirectionAttribute::kSendrecv, + mSdp->GetMediaSection(2).GetAttributeList().GetDirection()); +} + +TEST_P(NewSdpTest, CheckCandidates) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)); + auto audio_candidates = + mSdp->GetMediaSection(0).GetAttributeList().GetCandidate(); + ASSERT_EQ(8U, audio_candidates.size()); + ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 62453 typ host", audio_candidates[0]); + ASSERT_EQ("2 1 UDP 1694236671 24.6.134.204 62453 typ srflx raddr 10.0.0.36 rport 62453", audio_candidates[1]); + ASSERT_EQ("3 1 UDP 100401151 162.222.183.171 49761 typ relay raddr 162.222.183.171 rport 49761", audio_candidates[2]); + ASSERT_EQ("6 1 UDP 16515071 162.222.183.171 51858 typ relay raddr 162.222.183.171 rport 51858", audio_candidates[3]); + ASSERT_EQ("3 2 UDP 100401150 162.222.183.171 62454 typ relay raddr 162.222.183.171 rport 62454", audio_candidates[4]); + ASSERT_EQ("2 2 UDP 1694236670 24.6.134.204 55428 typ srflx raddr 10.0.0.36 rport 55428", audio_candidates[5]); + ASSERT_EQ("6 2 UDP 16515070 162.222.183.171 50340 typ relay raddr 162.222.183.171 rport 50340", audio_candidates[6]); + ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 55428 typ host", audio_candidates[7]); + + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)); + auto video_candidates = + mSdp->GetMediaSection(1).GetAttributeList().GetCandidate(); + ASSERT_EQ(8U, video_candidates.size()); + ASSERT_EQ("0 1 UDP 2130379007 10.0.0.36 59530 typ host", video_candidates[0]); + ASSERT_EQ("0 2 UDP 2130379006 10.0.0.36 64378 typ host", video_candidates[1]); + ASSERT_EQ("2 2 UDP 1694236670 24.6.134.204 64378 typ srflx raddr 10.0.0.36 rport 64378", video_candidates[2]); + ASSERT_EQ("6 2 UDP 16515070 162.222.183.171 64941 typ relay raddr 162.222.183.171 rport 64941", video_candidates[3]); + ASSERT_EQ("6 1 UDP 16515071 162.222.183.171 64800 typ relay raddr 162.222.183.171 rport 64800", video_candidates[4]); + ASSERT_EQ("2 1 UDP 1694236671 24.6.134.204 59530 typ srflx raddr 10.0.0.36 rport 59530", video_candidates[5]); + ASSERT_EQ("3 1 UDP 100401151 162.222.183.171 62935 typ relay raddr 162.222.183.171 rport 62935", video_candidates[6]); + ASSERT_EQ("3 2 UDP 100401150 162.222.183.171 61026 typ relay raddr 162.222.183.171 rport 61026", video_candidates[7]); + + ASSERT_FALSE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)); +} + +TEST_P(NewSdpTest, CheckMid) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_EQ("first", mSdp->GetMediaSection(0).GetAttributeList().GetMid()); + ASSERT_EQ("second", mSdp->GetMediaSection(1).GetAttributeList().GetMid()); + ASSERT_EQ("third", mSdp->GetMediaSection(2).GetAttributeList().GetMid()); +} + +TEST_P(NewSdpTest, CheckMsid) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kMsidSemanticAttribute)); + // note that we lose the extra pieces here + // it's not worth it to save them until they mean something + ASSERT_EQ("WMS", mSdp->GetAttributeList().GetMsidSemantic()); + + const SdpMsidAttributeList& msids1 = + mSdp->GetMediaSection(0).GetAttributeList().GetMsid(); + ASSERT_EQ(1U, msids1.mMsids.size()); + ASSERT_EQ("track", msids1.mMsids[0].identifier); + ASSERT_EQ("stream", msids1.mMsids[0].appdata); + const SdpMsidAttributeList& msids2 = + mSdp->GetMediaSection(1).GetAttributeList().GetMsid(); + ASSERT_EQ(2U, msids2.mMsids.size()); + ASSERT_EQ("tracka", msids2.mMsids[0].identifier); + ASSERT_EQ("streama", msids2.mMsids[0].appdata); + ASSERT_EQ("trackb", msids2.mMsids[1].identifier); + ASSERT_EQ("streamb", msids2.mMsids[1].appdata); + const SdpMsidAttributeList& msids3 = + mSdp->GetMediaSection(2).GetAttributeList().GetMsid(); + ASSERT_EQ(1U, msids3.mMsids.size()); + ASSERT_EQ("noappdata", msids3.mMsids[0].identifier); + ASSERT_EQ("", msids3.mMsids[0].appdata); +} + +TEST_P(NewSdpTest, CheckMediaLevelIceUfrag) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kIceUfragAttribute, true)); + ASSERT_EQ("00000000", + mSdp->GetMediaSection(0).GetAttributeList().GetIceUfrag()); + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kIceUfragAttribute, false)); + + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kIceUfragAttribute, true)); + ASSERT_EQ("4a799b2e", + mSdp->GetMediaSection(1).GetAttributeList().GetIceUfrag()); +} + +TEST_P(NewSdpTest, CheckMediaLevelIcePwd) { + ParseSdp(kBasicAudioVideoOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kIcePwdAttribute)); + ASSERT_EQ("0000000000000000000000000000000", + mSdp->GetMediaSection(0).GetAttributeList().GetIcePwd()); + + ASSERT_TRUE(mSdp->GetMediaSection(1).GetAttributeList().HasAttribute( + SdpAttribute::kIcePwdAttribute)); + ASSERT_EQ("e4cc12a910f106a0a744719425510e17", + mSdp->GetMediaSection(1).GetAttributeList().GetIcePwd()); +} + +TEST_P(NewSdpTest, CheckGroups) { + ParseSdp(kBasicAudioVideoOffer); + const SdpGroupAttributeList& group = mSdp->GetAttributeList().GetGroup(); + const SdpGroupAttributeList::Group& group1 = group.mGroups[0]; + ASSERT_EQ(SdpGroupAttributeList::kBundle, group1.semantics); + ASSERT_EQ(2U, group1.tags.size()); + ASSERT_EQ("first", group1.tags[0]); + ASSERT_EQ("second", group1.tags[1]); + + const SdpGroupAttributeList::Group& group2 = group.mGroups[1]; + ASSERT_EQ(SdpGroupAttributeList::kBundle, group2.semantics); + ASSERT_EQ(1U, group2.tags.size()); + ASSERT_EQ("third", group2.tags[0]); + + const SdpGroupAttributeList::Group& group3 = group.mGroups[2]; + ASSERT_EQ(SdpGroupAttributeList::kLs, group3.semantics); + ASSERT_EQ(2U, group3.tags.size()); + ASSERT_EQ("first", group3.tags[0]); + ASSERT_EQ("third", group3.tags[1]); +} + +// SDP from a basic A/V call with data channel FFX/FFX +const std::string kBasicAudioVideoDataOffer = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"t=0 0" CRLF +"a=ice-ufrag:8a39d2ae" CRLF +"a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF +"a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:109 opus/48000/2" CRLF +"a=ptime:20" CRLF +"a=rtpmap:9 G722/8000" CRLF +"a=rtpmap:0 PCMU/8000" CRLF +"a=rtpmap:8 PCMA/8000" CRLF +"a=rtpmap:101 telephone-event/8000" CRLF +"a=fmtp:101 0-15" CRLF +"a=sendrecv" CRLF +"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level" CRLF +"a=extmap:2/sendonly some_extension" CRLF +"a=extmap:3 some_other_extension some_params some more params" CRLF +"a=setup:actpass" CRLF +"a=rtcp-mux" CRLF +"m=video 9 RTP/SAVPF 120 126 97" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF +"a=rtpmap:126 H264/90000" CRLF +"a=fmtp:126 profile-level-id=42e01f;packetization-mode=1" CRLF +"a=rtpmap:97 H264/90000" CRLF +"a=fmtp:97 profile-level-id=42e01f" CRLF +"a=sendrecv" CRLF +// sipcc barfs on this, despite that it is valid syntax +// Do we care about fixing? +//"a=rtcp-fb:120 ack" CRLF // Should be ignored by sipcc +"a=rtcp-fb:120 ack rpsi" CRLF +"a=rtcp-fb:120 ack app foo" CRLF +"a=rtcp-fb:120 ack foo" CRLF // Should be ignored +"a=rtcp-fb:120 nack" CRLF +"a=rtcp-fb:120 nack sli" CRLF +"a=rtcp-fb:120 nack pli" CRLF +"a=rtcp-fb:120 nack rpsi" CRLF +"a=rtcp-fb:120 nack app foo" CRLF +"a=rtcp-fb:120 nack foo" CRLF // Should be ignored +"a=rtcp-fb:120 ccm fir" CRLF +"a=rtcp-fb:120 ccm tmmbr" CRLF +"a=rtcp-fb:120 ccm tstr" CRLF +"a=rtcp-fb:120 ccm vbcm" CRLF +"a=rtcp-fb:120 ccm foo" CRLF // Should be ignored +"a=rtcp-fb:120 trr-int 10" CRLF +"a=rtcp-fb:120 foo" CRLF // Should be ignored +"a=rtcp-fb:126 nack" CRLF +"a=rtcp-fb:126 nack pli" CRLF +"a=rtcp-fb:126 ccm fir" CRLF +"a=rtcp-fb:97 nack" CRLF +"a=rtcp-fb:97 nack pli" CRLF +"a=rtcp-fb:97 ccm fir" CRLF +"a=setup:actpass" CRLF +"a=rtcp-mux" CRLF +"m=application 9 DTLS/SCTP 5000" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=sctpmap:5000 webrtc-datachannel 16" CRLF +"a=setup:actpass" CRLF; + +TEST_P(NewSdpTest, BasicAudioVideoDataSdpParse) { + ParseSdp(kBasicAudioVideoDataOffer); + ASSERT_EQ(0U, mParser.GetParseErrors().size()) << + "Got parse errors: " << GetParseErrors(); +} + +TEST_P(NewSdpTest, CheckApplicationParameters) { + ParseSdp(kBasicAudioVideoDataOffer); + ASSERT_TRUE(mSdp); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + ASSERT_EQ(SdpMediaSection::kAudio, mSdp->GetMediaSection(0).GetMediaType()) + << "Wrong type for first media section"; + ASSERT_EQ(SdpMediaSection::kVideo, mSdp->GetMediaSection(1).GetMediaType()) + << "Wrong type for second media section"; + ASSERT_EQ(SdpMediaSection::kApplication, mSdp->GetMediaSection(2).GetMediaType()) + << "Wrong type for third media section"; + + ASSERT_EQ(SdpMediaSection::kDtlsSctp, + mSdp->GetMediaSection(2).GetProtocol()) + << "Wrong protocol for application"; + auto app_formats = mSdp->GetMediaSection(2).GetFormats(); + ASSERT_EQ(1U, app_formats.size()) << "Wrong number of formats for audio"; + ASSERT_EQ("5000", app_formats[0]); + + const SdpConnection& conn3 = mSdp->GetMediaSection(2).GetConnection(); + ASSERT_EQ(sdp::kIPv4, conn3.GetAddrType()); + ASSERT_EQ("0.0.0.0", conn3.GetAddress()); + ASSERT_EQ(0U, conn3.GetTtl()); + ASSERT_EQ(0U, conn3.GetCount()); + + ASSERT_TRUE(mSdp->GetMediaSection(2).GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)); + ASSERT_EQ(SdpSetupAttribute::kActpass, + mSdp->GetMediaSection(2).GetAttributeList().GetSetup().mRole); +} + +TEST_P(NewSdpTest, CheckExtmap) { + ParseSdp(kBasicAudioVideoDataOffer); + ASSERT_TRUE(mSdp); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kExtmapAttribute)); + + auto extmaps = + mSdp->GetMediaSection(0).GetAttributeList().GetExtmap().mExtmaps; + ASSERT_EQ(3U, extmaps.size()); + + ASSERT_EQ(1U, extmaps[0].entry); + ASSERT_FALSE(extmaps[0].direction_specified); + ASSERT_EQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", + extmaps[0].extensionname); + ASSERT_EQ("", + extmaps[0].extensionattributes); + + ASSERT_EQ(2U, extmaps[1].entry); + ASSERT_TRUE(extmaps[1].direction_specified); + ASSERT_EQ(SdpDirectionAttribute::kSendonly, extmaps[1].direction); + ASSERT_EQ("some_extension", + extmaps[1].extensionname); + ASSERT_EQ("", + extmaps[1].extensionattributes); + + ASSERT_EQ(3U, extmaps[2].entry); + ASSERT_FALSE(extmaps[2].direction_specified); + ASSERT_EQ("some_other_extension", + extmaps[2].extensionname); + ASSERT_EQ("some_params some more params", + extmaps[2].extensionattributes); +} + +TEST_P(NewSdpTest, CheckRtcpFb) { + ParseSdp(kBasicAudioVideoDataOffer); + ASSERT_TRUE(mSdp); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) << "Wrong number of media sections"; + + auto& video_attrs = mSdp->GetMediaSection(1).GetAttributeList(); + ASSERT_TRUE(video_attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)); + auto& rtcpfbs = video_attrs.GetRtcpFb().mFeedbacks; + ASSERT_EQ(18U, rtcpfbs.size()); + CheckRtcpFb(rtcpfbs[0], "120", SdpRtcpFbAttributeList::kAck, "rpsi"); + CheckRtcpFb(rtcpfbs[1], "120", SdpRtcpFbAttributeList::kAck, "app", "foo"); + CheckRtcpFb(rtcpfbs[2], "120", SdpRtcpFbAttributeList::kNack, ""); + CheckRtcpFb(rtcpfbs[3], "120", SdpRtcpFbAttributeList::kNack, "sli"); + CheckRtcpFb(rtcpfbs[4], "120", SdpRtcpFbAttributeList::kNack, "pli"); + CheckRtcpFb(rtcpfbs[5], "120", SdpRtcpFbAttributeList::kNack, "rpsi"); + CheckRtcpFb(rtcpfbs[6], "120", SdpRtcpFbAttributeList::kNack, "app", "foo"); + CheckRtcpFb(rtcpfbs[7], "120", SdpRtcpFbAttributeList::kCcm, "fir"); + CheckRtcpFb(rtcpfbs[8], "120", SdpRtcpFbAttributeList::kCcm, "tmmbr"); + CheckRtcpFb(rtcpfbs[9], "120", SdpRtcpFbAttributeList::kCcm, "tstr"); + CheckRtcpFb(rtcpfbs[10], "120", SdpRtcpFbAttributeList::kCcm, "vbcm"); + CheckRtcpFb(rtcpfbs[11], "120", SdpRtcpFbAttributeList::kTrrInt, "10"); + CheckRtcpFb(rtcpfbs[12], "126", SdpRtcpFbAttributeList::kNack, ""); + CheckRtcpFb(rtcpfbs[13], "126", SdpRtcpFbAttributeList::kNack, "pli"); + CheckRtcpFb(rtcpfbs[14], "126", SdpRtcpFbAttributeList::kCcm, "fir"); + CheckRtcpFb(rtcpfbs[15], "97", SdpRtcpFbAttributeList::kNack, ""); + CheckRtcpFb(rtcpfbs[16], "97", SdpRtcpFbAttributeList::kNack, "pli"); + CheckRtcpFb(rtcpfbs[17], "97", SdpRtcpFbAttributeList::kCcm, "fir"); +} + +TEST_P(NewSdpTest, CheckSctpmap) { + ParseSdp(kBasicAudioVideoDataOffer); + ASSERT_TRUE(mSdp) << "Parse failed: " << GetParseErrors(); + ASSERT_EQ(3U, mSdp->GetMediaSectionCount()) + << "Wrong number of media sections"; + + const SdpMediaSection& appsec = mSdp->GetMediaSection(2); + ASSERT_TRUE( + appsec.GetAttributeList().HasAttribute(SdpAttribute::kSctpmapAttribute)); + const SdpSctpmapAttributeList& sctpmap = + appsec.GetAttributeList().GetSctpmap(); + + ASSERT_EQ(1U, sctpmap.mSctpmaps.size()) + << "Wrong number of sctpmap attributes"; + ASSERT_EQ(1U, appsec.GetFormats().size()); + + // Need to know name of type + CheckSctpmap("5000", + "webrtc-datachannel", + 16, + appsec.GetFormats()[0], + sctpmap); +} + +const std::string kNewSctpmapOfferDraft07 = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 27987 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"t=0 0" CRLF +"a=ice-ufrag:8a39d2ae" CRLF +"a=ice-pwd:601d53aba51a318351b3ecf5ee00048f" CRLF +"a=fingerprint:sha-256 30:FF:8E:2B:AC:9D:ED:70:18:10:67:C8:AE:9E:68:F3:86:53:51:B0:AC:31:B7:BE:6D:CF:A4:2E:D3:6E:B4:28" CRLF +"m=application 9 DTLS/SCTP webrtc-datachannel" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=fmtp:webrtc-datachannel max-message-size=100000" CRLF +"a=sctp-port 5000" CRLF +"a=setup:actpass" CRLF; + +TEST_P(NewSdpTest, NewSctpmapSdpParse) { + ParseSdp(kNewSctpmapOfferDraft07, false); +} + +INSTANTIATE_TEST_CASE_P(RoundTripSerialize, + NewSdpTest, + ::testing::Values(false, true)); + +const std::string kCandidateInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=candidate:0 1 UDP 2130379007 10.0.0.36 62453 typ host" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:109 opus/48000/2" CRLF; + +// This may or may not parse, but if it does, the errant candidate attribute +// should be ignored. +TEST_P(NewSdpTest, CheckCandidateInSessionLevel) { + ParseSdp(kCandidateInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)); + } +} + +const std::string kBundleOnlyInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=bundle-only" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:109 opus/48000/2" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckBundleOnlyInSessionLevel) { + ParseSdp(kBundleOnlyInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)); + } +} + +const std::string kFmtpInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=fmtp:109 0-15" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:109 opus/48000/2" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckFmtpInSessionLevel) { + ParseSdp(kFmtpInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kFmtpAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kFmtpAttribute)); + } +} + +const std::string kIceMismatchInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ice-mismatch" CRLF +"m=audio 9 RTP/SAVPF 109 9 0 8 101" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:109 opus/48000/2" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckIceMismatchInSessionLevel) { + ParseSdp(kIceMismatchInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kIceMismatchAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kIceMismatchAttribute)); + } +} + +const std::string kImageattrInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=imageattr:120 send * recv *" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckImageattrInSessionLevel) { + ParseSdp(kImageattrInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kImageattrAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kImageattrAttribute)); + } +} + +const std::string kLabelInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=label:foobar" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckLabelInSessionLevel) { + ParseSdp(kLabelInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kLabelAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kLabelAttribute)); + } +} + +const std::string kMaxptimeInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=maxptime:100" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckMaxptimeInSessionLevel) { + ParseSdp(kMaxptimeInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kMaxptimeAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kMaxptimeAttribute)); + } +} + +const std::string kMidInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=mid:foobar" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckMidInSessionLevel) { + ParseSdp(kMidInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)); + } +} + +const std::string kMsidInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=msid:foobar" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckMsidInSessionLevel) { + ParseSdp(kMsidInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kMsidAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kMsidAttribute)); + } +} + +const std::string kPtimeInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ptime:50" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckPtimeInSessionLevel) { + ParseSdp(kPtimeInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kPtimeAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kPtimeAttribute)); + } +} + +const std::string kRemoteCandidatesInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=remote-candidates:0 10.0.0.1 5555" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRemoteCandidatesInSessionLevel) { + ParseSdp(kRemoteCandidatesInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRemoteCandidatesAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRemoteCandidatesAttribute)); + } +} + +const std::string kRtcpInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=rtcp:5555" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRtcpInSessionLevel) { + ParseSdp(kRtcpInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtcpAttribute)); + } +} + +const std::string kRtcpFbInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=rtcp-fb:120 nack" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRtcpFbInSessionLevel) { + ParseSdp(kRtcpFbInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpFbAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtcpFbAttribute)); + } +} + +const std::string kRtcpMuxInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=rtcp-mux" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRtcpMuxInSessionLevel) { + ParseSdp(kRtcpMuxInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpMuxAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtcpMuxAttribute)); + } +} + +const std::string kRtcpRsizeInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=rtcp-rsize" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRtcpRsizeInSessionLevel) { + ParseSdp(kRtcpRsizeInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtcpRsizeAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtcpRsizeAttribute)); + } +} + +const std::string kRtpmapInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=rtpmap:120 VP8/90000" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckRtpmapInSessionLevel) { + ParseSdp(kRtpmapInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kRtpmapAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtpmapAttribute)); + } +} + +const std::string kSctpmapInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=sctpmap:5000" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckSctpmapInSessionLevel) { + ParseSdp(kSctpmapInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kSctpmapAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kSctpmapAttribute)); + } +} + +const std::string kSsrcInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ssrc:5000" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckSsrcInSessionLevel) { + ParseSdp(kSsrcInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kSsrcAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kSsrcAttribute)); + } +} + +const std::string kSsrcGroupInSessionSDP = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"a=ssrc-group:FID 5000" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +// This may or may not parse, but if it does, the errant attribute +// should be ignored. +TEST_P(NewSdpTest, CheckSsrcGroupInSessionLevel) { + ParseSdp(kSsrcGroupInSessionSDP, false); + if (mSdp) { + ASSERT_FALSE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kSsrcGroupAttribute)); + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kSsrcGroupAttribute)); + } +} + +const std::string kNoAttributes = +"v=0" CRLF +"o=Mozilla-SIPUA-35.0a1 5184 0 IN IP4 0.0.0.0" CRLF +"s=SIP Call" CRLF +"c=IN IP4 224.0.0.1/100/12" CRLF +"t=0 0" CRLF +"m=video 9 RTP/SAVPF 120" CRLF +"c=IN IP4 0.0.0.0" CRLF +"a=rtpmap:120 VP8/90000" CRLF; + +TEST_P(NewSdpTest, CheckNoAttributes) { + ParseSdp(kNoAttributes); + + for (auto a = static_cast(SdpAttribute::kFirstAttribute); + a <= static_cast(SdpAttribute::kLastAttribute); + ++a) { + + SdpAttribute::AttributeType type = + static_cast(a); + + // rtpmap is a special case right now, we throw parse errors if it is + // missing, and then insert one. + // direction is another special case that gets a default if not present + if (type != SdpAttribute::kRtpmapAttribute && + type != SdpAttribute::kDirectionAttribute) { + ASSERT_FALSE( + mSdp->GetMediaSection(0).GetAttributeList().HasAttribute(type)) + << "Attribute " << a << " should not have been present at media level"; + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute(type)) + << "Attribute " << a << " should not have been present at session level"; + } + } + + ASSERT_FALSE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kRtpmapAttribute)); + + ASSERT_TRUE(mSdp->GetMediaSection(0).GetAttributeList().HasAttribute( + SdpAttribute::kDirectionAttribute)); + ASSERT_EQ(SdpDirectionAttribute::kSendrecv, + mSdp->GetMediaSection(0).GetAttributeList().GetDirection()); + ASSERT_TRUE(mSdp->GetAttributeList().HasAttribute( + SdpAttribute::kDirectionAttribute)); + ASSERT_EQ(SdpDirectionAttribute::kSendrecv, + mSdp->GetAttributeList().GetDirection()); +} + +TEST(NewSdpTestNoFixture, CheckAttributeTypeSerialize) { + for (auto a = static_cast(SdpAttribute::kFirstAttribute); + a <= static_cast(SdpAttribute::kLastAttribute); + ++a) { + + SdpAttribute::AttributeType type = + static_cast(a); + + // Direction attributes are handled a little differently + if (type != SdpAttribute::kDirectionAttribute) { + std::ostringstream os; + os << type; + ASSERT_NE("", os.str()); + } + } +} + } // End namespace test. int main(int argc, char **argv) {