blob: 769e3807a5406b1bfcc76289964b5d1166c5a2b0 [file] [log] [blame]
/*
* Copyright 2017 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 "api/jsep.h"
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "api/candidate.h"
#include "api/sequence_checker.h"
#include "api/webrtc_sdp.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/transport_info.h"
#include "pc/session_description.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/net_helper.h"
#include "rtc_base/net_helpers.h"
#include "rtc_base/socket_address.h"
namespace webrtc {
namespace {
constexpr char kDummyAddress[] = "0.0.0.0";
constexpr int kDummyPort = 9;
// Update the connection address for the MediaContentDescription based on the
// candidates.
void UpdateConnectionAddress(const IceCandidateCollection& candidate_collection,
MediaContentDescription* media_desc) {
int port = kDummyPort;
std::string ip = kDummyAddress;
std::string hostname;
int current_preference = 0; // Start with lowest preference.
int current_family = AF_UNSPEC;
for (size_t i = 0; i < candidate_collection.count(); ++i) {
const IceCandidate* jsep_candidate = candidate_collection.at(i);
if (jsep_candidate->candidate().component() !=
ICE_CANDIDATE_COMPONENT_RTP) {
continue;
}
// Default destination should be UDP only.
if (jsep_candidate->candidate().protocol() != UDP_PROTOCOL_NAME) {
continue;
}
const int preference = jsep_candidate->candidate().type_preference();
const int family = jsep_candidate->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;
}
current_preference = preference;
current_family = family;
const SocketAddress& candidate_addr = jsep_candidate->candidate().address();
port = candidate_addr.port();
ip = candidate_addr.ipaddr().ToString();
hostname = candidate_addr.hostname();
}
SocketAddress connection_addr(ip, port);
if (IPIsUnspec(connection_addr.ipaddr()) && !hostname.empty()) {
// When a hostname candidate becomes the (default) connection address,
// we use the dummy address 0.0.0.0 and port 9 in the c= and the m= lines.
//
// We have observed in deployment that with a FQDN in a c= line, SDP parsing
// could fail in other JSEP implementations. We note that the wildcard
// addresses (0.0.0.0 or ::) with port 9 are given the exception as the
// connection address that will not result in an ICE mismatch
// (draft-ietf-mmusic-ice-sip-sdp). Also, 0.0.0.0 or :: can be used as the
// connection address in the initial offer or answer with trickle ICE
// if the offerer or answerer does not want to include the host IP address
// (draft-ietf-mmusic-trickle-ice-sip), and in particular 0.0.0.0 has been
// widely deployed for this use without outstanding compatibility issues.
// Combining the above considerations, we use 0.0.0.0 with port 9 to
// populate the c= and the m= lines. See `BuildMediaDescription` in
// webrtc_sdp.cc for the SDP generation with
// `media_desc->connection_address()`.
connection_addr = SocketAddress(kDummyAddress, kDummyPort);
}
media_desc->set_connection_address(connection_addr);
}
std::vector<IceCandidateCollection> CloneCandidateCollection(
const std::vector<IceCandidateCollection>& original) {
std::vector<IceCandidateCollection> ret;
ret.reserve(original.size());
for (const auto& collection : original) {
ret.push_back(collection.Clone());
}
return ret;
}
} // namespace
const char SessionDescriptionInterface::kOffer[] = "offer";
const char SessionDescriptionInterface::kPrAnswer[] = "pranswer";
const char SessionDescriptionInterface::kAnswer[] = "answer";
const char SessionDescriptionInterface::kRollback[] = "rollback";
const char* SdpTypeToString(SdpType type) {
switch (type) {
case SdpType::kOffer:
return SessionDescriptionInterface::kOffer;
case SdpType::kPrAnswer:
return SessionDescriptionInterface::kPrAnswer;
case SdpType::kAnswer:
return SessionDescriptionInterface::kAnswer;
case SdpType::kRollback:
return SessionDescriptionInterface::kRollback;
}
return "";
}
std::optional<SdpType> SdpTypeFromString(const std::string& type_str) {
if (type_str == SessionDescriptionInterface::kOffer) {
return SdpType::kOffer;
} else if (type_str == SessionDescriptionInterface::kPrAnswer) {
return SdpType::kPrAnswer;
} else if (type_str == SessionDescriptionInterface::kAnswer) {
return SdpType::kAnswer;
} else if (type_str == SessionDescriptionInterface::kRollback) {
return SdpType::kRollback;
} else {
return std::nullopt;
}
}
std::unique_ptr<IceCandidate> CreateIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const Candidate& candidate) {
return std::make_unique<IceCandidate>(sdp_mid, sdp_mline_index, candidate);
}
IceCandidate* CreateIceCandidate(const std::string& sdp_mid,
int sdp_mline_index,
const std::string& sdp,
SdpParseError* error) {
return IceCandidate::Create(sdp_mid, sdp_mline_index, sdp, error).release();
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
absl::string_view sdp) {
return CreateSessionDescription(type, sdp, nullptr);
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
absl::string_view sdp,
SdpParseError* error_out) {
if (type == SdpType::kRollback) {
return CreateRollbackSessionDescription();
}
return SdpDeserialize(type, sdp, error_out);
}
std::unique_ptr<SessionDescriptionInterface> CreateSessionDescription(
SdpType type,
absl::string_view session_id,
absl::string_view session_version,
std::unique_ptr<SessionDescription> description) {
return SessionDescriptionInterface::Create(type, std::move(description),
session_id, session_version);
}
std::unique_ptr<SessionDescriptionInterface> CreateRollbackSessionDescription(
absl::string_view session_id,
absl::string_view session_version) {
return SessionDescriptionInterface::Create(
SdpType::kRollback, /*description=*/nullptr, session_id, session_version);
}
// static
std::unique_ptr<SessionDescriptionInterface>
SessionDescriptionInterface::Create(
SdpType type,
std::unique_ptr<SessionDescription> description,
absl::string_view id,
absl::string_view version,
std::vector<IceCandidateCollection> candidates) {
if (!description && type != SdpType::kRollback)
return nullptr;
return absl::WrapUnique(new SessionDescriptionInterface(
type, std::move(description), id, version, std::move(candidates)));
}
SessionDescriptionInterface::~SessionDescriptionInterface() = default;
void SessionDescriptionInterface::RelinquishThreadOwnership() {
// Ideally we should require that the method can only be called from the
// thread that the sequence checker is currently attached to. However that's
// not compatible with some cases outside of webrtc where initializations
// happens on one thread and then the object is moved to a second thread (e.g.
// signaling) where a call is made into webrtc. At that point we'd hit a
// dcheck like this in webrtc: RTC_DCHECK_RUN_ON(&sequence_checker_);
sequence_checker_.Detach();
// Tie the checker to the current thread, which permits iterating
// `candidate_collection_`
RTC_DCHECK_RUN_ON(&sequence_checker_);
for (IceCandidateCollection& collection : candidate_collection_) {
collection.RelinquishThreadOwnership();
}
sequence_checker_.Detach(); // Unties the checker from the current thread.
}
SessionDescriptionInterface::SessionDescriptionInterface(
SdpType type,
std::unique_ptr<SessionDescription> desc,
absl::string_view id,
absl::string_view version,
std::vector<IceCandidateCollection> candidates)
: sdp_type_(type),
id_(id),
version_(version),
description_(std::move(desc)),
candidate_collection_(std::move(candidates)) {
RTC_DCHECK(description() || type == SdpType::kRollback);
RTC_DCHECK(candidate_collection_.empty() ||
candidate_collection_.size() == number_of_mediasections());
candidate_collection_.resize(number_of_mediasections());
}
size_t SessionDescriptionInterface::number_of_mediasections() const {
return description_ ? description_->contents().size() : 0u;
}
std::unique_ptr<SessionDescriptionInterface>
SessionDescriptionInterface::Clone() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return SessionDescriptionInterface::Create(
sdp_type_, description() ? description()->Clone() : nullptr, id(),
version(), CloneCandidateCollection(candidate_collection_));
}
bool SessionDescriptionInterface::AddCandidate(const IceCandidate* candidate) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (!candidate)
return false;
size_t index = 0;
if (!GetMediasectionIndex(candidate, &index)) {
return false;
}
ContentInfo& content = description()->contents()[index];
const TransportInfo* transport_info =
description()->GetTransportInfoByName(content.mid());
if (!transport_info) {
return false;
}
Candidate updated_candidate = candidate->candidate();
if (updated_candidate.username().empty()) {
updated_candidate.set_username(transport_info->description.ice_ufrag);
}
if (updated_candidate.password().empty()) {
updated_candidate.set_password(transport_info->description.ice_pwd);
}
// Use `content.mid()` as the mid for the updated candidate. The
// `candidate->sdp_mid()` property *should* be the same. However, in some
// cases specifying an empty mid but a valid index is a way to add a candidate
// without knowing (or caring about) the mid. This is done in several tests.
RTC_DCHECK(candidate->sdp_mid().empty() ||
candidate->sdp_mid() == content.mid())
<< "sdp_mid='" << candidate->sdp_mid() << "' content.mid()='"
<< content.mid() << "'";
auto updated_candidate_wrapper = std::make_unique<IceCandidate>(
content.mid(), static_cast<int>(index), updated_candidate);
IceCandidateCollection& candidates = candidate_collection_[index];
if (!candidates.HasCandidate(updated_candidate_wrapper.get())) {
candidates.add(std::move(updated_candidate_wrapper));
UpdateConnectionAddress(candidates, content.media_description());
}
return true;
}
bool SessionDescriptionInterface::RemoveCandidate(
const IceCandidate* candidate) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
size_t index = 0u;
if (!GetMediasectionIndex(candidate, &index)) {
return false;
}
IceCandidateCollection& candidates = candidate_collection_[index];
if (!candidates.remove(candidate)) {
return false;
}
UpdateConnectionAddress(candidates,
description()->contents()[index].media_description());
return true;
}
const IceCandidateCollection* SessionDescriptionInterface::candidates(
size_t mediasection_index) const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (mediasection_index >= candidate_collection_.size())
return nullptr;
return &candidate_collection_[mediasection_index];
}
std::string SessionDescriptionInterface::ToString() const {
return SdpSerialize(*this);
}
bool SessionDescriptionInterface::IsValidMLineIndex(int index) const {
RTC_DCHECK(description());
return index >= 0 &&
index < static_cast<int>(description()->contents().size());
}
bool SessionDescriptionInterface::GetMediasectionIndex(
const IceCandidate* candidate,
size_t* index) const {
if (!candidate || !index || !description()) {
return false;
}
auto mid = candidate->sdp_mid();
if (!mid.empty()) {
*index = GetMediasectionIndex(mid);
} else {
// An sdp_mline_index of -1 will be treated as invalid.
*index = static_cast<size_t>(candidate->sdp_mline_index());
}
return IsValidMLineIndex(*index);
}
int SessionDescriptionInterface::GetMediasectionIndex(
absl::string_view mid) const {
const auto& contents = description()->contents();
auto it =
std::find_if(contents.begin(), contents.end(),
[&](const auto& content) { return mid == content.mid(); });
return it == contents.end() ? -1 : std::distance(contents.begin(), it);
}
} // namespace webrtc