Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/libwebrtc/pc/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 142 kB image not shown  

Quelle  webrtc_sdp.cc   Sprache: C

 
/*
 *  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 <optional>
#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 "absl/strings/string_view.h"
#include "api/candidate.h"
#include "api/jsep.h"
#include "api/jsep_ice_candidate.h"
#include "api/jsep_session_description.h"
#include "api/media_types.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/session_description.h"
#include "pc/simulcast_description.h"
#include "pc/simulcast_sdp_serializer.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/net_helpers.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/strings/string_builder.h"

using cricket::AudioContentDescription;
using cricket::Candidate;
using cricket::Candidates;
using cricket::ContentInfo;
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 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;
};
using SsrcInfoVec = std::vector<SsrcInfo>;
using SsrcGroupVec = std::vector<SsrcGroup>;

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 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 std::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 std::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 std::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 std::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 std::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);
  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 if (msid_signaling == cricket::kMsidSignalingNotUsed) {
      // Since no media streams isn't supported with older SDP signaling, we
      // use a default stream id.
      stream_ids.push_back(kDefaultMsid);
    }

    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;
  }
  for (StreamParams& stream : *tracks) {
    // If a track ID wasn't populated from the SSRC attributes OR the
    // msid attribute, use default/random values. This happens after
    // deduplication.
    if (stream.id.empty()) {
      stream.id = rtc::CreateRandomString(8);
    }
  }
}

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);
    }
  }
}

// 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 = 0;  // Start with lowest preference
  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 = candidate.type_preference();
    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_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;
  return os.Release();
}

// 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.
  // TODO(bugs.webrtc.org/10421): Change to & cricket::kMsidSignalingSemantic
  // when we think it's safe to do so, so that we gradually fade out this old
  // line that was removed from the specification.
  if (desc->msid_signaling() != cricket::kMsidSignalingNotUsed) {
    InitAttrLine(kAttributeMsidSemantics, &os);
    os << kSdpDelimiterColon << " " << kMediaStreamSemantic;

    // TODO(bugs.webrtc.org/10421): this code only looks at the first
    // audio/video content. Fixing that might result in much larger SDP and the
    // msid-semantic line should eventually go away so this is not worth fixing.
    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
                  << ":"
                     "";
      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);

  std::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);
  }

  IceCandidateType candidate_type;
  const absl::string_view type = fields[7];
  if (type == kCandidateHost) {
    candidate_type = IceCandidateType::kHost;
  } else if (type == kCandidateSrflx) {
    candidate_type = IceCandidateType::kSrflx;
  } else if (type == kCandidateRelay) {
    candidate_type = IceCandidateType::kRelay;
  } else if (type == kCandidatePrflx) {
    candidate_type = IceCandidateType::kPrflx;
  } 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_AUDIO ||
      media_type == cricket::MEDIA_TYPE_VIDEO) {
    type = media_type == cricket::MEDIA_TYPE_AUDIO ? kMediaTypeAudio
                                                   : kMediaTypeVideo;
    for (const cricket::Codec& codec : media_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) {
  SimulcastSdpSerializer 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 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.
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=84 H=96 G=90

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.