diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/pc/webrtc_sdp.cc | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/pc/webrtc_sdp.cc')
-rw-r--r-- | third_party/libwebrtc/pc/webrtc_sdp.cc | 3868 |
1 files changed, 3868 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/webrtc_sdp.cc b/third_party/libwebrtc/pc/webrtc_sdp.cc new file mode 100644 index 0000000000..3b347466d8 --- /dev/null +++ b/third_party/libwebrtc/pc/webrtc_sdp.cc @@ -0,0 +1,3868 @@ +/* + * Copyright 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "pc/webrtc_sdp.h" + +#include <ctype.h> +#include <limits.h> + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "api/candidate.h" +#include "api/crypto_params.h" +#include "api/jsep_ice_candidate.h" +#include "api/jsep_session_description.h" +#include "api/media_types.h" +// for RtpExtension +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/rtc_error.h" +#include "api/rtp_parameters.h" +#include "api/rtp_transceiver_direction.h" +#include "media/base/codec.h" +#include "media/base/media_constants.h" +#include "media/base/rid_description.h" +#include "media/base/rtp_utils.h" +#include "media/base/stream_params.h" +#include "media/sctp/sctp_transport_internal.h" +#include "p2p/base/candidate_pair_interface.h" +#include "p2p/base/ice_transport_internal.h" +#include "p2p/base/p2p_constants.h" +#include "p2p/base/port.h" +#include "p2p/base/port_interface.h" +#include "p2p/base/transport_description.h" +#include "p2p/base/transport_info.h" +#include "pc/media_protocol_names.h" +#include "pc/media_session.h" +#include "pc/sdp_serializer.h" +#include "pc/session_description.h" +#include "pc/simulcast_description.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/helpers.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/logging.h" +#include "rtc_base/net_helper.h" +#include "rtc_base/network_constants.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/ssl_fingerprint.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/strings/string_builder.h" + +using cricket::AudioContentDescription; +using cricket::Candidate; +using cricket::Candidates; +using cricket::ContentInfo; +using cricket::CryptoParams; +using cricket::ICE_CANDIDATE_COMPONENT_RTCP; +using cricket::ICE_CANDIDATE_COMPONENT_RTP; +using cricket::kApplicationSpecificBandwidth; +using cricket::kCodecParamMaxPTime; +using cricket::kCodecParamMinPTime; +using cricket::kCodecParamPTime; +using cricket::kTransportSpecificBandwidth; +using cricket::MediaContentDescription; +using cricket::MediaProtocolType; +using cricket::MediaType; +using cricket::RidDescription; +using cricket::RtpHeaderExtensions; +using cricket::SctpDataContentDescription; +using cricket::SimulcastDescription; +using cricket::SimulcastLayer; +using cricket::SimulcastLayerList; +using cricket::SsrcGroup; +using cricket::StreamParams; +using cricket::StreamParamsVec; +using cricket::TransportDescription; +using cricket::TransportInfo; +using cricket::UnsupportedContentDescription; +using cricket::VideoContentDescription; +using rtc::SocketAddress; + +// TODO(deadbeef): Switch to using anonymous namespace rather than declaring +// everything "static". +namespace webrtc { + +// Line type +// RFC 4566 +// An SDP session description consists of a number of lines of text of +// the form: +// <type>=<value> +// where <type> MUST be exactly one case-significant character. + +// Check if passed character is a "token-char" from RFC 4566. +// https://datatracker.ietf.org/doc/html/rfc4566#section-9 +// token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 +// / %x41-5A / %x5E-7E +bool IsTokenChar(char ch) { + return ch == 0x21 || (ch >= 0x23 && ch <= 0x27) || ch == 0x2a || ch == 0x2b || + ch == 0x2d || ch == 0x2e || (ch >= 0x30 && ch <= 0x39) || + (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x5e && ch <= 0x7e); +} +static const int kLinePrefixLength = 2; // Length of <type>= +static const char kLineTypeVersion = 'v'; +static const char kLineTypeOrigin = 'o'; +static const char kLineTypeSessionName = 's'; +static const char kLineTypeSessionInfo = 'i'; +static const char kLineTypeSessionUri = 'u'; +static const char kLineTypeSessionEmail = 'e'; +static const char kLineTypeSessionPhone = 'p'; +static const char kLineTypeSessionBandwidth = 'b'; +static const char kLineTypeTiming = 't'; +static const char kLineTypeRepeatTimes = 'r'; +static const char kLineTypeTimeZone = 'z'; +static const char kLineTypeEncryptionKey = 'k'; +static const char kLineTypeMedia = 'm'; +static const char kLineTypeConnection = 'c'; +static const char kLineTypeAttributes = 'a'; + +// Attributes +static const char kAttributeGroup[] = "group"; +static const char kAttributeMid[] = "mid"; +static const char kAttributeMsid[] = "msid"; +static const char kAttributeBundleOnly[] = "bundle-only"; +static const char kAttributeRtcpMux[] = "rtcp-mux"; +static const char kAttributeRtcpReducedSize[] = "rtcp-rsize"; +static const char kAttributeSsrc[] = "ssrc"; +static const char kSsrcAttributeCname[] = "cname"; +static const char kAttributeExtmapAllowMixed[] = "extmap-allow-mixed"; +static const char kAttributeExtmap[] = "extmap"; +// draft-alvestrand-mmusic-msid-01 +// a=msid-semantic: WMS +// This is a legacy field supported only for Plan B semantics. +static const char kAttributeMsidSemantics[] = "msid-semantic"; +static const char kMediaStreamSemantic[] = "WMS"; +static const char kSsrcAttributeMsid[] = "msid"; +static const char kDefaultMsid[] = "default"; +static const char kNoStreamMsid[] = "-"; +static const char kAttributeSsrcGroup[] = "ssrc-group"; +static const char kAttributeCrypto[] = "crypto"; +static const char kAttributeCandidate[] = "candidate"; +static const char kAttributeCandidateTyp[] = "typ"; +static const char kAttributeCandidateRaddr[] = "raddr"; +static const char kAttributeCandidateRport[] = "rport"; +static const char kAttributeCandidateUfrag[] = "ufrag"; +static const char kAttributeCandidatePwd[] = "pwd"; +static const char kAttributeCandidateGeneration[] = "generation"; +static const char kAttributeCandidateNetworkId[] = "network-id"; +static const char kAttributeCandidateNetworkCost[] = "network-cost"; +static const char kAttributeFingerprint[] = "fingerprint"; +static const char kAttributeSetup[] = "setup"; +static const char kAttributeFmtp[] = "fmtp"; +static const char kAttributeRtpmap[] = "rtpmap"; +static const char kAttributeSctpmap[] = "sctpmap"; +static const char kAttributeRtcp[] = "rtcp"; +static const char kAttributeIceUfrag[] = "ice-ufrag"; +static const char kAttributeIcePwd[] = "ice-pwd"; +static const char kAttributeIceLite[] = "ice-lite"; +static const char kAttributeIceOption[] = "ice-options"; +static const char kAttributeSendOnly[] = "sendonly"; +static const char kAttributeRecvOnly[] = "recvonly"; +static const char kAttributeRtcpFb[] = "rtcp-fb"; +static const char kAttributeSendRecv[] = "sendrecv"; +static const char kAttributeInactive[] = "inactive"; +// draft-ietf-mmusic-sctp-sdp-26 +// a=sctp-port, a=max-message-size +static const char kAttributeSctpPort[] = "sctp-port"; +static const char kAttributeMaxMessageSize[] = "max-message-size"; +static const int kDefaultSctpMaxMessageSize = 65536; +// draft-ietf-mmusic-sdp-simulcast-13 +// a=simulcast +static const char kAttributeSimulcast[] = "simulcast"; +// draft-ietf-mmusic-rid-15 +// a=rid +static const char kAttributeRid[] = "rid"; +static const char kAttributePacketization[] = "packetization"; + +// Experimental flags +static const char kAttributeXGoogleFlag[] = "x-google-flag"; +static const char kValueConference[] = "conference"; + +static const char kAttributeRtcpRemoteEstimate[] = "remote-net-estimate"; + +// Candidate +static const char kCandidateHost[] = "host"; +static const char kCandidateSrflx[] = "srflx"; +static const char kCandidatePrflx[] = "prflx"; +static const char kCandidateRelay[] = "relay"; +static const char kTcpCandidateType[] = "tcptype"; + +// rtc::StringBuilder doesn't have a << overload for chars, while rtc::split and +// rtc::tokenize_first both take a char delimiter. To handle both cases these +// constants come in pairs of a chars and length-one strings. +static const char kSdpDelimiterEqual[] = "="; +static const char kSdpDelimiterEqualChar = '='; +static const char kSdpDelimiterSpace[] = " "; +static const char kSdpDelimiterSpaceChar = ' '; +static const char kSdpDelimiterColon[] = ":"; +static const char kSdpDelimiterColonChar = ':'; +static const char kSdpDelimiterSemicolon[] = ";"; +static const char kSdpDelimiterSemicolonChar = ';'; +static const char kSdpDelimiterSlashChar = '/'; +static const char kNewLineChar = '\n'; +static const char kReturnChar = '\r'; +static const char kLineBreak[] = "\r\n"; + +// TODO(deadbeef): Generate the Session and Time description +// instead of hardcoding. +static const char kSessionVersion[] = "v=0"; +// RFC 4566 +static const char kSessionOriginUsername[] = "-"; +static const char kSessionOriginSessionId[] = "0"; +static const char kSessionOriginSessionVersion[] = "0"; +static const char kSessionOriginNettype[] = "IN"; +static const char kSessionOriginAddrtype[] = "IP4"; +static const char kSessionOriginAddress[] = "127.0.0.1"; +static const char kSessionName[] = "s=-"; +static const char kTimeDescription[] = "t=0 0"; +static const char kAttrGroup[] = "a=group:BUNDLE"; +static const char kConnectionNettype[] = "IN"; +static const char kConnectionIpv4Addrtype[] = "IP4"; +static const char kConnectionIpv6Addrtype[] = "IP6"; +static const char kMediaTypeVideo[] = "video"; +static const char kMediaTypeAudio[] = "audio"; +static const char kMediaTypeData[] = "application"; +static const char kMediaPortRejected[] = "0"; +// draft-ietf-mmusic-trickle-ice-01 +// When no candidates have been gathered, set the connection +// address to IP6 ::. +// TODO(perkj): FF can not parse IP6 ::. See http://crbug/430333 +// Use IPV4 per default. +static const char kDummyAddress[] = "0.0.0.0"; +static const char kDummyPort[] = "9"; + +static const char kDefaultSctpmapProtocol[] = "webrtc-datachannel"; + +// RTP payload type is in the 0-127 range. Use -1 to indicate "all" payload +// types. +const int kWildcardPayloadType = -1; + +// Maximum number of channels allowed. +static const size_t kMaxNumberOfChannels = 24; + +struct SsrcInfo { + uint32_t ssrc_id; + std::string cname; + std::string stream_id; + std::string track_id; +}; +typedef std::vector<SsrcInfo> SsrcInfoVec; +typedef std::vector<SsrcGroup> SsrcGroupVec; + +template <class T> +static void AddFmtpLine(const T& codec, std::string* message); +static void BuildMediaDescription(const ContentInfo* content_info, + const TransportInfo* transport_info, + const cricket::MediaType media_type, + const std::vector<Candidate>& candidates, + int msid_signaling, + std::string* message); +static void BuildMediaLine(const cricket::MediaType media_type, + const ContentInfo* content_info, + const MediaContentDescription* media_desc, + std::string* message); +static void BuildRtpContentAttributes(const MediaContentDescription* media_desc, + const cricket::MediaType media_type, + int msid_signaling, + std::string* message); +static void BuildRtpHeaderExtensions(const RtpHeaderExtensions& extensions, + std::string* message); +static void BuildRtpmap(const MediaContentDescription* media_desc, + const cricket::MediaType media_type, + std::string* message); +static void BuildCandidate(const std::vector<Candidate>& candidates, + bool include_ufrag, + std::string* message); +static void BuildIceUfragPwd(const TransportInfo* transport_info, + std::string* message); +static void BuildDtlsFingerprintSetup(const TransportInfo* transport_info, + std::string* message); +static void BuildIceOptions(const std::vector<std::string>& transport_options, + std::string* message); +static bool ParseSessionDescription(absl::string_view message, + size_t* pos, + std::string* session_id, + std::string* session_version, + TransportDescription* session_td, + RtpHeaderExtensions* session_extmaps, + rtc::SocketAddress* connection_addr, + cricket::SessionDescription* desc, + SdpParseError* error); +static bool ParseMediaDescription( + absl::string_view message, + const TransportDescription& session_td, + const RtpHeaderExtensions& session_extmaps, + size_t* pos, + const rtc::SocketAddress& session_connection_addr, + cricket::SessionDescription* desc, + std::vector<std::unique_ptr<JsepIceCandidate>>* candidates, + SdpParseError* error); +static bool ParseContent( + absl::string_view message, + const cricket::MediaType media_type, + int mline_index, + absl::string_view protocol, + const std::vector<int>& payload_types, + size_t* pos, + std::string* content_name, + bool* bundle_only, + int* msid_signaling, + MediaContentDescription* media_desc, + TransportDescription* transport, + std::vector<std::unique_ptr<JsepIceCandidate>>* candidates, + SdpParseError* error); +static bool ParseGroupAttribute(absl::string_view line, + cricket::SessionDescription* desc, + SdpParseError* error); +static bool ParseSsrcAttribute(absl::string_view line, + SsrcInfoVec* ssrc_infos, + int* msid_signaling, + SdpParseError* error); +static bool ParseSsrcGroupAttribute(absl::string_view line, + SsrcGroupVec* ssrc_groups, + SdpParseError* error); +static bool ParseCryptoAttribute(absl::string_view line, + MediaContentDescription* media_desc, + SdpParseError* error); +static bool ParseRtpmapAttribute(absl::string_view line, + const cricket::MediaType media_type, + const std::vector<int>& payload_types, + MediaContentDescription* media_desc, + SdpParseError* error); +static bool ParseFmtpAttributes(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error); +static bool ParseFmtpParam(absl::string_view line, + std::string* parameter, + std::string* value, + SdpParseError* error); +static bool ParsePacketizationAttribute(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error); +static bool ParseRtcpFbAttribute(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error); +static bool ParseIceOptions(absl::string_view line, + std::vector<std::string>* transport_options, + SdpParseError* error); +static bool ParseExtmap(absl::string_view line, + RtpExtension* extmap, + SdpParseError* error); +static bool ParseFingerprintAttribute( + absl::string_view line, + std::unique_ptr<rtc::SSLFingerprint>* fingerprint, + SdpParseError* error); +static bool ParseDtlsSetup(absl::string_view line, + cricket::ConnectionRole* role, + SdpParseError* error); +static bool ParseMsidAttribute(absl::string_view line, + std::vector<std::string>* stream_ids, + std::string* track_id, + SdpParseError* error); + +static void RemoveInvalidRidDescriptions(const std::vector<int>& payload_types, + std::vector<RidDescription>* rids); + +static SimulcastLayerList RemoveRidsFromSimulcastLayerList( + const std::set<std::string>& to_remove, + const SimulcastLayerList& layers); + +static void RemoveInvalidRidsFromSimulcast( + const std::vector<RidDescription>& rids, + SimulcastDescription* simulcast); + +// Helper functions + +// Below ParseFailed*** functions output the line that caused the parsing +// failure and the detailed reason (`description`) of the failure to `error`. +// The functions always return false so that they can be used directly in the +// following way when error happens: +// "return ParseFailed***(...);" + +// The line starting at `line_start` of `message` is the failing line. +// The reason for the failure should be provided in the `description`. +// An example of a description could be "unknown character". +static bool ParseFailed(absl::string_view message, + size_t line_start, + std::string description, + SdpParseError* error) { + // Get the first line of `message` from `line_start`. + absl::string_view first_line; + size_t line_end = message.find(kNewLineChar, line_start); + if (line_end != std::string::npos) { + if (line_end > 0 && (message.at(line_end - 1) == kReturnChar)) { + --line_end; + } + first_line = message.substr(line_start, (line_end - line_start)); + } else { + first_line = message.substr(line_start); + } + + RTC_LOG(LS_ERROR) << "Failed to parse: \"" << first_line + << "\". Reason: " << description; + if (error) { + // TODO(bugs.webrtc.org/13220): In C++17, we can use plain assignment, with + // a string_view on the right hand side. + error->line.assign(first_line.data(), first_line.size()); + error->description = std::move(description); + } + return false; +} + +// `line` is the failing line. The reason for the failure should be +// provided in the `description`. +static bool ParseFailed(absl::string_view line, + std::string description, + SdpParseError* error) { + return ParseFailed(line, 0, std::move(description), error); +} + +// Parses failure where the failing SDP line isn't know or there are multiple +// failing lines. +static bool ParseFailed(std::string description, SdpParseError* error) { + return ParseFailed("", std::move(description), error); +} + +// `line` is the failing line. The failure is due to the fact that `line` +// doesn't have `expected_fields` fields. +static bool ParseFailedExpectFieldNum(absl::string_view line, + int expected_fields, + SdpParseError* error) { + rtc::StringBuilder description; + description << "Expects " << expected_fields << " fields."; + return ParseFailed(line, description.Release(), error); +} + +// `line` is the failing line. The failure is due to the fact that `line` has +// less than `expected_min_fields` fields. +static bool ParseFailedExpectMinFieldNum(absl::string_view line, + int expected_min_fields, + SdpParseError* error) { + rtc::StringBuilder description; + description << "Expects at least " << expected_min_fields << " fields."; + return ParseFailed(line, description.Release(), error); +} + +// `line` is the failing line. The failure is due to the fact that it failed to +// get the value of `attribute`. +static bool ParseFailedGetValue(absl::string_view line, + absl::string_view attribute, + SdpParseError* error) { + rtc::StringBuilder description; + description << "Failed to get the value of attribute: " << attribute; + return ParseFailed(line, description.Release(), error); +} + +// The line starting at `line_start` of `message` is the failing line. The +// failure is due to the line type (e.g. the "m" part of the "m-line") +// not matching what is expected. The expected line type should be +// provided as `line_type`. +static bool ParseFailedExpectLine(absl::string_view message, + size_t line_start, + const char line_type, + absl::string_view line_value, + SdpParseError* error) { + rtc::StringBuilder description; + description << "Expect line: " << std::string(1, line_type) << "=" + << line_value; + return ParseFailed(message, line_start, description.Release(), error); +} + +static bool AddLine(absl::string_view line, std::string* message) { + if (!message) + return false; + + message->append(line.data(), line.size()); + message->append(kLineBreak); + return true; +} + +// Trim return character, if any. +static absl::string_view TrimReturnChar(absl::string_view line) { + if (!line.empty() && line.back() == kReturnChar) { + line.remove_suffix(1); + } + return line; +} + +// Gets line of `message` starting at `pos`, and checks overall SDP syntax. On +// success, advances `pos` to the next line. +static absl::optional<absl::string_view> GetLine(absl::string_view message, + size_t* pos) { + size_t line_end = message.find(kNewLineChar, *pos); + if (line_end == absl::string_view::npos) { + return absl::nullopt; + } + absl::string_view line = + TrimReturnChar(message.substr(*pos, line_end - *pos)); + + // RFC 4566 + // An SDP session description consists of a number of lines of text of + // the form: + // <type>=<value> + // where <type> MUST be exactly one case-significant character and + // <value> is structured text whose format depends on <type>. + // Whitespace MUST NOT be used on either side of the "=" sign. + // + // However, an exception to the whitespace rule is made for "s=", since + // RFC4566 also says: + // + // If a session has no meaningful name, the value "s= " SHOULD be used + // (i.e., a single space as the session name). + if (line.length() < 3 || !islower(static_cast<unsigned char>(line[0])) || + line[1] != kSdpDelimiterEqualChar || + (line[0] != kLineTypeSessionName && line[2] == kSdpDelimiterSpaceChar)) { + return absl::nullopt; + } + *pos = line_end + 1; + return line; +} + +// Init `os` to "`type`=`value`". +static void InitLine(const char type, + absl::string_view value, + rtc::StringBuilder* os) { + os->Clear(); + *os << std::string(1, type) << kSdpDelimiterEqual << value; +} + +// Init `os` to "a=`attribute`". +static void InitAttrLine(absl::string_view attribute, rtc::StringBuilder* os) { + InitLine(kLineTypeAttributes, attribute, os); +} + +// Writes a SDP attribute line based on `attribute` and `value` to `message`. +static void AddAttributeLine(absl::string_view attribute, + int value, + std::string* message) { + rtc::StringBuilder os; + InitAttrLine(attribute, &os); + os << kSdpDelimiterColon << value; + AddLine(os.str(), message); +} + +static bool IsLineType(absl::string_view message, + const char type, + size_t line_start) { + if (message.size() < line_start + kLinePrefixLength) { + return false; + } + return (message[line_start] == type && + message[line_start + 1] == kSdpDelimiterEqualChar); +} + +static bool IsLineType(absl::string_view line, const char type) { + return IsLineType(line, type, 0); +} + +static absl::optional<absl::string_view> +GetLineWithType(absl::string_view message, size_t* pos, const char type) { + if (IsLineType(message, type, *pos)) { + return GetLine(message, pos); + } + return absl::nullopt; +} + +static bool HasAttribute(absl::string_view line, absl::string_view attribute) { + if (line.compare(kLinePrefixLength, attribute.size(), attribute) == 0) { + // Make sure that the match is not only a partial match. If length of + // strings doesn't match, the next character of the line must be ':' or ' '. + // This function is also used for media descriptions (e.g., "m=audio 9..."), + // hence the need to also allow space in the end. + RTC_CHECK_LE(kLinePrefixLength + attribute.size(), line.size()); + if ((kLinePrefixLength + attribute.size()) == line.size() || + line[kLinePrefixLength + attribute.size()] == kSdpDelimiterColonChar || + line[kLinePrefixLength + attribute.size()] == kSdpDelimiterSpaceChar) { + return true; + } + } + return false; +} + +static bool AddSsrcLine(uint32_t ssrc_id, + absl::string_view attribute, + absl::string_view value, + std::string* message) { + // RFC 5576 + // a=ssrc:<ssrc-id> <attribute>:<value> + rtc::StringBuilder os; + InitAttrLine(kAttributeSsrc, &os); + os << kSdpDelimiterColon << ssrc_id << kSdpDelimiterSpace << attribute + << kSdpDelimiterColon << value; + return AddLine(os.str(), message); +} + +// Get value only from <attribute>:<value>. +static bool GetValue(absl::string_view message, + absl::string_view attribute, + std::string* value, + SdpParseError* error) { + std::string leftpart; + if (!rtc::tokenize_first(message, kSdpDelimiterColonChar, &leftpart, value)) { + return ParseFailedGetValue(message, attribute, error); + } + // The left part should end with the expected attribute. + if (leftpart.length() < attribute.length() || + absl::string_view(leftpart).compare( + leftpart.length() - attribute.length(), attribute.length(), + attribute) != 0) { + return ParseFailedGetValue(message, attribute, error); + } + return true; +} + +// Get a single [token] from <attribute>:<token> +static bool GetSingleTokenValue(absl::string_view message, + absl::string_view attribute, + std::string* value, + SdpParseError* error) { + if (!GetValue(message, attribute, value, error)) { + return false; + } + if (!absl::c_all_of(absl::string_view(*value), IsTokenChar)) { + rtc::StringBuilder description; + description << "Illegal character found in the value of " << attribute; + return ParseFailed(message, description.Release(), error); + } + return true; +} + +static bool CaseInsensitiveFind(std::string str1, std::string str2) { + absl::c_transform(str1, str1.begin(), ::tolower); + absl::c_transform(str2, str2.begin(), ::tolower); + return str1.find(str2) != std::string::npos; +} + +template <class T> +static bool GetValueFromString(absl::string_view line, + absl::string_view s, + T* t, + SdpParseError* error) { + if (!rtc::FromString(s, t)) { + rtc::StringBuilder description; + description << "Invalid value: " << s << "."; + return ParseFailed(line, description.Release(), error); + } + return true; +} + +static bool GetPayloadTypeFromString(absl::string_view line, + absl::string_view s, + int* payload_type, + SdpParseError* error) { + return GetValueFromString(line, s, payload_type, error) && + cricket::IsValidRtpPayloadType(*payload_type); +} + +// Creates a StreamParams track in the case when no SSRC lines are signaled. +// This is a track that does not contain SSRCs and only contains +// stream_ids/track_id if it's signaled with a=msid lines. +void CreateTrackWithNoSsrcs(const std::vector<std::string>& msid_stream_ids, + absl::string_view msid_track_id, + const std::vector<RidDescription>& rids, + StreamParamsVec* tracks) { + StreamParams track; + if (msid_track_id.empty() && rids.empty()) { + // We only create an unsignaled track if a=msid lines were signaled. + RTC_LOG(LS_INFO) << "MSID not signaled, skipping creation of StreamParams"; + return; + } + track.set_stream_ids(msid_stream_ids); + track.id = std::string(msid_track_id); + track.set_rids(rids); + tracks->push_back(track); +} + +// Creates the StreamParams tracks, for the case when SSRC lines are signaled. +// `msid_stream_ids` and `msid_track_id` represent the stream/track ID from the +// "a=msid" attribute, if it exists. They are empty if the attribute does not +// exist. We prioritize getting stream_ids/track_ids signaled in a=msid lines. +void CreateTracksFromSsrcInfos(const SsrcInfoVec& ssrc_infos, + const std::vector<std::string>& msid_stream_ids, + absl::string_view msid_track_id, + StreamParamsVec* tracks, + int msid_signaling) { + RTC_DCHECK(tracks != NULL); + for (const SsrcInfo& ssrc_info : ssrc_infos) { + // According to https://tools.ietf.org/html/rfc5576#section-6.1, the CNAME + // attribute is mandatory, but we relax that restriction. + if (ssrc_info.cname.empty()) { + RTC_LOG(LS_WARNING) << "CNAME attribute missing for SSRC " + << ssrc_info.ssrc_id; + } + std::vector<std::string> stream_ids; + std::string track_id; + if (msid_signaling & cricket::kMsidSignalingMediaSection) { + // This is the case with Unified Plan SDP msid signaling. + stream_ids = msid_stream_ids; + track_id = std::string(msid_track_id); + } else if (msid_signaling & cricket::kMsidSignalingSsrcAttribute) { + // This is the case with Plan B SDP msid signaling. + stream_ids.push_back(ssrc_info.stream_id); + track_id = ssrc_info.track_id; + } else { + // Since no media streams isn't supported with older SDP signaling, we + // use a default a stream id. + stream_ids.push_back(kDefaultMsid); + } + // If a track ID wasn't populated from the SSRC attributes OR the + // msid attribute, use default/random values. + if (track_id.empty()) { + // TODO(ronghuawu): What should we do if the track id doesn't appear? + // Create random string (which will be used as track label later)? + track_id = rtc::CreateRandomString(8); + } + + auto track_it = absl::c_find_if( + *tracks, + [track_id](const StreamParams& track) { return track.id == track_id; }); + if (track_it == tracks->end()) { + // If we don't find an existing track, create a new one. + tracks->push_back(StreamParams()); + track_it = tracks->end() - 1; + } + StreamParams& track = *track_it; + track.add_ssrc(ssrc_info.ssrc_id); + track.cname = ssrc_info.cname; + track.set_stream_ids(stream_ids); + track.id = track_id; + } +} + +void GetMediaStreamIds(const ContentInfo* content, + std::set<std::string>* labels) { + for (const StreamParams& stream_params : + content->media_description()->streams()) { + for (const std::string& stream_id : stream_params.stream_ids()) { + labels->insert(stream_id); + } + } +} + +// RFC 5245 +// It is RECOMMENDED that default candidates be chosen based on the +// likelihood of those candidates to work with the peer that is being +// contacted. It is RECOMMENDED that relayed > reflexive > host. +static const int kPreferenceUnknown = 0; +static const int kPreferenceHost = 1; +static const int kPreferenceReflexive = 2; +static const int kPreferenceRelayed = 3; + +static int GetCandidatePreferenceFromType(absl::string_view type) { + int preference = kPreferenceUnknown; + if (type == cricket::LOCAL_PORT_TYPE) { + preference = kPreferenceHost; + } else if (type == cricket::STUN_PORT_TYPE) { + preference = kPreferenceReflexive; + } else if (type == cricket::RELAY_PORT_TYPE) { + preference = kPreferenceRelayed; + } else { + RTC_DCHECK_NOTREACHED(); + } + return preference; +} + +// Get ip and port of the default destination from the `candidates` with the +// given value of `component_id`. The default candidate should be the one most +// likely to work, typically IPv4 relay. +// RFC 5245 +// The value of `component_id` currently supported are 1 (RTP) and 2 (RTCP). +// TODO(deadbeef): Decide the default destination in webrtcsession and +// pass it down via SessionDescription. +static void GetDefaultDestination(const std::vector<Candidate>& candidates, + int component_id, + std::string* port, + std::string* ip, + std::string* addr_type) { + *addr_type = kConnectionIpv4Addrtype; + *port = kDummyPort; + *ip = kDummyAddress; + int current_preference = kPreferenceUnknown; + int current_family = AF_UNSPEC; + for (const Candidate& candidate : candidates) { + if (candidate.component() != component_id) { + continue; + } + // Default destination should be UDP only. + if (candidate.protocol() != cricket::UDP_PROTOCOL_NAME) { + continue; + } + const int preference = GetCandidatePreferenceFromType(candidate.type()); + const int family = candidate.address().ipaddr().family(); + // See if this candidate is more preferable then the current one if it's the + // same family. Or if the current family is IPv4 already so we could safely + // ignore all IPv6 ones. WebRTC bug 4269. + // http://code.google.com/p/webrtc/issues/detail?id=4269 + if ((preference <= current_preference && current_family == family) || + (current_family == AF_INET && family == AF_INET6)) { + continue; + } + if (family == AF_INET) { + addr_type->assign(kConnectionIpv4Addrtype); + } else if (family == AF_INET6) { + addr_type->assign(kConnectionIpv6Addrtype); + } + current_preference = preference; + current_family = family; + *port = candidate.address().PortAsString(); + *ip = candidate.address().ipaddr().ToString(); + } +} + +// Gets "a=rtcp" line if found default RTCP candidate from `candidates`. +static std::string GetRtcpLine(const std::vector<Candidate>& candidates) { + std::string rtcp_line, rtcp_port, rtcp_ip, addr_type; + GetDefaultDestination(candidates, ICE_CANDIDATE_COMPONENT_RTCP, &rtcp_port, + &rtcp_ip, &addr_type); + // Found default RTCP candidate. + // RFC 5245 + // If the agent is utilizing RTCP, it MUST encode the RTCP candidate + // using the a=rtcp attribute as defined in RFC 3605. + + // RFC 3605 + // rtcp-attribute = "a=rtcp:" port [nettype space addrtype space + // connection-address] CRLF + rtc::StringBuilder os; + InitAttrLine(kAttributeRtcp, &os); + os << kSdpDelimiterColon << rtcp_port << " " << kConnectionNettype << " " + << addr_type << " " << rtcp_ip; + rtcp_line = os.str(); + return rtcp_line; +} + +// Get candidates according to the mline index from SessionDescriptionInterface. +static void GetCandidatesByMindex(const SessionDescriptionInterface& desci, + int mline_index, + std::vector<Candidate>* candidates) { + if (!candidates) { + return; + } + const IceCandidateCollection* cc = desci.candidates(mline_index); + for (size_t i = 0; i < cc->count(); ++i) { + const IceCandidateInterface* candidate = cc->at(i); + candidates->push_back(candidate->candidate()); + } +} + +static bool IsValidPort(int port) { + return port >= 0 && port <= 65535; +} + +std::string SdpSerialize(const JsepSessionDescription& jdesc) { + const cricket::SessionDescription* desc = jdesc.description(); + if (!desc) { + return ""; + } + + std::string message; + + // Session Description. + AddLine(kSessionVersion, &message); + // Session Origin + // RFC 4566 + // o=<username> <sess-id> <sess-version> <nettype> <addrtype> + // <unicast-address> + rtc::StringBuilder os; + InitLine(kLineTypeOrigin, kSessionOriginUsername, &os); + const std::string& session_id = + jdesc.session_id().empty() ? kSessionOriginSessionId : jdesc.session_id(); + const std::string& session_version = jdesc.session_version().empty() + ? kSessionOriginSessionVersion + : jdesc.session_version(); + os << " " << session_id << " " << session_version << " " + << kSessionOriginNettype << " " << kSessionOriginAddrtype << " " + << kSessionOriginAddress; + AddLine(os.str(), &message); + AddLine(kSessionName, &message); + + // Time Description. + AddLine(kTimeDescription, &message); + + // BUNDLE Groups + std::vector<const cricket::ContentGroup*> groups = + desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE); + for (const cricket::ContentGroup* group : groups) { + std::string group_line = kAttrGroup; + RTC_DCHECK(group != NULL); + for (const std::string& content_name : group->content_names()) { + group_line.append(" "); + group_line.append(content_name); + } + AddLine(group_line, &message); + } + + // Mixed one- and two-byte header extension. + if (desc->extmap_allow_mixed()) { + InitAttrLine(kAttributeExtmapAllowMixed, &os); + AddLine(os.str(), &message); + } + + // MediaStream semantics + InitAttrLine(kAttributeMsidSemantics, &os); + os << kSdpDelimiterColon << " " << kMediaStreamSemantic; + + std::set<std::string> media_stream_ids; + const ContentInfo* audio_content = GetFirstAudioContent(desc); + if (audio_content) + GetMediaStreamIds(audio_content, &media_stream_ids); + + const ContentInfo* video_content = GetFirstVideoContent(desc); + if (video_content) + GetMediaStreamIds(video_content, &media_stream_ids); + + for (const std::string& id : media_stream_ids) { + os << " " << id; + } + AddLine(os.str(), &message); + + // a=ice-lite + // + // TODO(deadbeef): It's weird that we need to iterate TransportInfos for + // this, when it's a session-level attribute. It really should be moved to a + // session-level structure like SessionDescription. + for (const cricket::TransportInfo& transport : desc->transport_infos()) { + if (transport.description.ice_mode == cricket::ICEMODE_LITE) { + InitAttrLine(kAttributeIceLite, &os); + AddLine(os.str(), &message); + break; + } + } + + // Preserve the order of the media contents. + int mline_index = -1; + for (const ContentInfo& content : desc->contents()) { + std::vector<Candidate> candidates; + GetCandidatesByMindex(jdesc, ++mline_index, &candidates); + BuildMediaDescription(&content, desc->GetTransportInfoByName(content.name), + content.media_description()->type(), candidates, + desc->msid_signaling(), &message); + } + return message; +} + +// Serializes the passed in IceCandidateInterface to a SDP string. +// candidate - The candidate to be serialized. +std::string SdpSerializeCandidate(const IceCandidateInterface& candidate) { + return SdpSerializeCandidate(candidate.candidate()); +} + +// Serializes a cricket Candidate. +std::string SdpSerializeCandidate(const cricket::Candidate& candidate) { + std::string message; + std::vector<cricket::Candidate> candidates(1, candidate); + BuildCandidate(candidates, true, &message); + // From WebRTC draft section 4.8.1.1 candidate-attribute will be + // just candidate:<candidate> not a=candidate:<blah>CRLF + RTC_DCHECK(message.find("a=") == 0); + message.erase(0, 2); + RTC_DCHECK(message.find(kLineBreak) == message.size() - 2); + message.resize(message.size() - 2); + return message; +} + +bool SdpDeserialize(absl::string_view message, + JsepSessionDescription* jdesc, + SdpParseError* error) { + std::string session_id; + std::string session_version; + TransportDescription session_td("", ""); + RtpHeaderExtensions session_extmaps; + rtc::SocketAddress session_connection_addr; + auto desc = std::make_unique<cricket::SessionDescription>(); + size_t current_pos = 0; + + // Session Description + if (!ParseSessionDescription(message, ¤t_pos, &session_id, + &session_version, &session_td, &session_extmaps, + &session_connection_addr, desc.get(), error)) { + return false; + } + + // Media Description + std::vector<std::unique_ptr<JsepIceCandidate>> candidates; + if (!ParseMediaDescription(message, session_td, session_extmaps, ¤t_pos, + session_connection_addr, desc.get(), &candidates, + error)) { + return false; + } + + jdesc->Initialize(std::move(desc), session_id, session_version); + + for (const auto& candidate : candidates) { + jdesc->AddCandidate(candidate.get()); + } + return true; +} + +bool SdpDeserializeCandidate(absl::string_view message, + JsepIceCandidate* jcandidate, + SdpParseError* error) { + RTC_DCHECK(jcandidate != NULL); + Candidate candidate; + if (!ParseCandidate(message, &candidate, error, true)) { + return false; + } + jcandidate->SetCandidate(candidate); + return true; +} + +bool SdpDeserializeCandidate(absl::string_view transport_name, + absl::string_view message, + cricket::Candidate* candidate, + SdpParseError* error) { + RTC_DCHECK(candidate != nullptr); + if (!ParseCandidate(message, candidate, error, true)) { + return false; + } + candidate->set_transport_name(transport_name); + return true; +} + +bool ParseCandidate(absl::string_view message, + Candidate* candidate, + SdpParseError* error, + bool is_raw) { + RTC_DCHECK(candidate != NULL); + + // Makes sure `message` contains only one line. + absl::string_view first_line; + + size_t line_end = message.find(kNewLineChar); + if (line_end == absl::string_view::npos) { + first_line = message; + } else if (line_end + 1 == message.size()) { + first_line = message.substr(0, line_end); + } else { + return ParseFailed(message, 0, "Expect one line only", error); + } + + // Trim return char, if any. + first_line = TrimReturnChar(first_line); + + // From WebRTC draft section 4.8.1.1 candidate-attribute should be + // candidate:<candidate> when trickled, but we still support + // a=candidate:<blah>CRLF for backward compatibility and for parsing a line + // from the SDP. + if (IsLineType(first_line, kLineTypeAttributes)) { + first_line = first_line.substr(kLinePrefixLength); + } + + std::string attribute_candidate; + std::string candidate_value; + + // `first_line` must be in the form of "candidate:<value>". + if (!rtc::tokenize_first(first_line, kSdpDelimiterColonChar, + &attribute_candidate, &candidate_value) || + attribute_candidate != kAttributeCandidate) { + if (is_raw) { + rtc::StringBuilder description; + description << "Expect line: " << kAttributeCandidate + << ":" + "<candidate-str>"; + return ParseFailed(first_line, 0, description.Release(), error); + } else { + return ParseFailedExpectLine(first_line, 0, kLineTypeAttributes, + kAttributeCandidate, error); + } + } + + std::vector<absl::string_view> fields = + rtc::split(candidate_value, kSdpDelimiterSpaceChar); + + // RFC 5245 + // a=candidate:<foundation> <component-id> <transport> <priority> + // <connection-address> <port> typ <candidate-types> + // [raddr <connection-address>] [rport <port>] + // *(SP extension-att-name SP extension-att-value) + const size_t expected_min_fields = 8; + if (fields.size() < expected_min_fields || + (fields[6] != kAttributeCandidateTyp)) { + return ParseFailedExpectMinFieldNum(first_line, expected_min_fields, error); + } + const absl::string_view foundation = fields[0]; + + int component_id = 0; + if (!GetValueFromString(first_line, fields[1], &component_id, error)) { + return false; + } + const absl::string_view transport = fields[2]; + uint32_t priority = 0; + if (!GetValueFromString(first_line, fields[3], &priority, error)) { + return false; + } + const absl::string_view connection_address = fields[4]; + int port = 0; + if (!GetValueFromString(first_line, fields[5], &port, error)) { + return false; + } + if (!IsValidPort(port)) { + return ParseFailed(first_line, "Invalid port number.", error); + } + SocketAddress address(connection_address, port); + + absl::optional<cricket::ProtocolType> protocol = + cricket::StringToProto(transport); + if (!protocol) { + return ParseFailed(first_line, "Unsupported transport type.", error); + } + bool tcp_protocol = false; + switch (*protocol) { + // Supported protocols. + case cricket::PROTO_UDP: + break; + case cricket::PROTO_TCP: + case cricket::PROTO_SSLTCP: + tcp_protocol = true; + break; + default: + return ParseFailed(first_line, "Unsupported transport type.", error); + } + + std::string candidate_type; + const absl::string_view type = fields[7]; + if (type == kCandidateHost) { + candidate_type = cricket::LOCAL_PORT_TYPE; + } else if (type == kCandidateSrflx) { + candidate_type = cricket::STUN_PORT_TYPE; + } else if (type == kCandidateRelay) { + candidate_type = cricket::RELAY_PORT_TYPE; + } else if (type == kCandidatePrflx) { + candidate_type = cricket::PRFLX_PORT_TYPE; + } else { + return ParseFailed(first_line, "Unsupported candidate type.", error); + } + + size_t current_position = expected_min_fields; + SocketAddress related_address; + // The 2 optional fields for related address + // [raddr <connection-address>] [rport <port>] + if (fields.size() >= (current_position + 2) && + fields[current_position] == kAttributeCandidateRaddr) { + related_address.SetIP(fields[++current_position]); + ++current_position; + } + if (fields.size() >= (current_position + 2) && + fields[current_position] == kAttributeCandidateRport) { + int port = 0; + if (!GetValueFromString(first_line, fields[++current_position], &port, + error)) { + return false; + } + if (!IsValidPort(port)) { + return ParseFailed(first_line, "Invalid port number.", error); + } + related_address.SetPort(port); + ++current_position; + } + + // If this is a TCP candidate, it has additional extension as defined in + // RFC 6544. + absl::string_view tcptype; + if (fields.size() >= (current_position + 2) && + fields[current_position] == kTcpCandidateType) { + tcptype = fields[++current_position]; + ++current_position; + + if (tcptype != cricket::TCPTYPE_ACTIVE_STR && + tcptype != cricket::TCPTYPE_PASSIVE_STR && + tcptype != cricket::TCPTYPE_SIMOPEN_STR) { + return ParseFailed(first_line, "Invalid TCP candidate type.", error); + } + + if (!tcp_protocol) { + return ParseFailed(first_line, "Invalid non-TCP candidate", error); + } + } else if (tcp_protocol) { + // We allow the tcptype to be missing, for backwards compatibility, + // treating it as a passive candidate. + // TODO(bugs.webrtc.org/11466): Treat a missing tcptype as an error? + tcptype = cricket::TCPTYPE_PASSIVE_STR; + } + + // Extension + // Though non-standard, we support the ICE ufrag and pwd being signaled on + // the candidate to avoid issues with confusing which generation a candidate + // belongs to when trickling multiple generations at the same time. + absl::string_view username; + absl::string_view password; + uint32_t generation = 0; + uint16_t network_id = 0; + uint16_t network_cost = 0; + for (size_t i = current_position; i + 1 < fields.size(); ++i) { + // RFC 5245 + // *(SP extension-att-name SP extension-att-value) + if (fields[i] == kAttributeCandidateGeneration) { + if (!GetValueFromString(first_line, fields[++i], &generation, error)) { + return false; + } + } else if (fields[i] == kAttributeCandidateUfrag) { + username = fields[++i]; + } else if (fields[i] == kAttributeCandidatePwd) { + password = fields[++i]; + } else if (fields[i] == kAttributeCandidateNetworkId) { + if (!GetValueFromString(first_line, fields[++i], &network_id, error)) { + return false; + } + } else if (fields[i] == kAttributeCandidateNetworkCost) { + if (!GetValueFromString(first_line, fields[++i], &network_cost, error)) { + return false; + } + network_cost = std::min(network_cost, rtc::kNetworkCostMax); + } else { + // Skip the unknown extension. + ++i; + } + } + + *candidate = Candidate(component_id, cricket::ProtoToString(*protocol), + address, priority, username, password, candidate_type, + generation, foundation, network_id, network_cost); + candidate->set_related_address(related_address); + candidate->set_tcptype(tcptype); + return true; +} + +bool ParseIceOptions(absl::string_view line, + std::vector<std::string>* transport_options, + SdpParseError* error) { + std::string ice_options; + if (!GetValue(line, kAttributeIceOption, &ice_options, error)) { + return false; + } + std::vector<absl::string_view> fields = + rtc::split(ice_options, kSdpDelimiterSpaceChar); + for (size_t i = 0; i < fields.size(); ++i) { + transport_options->emplace_back(fields[i]); + } + return true; +} + +bool ParseSctpPort(absl::string_view line, + int* sctp_port, + SdpParseError* error) { + // draft-ietf-mmusic-sctp-sdp-26 + // a=sctp-port + const size_t expected_min_fields = 2; + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterColonChar); + if (fields.size() < expected_min_fields) { + fields = rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + } + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + if (!rtc::FromString(fields[1], sctp_port)) { + return ParseFailed(line, "Invalid sctp port value.", error); + } + return true; +} + +bool ParseSctpMaxMessageSize(absl::string_view line, + int* max_message_size, + SdpParseError* error) { + // draft-ietf-mmusic-sctp-sdp-26 + // a=max-message-size:199999 + const size_t expected_min_fields = 2; + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterColonChar); + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + if (!rtc::FromString(fields[1], max_message_size)) { + return ParseFailed(line, "Invalid SCTP max message size.", error); + } + return true; +} + +bool ParseExtmap(absl::string_view line, + RtpExtension* extmap, + SdpParseError* error) { + // RFC 5285 + // a=extmap:<value>["/"<direction>] <URI> <extensionattributes> + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + const size_t expected_min_fields = 2; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + absl::string_view uri = fields[1]; + + std::string value_direction; + if (!GetValue(fields[0], kAttributeExtmap, &value_direction, error)) { + return false; + } + std::vector<absl::string_view> sub_fields = + rtc::split(value_direction, kSdpDelimiterSlashChar); + int value = 0; + if (!GetValueFromString(line, sub_fields[0], &value, error)) { + return false; + } + + bool encrypted = false; + if (uri == RtpExtension::kEncryptHeaderExtensionsUri) { + // RFC 6904 + // a=extmap:<value["/"<direction>] urn:ietf:params:rtp-hdrext:encrypt <URI> + // <extensionattributes> + const size_t expected_min_fields_encrypted = expected_min_fields + 1; + if (fields.size() < expected_min_fields_encrypted) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields_encrypted, + error); + } + + encrypted = true; + uri = fields[2]; + if (uri == RtpExtension::kEncryptHeaderExtensionsUri) { + return ParseFailed(line, "Recursive encrypted header.", error); + } + } + + *extmap = RtpExtension(uri, value, encrypted); + return true; +} + +static void BuildSctpContentAttributes( + std::string* message, + const cricket::SctpDataContentDescription* data_desc) { + rtc::StringBuilder os; + if (data_desc->use_sctpmap()) { + // draft-ietf-mmusic-sctp-sdp-04 + // a=sctpmap:sctpmap-number protocol [streams] + rtc::StringBuilder os; + InitAttrLine(kAttributeSctpmap, &os); + os << kSdpDelimiterColon << data_desc->port() << kSdpDelimiterSpace + << kDefaultSctpmapProtocol << kSdpDelimiterSpace + << cricket::kMaxSctpStreams; + AddLine(os.str(), message); + } else { + // draft-ietf-mmusic-sctp-sdp-23 + // a=sctp-port:<port> + InitAttrLine(kAttributeSctpPort, &os); + os << kSdpDelimiterColon << data_desc->port(); + AddLine(os.str(), message); + if (data_desc->max_message_size() != kDefaultSctpMaxMessageSize) { + InitAttrLine(kAttributeMaxMessageSize, &os); + os << kSdpDelimiterColon << data_desc->max_message_size(); + AddLine(os.str(), message); + } + } +} + +void BuildIceUfragPwd(const TransportInfo* transport_info, + std::string* message) { + RTC_DCHECK(transport_info); + + rtc::StringBuilder os; + // RFC 5245 + // ice-pwd-att = "ice-pwd" ":" password + // ice-ufrag-att = "ice-ufrag" ":" ufrag + // ice-ufrag + if (!transport_info->description.ice_ufrag.empty()) { + InitAttrLine(kAttributeIceUfrag, &os); + os << kSdpDelimiterColon << transport_info->description.ice_ufrag; + AddLine(os.str(), message); + } + // ice-pwd + if (!transport_info->description.ice_pwd.empty()) { + InitAttrLine(kAttributeIcePwd, &os); + os << kSdpDelimiterColon << transport_info->description.ice_pwd; + AddLine(os.str(), message); + } +} + +void BuildDtlsFingerprintSetup(const TransportInfo* transport_info, + std::string* message) { + RTC_DCHECK(transport_info); + + rtc::StringBuilder os; + // RFC 4572 + // fingerprint-attribute = + // "fingerprint" ":" hash-func SP fingerprint + // When using max-bundle this is already included at session level. + // Insert the fingerprint attribute. + auto fingerprint = transport_info->description.identity_fingerprint.get(); + if (!fingerprint) { + return; + } + InitAttrLine(kAttributeFingerprint, &os); + os << kSdpDelimiterColon << fingerprint->algorithm << kSdpDelimiterSpace + << fingerprint->GetRfc4572Fingerprint(); + AddLine(os.str(), message); + + // Inserting setup attribute. + if (transport_info->description.connection_role != + cricket::CONNECTIONROLE_NONE) { + // Making sure we are not using "passive" mode. + cricket::ConnectionRole role = transport_info->description.connection_role; + std::string dtls_role_str; + const bool success = cricket::ConnectionRoleToString(role, &dtls_role_str); + RTC_DCHECK(success); + InitAttrLine(kAttributeSetup, &os); + os << kSdpDelimiterColon << dtls_role_str; + AddLine(os.str(), message); + } +} + +void BuildMediaLine(const cricket::MediaType media_type, + const ContentInfo* content_info, + const MediaContentDescription* media_desc, + std::string* message) { + rtc::StringBuilder os; + + // RFC 4566 + // m=<media> <port> <proto> <fmt> + // fmt is a list of payload type numbers that MAY be used in the session. + std::string type; + std::string fmt; + if (media_type == cricket::MEDIA_TYPE_VIDEO) { + type = kMediaTypeVideo; + const VideoContentDescription* video_desc = media_desc->as_video(); + for (const cricket::VideoCodec& codec : video_desc->codecs()) { + fmt.append(" "); + fmt.append(rtc::ToString(codec.id)); + } + } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { + type = kMediaTypeAudio; + const AudioContentDescription* audio_desc = media_desc->as_audio(); + for (const cricket::AudioCodec& codec : audio_desc->codecs()) { + fmt.append(" "); + fmt.append(rtc::ToString(codec.id)); + } + } else if (media_type == cricket::MEDIA_TYPE_DATA) { + type = kMediaTypeData; + const cricket::SctpDataContentDescription* sctp_data_desc = + media_desc->as_sctp(); + if (sctp_data_desc) { + fmt.append(" "); + + if (sctp_data_desc->use_sctpmap()) { + fmt.append(rtc::ToString(sctp_data_desc->port())); + } else { + fmt.append(kDefaultSctpmapProtocol); + } + } else { + RTC_DCHECK_NOTREACHED() << "Data description without SCTP"; + } + } else if (media_type == cricket::MEDIA_TYPE_UNSUPPORTED) { + const UnsupportedContentDescription* unsupported_desc = + media_desc->as_unsupported(); + type = unsupported_desc->media_type(); + } else { + RTC_DCHECK_NOTREACHED(); + } + // The fmt must never be empty. If no codecs are found, set the fmt attribute + // to 0. + if (fmt.empty()) { + fmt = " 0"; + } + + // The port number in the m line will be updated later when associated with + // the candidates. + // + // A port value of 0 indicates that the m= section is rejected. + // RFC 3264 + // To reject an offered stream, the port number in the corresponding stream in + // the answer MUST be set to zero. + // + // However, the BUNDLE draft adds a new meaning to port zero, when used along + // with a=bundle-only. + std::string port = kDummyPort; + if (content_info->rejected || content_info->bundle_only) { + port = kMediaPortRejected; + } else if (!media_desc->connection_address().IsNil()) { + port = rtc::ToString(media_desc->connection_address().port()); + } + + // Add the m and c lines. + InitLine(kLineTypeMedia, type, &os); + os << " " << port << " " << media_desc->protocol() << fmt; + AddLine(os.str(), message); +} + +void BuildMediaDescription(const ContentInfo* content_info, + const TransportInfo* transport_info, + const cricket::MediaType media_type, + const std::vector<Candidate>& candidates, + int msid_signaling, + std::string* message) { + RTC_DCHECK(message); + if (!content_info) { + return; + } + rtc::StringBuilder os; + const MediaContentDescription* media_desc = content_info->media_description(); + RTC_DCHECK(media_desc); + + // Add the m line. + BuildMediaLine(media_type, content_info, media_desc, message); + // Add the c line. + InitLine(kLineTypeConnection, kConnectionNettype, &os); + if (media_desc->connection_address().IsNil()) { + os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress; + } else if (media_desc->connection_address().family() == AF_INET) { + os << " " << kConnectionIpv4Addrtype << " " + << media_desc->connection_address().ipaddr().ToString(); + } else if (media_desc->connection_address().family() == AF_INET6) { + os << " " << kConnectionIpv6Addrtype << " " + << media_desc->connection_address().ipaddr().ToString(); + } else { + os << " " << kConnectionIpv4Addrtype << " " << kDummyAddress; + } + AddLine(os.str(), message); + + // RFC 4566 + // b=AS:<bandwidth> or + // b=TIAS:<bandwidth> + int bandwidth = media_desc->bandwidth(); + std::string bandwidth_type = media_desc->bandwidth_type(); + if (bandwidth_type == kApplicationSpecificBandwidth && bandwidth >= 1000) { + InitLine(kLineTypeSessionBandwidth, bandwidth_type, &os); + bandwidth /= 1000; + os << kSdpDelimiterColon << bandwidth; + AddLine(os.str(), message); + } else if (bandwidth_type == kTransportSpecificBandwidth && bandwidth > 0) { + InitLine(kLineTypeSessionBandwidth, bandwidth_type, &os); + os << kSdpDelimiterColon << bandwidth; + AddLine(os.str(), message); + } + + // Add the a=bundle-only line. + if (content_info->bundle_only) { + InitAttrLine(kAttributeBundleOnly, &os); + AddLine(os.str(), message); + } + + // Add the a=rtcp line. + if (cricket::IsRtpProtocol(media_desc->protocol())) { + std::string rtcp_line = GetRtcpLine(candidates); + if (!rtcp_line.empty()) { + AddLine(rtcp_line, message); + } + } + + // Build the a=candidate lines. We don't include ufrag and pwd in the + // candidates in the SDP to avoid redundancy. + BuildCandidate(candidates, false, message); + + // Use the transport_info to build the media level ice-ufrag, ice-pwd + // and DTLS fingerprint and setup attributes. + if (transport_info) { + BuildIceUfragPwd(transport_info, message); + + // draft-petithuguenin-mmusic-ice-attributes-level-03 + BuildIceOptions(transport_info->description.transport_options, message); + + // Also include the DTLS fingerprint and setup attribute if available. + BuildDtlsFingerprintSetup(transport_info, message); + } + + // RFC 3388 + // mid-attribute = "a=mid:" identification-tag + // identification-tag = token + // Use the content name as the mid identification-tag. + InitAttrLine(kAttributeMid, &os); + os << kSdpDelimiterColon << content_info->name; + AddLine(os.str(), message); + + if (cricket::IsDtlsSctp(media_desc->protocol())) { + const cricket::SctpDataContentDescription* data_desc = + media_desc->as_sctp(); + BuildSctpContentAttributes(message, data_desc); + } else if (cricket::IsRtpProtocol(media_desc->protocol())) { + BuildRtpContentAttributes(media_desc, media_type, msid_signaling, message); + } +} + +void BuildRtpContentAttributes(const MediaContentDescription* media_desc, + const cricket::MediaType media_type, + int msid_signaling, + std::string* message) { + SdpSerializer serializer; + rtc::StringBuilder os; + // RFC 8285 + // a=extmap-allow-mixed + // The attribute MUST be either on session level or media level. We support + // responding on both levels, however, we don't respond on media level if it's + // set on session level. + if (media_desc->extmap_allow_mixed_enum() == + MediaContentDescription::kMedia) { + InitAttrLine(kAttributeExtmapAllowMixed, &os); + AddLine(os.str(), message); + } + BuildRtpHeaderExtensions(media_desc->rtp_header_extensions(), message); + + // RFC 3264 + // a=sendrecv || a=sendonly || a=sendrecv || a=inactive + switch (media_desc->direction()) { + // Special case that for sdp purposes should be treated same as inactive. + case RtpTransceiverDirection::kStopped: + case RtpTransceiverDirection::kInactive: + InitAttrLine(kAttributeInactive, &os); + break; + case RtpTransceiverDirection::kSendOnly: + InitAttrLine(kAttributeSendOnly, &os); + break; + case RtpTransceiverDirection::kRecvOnly: + InitAttrLine(kAttributeRecvOnly, &os); + break; + case RtpTransceiverDirection::kSendRecv: + InitAttrLine(kAttributeSendRecv, &os); + break; + default: + RTC_DCHECK_NOTREACHED(); + InitAttrLine(kAttributeSendRecv, &os); + break; + } + AddLine(os.str(), message); + + // Specified in https://datatracker.ietf.org/doc/draft-ietf-mmusic-msid/16/ + // a=msid:<msid-id> <msid-appdata> + // The msid-id is a 1*64 token char representing the media stream id, and the + // msid-appdata is a 1*64 token char representing the track id. There is a + // line for every media stream, with a special msid-id value of "-" + // representing no streams. The value of "msid-appdata" MUST be identical for + // all lines. + if (msid_signaling & cricket::kMsidSignalingMediaSection) { + const StreamParamsVec& streams = media_desc->streams(); + if (streams.size() == 1u) { + const StreamParams& track = streams[0]; + std::vector<std::string> stream_ids = track.stream_ids(); + if (stream_ids.empty()) { + stream_ids.push_back(kNoStreamMsid); + } + for (const std::string& stream_id : stream_ids) { + InitAttrLine(kAttributeMsid, &os); + os << kSdpDelimiterColon << stream_id << kSdpDelimiterSpace << track.id; + AddLine(os.str(), message); + } + } else if (streams.size() > 1u) { + RTC_LOG(LS_WARNING) + << "Trying to serialize Unified Plan SDP with more than " + "one track in a media section. Omitting 'a=msid'."; + } + } + + // RFC 5761 + // a=rtcp-mux + if (media_desc->rtcp_mux()) { + InitAttrLine(kAttributeRtcpMux, &os); + AddLine(os.str(), message); + } + + // RFC 5506 + // a=rtcp-rsize + if (media_desc->rtcp_reduced_size()) { + InitAttrLine(kAttributeRtcpReducedSize, &os); + AddLine(os.str(), message); + } + + if (media_desc->conference_mode()) { + InitAttrLine(kAttributeXGoogleFlag, &os); + os << kSdpDelimiterColon << kValueConference; + AddLine(os.str(), message); + } + + if (media_desc->remote_estimate()) { + InitAttrLine(kAttributeRtcpRemoteEstimate, &os); + AddLine(os.str(), message); + } + + // RFC 4568 + // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] + for (const CryptoParams& crypto_params : media_desc->cryptos()) { + InitAttrLine(kAttributeCrypto, &os); + os << kSdpDelimiterColon << crypto_params.tag << " " + << crypto_params.cipher_suite << " " << crypto_params.key_params; + if (!crypto_params.session_params.empty()) { + os << " " << crypto_params.session_params; + } + AddLine(os.str(), message); + } + + // RFC 4566 + // a=rtpmap:<payload type> <encoding name>/<clock rate> + // [/<encodingparameters>] + BuildRtpmap(media_desc, media_type, message); + + for (const StreamParams& track : media_desc->streams()) { + // Build the ssrc-group lines. + for (const SsrcGroup& ssrc_group : track.ssrc_groups) { + // RFC 5576 + // a=ssrc-group:<semantics> <ssrc-id> ... + if (ssrc_group.ssrcs.empty()) { + continue; + } + InitAttrLine(kAttributeSsrcGroup, &os); + os << kSdpDelimiterColon << ssrc_group.semantics; + for (uint32_t ssrc : ssrc_group.ssrcs) { + os << kSdpDelimiterSpace << rtc::ToString(ssrc); + } + AddLine(os.str(), message); + } + // Build the ssrc lines for each ssrc. + for (uint32_t ssrc : track.ssrcs) { + // RFC 5576 + // a=ssrc:<ssrc-id> cname:<value> + AddSsrcLine(ssrc, kSsrcAttributeCname, track.cname, message); + + if (msid_signaling & cricket::kMsidSignalingSsrcAttribute) { + // draft-alvestrand-mmusic-msid-00 + // a=ssrc:<ssrc-id> msid:identifier [appdata] + // The appdata consists of the "id" attribute of a MediaStreamTrack, + // which corresponds to the "id" attribute of StreamParams. + // Since a=ssrc msid signaling is used in Plan B SDP semantics, and + // multiple stream ids are not supported for Plan B, we are only adding + // a line for the first media stream id here. + const std::string& track_stream_id = track.first_stream_id(); + // We use a special msid-id value of "-" to represent no streams, + // for Unified Plan compatibility. Plan B will always have a + // track_stream_id. + const std::string& stream_id = + track_stream_id.empty() ? kNoStreamMsid : track_stream_id; + InitAttrLine(kAttributeSsrc, &os); + os << kSdpDelimiterColon << ssrc << kSdpDelimiterSpace + << kSsrcAttributeMsid << kSdpDelimiterColon << stream_id + << kSdpDelimiterSpace << track.id; + AddLine(os.str(), message); + } + } + + // Build the rid lines for each layer of the track + for (const RidDescription& rid_description : track.rids()) { + InitAttrLine(kAttributeRid, &os); + os << kSdpDelimiterColon + << serializer.SerializeRidDescription(rid_description); + AddLine(os.str(), message); + } + } + + for (const RidDescription& rid_description : media_desc->receive_rids()) { + InitAttrLine(kAttributeRid, &os); + os << kSdpDelimiterColon + << serializer.SerializeRidDescription(rid_description); + AddLine(os.str(), message); + } + + // Simulcast (a=simulcast) + // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1 + if (media_desc->HasSimulcast()) { + const auto& simulcast = media_desc->simulcast_description(); + InitAttrLine(kAttributeSimulcast, &os); + os << kSdpDelimiterColon + << serializer.SerializeSimulcastDescription(simulcast); + AddLine(os.str(), message); + } +} + +void BuildRtpHeaderExtensions(const RtpHeaderExtensions& extensions, + std::string* message) { + rtc::StringBuilder os; + + // RFC 8285 + // a=extmap:<value>["/"<direction>] <URI> <extensionattributes> + // The definitions MUST be either all session level or all media level. This + // implementation uses all media level. + for (const RtpExtension& extension : extensions) { + InitAttrLine(kAttributeExtmap, &os); + os << kSdpDelimiterColon << extension.id; + if (extension.encrypt) { + os << kSdpDelimiterSpace << RtpExtension::kEncryptHeaderExtensionsUri; + } + os << kSdpDelimiterSpace << extension.uri; + AddLine(os.str(), message); + } +} + +void WriteFmtpHeader(int payload_type, rtc::StringBuilder* os) { + // fmtp header: a=fmtp:`payload_type` <parameters> + // Add a=fmtp + InitAttrLine(kAttributeFmtp, os); + // Add :`payload_type` + *os << kSdpDelimiterColon << payload_type; +} + +void WritePacketizationHeader(int payload_type, rtc::StringBuilder* os) { + // packetization header: a=packetization:`payload_type` <packetization_format> + // Add a=packetization + InitAttrLine(kAttributePacketization, os); + // Add :`payload_type` + *os << kSdpDelimiterColon << payload_type; +} + +void WriteRtcpFbHeader(int payload_type, rtc::StringBuilder* os) { + // rtcp-fb header: a=rtcp-fb:`payload_type` + // <parameters>/<ccm <ccm_parameters>> + // Add a=rtcp-fb + InitAttrLine(kAttributeRtcpFb, os); + // Add : + *os << kSdpDelimiterColon; + if (payload_type == kWildcardPayloadType) { + *os << "*"; + } else { + *os << payload_type; + } +} + +void WriteFmtpParameter(absl::string_view parameter_name, + absl::string_view parameter_value, + rtc::StringBuilder* os) { + if (parameter_name.empty()) { + // RFC 2198 and RFC 4733 don't use key-value pairs. + *os << parameter_value; + } else { + // fmtp parameters: `parameter_name`=`parameter_value` + *os << parameter_name << kSdpDelimiterEqual << parameter_value; + } +} + +bool IsFmtpParam(absl::string_view name) { + // RFC 4855, section 3 specifies the mapping of media format parameters to SDP + // parameters. Only ptime, maxptime, channels and rate are placed outside of + // the fmtp line. In WebRTC, channels and rate are already handled separately + // and thus not included in the CodecParameterMap. + return name != kCodecParamPTime && name != kCodecParamMaxPTime; +} + +bool WriteFmtpParameters(const cricket::CodecParameterMap& parameters, + rtc::StringBuilder* os) { + bool empty = true; + const char* delimiter = ""; // No delimiter before first parameter. + for (const auto& entry : parameters) { + const std::string& key = entry.first; + const std::string& value = entry.second; + + if (IsFmtpParam(key)) { + *os << delimiter; + // A semicolon before each subsequent parameter. + delimiter = kSdpDelimiterSemicolon; + WriteFmtpParameter(key, value, os); + empty = false; + } + } + + return !empty; +} + +template <class T> +void AddFmtpLine(const T& codec, std::string* message) { + rtc::StringBuilder os; + WriteFmtpHeader(codec.id, &os); + os << kSdpDelimiterSpace; + // Create FMTP line and check that it's nonempty. + if (WriteFmtpParameters(codec.params, &os)) { + AddLine(os.str(), message); + } + return; +} + +template <class T> +void AddPacketizationLine(const T& codec, std::string* message) { + if (!codec.packetization) { + return; + } + rtc::StringBuilder os; + WritePacketizationHeader(codec.id, &os); + os << " " << *codec.packetization; + AddLine(os.str(), message); +} + +template <class T> +void AddRtcpFbLines(const T& codec, std::string* message) { + for (const cricket::FeedbackParam& param : codec.feedback_params.params()) { + rtc::StringBuilder os; + WriteRtcpFbHeader(codec.id, &os); + os << " " << param.id(); + if (!param.param().empty()) { + os << " " << param.param(); + } + AddLine(os.str(), message); + } +} + +bool GetMinValue(const std::vector<int>& values, int* value) { + if (values.empty()) { + return false; + } + auto it = absl::c_min_element(values); + *value = *it; + return true; +} + +bool GetParameter(const std::string& name, + const cricket::CodecParameterMap& params, + int* value) { + std::map<std::string, std::string>::const_iterator found = params.find(name); + if (found == params.end()) { + return false; + } + if (!rtc::FromString(found->second, value)) { + return false; + } + return true; +} + +void BuildRtpmap(const MediaContentDescription* media_desc, + const cricket::MediaType media_type, + std::string* message) { + RTC_DCHECK(message != NULL); + RTC_DCHECK(media_desc != NULL); + rtc::StringBuilder os; + if (media_type == cricket::MEDIA_TYPE_VIDEO) { + for (const cricket::VideoCodec& codec : media_desc->as_video()->codecs()) { + // RFC 4566 + // a=rtpmap:<payload type> <encoding name>/<clock rate> + // [/<encodingparameters>] + if (codec.id != kWildcardPayloadType) { + InitAttrLine(kAttributeRtpmap, &os); + os << kSdpDelimiterColon << codec.id << " " << codec.name << "/" + << cricket::kVideoCodecClockrate; + AddLine(os.str(), message); + } + AddPacketizationLine(codec, message); + AddRtcpFbLines(codec, message); + AddFmtpLine(codec, message); + } + } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { + std::vector<int> ptimes; + std::vector<int> maxptimes; + int max_minptime = 0; + for (const cricket::AudioCodec& codec : media_desc->as_audio()->codecs()) { + RTC_DCHECK(!codec.name.empty()); + // RFC 4566 + // a=rtpmap:<payload type> <encoding name>/<clock rate> + // [/<encodingparameters>] + InitAttrLine(kAttributeRtpmap, &os); + os << kSdpDelimiterColon << codec.id << " "; + os << codec.name << "/" << codec.clockrate; + if (codec.channels != 1) { + os << "/" << codec.channels; + } + AddLine(os.str(), message); + AddRtcpFbLines(codec, message); + AddFmtpLine(codec, message); + int minptime = 0; + if (GetParameter(kCodecParamMinPTime, codec.params, &minptime)) { + max_minptime = std::max(minptime, max_minptime); + } + int ptime; + if (GetParameter(kCodecParamPTime, codec.params, &ptime)) { + ptimes.push_back(ptime); + } + int maxptime; + if (GetParameter(kCodecParamMaxPTime, codec.params, &maxptime)) { + maxptimes.push_back(maxptime); + } + } + // Populate the maxptime attribute with the smallest maxptime of all codecs + // under the same m-line. + int min_maxptime = INT_MAX; + if (GetMinValue(maxptimes, &min_maxptime)) { + AddAttributeLine(kCodecParamMaxPTime, min_maxptime, message); + } + RTC_DCHECK_GE(min_maxptime, max_minptime); + // Populate the ptime attribute with the smallest ptime or the largest + // minptime, whichever is the largest, for all codecs under the same m-line. + int ptime = INT_MAX; + if (GetMinValue(ptimes, &ptime)) { + ptime = std::min(ptime, min_maxptime); + ptime = std::max(ptime, max_minptime); + AddAttributeLine(kCodecParamPTime, ptime, message); + } + } +} + +void BuildCandidate(const std::vector<Candidate>& candidates, + bool include_ufrag, + std::string* message) { + rtc::StringBuilder os; + + for (const Candidate& candidate : candidates) { + // RFC 5245 + // a=candidate:<foundation> <component-id> <transport> <priority> + // <connection-address> <port> typ <candidate-types> + // [raddr <connection-address>] [rport <port>] + // *(SP extension-att-name SP extension-att-value) + std::string type; + // Map the cricket candidate type to "host" / "srflx" / "prflx" / "relay" + if (candidate.type() == cricket::LOCAL_PORT_TYPE) { + type = kCandidateHost; + } else if (candidate.type() == cricket::STUN_PORT_TYPE) { + type = kCandidateSrflx; + } else if (candidate.type() == cricket::RELAY_PORT_TYPE) { + type = kCandidateRelay; + } else if (candidate.type() == cricket::PRFLX_PORT_TYPE) { + type = kCandidatePrflx; + // Peer reflexive candidate may be signaled for being removed. + } else { + RTC_DCHECK_NOTREACHED(); + // Never write out candidates if we don't know the type. + continue; + } + + InitAttrLine(kAttributeCandidate, &os); + os << kSdpDelimiterColon << candidate.foundation() << " " + << candidate.component() << " " << candidate.protocol() << " " + << candidate.priority() << " " + << (candidate.address().ipaddr().IsNil() + ? candidate.address().hostname() + : candidate.address().ipaddr().ToString()) + << " " << candidate.address().PortAsString() << " " + << kAttributeCandidateTyp << " " << type << " "; + + // Related address + if (!candidate.related_address().IsNil()) { + os << kAttributeCandidateRaddr << " " + << candidate.related_address().ipaddr().ToString() << " " + << kAttributeCandidateRport << " " + << candidate.related_address().PortAsString() << " "; + } + + // Note that we allow the tcptype to be missing, for backwards + // compatibility; the implementation treats this as a passive candidate. + // TODO(bugs.webrtc.org/11466): Treat a missing tcptype as an error? + if (candidate.protocol() == cricket::TCP_PROTOCOL_NAME && + !candidate.tcptype().empty()) { + os << kTcpCandidateType << " " << candidate.tcptype() << " "; + } + + // Extensions + os << kAttributeCandidateGeneration << " " << candidate.generation(); + if (include_ufrag && !candidate.username().empty()) { + os << " " << kAttributeCandidateUfrag << " " << candidate.username(); + } + if (candidate.network_id() > 0) { + os << " " << kAttributeCandidateNetworkId << " " + << candidate.network_id(); + } + if (candidate.network_cost() > 0) { + os << " " << kAttributeCandidateNetworkCost << " " + << candidate.network_cost(); + } + + AddLine(os.str(), message); + } +} + +void BuildIceOptions(const std::vector<std::string>& transport_options, + std::string* message) { + if (!transport_options.empty()) { + rtc::StringBuilder os; + InitAttrLine(kAttributeIceOption, &os); + os << kSdpDelimiterColon << transport_options[0]; + for (size_t i = 1; i < transport_options.size(); ++i) { + os << kSdpDelimiterSpace << transport_options[i]; + } + AddLine(os.str(), message); + } +} + +bool ParseConnectionData(absl::string_view line, + rtc::SocketAddress* addr, + SdpParseError* error) { + // Parse the line from left to right. + std::string token; + std::string rightpart; + // RFC 4566 + // c=<nettype> <addrtype> <connection-address> + // Skip the "c=" + if (!rtc::tokenize_first(line, kSdpDelimiterEqualChar, &token, &rightpart)) { + return ParseFailed(line, "Failed to parse the network type.", error); + } + + // Extract and verify the <nettype> + if (!rtc::tokenize_first(rightpart, kSdpDelimiterSpaceChar, &token, + &rightpart) || + token != kConnectionNettype) { + return ParseFailed(line, + "Failed to parse the connection data. The network type " + "is not currently supported.", + error); + } + + // Extract the "<addrtype>" and "<connection-address>". + if (!rtc::tokenize_first(rightpart, kSdpDelimiterSpaceChar, &token, + &rightpart)) { + return ParseFailed(line, "Failed to parse the address type.", error); + } + + // The rightpart part should be the IP address without the slash which is used + // for multicast. + if (rightpart.find('/') != std::string::npos) { + return ParseFailed(line, + "Failed to parse the connection data. Multicast is not " + "currently supported.", + error); + } + addr->SetIP(rightpart); + + // Verify that the addrtype matches the type of the parsed address. + if ((addr->family() == AF_INET && token != "IP4") || + (addr->family() == AF_INET6 && token != "IP6")) { + addr->Clear(); + return ParseFailed( + line, + "Failed to parse the connection data. The address type is mismatching.", + error); + } + return true; +} + +bool ParseSessionDescription(absl::string_view message, + size_t* pos, + std::string* session_id, + std::string* session_version, + TransportDescription* session_td, + RtpHeaderExtensions* session_extmaps, + rtc::SocketAddress* connection_addr, + cricket::SessionDescription* desc, + SdpParseError* error) { + absl::optional<absl::string_view> line; + + desc->set_msid_supported(false); + desc->set_extmap_allow_mixed(false); + // RFC 4566 + // v= (protocol version) + line = GetLineWithType(message, pos, kLineTypeVersion); + if (!line) { + return ParseFailedExpectLine(message, *pos, kLineTypeVersion, std::string(), + error); + } + // RFC 4566 + // o=<username> <sess-id> <sess-version> <nettype> <addrtype> + // <unicast-address> + line = GetLineWithType(message, pos, kLineTypeOrigin); + if (!line) { + return ParseFailedExpectLine(message, *pos, kLineTypeOrigin, std::string(), + error); + } + std::vector<absl::string_view> fields = + rtc::split(line->substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + const size_t expected_fields = 6; + if (fields.size() != expected_fields) { + return ParseFailedExpectFieldNum(*line, expected_fields, error); + } + *session_id = std::string(fields[1]); + *session_version = std::string(fields[2]); + + // RFC 4566 + // s= (session name) + line = GetLineWithType(message, pos, kLineTypeSessionName); + if (!line) { + return ParseFailedExpectLine(message, *pos, kLineTypeSessionName, + std::string(), error); + } + + // optional lines + // Those are the optional lines, so shouldn't return false if not present. + // RFC 4566 + // i=* (session information) + GetLineWithType(message, pos, kLineTypeSessionInfo); + + // RFC 4566 + // u=* (URI of description) + GetLineWithType(message, pos, kLineTypeSessionUri); + + // RFC 4566 + // e=* (email address) + GetLineWithType(message, pos, kLineTypeSessionEmail); + + // RFC 4566 + // p=* (phone number) + GetLineWithType(message, pos, kLineTypeSessionPhone); + + // RFC 4566 + // c=* (connection information -- not required if included in + // all media) + if (absl::optional<absl::string_view> cline = + GetLineWithType(message, pos, kLineTypeConnection); + cline.has_value()) { + if (!ParseConnectionData(*cline, connection_addr, error)) { + return false; + } + } + + // RFC 4566 + // b=* (zero or more bandwidth information lines) + while (GetLineWithType(message, pos, kLineTypeSessionBandwidth).has_value()) { + // By pass zero or more b lines. + } + + // RFC 4566 + // One or more time descriptions ("t=" and "r=" lines; see below) + // t= (time the session is active) + // r=* (zero or more repeat times) + // Ensure there's at least one time description + if (!GetLineWithType(message, pos, kLineTypeTiming).has_value()) { + return ParseFailedExpectLine(message, *pos, kLineTypeTiming, std::string(), + error); + } + + while (GetLineWithType(message, pos, kLineTypeRepeatTimes).has_value()) { + // By pass zero or more r lines. + } + + // Go through the rest of the time descriptions + while (GetLineWithType(message, pos, kLineTypeTiming).has_value()) { + while (GetLineWithType(message, pos, kLineTypeRepeatTimes).has_value()) { + // By pass zero or more r lines. + } + } + + // RFC 4566 + // z=* (time zone adjustments) + GetLineWithType(message, pos, kLineTypeTimeZone); + + // RFC 4566 + // k=* (encryption key) + GetLineWithType(message, pos, kLineTypeEncryptionKey); + + // RFC 4566 + // a=* (zero or more session attribute lines) + while (absl::optional<absl::string_view> aline = + GetLineWithType(message, pos, kLineTypeAttributes)) { + if (HasAttribute(*aline, kAttributeGroup)) { + if (!ParseGroupAttribute(*aline, desc, error)) { + return false; + } + } else if (HasAttribute(*aline, kAttributeIceUfrag)) { + if (!GetValue(*aline, kAttributeIceUfrag, &(session_td->ice_ufrag), + error)) { + return false; + } + } else if (HasAttribute(*aline, kAttributeIcePwd)) { + if (!GetValue(*aline, kAttributeIcePwd, &(session_td->ice_pwd), error)) { + return false; + } + } else if (HasAttribute(*aline, kAttributeIceLite)) { + session_td->ice_mode = cricket::ICEMODE_LITE; + } else if (HasAttribute(*aline, kAttributeIceOption)) { + if (!ParseIceOptions(*aline, &(session_td->transport_options), error)) { + return false; + } + } else if (HasAttribute(*aline, kAttributeFingerprint)) { + if (session_td->identity_fingerprint.get()) { + return ParseFailed( + *aline, + "Can't have multiple fingerprint attributes at the same level.", + error); + } + std::unique_ptr<rtc::SSLFingerprint> fingerprint; + if (!ParseFingerprintAttribute(*aline, &fingerprint, error)) { + return false; + } + session_td->identity_fingerprint = std::move(fingerprint); + } else if (HasAttribute(*aline, kAttributeSetup)) { + if (!ParseDtlsSetup(*aline, &(session_td->connection_role), error)) { + return false; + } + } else if (HasAttribute(*aline, kAttributeMsidSemantics)) { + std::string semantics; + if (!GetValue(*aline, kAttributeMsidSemantics, &semantics, error)) { + return false; + } + desc->set_msid_supported( + CaseInsensitiveFind(semantics, kMediaStreamSemantic)); + } else if (HasAttribute(*aline, kAttributeExtmapAllowMixed)) { + desc->set_extmap_allow_mixed(true); + } else if (HasAttribute(*aline, kAttributeExtmap)) { + RtpExtension extmap; + if (!ParseExtmap(*aline, &extmap, error)) { + return false; + } + session_extmaps->push_back(extmap); + } + } + return true; +} + +bool ParseGroupAttribute(absl::string_view line, + cricket::SessionDescription* desc, + SdpParseError* error) { + RTC_DCHECK(desc != NULL); + + // RFC 5888 and draft-holmberg-mmusic-sdp-bundle-negotiation-00 + // a=group:BUNDLE video voice + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + std::string semantics; + if (!GetValue(fields[0], kAttributeGroup, &semantics, error)) { + return false; + } + cricket::ContentGroup group(semantics); + for (size_t i = 1; i < fields.size(); ++i) { + group.AddContentName(fields[i]); + } + desc->AddGroup(group); + return true; +} + +static bool ParseFingerprintAttribute( + absl::string_view line, + std::unique_ptr<rtc::SSLFingerprint>* fingerprint, + SdpParseError* error) { + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + const size_t expected_fields = 2; + if (fields.size() != expected_fields) { + return ParseFailedExpectFieldNum(line, expected_fields, error); + } + + // The first field here is "fingerprint:<hash>. + std::string algorithm; + if (!GetValue(fields[0], kAttributeFingerprint, &algorithm, error)) { + return false; + } + + // Downcase the algorithm. Note that we don't need to downcase the + // fingerprint because hex_decode can handle upper-case. + absl::c_transform(algorithm, algorithm.begin(), ::tolower); + + // The second field is the digest value. De-hexify it. + *fingerprint = + rtc::SSLFingerprint::CreateUniqueFromRfc4572(algorithm, fields[1]); + if (!*fingerprint) { + return ParseFailed(line, "Failed to create fingerprint from the digest.", + error); + } + + return true; +} + +static bool ParseDtlsSetup(absl::string_view line, + cricket::ConnectionRole* role_ptr, + SdpParseError* error) { + // setup-attr = "a=setup:" role + // role = "active" / "passive" / "actpass" / "holdconn" + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterColonChar); + const size_t expected_fields = 2; + if (fields.size() != expected_fields) { + return ParseFailedExpectFieldNum(line, expected_fields, error); + } + if (absl::optional<cricket::ConnectionRole> role = + cricket::StringToConnectionRole(fields[1]); + role.has_value()) { + *role_ptr = *role; + return true; + } + return ParseFailed(line, "Invalid attribute value.", error); +} + +static bool ParseMsidAttribute(absl::string_view line, + std::vector<std::string>* stream_ids, + std::string* track_id, + SdpParseError* error) { + // https://datatracker.ietf.org/doc/rfc8830/ + // a=msid:<msid-value> + // msid-value = msid-id [ SP msid-appdata ] + // msid-id = 1*64token-char ; see RFC 4566 + // msid-appdata = 1*64token-char ; see RFC 4566 + // Note that JSEP stipulates not sending msid-appdata so + // a=msid:<stream id> <track id> + // is supported for backward compability reasons only. + std::vector<std::string> fields; + size_t num_fields = rtc::tokenize(line.substr(kLinePrefixLength), + kSdpDelimiterSpaceChar, &fields); + if (num_fields < 1 || num_fields > 2) { + return ParseFailed(line, "Expected a stream ID and optionally a track ID", + error); + } + if (num_fields == 1) { + if (line.back() == kSdpDelimiterSpaceChar) { + return ParseFailed(line, "Missing track ID in msid attribute.", error); + } + if (!track_id->empty()) { + fields.push_back(*track_id); + } else { + // Ending with an empty string track will cause a random track id + // to be generated later in the process. + fields.push_back(""); + } + } + RTC_DCHECK_EQ(fields.size(), 2); + + // All track ids should be the same within an m section in a Unified Plan SDP. + if (!track_id->empty() && track_id->compare(fields[1]) != 0) { + return ParseFailed( + line, "Two different track IDs in msid attribute in one m= section", + error); + } + *track_id = fields[1]; + + // msid:<msid-id> + std::string new_stream_id; + if (!GetValue(fields[0], kAttributeMsid, &new_stream_id, error)) { + return false; + } + if (new_stream_id.empty()) { + return ParseFailed(line, "Missing stream ID in msid attribute.", error); + } + // The special value "-" indicates "no MediaStream". + if (new_stream_id.compare(kNoStreamMsid) != 0 && + !absl::c_any_of(*stream_ids, + [&new_stream_id](const std::string& existing_stream_id) { + return new_stream_id == existing_stream_id; + })) { + stream_ids->push_back(new_stream_id); + } + return true; +} + +static void RemoveInvalidRidDescriptions(const std::vector<int>& payload_types, + std::vector<RidDescription>* rids) { + RTC_DCHECK(rids); + std::set<std::string> to_remove; + std::set<std::string> unique_rids; + + // Check the rids to see which ones should be removed. + for (RidDescription& rid : *rids) { + // In the case of a duplicate, the entire "a=rid" line, and all "a=rid" + // lines with rid-ids that duplicate this line, are discarded and MUST NOT + // be included in the SDP Answer. + auto pair = unique_rids.insert(rid.rid); + // Insert will "fail" if element already exists. + if (!pair.second) { + to_remove.insert(rid.rid); + continue; + } + + // If the "a=rid" line contains a "pt=", the list of payload types + // is verified against the list of valid payload types for the media + // section (that is, those listed on the "m=" line). Any PT missing + // from the "m=" line is discarded from the set of values in the + // "pt=". If no values are left in the "pt=" parameter after this + // processing, then the "a=rid" line is discarded. + if (rid.payload_types.empty()) { + // If formats were not specified, rid should not be removed. + continue; + } + + // Note: Spec does not mention how to handle duplicate formats. + // Media section does not handle duplicates either. + std::set<int> removed_formats; + for (int payload_type : rid.payload_types) { + if (!absl::c_linear_search(payload_types, payload_type)) { + removed_formats.insert(payload_type); + } + } + + rid.payload_types.erase( + std::remove_if(rid.payload_types.begin(), rid.payload_types.end(), + [&removed_formats](int format) { + return removed_formats.count(format) > 0; + }), + rid.payload_types.end()); + + // If all formats were removed then remove the rid alogether. + if (rid.payload_types.empty()) { + to_remove.insert(rid.rid); + } + } + + // Remove every rid description that appears in the to_remove list. + if (!to_remove.empty()) { + rids->erase(std::remove_if(rids->begin(), rids->end(), + [&to_remove](const RidDescription& rid) { + return to_remove.count(rid.rid) > 0; + }), + rids->end()); + } +} + +// Create a new list (because SimulcastLayerList is immutable) without any +// layers that have a rid in the to_remove list. +// If a group of alternatives is empty after removing layers, the group should +// be removed altogether. +static SimulcastLayerList RemoveRidsFromSimulcastLayerList( + const std::set<std::string>& to_remove, + const SimulcastLayerList& layers) { + SimulcastLayerList result; + for (const std::vector<SimulcastLayer>& vector : layers) { + std::vector<SimulcastLayer> new_layers; + for (const SimulcastLayer& layer : vector) { + if (to_remove.find(layer.rid) == to_remove.end()) { + new_layers.push_back(layer); + } + } + // If all layers were removed, do not add an entry. + if (!new_layers.empty()) { + result.AddLayerWithAlternatives(new_layers); + } + } + + return result; +} + +// Will remove Simulcast Layers if: +// 1. They appear in both send and receive directions. +// 2. They do not appear in the list of `valid_rids`. +static void RemoveInvalidRidsFromSimulcast( + const std::vector<RidDescription>& valid_rids, + SimulcastDescription* simulcast) { + RTC_DCHECK(simulcast); + std::set<std::string> to_remove; + std::vector<SimulcastLayer> all_send_layers = + simulcast->send_layers().GetAllLayers(); + std::vector<SimulcastLayer> all_receive_layers = + simulcast->receive_layers().GetAllLayers(); + + // If a rid appears in both send and receive directions, remove it from both. + // This algorithm runs in O(n^2) time, but for small n (as is the case with + // simulcast layers) it should still perform well. + for (const SimulcastLayer& send_layer : all_send_layers) { + if (absl::c_any_of(all_receive_layers, + [&send_layer](const SimulcastLayer& layer) { + return layer.rid == send_layer.rid; + })) { + to_remove.insert(send_layer.rid); + } + } + + // Add any rid that is not in the valid list to the remove set. + for (const SimulcastLayer& send_layer : all_send_layers) { + if (absl::c_none_of(valid_rids, [&send_layer](const RidDescription& rid) { + return send_layer.rid == rid.rid && + rid.direction == cricket::RidDirection::kSend; + })) { + to_remove.insert(send_layer.rid); + } + } + + // Add any rid that is not in the valid list to the remove set. + for (const SimulcastLayer& receive_layer : all_receive_layers) { + if (absl::c_none_of( + valid_rids, [&receive_layer](const RidDescription& rid) { + return receive_layer.rid == rid.rid && + rid.direction == cricket::RidDirection::kReceive; + })) { + to_remove.insert(receive_layer.rid); + } + } + + simulcast->send_layers() = + RemoveRidsFromSimulcastLayerList(to_remove, simulcast->send_layers()); + simulcast->receive_layers() = + RemoveRidsFromSimulcastLayerList(to_remove, simulcast->receive_layers()); +} + +// RFC 3551 +// PT encoding media type clock rate channels +// name (Hz) +// 0 PCMU A 8,000 1 +// 1 reserved A +// 2 reserved A +// 3 GSM A 8,000 1 +// 4 G723 A 8,000 1 +// 5 DVI4 A 8,000 1 +// 6 DVI4 A 16,000 1 +// 7 LPC A 8,000 1 +// 8 PCMA A 8,000 1 +// 9 G722 A 8,000 1 +// 10 L16 A 44,100 2 +// 11 L16 A 44,100 1 +// 12 QCELP A 8,000 1 +// 13 CN A 8,000 1 +// 14 MPA A 90,000 (see text) +// 15 G728 A 8,000 1 +// 16 DVI4 A 11,025 1 +// 17 DVI4 A 22,050 1 +// 18 G729 A 8,000 1 +struct StaticPayloadAudioCodec { + const char* name; + int clockrate; + size_t channels; +}; +static const StaticPayloadAudioCodec kStaticPayloadAudioCodecs[] = { + {"PCMU", 8000, 1}, {"reserved", 0, 0}, {"reserved", 0, 0}, + {"GSM", 8000, 1}, {"G723", 8000, 1}, {"DVI4", 8000, 1}, + {"DVI4", 16000, 1}, {"LPC", 8000, 1}, {"PCMA", 8000, 1}, + {"G722", 8000, 1}, {"L16", 44100, 2}, {"L16", 44100, 1}, + {"QCELP", 8000, 1}, {"CN", 8000, 1}, {"MPA", 90000, 1}, + {"G728", 8000, 1}, {"DVI4", 11025, 1}, {"DVI4", 22050, 1}, + {"G729", 8000, 1}, +}; + +void MaybeCreateStaticPayloadAudioCodecs(const std::vector<int>& fmts, + AudioContentDescription* media_desc) { + if (!media_desc) { + return; + } + RTC_DCHECK(media_desc->codecs().empty()); + for (int payload_type : fmts) { + if (!media_desc->HasCodec(payload_type) && payload_type >= 0 && + static_cast<uint32_t>(payload_type) < + arraysize(kStaticPayloadAudioCodecs)) { + std::string encoding_name = kStaticPayloadAudioCodecs[payload_type].name; + int clock_rate = kStaticPayloadAudioCodecs[payload_type].clockrate; + size_t channels = kStaticPayloadAudioCodecs[payload_type].channels; + media_desc->AddCodec(cricket::AudioCodec(payload_type, encoding_name, + clock_rate, 0, channels)); + } + } +} + +template <class C> +static std::unique_ptr<C> ParseContentDescription( + absl::string_view message, + const cricket::MediaType media_type, + int mline_index, + absl::string_view protocol, + const std::vector<int>& payload_types, + size_t* pos, + std::string* content_name, + bool* bundle_only, + int* msid_signaling, + TransportDescription* transport, + std::vector<std::unique_ptr<JsepIceCandidate>>* candidates, + webrtc::SdpParseError* error) { + auto media_desc = std::make_unique<C>(); + media_desc->set_extmap_allow_mixed_enum(MediaContentDescription::kNo); + if (!ParseContent(message, media_type, mline_index, protocol, payload_types, + pos, content_name, bundle_only, msid_signaling, + media_desc.get(), transport, candidates, error)) { + return nullptr; + } + // Sort the codecs according to the m-line fmt list. + std::unordered_map<int, int> payload_type_preferences; + // "size + 1" so that the lowest preference payload type has a preference of + // 1, which is greater than the default (0) for payload types not in the fmt + // list. + int preference = static_cast<int>(payload_types.size() + 1); + for (int pt : payload_types) { + payload_type_preferences[pt] = preference--; + } + std::vector<typename C::CodecType> codecs = media_desc->codecs(); + absl::c_sort( + codecs, [&payload_type_preferences](const typename C::CodecType& a, + const typename C::CodecType& b) { + return payload_type_preferences[a.id] > payload_type_preferences[b.id]; + }); + media_desc->set_codecs(codecs); + return media_desc; +} + +bool ParseMediaDescription( + absl::string_view message, + const TransportDescription& session_td, + const RtpHeaderExtensions& session_extmaps, + size_t* pos, + const rtc::SocketAddress& session_connection_addr, + cricket::SessionDescription* desc, + std::vector<std::unique_ptr<JsepIceCandidate>>* candidates, + SdpParseError* error) { + RTC_DCHECK(desc != NULL); + int mline_index = -1; + int msid_signaling = 0; + + // Zero or more media descriptions + // RFC 4566 + // m=<media> <port> <proto> <fmt> + while (absl::optional<absl::string_view> mline = + GetLineWithType(message, pos, kLineTypeMedia)) { + ++mline_index; + + std::vector<absl::string_view> fields = + rtc::split(mline->substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + + const size_t expected_min_fields = 4; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(*mline, expected_min_fields, error); + } + bool port_rejected = false; + // RFC 3264 + // To reject an offered stream, the port number in the corresponding stream + // in the answer MUST be set to zero. + if (fields[1] == kMediaPortRejected) { + port_rejected = true; + } + + int port = 0; + if (!rtc::FromString<int>(fields[1], &port) || !IsValidPort(port)) { + return ParseFailed(*mline, "The port number is invalid", error); + } + absl::string_view protocol = fields[2]; + + // <fmt> + std::vector<int> payload_types; + if (cricket::IsRtpProtocol(protocol)) { + for (size_t j = 3; j < fields.size(); ++j) { + int pl = 0; + if (!GetPayloadTypeFromString(*mline, fields[j], &pl, error)) { + return false; + } + payload_types.push_back(pl); + } + } + + // Make a temporary TransportDescription based on `session_td`. + // Some of this gets overwritten by ParseContent. + TransportDescription transport( + session_td.transport_options, session_td.ice_ufrag, session_td.ice_pwd, + session_td.ice_mode, session_td.connection_role, + session_td.identity_fingerprint.get()); + + std::unique_ptr<MediaContentDescription> content; + std::string content_name; + bool bundle_only = false; + int section_msid_signaling = 0; + absl::string_view media_type = fields[0]; + if ((media_type == kMediaTypeVideo || media_type == kMediaTypeAudio) && + !cricket::IsRtpProtocol(protocol)) { + return ParseFailed(*mline, "Unsupported protocol for media type", error); + } + if (media_type == kMediaTypeVideo) { + content = ParseContentDescription<VideoContentDescription>( + message, cricket::MEDIA_TYPE_VIDEO, mline_index, protocol, + payload_types, pos, &content_name, &bundle_only, + §ion_msid_signaling, &transport, candidates, error); + } else if (media_type == kMediaTypeAudio) { + content = ParseContentDescription<AudioContentDescription>( + message, cricket::MEDIA_TYPE_AUDIO, mline_index, protocol, + payload_types, pos, &content_name, &bundle_only, + §ion_msid_signaling, &transport, candidates, error); + } else if (media_type == kMediaTypeData) { + if (cricket::IsDtlsSctp(protocol)) { + // The draft-03 format is: + // m=application <port> DTLS/SCTP <sctp-port>... + // use_sctpmap should be false. + // The draft-26 format is: + // m=application <port> UDP/DTLS/SCTP webrtc-datachannel + // use_sctpmap should be false. + auto data_desc = std::make_unique<SctpDataContentDescription>(); + // Default max message size is 64K + // according to draft-ietf-mmusic-sctp-sdp-26 + data_desc->set_max_message_size(kDefaultSctpMaxMessageSize); + int p; + if (rtc::FromString(fields[3], &p)) { + data_desc->set_port(p); + } else if (fields[3] == kDefaultSctpmapProtocol) { + data_desc->set_use_sctpmap(false); + } + if (!ParseContent(message, cricket::MEDIA_TYPE_DATA, mline_index, + protocol, payload_types, pos, &content_name, + &bundle_only, §ion_msid_signaling, + data_desc.get(), &transport, candidates, error)) { + return false; + } + data_desc->set_protocol(protocol); + content = std::move(data_desc); + } else { + return ParseFailed(*mline, "Unsupported protocol for media type", + error); + } + } else { + RTC_LOG(LS_WARNING) << "Unsupported media type: " << *mline; + auto unsupported_desc = + std::make_unique<UnsupportedContentDescription>(media_type); + if (!ParseContent(message, cricket::MEDIA_TYPE_UNSUPPORTED, mline_index, + protocol, payload_types, pos, &content_name, + &bundle_only, §ion_msid_signaling, + unsupported_desc.get(), &transport, candidates, + error)) { + return false; + } + unsupported_desc->set_protocol(protocol); + content = std::move(unsupported_desc); + } + if (!content.get()) { + // ParseContentDescription returns NULL if failed. + return false; + } + + msid_signaling |= section_msid_signaling; + + bool content_rejected = false; + // A port of 0 is not interpreted as a rejected m= section when it's + // used along with a=bundle-only. + if (bundle_only) { + if (!port_rejected) { + // Usage of bundle-only with a nonzero port is unspecified. So just + // ignore bundle-only if we see this. + bundle_only = false; + RTC_LOG(LS_WARNING) + << "a=bundle-only attribute observed with a nonzero " + "port; this usage is unspecified so the attribute is being " + "ignored."; + } + } else { + // If not using bundle-only, interpret port 0 in the normal way; the m= + // section is being rejected. + content_rejected = port_rejected; + } + + if (content->as_unsupported()) { + content_rejected = true; + } else if (cricket::IsRtpProtocol(protocol) && !content->as_sctp()) { + content->set_protocol(std::string(protocol)); + // Set the extmap. + if (!session_extmaps.empty() && + !content->rtp_header_extensions().empty()) { + return ParseFailed("", + "The a=extmap MUST be either all session level or " + "all media level.", + error); + } + for (size_t i = 0; i < session_extmaps.size(); ++i) { + content->AddRtpHeaderExtension(session_extmaps[i]); + } + } else if (content->as_sctp()) { + // Do nothing, it's OK + } else { + RTC_LOG(LS_WARNING) << "Parse failed with unknown protocol " << protocol; + return false; + } + + // Use the session level connection address if the media level addresses are + // not specified. + rtc::SocketAddress address; + address = content->connection_address().IsNil() + ? session_connection_addr + : content->connection_address(); + address.SetPort(port); + content->set_connection_address(address); + + desc->AddContent(content_name, + cricket::IsDtlsSctp(protocol) ? MediaProtocolType::kSctp + : MediaProtocolType::kRtp, + content_rejected, bundle_only, std::move(content)); + // Create TransportInfo with the media level "ice-pwd" and "ice-ufrag". + desc->AddTransportInfo(TransportInfo(content_name, transport)); + } + + desc->set_msid_signaling(msid_signaling); + + size_t end_of_message = message.size(); + if (mline_index == -1 && *pos != end_of_message) { + ParseFailed(message, *pos, "Expects m line.", error); + return false; + } + return true; +} + +bool VerifyCodec(const cricket::Codec& codec) { + // Codec has not been populated correctly unless the name has been set. This + // can happen if an SDP has an fmtp or rtcp-fb with a payload type but doesn't + // have a corresponding "rtpmap" line. + return !codec.name.empty(); +} + +bool VerifyAudioCodecs(const AudioContentDescription* audio_desc) { + return absl::c_all_of(audio_desc->codecs(), &VerifyCodec); +} + +bool VerifyVideoCodecs(const VideoContentDescription* video_desc) { + return absl::c_all_of(video_desc->codecs(), &VerifyCodec); +} + +void AddParameters(const cricket::CodecParameterMap& parameters, + cricket::Codec* codec) { + for (const auto& entry : parameters) { + const std::string& key = entry.first; + const std::string& value = entry.second; + codec->SetParam(key, value); + } +} + +void AddFeedbackParameter(const cricket::FeedbackParam& feedback_param, + cricket::Codec* codec) { + codec->AddFeedbackParam(feedback_param); +} + +void AddFeedbackParameters(const cricket::FeedbackParams& feedback_params, + cricket::Codec* codec) { + for (const cricket::FeedbackParam& param : feedback_params.params()) { + codec->AddFeedbackParam(param); + } +} + +// Gets the current codec setting associated with `payload_type`. If there +// is no Codec associated with that payload type it returns an empty codec +// with that payload type. +template <class T> +T GetCodecWithPayloadType(const std::vector<T>& codecs, int payload_type) { + const T* codec = FindCodecById(codecs, payload_type); + if (codec) + return *codec; + // Return empty codec with `payload_type`. + T ret_val; + ret_val.id = payload_type; + return ret_val; +} + +// Updates or creates a new codec entry in the media description. +template <class T, class U> +void AddOrReplaceCodec(MediaContentDescription* content_desc, const U& codec) { + T* desc = static_cast<T*>(content_desc); + std::vector<U> codecs = desc->codecs(); + bool found = false; + for (U& existing_codec : codecs) { + if (codec.id == existing_codec.id) { + // Overwrite existing codec with the new codec. + existing_codec = codec; + found = true; + break; + } + } + if (!found) { + desc->AddCodec(codec); + return; + } + desc->set_codecs(codecs); +} + +// Adds or updates existing codec corresponding to `payload_type` according +// to `parameters`. +template <class T, class U> +void UpdateCodec(MediaContentDescription* content_desc, + int payload_type, + const cricket::CodecParameterMap& parameters) { + // Codec might already have been populated (from rtpmap). + U new_codec = GetCodecWithPayloadType(static_cast<T*>(content_desc)->codecs(), + payload_type); + AddParameters(parameters, &new_codec); + AddOrReplaceCodec<T, U>(content_desc, new_codec); +} + +// Adds or updates existing codec corresponding to `payload_type` according +// to `feedback_param`. +template <class T, class U> +void UpdateCodec(MediaContentDescription* content_desc, + int payload_type, + const cricket::FeedbackParam& feedback_param) { + // Codec might already have been populated (from rtpmap). + U new_codec = GetCodecWithPayloadType(static_cast<T*>(content_desc)->codecs(), + payload_type); + AddFeedbackParameter(feedback_param, &new_codec); + AddOrReplaceCodec<T, U>(content_desc, new_codec); +} + +// Adds or updates existing video codec corresponding to `payload_type` +// according to `packetization`. +void UpdateVideoCodecPacketization(VideoContentDescription* video_desc, + int payload_type, + absl::string_view packetization) { + if (packetization != cricket::kPacketizationParamRaw) { + // Ignore unsupported packetization attribute. + return; + } + + // Codec might already have been populated (from rtpmap). + cricket::VideoCodec codec = + GetCodecWithPayloadType(video_desc->codecs(), payload_type); + codec.packetization = std::string(packetization); + AddOrReplaceCodec<VideoContentDescription, cricket::VideoCodec>(video_desc, + codec); +} + +template <class T> +bool PopWildcardCodec(std::vector<T>* codecs, T* wildcard_codec) { + for (auto iter = codecs->begin(); iter != codecs->end(); ++iter) { + if (iter->id == kWildcardPayloadType) { + *wildcard_codec = *iter; + codecs->erase(iter); + return true; + } + } + return false; +} + +template <class T> +void UpdateFromWildcardCodecs(cricket::MediaContentDescriptionImpl<T>* desc) { + auto codecs = desc->codecs(); + T wildcard_codec; + if (!PopWildcardCodec(&codecs, &wildcard_codec)) { + return; + } + for (auto& codec : codecs) { + AddFeedbackParameters(wildcard_codec.feedback_params, &codec); + } + desc->set_codecs(codecs); +} + +void AddAudioAttribute(const std::string& name, + absl::string_view value, + AudioContentDescription* audio_desc) { + if (value.empty()) { + return; + } + std::vector<cricket::AudioCodec> codecs = audio_desc->codecs(); + for (cricket::AudioCodec& codec : codecs) { + codec.params[name] = std::string(value); + } + audio_desc->set_codecs(codecs); +} + +bool ParseContent(absl::string_view message, + const cricket::MediaType media_type, + int mline_index, + absl::string_view protocol, + const std::vector<int>& payload_types, + size_t* pos, + std::string* content_name, + bool* bundle_only, + int* msid_signaling, + MediaContentDescription* media_desc, + TransportDescription* transport, + std::vector<std::unique_ptr<JsepIceCandidate>>* candidates, + SdpParseError* error) { + RTC_DCHECK(media_desc != NULL); + RTC_DCHECK(content_name != NULL); + RTC_DCHECK(transport != NULL); + + if (media_type == cricket::MEDIA_TYPE_AUDIO) { + MaybeCreateStaticPayloadAudioCodecs(payload_types, media_desc->as_audio()); + } + + // The media level "ice-ufrag" and "ice-pwd". + // The candidates before update the media level "ice-pwd" and "ice-ufrag". + Candidates candidates_orig; + std::string mline_id; + // Tracks created out of the ssrc attributes. + StreamParamsVec tracks; + SsrcInfoVec ssrc_infos; + SsrcGroupVec ssrc_groups; + std::string maxptime_as_string; + std::string ptime_as_string; + std::vector<std::string> stream_ids; + std::string track_id; + SdpSerializer deserializer; + std::vector<RidDescription> rids; + SimulcastDescription simulcast; + + // Loop until the next m line + while (!IsLineType(message, kLineTypeMedia, *pos)) { + absl::optional<absl::string_view> line = GetLine(message, pos); + if (!line.has_value()) { + if (*pos >= message.size()) { + break; // Done parsing + } else { + return ParseFailed(message, *pos, "Invalid SDP line.", error); + } + } + + // RFC 4566 + // b=* (zero or more bandwidth information lines) + if (IsLineType(*line, kLineTypeSessionBandwidth)) { + std::string bandwidth; + std::string bandwidth_type; + if (!rtc::tokenize_first(line->substr(kLinePrefixLength), + kSdpDelimiterColonChar, &bandwidth_type, + &bandwidth)) { + return ParseFailed( + *line, + "b= syntax error, does not match b=<modifier>:<bandwidth-value>.", + error); + } + if (!(bandwidth_type == kApplicationSpecificBandwidth || + bandwidth_type == kTransportSpecificBandwidth)) { + // Ignore unknown bandwidth types. + continue; + } + int b = 0; + if (!GetValueFromString(*line, bandwidth, &b, error)) { + return false; + } + // TODO(deadbeef): Historically, applications may be setting a value + // of -1 to mean "unset any previously set bandwidth limit", even + // though ommitting the "b=AS" entirely will do just that. Once we've + // transitioned applications to doing the right thing, it would be + // better to treat this as a hard error instead of just ignoring it. + if (bandwidth_type == kApplicationSpecificBandwidth && b == -1) { + RTC_LOG(LS_WARNING) << "Ignoring \"b=AS:-1\"; will be treated as \"no " + "bandwidth limit\"."; + continue; + } + if (b < 0) { + return ParseFailed( + *line, "b=" + bandwidth_type + " value can't be negative.", error); + } + // Convert values. Prevent integer overflow. + if (bandwidth_type == kApplicationSpecificBandwidth) { + b = std::min(b, INT_MAX / 1000) * 1000; + } else { + b = std::min(b, INT_MAX); + } + media_desc->set_bandwidth(b); + media_desc->set_bandwidth_type(bandwidth_type); + continue; + } + + // Parse the media level connection data. + if (IsLineType(*line, kLineTypeConnection)) { + rtc::SocketAddress addr; + if (!ParseConnectionData(*line, &addr, error)) { + return false; + } + media_desc->set_connection_address(addr); + continue; + } + + if (!IsLineType(*line, kLineTypeAttributes)) { + // TODO(deadbeef): Handle other lines if needed. + RTC_LOG(LS_VERBOSE) << "Ignored line: " << *line; + continue; + } + + // Handle attributes common to SCTP and RTP. + if (HasAttribute(*line, kAttributeMid)) { + // RFC 3388 + // mid-attribute = "a=mid:" identification-tag + // identification-tag = token + // Use the mid identification-tag as the content name. + if (!GetSingleTokenValue(*line, kAttributeMid, &mline_id, error)) { + return false; + } + *content_name = mline_id; + } else if (HasAttribute(*line, kAttributeBundleOnly)) { + *bundle_only = true; + } else if (HasAttribute(*line, kAttributeCandidate)) { + Candidate candidate; + if (!ParseCandidate(*line, &candidate, error, false)) { + return false; + } + // ParseCandidate will parse non-standard ufrag and password attributes, + // since it's used for candidate trickling, but we only want to process + // the "a=ice-ufrag"/"a=ice-pwd" values in a session description, so + // strip them off at this point. + candidate.set_username(std::string()); + candidate.set_password(std::string()); + candidates_orig.push_back(candidate); + } else if (HasAttribute(*line, kAttributeIceUfrag)) { + if (!GetValue(*line, kAttributeIceUfrag, &transport->ice_ufrag, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeIcePwd)) { + if (!GetValue(*line, kAttributeIcePwd, &transport->ice_pwd, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeIceOption)) { + if (!ParseIceOptions(*line, &transport->transport_options, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeFmtp)) { + if (!ParseFmtpAttributes(*line, media_type, media_desc, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeFingerprint)) { + std::unique_ptr<rtc::SSLFingerprint> fingerprint; + if (!ParseFingerprintAttribute(*line, &fingerprint, error)) { + return false; + } + transport->identity_fingerprint = std::move(fingerprint); + } else if (HasAttribute(*line, kAttributeSetup)) { + if (!ParseDtlsSetup(*line, &(transport->connection_role), error)) { + return false; + } + } else if (cricket::IsDtlsSctp(protocol) && + media_type == cricket::MEDIA_TYPE_DATA) { + // + // SCTP specific attributes + // + if (HasAttribute(*line, kAttributeSctpPort)) { + if (media_desc->as_sctp()->use_sctpmap()) { + return ParseFailed( + *line, "sctp-port attribute can't be used with sctpmap.", error); + } + int sctp_port; + if (!ParseSctpPort(*line, &sctp_port, error)) { + return false; + } + media_desc->as_sctp()->set_port(sctp_port); + } else if (HasAttribute(*line, kAttributeMaxMessageSize)) { + int max_message_size; + if (!ParseSctpMaxMessageSize(*line, &max_message_size, error)) { + return false; + } + media_desc->as_sctp()->set_max_message_size(max_message_size); + } else if (HasAttribute(*line, kAttributeSctpmap)) { + // Ignore a=sctpmap: from early versions of draft-ietf-mmusic-sctp-sdp + continue; + } + } else if (cricket::IsRtpProtocol(protocol)) { + // + // RTP specific attributes + // + if (HasAttribute(*line, kAttributeRtcpMux)) { + media_desc->set_rtcp_mux(true); + } else if (HasAttribute(*line, kAttributeRtcpReducedSize)) { + media_desc->set_rtcp_reduced_size(true); + } else if (HasAttribute(*line, kAttributeRtcpRemoteEstimate)) { + media_desc->set_remote_estimate(true); + } else if (HasAttribute(*line, kAttributeSsrcGroup)) { + if (!ParseSsrcGroupAttribute(*line, &ssrc_groups, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeSsrc)) { + if (!ParseSsrcAttribute(*line, &ssrc_infos, msid_signaling, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeCrypto)) { + if (!ParseCryptoAttribute(*line, media_desc, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeRtpmap)) { + if (!ParseRtpmapAttribute(*line, media_type, payload_types, media_desc, + error)) { + return false; + } + } else if (HasAttribute(*line, kCodecParamMaxPTime)) { + if (!GetValue(*line, kCodecParamMaxPTime, &maxptime_as_string, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributePacketization)) { + if (!ParsePacketizationAttribute(*line, media_type, media_desc, + error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeRtcpFb)) { + if (!ParseRtcpFbAttribute(*line, media_type, media_desc, error)) { + return false; + } + } else if (HasAttribute(*line, kCodecParamPTime)) { + if (!GetValue(*line, kCodecParamPTime, &ptime_as_string, error)) { + return false; + } + } else if (HasAttribute(*line, kAttributeSendOnly)) { + media_desc->set_direction(RtpTransceiverDirection::kSendOnly); + } else if (HasAttribute(*line, kAttributeRecvOnly)) { + media_desc->set_direction(RtpTransceiverDirection::kRecvOnly); + } else if (HasAttribute(*line, kAttributeInactive)) { + media_desc->set_direction(RtpTransceiverDirection::kInactive); + } else if (HasAttribute(*line, kAttributeSendRecv)) { + media_desc->set_direction(RtpTransceiverDirection::kSendRecv); + } else if (HasAttribute(*line, kAttributeExtmapAllowMixed)) { + media_desc->set_extmap_allow_mixed_enum( + MediaContentDescription::kMedia); + } else if (HasAttribute(*line, kAttributeExtmap)) { + RtpExtension extmap; + if (!ParseExtmap(*line, &extmap, error)) { + return false; + } + media_desc->AddRtpHeaderExtension(extmap); + } else if (HasAttribute(*line, kAttributeXGoogleFlag)) { + // Experimental attribute. Conference mode activates more aggressive + // AEC and NS settings. + // TODO(deadbeef): expose API to set these directly. + std::string flag_value; + if (!GetValue(*line, kAttributeXGoogleFlag, &flag_value, error)) { + return false; + } + if (flag_value.compare(kValueConference) == 0) + media_desc->set_conference_mode(true); + } else if (HasAttribute(*line, kAttributeMsid)) { + if (!ParseMsidAttribute(*line, &stream_ids, &track_id, error)) { + return false; + } + *msid_signaling |= cricket::kMsidSignalingMediaSection; + } else if (HasAttribute(*line, kAttributeRid)) { + const size_t kRidPrefixLength = + kLinePrefixLength + arraysize(kAttributeRid); + if (line->size() <= kRidPrefixLength) { + RTC_LOG(LS_INFO) << "Ignoring empty RID attribute: " << *line; + continue; + } + RTCErrorOr<RidDescription> error_or_rid_description = + deserializer.DeserializeRidDescription( + line->substr(kRidPrefixLength)); + + // Malformed a=rid lines are discarded. + if (!error_or_rid_description.ok()) { + RTC_LOG(LS_INFO) << "Ignoring malformed RID line: '" << *line + << "'. Error: " + << error_or_rid_description.error().message(); + continue; + } + + rids.push_back(error_or_rid_description.MoveValue()); + } else if (HasAttribute(*line, kAttributeSimulcast)) { + const size_t kSimulcastPrefixLength = + kLinePrefixLength + arraysize(kAttributeSimulcast); + if (line->size() <= kSimulcastPrefixLength) { + return ParseFailed(*line, "Simulcast attribute is empty.", error); + } + + if (!simulcast.empty()) { + return ParseFailed(*line, "Multiple Simulcast attributes specified.", + error); + } + + RTCErrorOr<SimulcastDescription> error_or_simulcast = + deserializer.DeserializeSimulcastDescription( + line->substr(kSimulcastPrefixLength)); + if (!error_or_simulcast.ok()) { + return ParseFailed(*line, + std::string("Malformed simulcast line: ") + + error_or_simulcast.error().message(), + error); + } + + simulcast = error_or_simulcast.value(); + } else if (HasAttribute(*line, kAttributeRtcp)) { + // Ignore and do not log a=rtcp line. + // JSEP section 5.8.2 (media section parsing) says to ignore it. + continue; + } else { + // Unrecognized attribute in RTP protocol. + RTC_LOG(LS_VERBOSE) << "Ignored line: " << *line; + continue; + } + } else { + // Only parse lines that we are interested of. + RTC_LOG(LS_VERBOSE) << "Ignored line: " << *line; + continue; + } + } + + // Remove duplicate or inconsistent rids. + RemoveInvalidRidDescriptions(payload_types, &rids); + + // If simulcast is specifed, split the rids into send and receive. + // Rids that do not appear in simulcast attribute will be removed. + std::vector<RidDescription> send_rids; + std::vector<RidDescription> receive_rids; + if (!simulcast.empty()) { + // Verify that the rids in simulcast match rids in sdp. + RemoveInvalidRidsFromSimulcast(rids, &simulcast); + + // Use simulcast description to figure out Send / Receive RIDs. + std::map<std::string, RidDescription> rid_map; + for (const RidDescription& rid : rids) { + rid_map[rid.rid] = rid; + } + + for (const auto& layer : simulcast.send_layers().GetAllLayers()) { + auto iter = rid_map.find(layer.rid); + RTC_DCHECK(iter != rid_map.end()); + send_rids.push_back(iter->second); + } + + for (const auto& layer : simulcast.receive_layers().GetAllLayers()) { + auto iter = rid_map.find(layer.rid); + RTC_DCHECK(iter != rid_map.end()); + receive_rids.push_back(iter->second); + } + + media_desc->set_simulcast_description(simulcast); + } else { + // RID is specified in RFC 8851, which identifies a lot of usages. + // We only support RFC 8853 usage of RID, not anything else. + // Ignore all RID parameters when a=simulcast is missing. + // In particular do NOT do send_rids = rids; + RTC_LOG(LS_VERBOSE) << "Ignoring send_rids without simulcast"; + } + + media_desc->set_receive_rids(receive_rids); + + // Create tracks from the `ssrc_infos`. + // If the stream_id/track_id for all SSRCS are identical, one StreamParams + // will be created in CreateTracksFromSsrcInfos, containing all the SSRCs from + // the m= section. + if (!ssrc_infos.empty()) { + CreateTracksFromSsrcInfos(ssrc_infos, stream_ids, track_id, &tracks, + *msid_signaling); + } else if (media_type != cricket::MEDIA_TYPE_DATA && + (*msid_signaling & cricket::kMsidSignalingMediaSection)) { + // If the stream_ids/track_id was signaled but SSRCs were unsignaled we + // still create a track. This isn't done for data media types because + // StreamParams aren't used for SCTP streams, and RTP data channels don't + // support unsignaled SSRCs. + // If track id was not specified, create a random one. + if (track_id.empty()) { + track_id = rtc::CreateRandomString(8); + } + CreateTrackWithNoSsrcs(stream_ids, track_id, send_rids, &tracks); + } + + // Add the ssrc group to the track. + for (const SsrcGroup& ssrc_group : ssrc_groups) { + if (ssrc_group.ssrcs.empty()) { + continue; + } + uint32_t ssrc = ssrc_group.ssrcs.front(); + for (StreamParams& track : tracks) { + if (track.has_ssrc(ssrc)) { + track.ssrc_groups.push_back(ssrc_group); + } + } + } + + // Add the new tracks to the `media_desc`. + for (StreamParams& track : tracks) { + media_desc->AddStream(track); + } + + if (media_type == cricket::MEDIA_TYPE_AUDIO) { + AudioContentDescription* audio_desc = media_desc->as_audio(); + UpdateFromWildcardCodecs(audio_desc); + + // Verify audio codec ensures that no audio codec has been populated with + // only fmtp. + if (!VerifyAudioCodecs(audio_desc)) { + return ParseFailed("Failed to parse audio codecs correctly.", error); + } + AddAudioAttribute(kCodecParamMaxPTime, maxptime_as_string, audio_desc); + AddAudioAttribute(kCodecParamPTime, ptime_as_string, audio_desc); + } + + if (media_type == cricket::MEDIA_TYPE_VIDEO) { + VideoContentDescription* video_desc = media_desc->as_video(); + UpdateFromWildcardCodecs(video_desc); + // Verify video codec ensures that no video codec has been populated with + // only rtcp-fb. + if (!VerifyVideoCodecs(video_desc)) { + return ParseFailed("Failed to parse video codecs correctly.", error); + } + } + + // RFC 5245 + // Update the candidates with the media level "ice-pwd" and "ice-ufrag". + for (Candidate& candidate : candidates_orig) { + RTC_DCHECK(candidate.username().empty() || + candidate.username() == transport->ice_ufrag); + candidate.set_username(transport->ice_ufrag); + RTC_DCHECK(candidate.password().empty()); + candidate.set_password(transport->ice_pwd); + candidates->push_back( + std::make_unique<JsepIceCandidate>(mline_id, mline_index, candidate)); + } + + return true; +} + +bool ParseSsrcAttribute(absl::string_view line, + SsrcInfoVec* ssrc_infos, + int* msid_signaling, + SdpParseError* error) { + RTC_DCHECK(ssrc_infos != NULL); + // RFC 5576 + // a=ssrc:<ssrc-id> <attribute> + // a=ssrc:<ssrc-id> <attribute>:<value> + std::string field1, field2; + if (!rtc::tokenize_first(line.substr(kLinePrefixLength), + kSdpDelimiterSpaceChar, &field1, &field2)) { + const size_t expected_fields = 2; + return ParseFailedExpectFieldNum(line, expected_fields, error); + } + + // ssrc:<ssrc-id> + std::string ssrc_id_s; + if (!GetValue(field1, kAttributeSsrc, &ssrc_id_s, error)) { + return false; + } + uint32_t ssrc_id = 0; + if (!GetValueFromString(line, ssrc_id_s, &ssrc_id, error)) { + return false; + } + + std::string attribute; + std::string value; + if (!rtc::tokenize_first(field2, kSdpDelimiterColonChar, &attribute, + &value)) { + rtc::StringBuilder description; + description << "Failed to get the ssrc attribute value from " << field2 + << ". Expected format <attribute>:<value>."; + return ParseFailed(line, description.Release(), error); + } + + // Check if there's already an item for this `ssrc_id`. Create a new one if + // there isn't. + auto ssrc_info_it = + absl::c_find_if(*ssrc_infos, [ssrc_id](const SsrcInfo& ssrc_info) { + return ssrc_info.ssrc_id == ssrc_id; + }); + if (ssrc_info_it == ssrc_infos->end()) { + SsrcInfo info; + info.ssrc_id = ssrc_id; + ssrc_infos->push_back(info); + ssrc_info_it = ssrc_infos->end() - 1; + } + SsrcInfo& ssrc_info = *ssrc_info_it; + + // Store the info to the `ssrc_info`. + if (attribute == kSsrcAttributeCname) { + // RFC 5576 + // cname:<value> + ssrc_info.cname = value; + } else if (attribute == kSsrcAttributeMsid) { + // draft-alvestrand-mmusic-msid-00 + // msid:identifier [appdata] + std::vector<absl::string_view> fields = + rtc::split(value, kSdpDelimiterSpaceChar); + if (fields.size() < 1 || fields.size() > 2) { + return ParseFailed( + line, "Expected format \"msid:<identifier>[ <appdata>]\".", error); + } + ssrc_info.stream_id = std::string(fields[0]); + if (fields.size() == 2) { + ssrc_info.track_id = std::string(fields[1]); + } + *msid_signaling |= cricket::kMsidSignalingSsrcAttribute; + } else { + RTC_LOG(LS_INFO) << "Ignored unknown ssrc-specific attribute: " << line; + } + return true; +} + +bool ParseSsrcGroupAttribute(absl::string_view line, + SsrcGroupVec* ssrc_groups, + SdpParseError* error) { + RTC_DCHECK(ssrc_groups != NULL); + // RFC 5576 + // a=ssrc-group:<semantics> <ssrc-id> ... + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + const size_t expected_min_fields = 2; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + std::string semantics; + if (!GetValue(fields[0], kAttributeSsrcGroup, &semantics, error)) { + return false; + } + std::vector<uint32_t> ssrcs; + for (size_t i = 1; i < fields.size(); ++i) { + uint32_t ssrc = 0; + if (!GetValueFromString(line, fields[i], &ssrc, error)) { + return false; + } + ssrcs.push_back(ssrc); + } + ssrc_groups->push_back(SsrcGroup(semantics, ssrcs)); + return true; +} + +bool ParseCryptoAttribute(absl::string_view line, + MediaContentDescription* media_desc, + SdpParseError* error) { + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + // RFC 4568 + // a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] + const size_t expected_min_fields = 3; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + std::string tag_value; + if (!GetValue(fields[0], kAttributeCrypto, &tag_value, error)) { + return false; + } + int tag = 0; + if (!GetValueFromString(line, tag_value, &tag, error)) { + return false; + } + const absl::string_view crypto_suite = fields[1]; + const absl::string_view key_params = fields[2]; + absl::string_view session_params; + if (fields.size() > 3) { + session_params = fields[3]; + } + + media_desc->AddCrypto( + CryptoParams(tag, crypto_suite, key_params, session_params)); + return true; +} + +// Updates or creates a new codec entry in the audio description with according +// to `name`, `clockrate`, `bitrate`, and `channels`. +void UpdateCodec(int payload_type, + absl::string_view name, + int clockrate, + int bitrate, + size_t channels, + AudioContentDescription* audio_desc) { + // Codec may already be populated with (only) optional parameters + // (from an fmtp). + cricket::AudioCodec codec = + GetCodecWithPayloadType(audio_desc->codecs(), payload_type); + codec.name = std::string(name); + codec.clockrate = clockrate; + codec.bitrate = bitrate; + codec.channels = channels; + AddOrReplaceCodec<AudioContentDescription, cricket::AudioCodec>(audio_desc, + codec); +} + +// Updates or creates a new codec entry in the video description according to +// `name`, `width`, `height`, and `framerate`. +void UpdateCodec(int payload_type, + absl::string_view name, + VideoContentDescription* video_desc) { + // Codec may already be populated with (only) optional parameters + // (from an fmtp). + cricket::VideoCodec codec = + GetCodecWithPayloadType(video_desc->codecs(), payload_type); + codec.name = std::string(name); + AddOrReplaceCodec<VideoContentDescription, cricket::VideoCodec>(video_desc, + codec); +} + +bool ParseRtpmapAttribute(absl::string_view line, + const cricket::MediaType media_type, + const std::vector<int>& payload_types, + MediaContentDescription* media_desc, + SdpParseError* error) { + static const int kFirstDynamicPayloadTypeLowerRange = 35; + std::vector<absl::string_view> fields = + rtc::split(line.substr(kLinePrefixLength), kSdpDelimiterSpaceChar); + // RFC 4566 + // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encodingparameters>] + const size_t expected_min_fields = 2; + if (fields.size() < expected_min_fields) { + return ParseFailedExpectMinFieldNum(line, expected_min_fields, error); + } + std::string payload_type_value; + if (!GetValue(fields[0], kAttributeRtpmap, &payload_type_value, error)) { + return false; + } + int payload_type = 0; + if (!GetPayloadTypeFromString(line, payload_type_value, &payload_type, + error)) { + return false; + } + + if (!absl::c_linear_search(payload_types, payload_type)) { + RTC_LOG(LS_WARNING) << "Ignore rtpmap line that did not appear in the " + "<fmt> of the m-line: " + << line; + return true; + } + std::vector<absl::string_view> codec_params = rtc::split(fields[1], '/'); + // <encoding name>/<clock rate>[/<encodingparameters>] + // 2 mandatory fields + if (codec_params.size() < 2 || codec_params.size() > 3) { + return ParseFailed(line, + "Expected format \"<encoding name>/<clock rate>" + "[/<encodingparameters>]\".", + error); + } + const absl::string_view encoding_name = codec_params[0]; + int clock_rate = 0; + if (!GetValueFromString(line, codec_params[1], &clock_rate, error)) { + return false; + } + + if (media_type == cricket::MEDIA_TYPE_VIDEO) { + VideoContentDescription* video_desc = media_desc->as_video(); + for (const cricket::VideoCodec& existing_codec : video_desc->codecs()) { + if (!existing_codec.name.empty() && payload_type == existing_codec.id && + (!absl::EqualsIgnoreCase(encoding_name, existing_codec.name) || + clock_rate != existing_codec.clockrate)) { + rtc::StringBuilder description; + description + << "Duplicate " + << (payload_type < kFirstDynamicPayloadTypeLowerRange + ? "statically assigned" + : "") + << " payload type with conflicting codec name or clock rate."; + return ParseFailed(line, description.Release(), error); + } + } + UpdateCodec(payload_type, encoding_name, video_desc); + } else if (media_type == cricket::MEDIA_TYPE_AUDIO) { + // RFC 4566 + // For audio streams, <encoding parameters> indicates the number + // of audio channels. This parameter is OPTIONAL and may be + // omitted if the number of channels is one, provided that no + // additional parameters are needed. + size_t channels = 1; + if (codec_params.size() == 3) { + if (!GetValueFromString(line, codec_params[2], &channels, error)) { + return false; + } + } + if (channels > kMaxNumberOfChannels) { + return ParseFailed(line, "At most 24 channels are supported.", error); + } + + AudioContentDescription* audio_desc = media_desc->as_audio(); + for (const cricket::AudioCodec& existing_codec : audio_desc->codecs()) { + // TODO(crbug.com/1338902) re-add checks for clockrate and number of + // channels. + if (!existing_codec.name.empty() && payload_type == existing_codec.id && + (!absl::EqualsIgnoreCase(encoding_name, existing_codec.name))) { + rtc::StringBuilder description; + description + << "Duplicate " + << (payload_type < kFirstDynamicPayloadTypeLowerRange + ? "statically assigned" + : "") + << " payload type with conflicting codec name or clock rate."; + return ParseFailed(line, description.Release(), error); + } + } + UpdateCodec(payload_type, encoding_name, clock_rate, 0, channels, + audio_desc); + } + return true; +} + +bool ParseFmtpParam(absl::string_view line, + std::string* parameter, + std::string* value, + SdpParseError* error) { + if (!rtc::tokenize_first(line, kSdpDelimiterEqualChar, parameter, value)) { + // Support for non-key-value lines like RFC 2198 or RFC 4733. + *parameter = ""; + *value = std::string(line); + return true; + } + // a=fmtp:<payload_type> <param1>=<value1>; <param2>=<value2>; ... + return true; +} + +bool ParseFmtpAttributes(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error) { + if (media_type != cricket::MEDIA_TYPE_AUDIO && + media_type != cricket::MEDIA_TYPE_VIDEO) { + return true; + } + + std::string line_payload; + std::string line_params; + + // https://tools.ietf.org/html/rfc4566#section-6 + // a=fmtp:<format> <format specific parameters> + // At least two fields, whereas the second one is any of the optional + // parameters. + if (!rtc::tokenize_first(line.substr(kLinePrefixLength), + kSdpDelimiterSpaceChar, &line_payload, + &line_params)) { + ParseFailedExpectMinFieldNum(line, 2, error); + return false; + } + + // Parse out the payload information. + std::string payload_type_str; + if (!GetValue(line_payload, kAttributeFmtp, &payload_type_str, error)) { + return false; + } + + int payload_type = 0; + if (!GetPayloadTypeFromString(line_payload, payload_type_str, &payload_type, + error)) { + return false; + } + + // Parse out format specific parameters. + cricket::CodecParameterMap codec_params; + for (absl::string_view param : + rtc::split(line_params, kSdpDelimiterSemicolonChar)) { + std::string name; + std::string value; + if (!ParseFmtpParam(absl::StripAsciiWhitespace(param), &name, &value, + error)) { + return false; + } + if (codec_params.find(name) != codec_params.end()) { + RTC_LOG(LS_INFO) << "Overwriting duplicate fmtp parameter with key \"" + << name << "\"."; + } + codec_params[name] = value; + } + + if (media_type == cricket::MEDIA_TYPE_AUDIO) { + UpdateCodec<AudioContentDescription, cricket::AudioCodec>( + media_desc, payload_type, codec_params); + } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { + UpdateCodec<VideoContentDescription, cricket::VideoCodec>( + media_desc, payload_type, codec_params); + } + return true; +} + +bool ParsePacketizationAttribute(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error) { + if (media_type != cricket::MEDIA_TYPE_VIDEO) { + return true; + } + std::vector<absl::string_view> packetization_fields = + rtc::split(line, kSdpDelimiterSpaceChar); + if (packetization_fields.size() < 2) { + return ParseFailedGetValue(line, kAttributePacketization, error); + } + std::string payload_type_string; + if (!GetValue(packetization_fields[0], kAttributePacketization, + &payload_type_string, error)) { + return false; + } + int payload_type; + if (!GetPayloadTypeFromString(line, payload_type_string, &payload_type, + error)) { + return false; + } + absl::string_view packetization = packetization_fields[1]; + UpdateVideoCodecPacketization(media_desc->as_video(), payload_type, + packetization); + return true; +} + +bool ParseRtcpFbAttribute(absl::string_view line, + const cricket::MediaType media_type, + MediaContentDescription* media_desc, + SdpParseError* error) { + if (media_type != cricket::MEDIA_TYPE_AUDIO && + media_type != cricket::MEDIA_TYPE_VIDEO) { + return true; + } + std::vector<absl::string_view> rtcp_fb_fields = + rtc::split(line, kSdpDelimiterSpaceChar); + if (rtcp_fb_fields.size() < 2) { + return ParseFailedGetValue(line, kAttributeRtcpFb, error); + } + std::string payload_type_string; + if (!GetValue(rtcp_fb_fields[0], kAttributeRtcpFb, &payload_type_string, + error)) { + return false; + } + int payload_type = kWildcardPayloadType; + if (payload_type_string != "*") { + if (!GetPayloadTypeFromString(line, payload_type_string, &payload_type, + error)) { + return false; + } + } + absl::string_view id = rtcp_fb_fields[1]; + std::string param = ""; + for (auto iter = rtcp_fb_fields.begin() + 2; iter != rtcp_fb_fields.end(); + ++iter) { + param.append(iter->data(), iter->length()); + } + const cricket::FeedbackParam feedback_param(id, param); + + if (media_type == cricket::MEDIA_TYPE_AUDIO) { + UpdateCodec<AudioContentDescription, cricket::AudioCodec>( + media_desc, payload_type, feedback_param); + } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { + UpdateCodec<VideoContentDescription, cricket::VideoCodec>( + media_desc, payload_type, feedback_param); + } + return true; +} + +} // namespace webrtc |