if (local.is_local()) {
if (remote.is_stun()) return kIceCandidatePairHostSrflx;
if (remote.is_relay()) return kIceCandidatePairHostRelay;
if (remote.is_prflx()) return kIceCandidatePairHostPrflx;
} else if (local.is_stun()) {
if (remote.is_local()) return kIceCandidatePairSrflxHost;
if (remote.is_stun()) return kIceCandidatePairSrflxSrflx;
if (remote.is_relay()) return kIceCandidatePairSrflxRelay;
if (remote.is_prflx()) return kIceCandidatePairSrflxPrflx;
} else if (local.is_relay()) {
if (remote.is_local()) return kIceCandidatePairRelayHost;
if (remote.is_stun()) return kIceCandidatePairRelaySrflx;
if (remote.is_relay()) return kIceCandidatePairRelayRelay;
if (remote.is_prflx()) return kIceCandidatePairRelayPrflx;
} else if (local.is_prflx()) {
if (remote.is_local()) return kIceCandidatePairPrflxHost;
if (remote.is_stun()) return kIceCandidatePairPrflxSrflx;
if (remote.is_relay()) return kIceCandidatePairPrflxRelay;
}
return kIceCandidatePairMax;
}
// Check if the changes of IceTransportsType motives an ice restart. bool NeedIceRestart(bool surface_ice_candidates_on_ice_transport_type_changed,
PeerConnectionInterface::IceTransportsType current,
PeerConnectionInterface::IceTransportsType modified) {
if (current == modified) { return false;
}
if (!surface_ice_candidates_on_ice_transport_type_changed) { returntrue;
}
auto current_filter = ConvertIceTransportTypeToCandidateFilter(current); auto modified_filter = ConvertIceTransportTypeToCandidateFilter(modified);
// If surface_ice_candidates_on_ice_transport_type_changed is true and we // extend the filter, then no ice restart is needed. return (current_filter & modified_filter) != current_filter;
}
// Checks for valid pool size range and if a previous value has already been // set, which is done via SetLocalDescription.
RTCError ValidateIceCandidatePoolSize(
int ice_candidate_pool_size,
std::optional<int> previous_ice_candidate_pool_size) { // Note that this isn't possible through chromium, since it's an unsigned // short in WebIDL.
if (ice_candidate_pool_size < 0 ||
ice_candidate_pool_size > static_cast<int>(UINT16_MAX)) { return RTCError(RTCErrorType::INVALID_RANGE);
}
// According to JSEP, after setLocalDescription, changing the candidate pool // size is not allowed, and changing the set of ICE servers will not result // in new candidates being gathered.
if (previous_ice_candidate_pool_size.has_value() &&
ice_candidate_pool_size != previous_ice_candidate_pool_size.value()) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_MODIFICATION)
<< "Can't change candidate pool size after calling " "SetLocalDescription.");
}
return RTCError::OK();
}
// The simplest (and most future-compatible) way to tell if a config was // modified in an invalid way is to copy each property we do support modifying, // then use operator==. There are far more properties we don't support modifying // than those we do, and more could be added. // This helper function accepts a proposed new `configuration` object, an // existing configuration and returns a valid, modified, configuration that's // based on the existing configuration, with modified properties copied from // `configuration`. // If the result of creating a modified configuration doesn't pass the above // `operator==` test or a call to `ValidateConfiguration()`, then the function // will return an error. Otherwise, the return value will be the new config.
RTCErrorOr<PeerConnectionInterface::RTCConfiguration> ApplyConfiguration( const PeerConnectionInterface::RTCConfiguration& configuration, const PeerConnectionInterface::RTCConfiguration& existing_configuration) {
PeerConnectionInterface::RTCConfiguration modified_config =
existing_configuration;
modified_config.type = configuration.type;
modified_config.crypto_options = configuration.crypto_options;
modified_config.always_negotiate_data_channels =
configuration.always_negotiate_data_channels;
if (configuration != modified_config) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_MODIFICATION)
<< "Modifying the configuration in an unsupported way.");
}
RTCError err = IceConfig(modified_config).IsValid();
if (!err.ok()) { return err;
}
// Enable DTLS by default if we have an identity store or a certificate. return (dependencies.cert_generator || !configuration.certificates.empty());
}
// Checks if the observer and description pointers are valid. // If there's an error, the function will return false and optionally // notify the observer asynchronously of the error. // If both are valid, the function will reset the thread ownership of // the description object and return true. template <typename Observer, typename Description> bool CheckValidSetDescription(Observer& observer,
Description& desc,
Thread* signaling) {
if (!observer) {
RTC_LOG(LS_ERROR) << "Observer is NULL."; return false;
} else if (!desc) {
signaling->PostTask(
ReportFailure(observer, RTCError(RTCErrorType::INVALID_PARAMETER, "SessionDescription is NULL."))); return false;
} // Make sure the description object now considers the current thread its home // by detaching from any potential previous thread.
desc->RelinquishThreadOwnership(); returntrue;
}
PeerConnection::PeerConnection( const PeerConnectionInterface::RTCConfiguration& configuration, const Environment& env,
scoped_refptr<ConnectionContext> context, const PeerConnectionFactoryInterface::Options& options, bool is_unified_plan,
std::unique_ptr<Call> call,
PeerConnectionDependencies& dependencies, const ServerAddresses& stun_servers, const std::vector<RelayServerConfig>& turn_servers, bool dtls_enabled)
: env_(env),
context_(context),
options_(options),
observer_(dependencies.observer),
is_unified_plan_(is_unified_plan),
dtls_enabled_(dtls_enabled),
configuration_(configuration),
async_dns_resolver_factory_(
std::move(dependencies.async_dns_resolver_factory)),
port_allocator_(std::move(dependencies.allocator)),
lna_permission_factory_(std::move(dependencies.lna_permission_factory)),
ice_transport_factory_(std::move(dependencies.ice_transport_factory)),
dtls_transport_factory_(std::move(dependencies.dtls_transport_factory)),
rtp_transport_factory_(std::move(dependencies.rtp_transport_factory)),
tls_cert_verifier_(std::move(dependencies.tls_cert_verifier)),
call_(std::move(call)),
network_thread_safety_(
PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true,
network_thread())),
worker_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue( /*alive=*/call_ != nullptr,
worker_thread())),
call_ptr_(call_.get()),
legacy_stats_(std::make_unique<LegacyStatsCollector>(this, env_.clock())),
stats_collector_(this, env_), // RFC 3264: The numeric value of the session id and version in the // o line MUST be representable with a "64 bit signed integer". // Due to this constraint session id `session_id_` is max limited to // LLONG_MAX.
session_id_(absl::StrCat(CreateRandomId64() & LLONG_MAX)),
data_channel_controller_(this),
message_handler_(signaling_thread()),
codec_lookup_helper_(
std::make_unique<CodecLookupHelperForPeerConnection>(this)),
weak_factory_(this) { // Field trials specific to the peerconnection should be owned by the `env`,
RTC_DCHECK(dependencies.trials == nullptr);
// In case `Close()` wasn't called, always make sure the controller cancels // potentially pending operations.
data_channel_controller_.PrepareForShutdown();
// Stop transceivers before destroying the stats collector because // AudioRtpSender has a reference to the LegacyStatsCollector that it will // update when stopping. The BaseChannels will eventually be deleted below // when all the network and worker tasks are executed.
sdp_handler_->GetMediaChannelTeardownTasks(network_tasks, worker_tasks);
// call_ must be destroyed on the worker thread.
worker_tasks.Add([this]() {
RTC_DCHECK_RUN_ON(worker_thread());
worker_thread_safety_->SetNotAlive();
call_.reset();
media_engine_ref_.reset();
});
network_tasks.Run();
worker_tasks.Run();
if (sdp_handler_) {
sdp_handler_->ResetSessionDescFactory();
}
if (!transport_controller_copy_) { return nullptr;
}
auto jsep_close_task = transport_controller_copy_->MakeCloseTask();
return [this, jsep_close_task = std::move(jsep_close_task)]() mutable
-> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
RTC_DCHECK_RUN_ON(network_thread());
if (network_thread_safety_->alive()) { // port_allocator_ and transport_controller_ live on the network thread // and must be destroyed there.
TeardownDataChannelTransport_n(RTCError::OK());
port_allocator_->DiscardCandidatePool();
scoped_refptr<StreamCollectionInterface> PeerConnection::local_streams() {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_CHECK(!IsUnifiedPlan()) << "local_streams is not available with Unified " "Plan SdpSemantics. Please use GetSenders " "instead."; return sdp_handler_->local_streams();
}
scoped_refptr<StreamCollectionInterface> PeerConnection::remote_streams() {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_CHECK(!IsUnifiedPlan()) << "remote_streams is not available with Unified " "Plan SdpSemantics. Please use GetReceivers " "instead."; return sdp_handler_->remote_streams();
}
bool PeerConnection::AddStream(MediaStreamInterface* local_stream) {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_CHECK(!IsUnifiedPlan()) << "AddStream is not available with Unified Plan " "SdpSemantics. Please use AddTrack instead.";
TRACE_EVENT0("webrtc", "PeerConnection::AddStream");
if (!ConfiguredForMedia()) {
RTC_LOG(LS_ERROR) << "AddStream: Not configured for media"; return false;
} return sdp_handler_->AddStream(local_stream);
}
void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) {
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(ConfiguredForMedia());
RTC_CHECK(!IsUnifiedPlan()) << "RemoveStream is not available with Unified " "Plan SdpSemantics. Please use RemoveTrack " "instead.";
TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream");
sdp_handler_->RemoveStream(local_stream);
}
RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(MediaType media_type, const RtpTransceiverInit& init) {
RTC_DCHECK_RUN_ON(signaling_thread());
if (!ConfiguredForMedia()) { return LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
<< "Not configured for media");
}
RTC_CHECK(IsUnifiedPlan())
<< "AddTransceiver is only available with Unified Plan SdpSemantics";
if (!(media_type == MediaType::AUDIO || media_type == MediaType::VIDEO)) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
<< "media type is not audio or video");
} return AddTransceiver(media_type, nullptr, init);
}
size_t num_rids = absl::c_count_if(init.send_encodings,
[](const RtpEncodingParameters& encoding) { return !encoding.rid.empty();
});
if (num_rids > 0 && num_rids != init.send_encodings.size()) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
<< "RIDs must be provided for either all or none of the " "send encodings.");
}
if (absl::c_any_of(init.send_encodings,
[](const RtpEncodingParameters& encoding) { return encoding.ssrc.has_value();
})) { return LOG_ERROR(
RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
<< "Attempted to set an unimplemented parameter of RtpParameters.");
}
// Encodings are dropped from the tail if too many are provided.
size_t max_simulcast_streams =
media_type == MediaType::VIDEO ? kMaxSimulcastStreams : 1u;
if (parameters.encodings.size() > max_simulcast_streams) {
parameters.encodings.erase(
parameters.encodings.begin() + max_simulcast_streams,
parameters.encodings.end());
}
// Single RID should be removed.
if (parameters.encodings.size() == 1 &&
!parameters.encodings[0].rid.empty()) {
RTC_LOG(LS_INFO) << "Removing RID: " << parameters.encodings[0].rid << ".";
parameters.encodings[0].rid.clear();
}
// If RIDs were not provided, they are generated for simulcast scenario.
if (parameters.encodings.size() > 1 && num_rids == 0) {
UniqueStringGenerator rid_generator;
for (RtpEncodingParameters& encoding : parameters.encodings) {
encoding.rid = rid_generator.GenerateString();
}
}
// If no encoding parameters were provided, a default entry is created.
if (parameters.encodings.empty()) {
parameters.encodings.push_back({});
}
if (UnimplementedRtpParameterHasValue(parameters)) { return LOG_ERROR(
RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
<< "Attempted to set an unimplemented parameter of RtpParameters.");
}
std::vector<Codec> codecs; // Gather the current codec capabilities to allow checking scalabilityMode and // codec selection against supported values.
CodecVendor codec_vendor(context_->media_engine(), false, trials());
if (media_type == MediaType::VIDEO) {
codecs = codec_vendor.video_send_codecs().codecs();
} else {
codecs = codec_vendor.audio_send_codecs().codecs();
}
auto result = CheckRtpParametersValues(parameters, codecs, std::nullopt,
env_.field_trials());
if (!result.ok()) {
if (result.type() == RTCErrorType::INVALID_MODIFICATION) {
result.set_type(RTCErrorType::UNSUPPORTED_OPERATION);
} return LOG_ERROR(RTCError(result.type()) << result.message());
}
RTC_LOG(LS_INFO) << "Adding " << MediaTypeToString(media_type)
<< " transceiver in response to a call to AddTransceiver."; // Set the sender ID equal to the track ID if the track is specified unless // that sender ID is already in use.
std::string sender_id = (track && !rtp_manager()->FindSenderById(track->id())
? track->id()
: CreateRandomUuid());
ScopedOperationsBatcher worker_tasks(context_->worker_thread()); auto transceiver = rtp_manager()->CreateAndAddTransceiver(
configuration_.media_config, sdp_handler_->audio_options(),
sdp_handler_->video_options(), configuration_.crypto_options,
sdp_handler_->video_bitrate_allocator_factory(), media_type, track,
init.stream_ids, parameters.encodings, /*header_extensions_to_negotiate=*/{}, /*simulcast_rejected=*/false, /*initial_simulcast_layers=*/{},
worker_tasks, sender_id);
RTCError error = worker_tasks.Run();
RTC_DCHECK(error.ok());
transceiver->internal()->set_direction(init.direction);
if (update_negotiation_needed) {
sdp_handler_->UpdateNegotiationNeeded();
}
scoped_refptr<RtpSenderInterface> PeerConnection::CreateSender( const std::string& kind, const std::string& stream_id) {
RTC_DCHECK_RUN_ON(signaling_thread());
if (!ConfiguredForMedia()) {
RTC_LOG(LS_ERROR) << "Not configured for media"; return nullptr;
}
RTC_CHECK(!IsUnifiedPlan()) << "CreateSender is not available with Unified " "Plan SdpSemantics. Please use AddTransceiver " "instead.";
TRACE_EVENT0("webrtc", "PeerConnection::CreateSender");
if (IsClosed()) { return nullptr;
}
// Internally we need to have one stream with Plan B semantics, so we // generate a random stream ID if not specified.
std::vector<std::string> stream_ids;
if (stream_id.empty()) {
stream_ids.push_back(CreateRandomUuid());
RTC_LOG(LS_INFO)
<< "No stream_id specified for sender. Generated stream ID: "
<< stream_ids[0];
} else {
stream_ids.push_back(stream_id);
}
// The LegacyStatsCollector is used to tell if a track is valid because it may // remember tracks that the PeerConnection previously removed.
if (track && !legacy_stats_->IsValidTrack(track->id())) {
RTC_LOG(LS_WARNING) << "Legacy GetStats is called with an invalid track: "
<< track->id(); return false;
}
message_handler_.PostGetStats(observer, legacy_stats_.get(), track);
void PeerConnection::GetStats(
scoped_refptr<RtpSenderInterface> selector,
scoped_refptr<RTCStatsCollectorCallback> callback) {
TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(callback);
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
scoped_refptr<RtpSenderInternal> internal_sender;
if (selector) {
for (constauto& proxy_transceiver :
rtp_manager()->transceivers()->List()) {
RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
for (constauto& proxy_sender :
proxy_transceiver->internal()->senders()) {
if (proxy_sender == selector) {
internal_sender = proxy_sender->internal(); break;
}
}
RTC_ALLOW_PLAN_B_DEPRECATION_END()
if (internal_sender) break;
}
} // If there is no `internal_sender` then `selector` is either null or does not // belong to the PeerConnection (in Plan B, senders can be removed from the // PeerConnection). This means that "all the stats objects representing the // selector" is an empty set. Invoking GetStatsReport() with a null selector // produces an empty stats report.
stats_collector_.GetStatsReport(internal_sender, callback);
}
void PeerConnection::GetStats(
scoped_refptr<RtpReceiverInterface> selector,
scoped_refptr<RTCStatsCollectorCallback> callback) {
TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
RTC_DCHECK_RUN_ON(signaling_thread());
RTC_DCHECK(callback);
RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
scoped_refptr<RtpReceiverInternal> internal_receiver;
if (selector) {
for (constauto& proxy_transceiver :
rtp_manager()->transceivers()->List()) {
RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
for (constauto& proxy_receiver :
proxy_transceiver->internal()->receivers()) {
if (proxy_receiver == selector) {
internal_receiver = proxy_receiver->internal(); break;
}
}
RTC_ALLOW_PLAN_B_DEPRECATION_END()
if (internal_receiver) break;
}
} // If there is no `internal_receiver` then `selector` is either null or does // not belong to the PeerConnection (in Plan B, receivers can be removed from // the PeerConnection). This means that "all the stats objects representing // the selector" is an empty set. Invoking GetStatsReport() with a null // selector produces an empty stats report.
stats_collector_.GetStatsReport(internal_receiver, callback);
}
// This method bypasses the proxy, so can be called from any thread. void PeerConnection::SetLocalDescription(
std::unique_ptr<SessionDescriptionInterface> desc,
scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
if (!CheckValidSetDescription(observer, desc, signaling_thread())) return;
if (has_local_description &&
configuration.crypto_options != configuration_.crypto_options) { return LOG_ERROR(RTCError(RTCErrorType::INVALID_MODIFICATION)
<< "Can't change crypto_options after calling " "SetLocalDescription.");
}
// Create a new, configuration object whose peerconnection config // will have been validated.
RTCErrorOr<RTCConfiguration> validated_config =
ApplyConfiguration(configuration, configuration_);
if (!validated_config.ok()) { return validated_config.error();
}
// Parse ICE servers before hopping to network thread.
ServerAddresses stun_servers;
std::vector<RelayServerConfig> turn_servers;
validate_error = ParseAndValidateIceServersFromConfiguration(
configuration, stun_servers, turn_servers);
if (!validate_error.ok()) { return validate_error;
}
NoteServerUsage(usage_pattern_, stun_servers, turn_servers);
// Apply part of the configuration on the network thread. In theory this // shouldn't fail.
std::vector<IceParameters> pooled_credentials; // TODO: webrtc:42222117 - For carrying `new_states` from the transport // controller on the network thread to the transport controller on the // signaling thread, we should instead use a // ScopedOperationsBatcher::BatchTaskWithFinalizer.
flat_map<std::string, JsepTransportController::TransportState> new_states;
if (!network_thread()->BlockingCall([&] {
RTC_DCHECK_RUN_ON(network_thread()); // As described in JSEP, calling setConfiguration with new ICE // servers or candidate policy must set a "needs-ice-restart" bit so // that the next offer triggers an ICE restart which will pick up // the changes.
if (needs_ice_restart)
transport_controller_->SetNeedsIceRestartFlag();
if (IsClosed()) { return;
} // Update stats here so that we have the most recent stats for tracks and // streams before the channels are closed.
legacy_stats_->UpdateStats(kStatsOutputLevelStandard);
if (ConfiguredForMedia()) {
for (RtpTransceiver* transceiver :
rtp_manager()->transceivers()->ListInternal()) {
if (!transceiver->stopped()) {
worker_tasks.Add(transceiver->GetStopTransceiverProcedure());
}
}
}
// Don't destroy BaseChannels until after stats has been cleaned up so that // the last stats request can still read from the channels. // TODO(tommi): The voice/video channels will be partially uninitialized on // the network thread (see `RtpTransceiver::ClearChannel`), partially on the // worker thread (see `PushNewMediaChannelAndDeleteChannel`) and then // eventually freed on the signaling thread. // It would be good to combine those steps with the teardown steps here.
{
ScopedOperationsBatcher network_tasks(network_thread());
sdp_handler_->GetMediaChannelTeardownTasks(network_tasks, worker_tasks);
network_tasks.AddWithFinalizer(MakeCloseOnNetworkThreadTask());
}
// The event log is used in the transport controller, which must be outlived // by the former. CreateOffer by the peer connection is implemented // asynchronously and if the peer connection is closed without resetting the // WebRTC session description factory, the session description factory would // call the transport controller.
sdp_handler_->ResetSessionDescFactory();
if (ConfiguredForMedia()) {
rtp_manager_->Close();
}
// Signal shutdown to the sdp handler. This invalidates weak pointers for // internal pending callbacks.
sdp_handler_->PrepareForShutdown();
data_channel_controller_.PrepareForShutdown();
// The .h file says that observer can be discarded after close() returns. // Make sure this is true.
observer_ = nullptr;
}
// The first connection state change to connected happens once per // connection which makes it a good point to report metrics.
if (new_state == PeerConnectionState::kConnected && !was_ever_connected_) {
was_ever_connected_ = true;
ReportFirstConnectUsageMetrics();
}
}
void PeerConnection::ReportFirstConnectUsageMetrics() { // Record bundle-policy from configuration. Done here from // connectionStateChange to limit to actually established connections.
BundlePolicyUsage policy = kBundlePolicyUsageMax; switch (configuration_.bundle_policy) { case kBundlePolicyBalanced:
policy = kBundlePolicyUsageBalanced; break; case kBundlePolicyMaxBundle:
policy = kBundlePolicyUsageMaxBundle; break; case kBundlePolicyMaxCompat:
policy = kBundlePolicyUsageMaxCompat; break;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.BundlePolicy", policy,
kBundlePolicyUsageMax);
// Record whether there was a local or remote provisional answer.
ProvisionalAnswerUsage pranswer = kProvisionalAnswerNotUsed;
if (local_description()->GetType() == SdpType::kPrAnswer) {
pranswer = kProvisionalAnswerLocal;
} else if (remote_description()->GetType() == SdpType::kPrAnswer) {
pranswer = kProvisionalAnswerRemote;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.ProvisionalAnswer", pranswer,
kProvisionalAnswerMax);
auto transport_infos = remote_description()->description()->transport_infos();
if (!transport_infos.empty()) { // Record the number of valid / invalid ice-ufrag. We do allow certain // non-spec ice-char for backward-compat reasons. At this point we know // that the ufrag/pwd consists of a valid ice-char or one of the four // not allowed characters since we have passed the IsIceChar check done // by the p2p transport description on setRemoteDescription calls. auto ice_parameters = transport_infos[0].description.GetIceParameters(); auto is_invalid_char = [](char c) { return c == '-' || c == '=' || c == '#' || c == '_';
}; bool isUsingInvalidIceCharInUfrag =
absl::c_any_of(ice_parameters.ufrag, is_invalid_char); bool isUsingInvalidIceCharInPwd =
absl::c_any_of(ice_parameters.pwd, is_invalid_char);
RTC_HISTOGRAM_BOOLEAN( "WebRTC.PeerConnection.ValidIceChars",
!(isUsingInvalidIceCharInUfrag || isUsingInvalidIceCharInPwd));
// Record whether the hash algorithm of the first transport's // DTLS fingerprint is still using SHA-1.
if (transport_infos[0].description.identity_fingerprint) {
RTC_HISTOGRAM_BOOLEAN( "WebRTC.PeerConnection.DtlsFingerprintLegacySha1",
absl::EqualsIgnoreCase(
transport_infos[0].description.identity_fingerprint->algorithm, "sha-1"));
}
}
// Record RtcpMuxPolicy setting.
RtcpMuxPolicyUsage rtcp_mux_policy = kRtcpMuxPolicyUsageMax; switch (configuration_.rtcp_mux_policy) { case kRtcpMuxPolicyNegotiate:
rtcp_mux_policy = kRtcpMuxPolicyUsageNegotiate; break; case kRtcpMuxPolicyRequire:
rtcp_mux_policy = kRtcpMuxPolicyUsageRequire; break;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.RtcpMuxPolicy",
rtcp_mux_policy, kRtcpMuxPolicyUsageMax); switch (local_description()->GetType()) { case SdpType::kOffer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.Offer.ConnectionEstablished",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kAnswer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.Answer.ConnectionEstablished",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kPrAnswer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.PrAnswer.ConnectionEstablished",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kRollback: // Rollback does not have SDP so can not be munged. break;
} bool negotiated_sctp_snap = false; const SessionDescription* desc = nullptr;
if (local_description()->GetType() == SdpType::kAnswer ||
local_description()->GetType() == SdpType::kPrAnswer) {
desc = local_description()->description();
} else if (remote_description()->GetType() == SdpType::kAnswer ||
remote_description()->GetType() == SdpType::kPrAnswer) {
desc = remote_description()->description();
}
if (!desc) {
RTC_LOG(LS_INFO) << "Connection established without an answer, local="
<< local_description()->GetType()
<< ", remote=" << remote_description()->GetType(); return;
} // Below this point, we assume that we have an answer in `desc` const ContentInfo* sctp_content = GetFirstDataContent(desc);
if (sctp_content && !sctp_content->rejected) { const SctpDataContentDescription* sctp_desc =
sctp_content->media_description()->as_sctp();
if (sctp_desc) {
negotiated_sctp_snap |= sctp_desc->sctp_init().has_value();
}
}
RTC_HISTOGRAM_BOOLEAN("WebRTC.PeerConnection.NegotiatedSctpSnap",
negotiated_sctp_snap); // Record congestion control mechanism in use, if any. // The information is taken from the last seen Answer SDP.
std::optional<RtcpFeedbackType> feedback_type;
for (constauto& content : desc->contents()) {
std::optional<RtcpFeedbackType> this_feedback_type =
content.media_description()->preferred_rtcp_cc_ack_type();
if (this_feedback_type) {
feedback_type = this_feedback_type; break;
}
}
if (!feedback_type) {
feedback_type = RtcpFeedbackType::NONE;
} // Note that NONE will be reported for datachannel-only calls. // Only TRANSPORT_CC and CCFB are currently reported.
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.NegotiatedFeedbackType", static_cast<int>(*feedback_type), static_cast<int>(RtcpFeedbackType::MAX));
}
void PeerConnection::ReportCloseUsageMetrics() {
if (!was_ever_connected_) { return;
}
RTC_DCHECK(local_description());
RTC_DCHECK(sdp_handler_); switch (local_description()->GetType()) { case SdpType::kOffer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.Offer.ConnectionClosed",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kAnswer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.Answer.ConnectionClosed",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kPrAnswer:
RTC_HISTOGRAM_ENUMERATION( "WebRTC.PeerConnection.SdpMunging.PrAnswer.ConnectionClosed",
sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); break; case SdpType::kRollback: // Rollback does not have SDP so can not be munged. break;
}
}
port_allocator_->Initialize(); // To handle both internal and externally created port allocator, we will // enable BUNDLE here.
int port_allocator_flags = port_allocator_->flags();
port_allocator_flags |= PORTALLOCATOR_ENABLE_SHARED_SOCKET |
PORTALLOCATOR_ENABLE_IPV6 |
PORTALLOCATOR_ENABLE_IPV6_ON_WIFI;
if (trials().IsDisabled("WebRTC-IPv6Default")) {
port_allocator_flags &= ~(PORTALLOCATOR_ENABLE_IPV6);
}
if (configuration.disable_ipv6_on_wifi) {
port_allocator_flags &= ~(PORTALLOCATOR_ENABLE_IPV6_ON_WIFI);
RTC_LOG(LS_INFO) << "IPv6 candidates on Wi-Fi are disabled.";
}
if (configuration.tcp_candidate_policy == kTcpCandidatePolicyDisabled) {
port_allocator_flags |= PORTALLOCATOR_DISABLE_TCP;
RTC_LOG(LS_INFO) << "TCP candidates are disabled.";
}
if (configuration.candidate_network_policy ==
kCandidateNetworkPolicyLowCost) {
port_allocator_flags |= PORTALLOCATOR_DISABLE_COSTLY_NETWORKS;
RTC_LOG(LS_INFO) << "Do not gather candidates on high-cost networks";
}
if (configuration.disable_link_local_networks) {
port_allocator_flags |= PORTALLOCATOR_DISABLE_LINK_LOCAL_NETWORKS;
RTC_LOG(LS_INFO) << "Disable candidates on link-local network interfaces.";
}
port_allocator_->set_flags(port_allocator_flags); // No step delay is used while allocating ports.
port_allocator_->set_step_delay(kMinimumStepDelay);
port_allocator_->SetCandidateFilter(
ConvertIceTransportTypeToCandidateFilter(configuration.type));
port_allocator_->set_max_ipv6_networks(configuration.max_ipv6_networks);
auto turn_servers_copy = turn_servers;
for (auto& turn_server : turn_servers_copy) {
turn_server.tls_cert_verifier = tls_cert_verifier_.get();
} // Call this last since it may create pooled allocator sessions using the // properties set above.
port_allocator_->SetConfiguration(
stun_servers, std::move(turn_servers_copy),
configuration.ice_candidate_pool_size,
configuration.GetTurnPortPrunePolicy(), configuration.turn_customizer,
configuration.stun_candidate_keepalive_interval);
bool PeerConnection::ReconfigurePortAllocator_n( const ServerAddresses& stun_servers, const std::vector<RelayServerConfig>& turn_servers,
IceTransportsType type,
int candidate_pool_size,
PortPrunePolicy turn_port_prune_policy,
TurnCustomizer* turn_customizer,
std::optional<int> stun_candidate_keepalive_interval, bool have_local_description) {
RTC_DCHECK_RUN_ON(network_thread());
port_allocator_->SetCandidateFilter(
ConvertIceTransportTypeToCandidateFilter(type)); // Add the custom tls turn servers if they exist. auto turn_servers_copy = turn_servers;
for (auto& turn_server : turn_servers_copy) {
turn_server.tls_cert_verifier = tls_cert_verifier_.get();
} // Call this last since it may create pooled allocator sessions using the // candidate filter set above. return port_allocator_->SetConfiguration(
stun_servers, std::move(turn_servers_copy), candidate_pool_size,
turn_port_prune_policy, turn_customizer,
stun_candidate_keepalive_interval);
}
bool PeerConnection::GetSslRole(const std::string& content_name,
SSLRole* role) {
RTC_DCHECK_RUN_ON(signaling_thread());
if (!local_description() || !remote_description()) {
RTC_LOG(LS_INFO)
<< "Local and Remote descriptions must be applied to get the " "SSL Role of the session."; return false;
}
auto dtls_role = transport_controller_s()->GetDtlsRole(content_name);
if (dtls_role) {
*role = *dtls_role; returntrue;
} return false;
}
void PeerConnection::OnTransportControllerConnectionState(
webrtc::IceConnectionState state) { switch (state) { case webrtc::kIceConnectionConnecting: // If the current state is Connected or Completed, then there were // writable channels but now there are not, so the next state must // be Disconnected. // kIceConnectionConnecting is currently used as the default, // un-connected state by the TransportController, so its only use is // detecting disconnections.
if (ice_connection_state_ ==
PeerConnectionInterface::kIceConnectionConnected ||
ice_connection_state_ ==
PeerConnectionInterface::kIceConnectionCompleted) {
SetIceConnectionState(
PeerConnectionInterface::kIceConnectionDisconnected);
} break; case webrtc::kIceConnectionFailed:
SetIceConnectionState(PeerConnectionInterface::kIceConnectionFailed); break; case webrtc::kIceConnectionConnected:
RTC_LOG(LS_INFO) << "Changing to ICE connected state because " "all transports are writable.";
{
std::vector<std::pair<std::string, MediaType>> transceiver_info;
if (ConfiguredForMedia()) {
for (constauto& t : rtp_manager()->transceivers()->List()) {
if (t->internal()->HasChannel()) {
std::optional<std::string> mid = t->mid();
if (mid) {
transceiver_info.emplace_back(*mid, t->media_type());
}
}
}
}
SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
NoteUsageEvent(UsageEvent::ICE_STATE_CONNECTED); break; case webrtc::kIceConnectionCompleted:
RTC_LOG(LS_INFO) << "Changing to ICE completed state because " "all transports are complete.";
if (ice_connection_state_ !=
PeerConnectionInterface::kIceConnectionConnected) { // If jumping directly from "checking" to "connected", // signal "connected" first.
SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
}
SetIceConnectionState(PeerConnectionInterface::kIceConnectionCompleted);
void PeerConnection::OnTransportControllerCandidatesGathered(
absl::string_view transport_name, const Candidates& candidates) { // TODO(bugs.webrtc.org/12427): Expect this to come in on the network thread // (not signaling as it currently does), handle appropriately.
int sdp_mline_index;
if (!GetLocalCandidateMediaIndex(transport_name, &sdp_mline_index)) {
RTC_LOG(LS_ERROR)
<< "OnTransportControllerCandidatesGathered: content name "
<< transport_name << " not found"; return;
}
for (Candidates::const_iterator citer = candidates.begin();
citer != candidates.end(); ++citer) { // Use transport_name as the candidate media id.
std::unique_ptr<IceCandidate> candidate(
new IceCandidate(transport_name, sdp_mline_index, *citer));
sdp_handler_->AddLocalIceCandidate(candidate.get());
OnIceCandidate(std::move(candidate));
}
}
// Returns the media index for a local ice candidate given the content name. bool PeerConnection::GetLocalCandidateMediaIndex(absl::string_view content_name,
int* sdp_mline_index) {
if (!local_description() || !sdp_mline_index) { return false;
}
bool content_found = false; const ContentInfos& contents = local_description()->description()->contents();
for (size_t index = 0; index < contents.size(); ++index) {
if (contents[index].mid() == content_name) {
*sdp_mline_index = static_cast<int>(index);
content_found = true; break;
}
} return content_found;
}
std::optional<std::string> PeerConnection::SetupDataChannelTransport_n(
absl::string_view mid) {
sctp_mid_n_ = std::string(mid);
DataChannelTransportInterface* transport =
transport_controller_->GetDataChannelTransport(*sctp_mid_n_);
if (!transport) { #ifndef WEBRTC_HAVE_SCTP
RTC_LOG(LS_ERROR) << "Data channel transport is not available"
<< " as WebRTC has been compiled without SCTP support " "(WEBRTC_HAVE_SCTP), mid="
<< mid; #else
RTC_LOG(LS_ERROR)
<< "Data channel transport is not available for data channels, mid="
<< mid; #endif
sctp_mid_n_ = std::nullopt; return std::nullopt;
}
std::optional<std::string> transport_name;
DtlsTransportInternal* dtls_transport =
transport_controller_->GetDtlsTransport(*sctp_mid_n_);
if (dtls_transport) {
transport_name = dtls_transport->transport_name();
} else { // Make sure we still set a valid string.
transport_name = std::string("");
}
void PeerConnection::TeardownDataChannelTransport_n(RTCError error) {
if (sctp_mid_n_) { // `sctp_mid_` may still be active through an SCTP transport. If not, unset // it.
RTC_LOG(LS_INFO) << "Tearing down data channel transport for mid="
<< *sctp_mid_n_;
sctp_mid_n_.reset();
}
// Returns false if bundle is enabled and rtcp_mux is disabled. bool PeerConnection::ValidateBundleSettings( const SessionDescription* desc, const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
if (bundle_groups_by_mid.empty()) returntrue;
const ContentInfos& contents = desc->contents();
for (ContentInfos::const_iterator citer = contents.begin();
citer != contents.end(); ++citer) { const ContentInfo* content = (&*citer);
RTC_DCHECK(content != nullptr); auto it = bundle_groups_by_mid.find(content->mid());
if (it != bundle_groups_by_mid.end() &&
!(content->rejected || content->bundle_only) &&
content->type == MediaProtocolType::kRtp) {
if (!HasRtcpMuxEnabled(content)) return false;
}
} // RTCP-MUX is enabled in all the contents. returntrue;
}
// Asynchronously adds remote candidates on the network thread. void PeerConnection::AddRemoteCandidate(absl::string_view mid, const Candidate& candidate) {
RTC_DCHECK_RUN_ON(signaling_thread());
if (candidate.network_type() != ADAPTER_TYPE_UNKNOWN) {
RTC_DLOG(LS_WARNING) << "Using candidate with adapter type set - this " "should only happen in test";
}
// Clear fields that do not make sense as remote candidates.
Candidate new_candidate(candidate);
new_candidate.set_network_type(ADAPTER_TYPE_UNKNOWN);
new_candidate.set_relay_protocol("");
new_candidate.set_underlying_type_for_vpn(ADAPTER_TYPE_UNKNOWN);
new_candidate.set_network_slice(NetworkSlice::NO_SLICE);
network_thread()->PostTask(SafeTask(
network_thread_safety_,
[this, mid = std::string(mid), candidate = new_candidate] {
RTC_DCHECK_RUN_ON(network_thread());
std::vector<Candidate> candidates = {candidate};
RTCError error =
transport_controller_->AddRemoteCandidates(mid, candidates);
if (error.ok()) {
signaling_thread()->PostTask(SafeTask(
signaling_thread_safety_.flag(),
[this, candidate = std::move(candidate)] {
ReportRemoteIceCandidateAdded(candidate); // Candidates successfully submitted for checking.
if (ice_connection_state() ==
PeerConnectionInterface::kIceConnectionNew ||
ice_connection_state() ==
PeerConnectionInterface::kIceConnectionDisconnected) { // If state is New, then the session has just gotten its first // remote ICE candidates, so go to Checking. If state is // Disconnected, the session is re-using old candidates or // receiving additional ones, so go to Checking. If state is // Connected, stay Connected. // TODO(bemasc): If state is Connected, and the new candidates // are for a newly added transport, then the state actually // _should_ move to checking. Add a way to distinguish that // case.
SetIceConnectionState(
PeerConnectionInterface::kIceConnectionChecking);
} // TODO(bemasc): If state is Completed, go back to Connected.
}));
} else {
RTC_LOG(LS_WARNING) << error.message();
}
}));
}
if (sctp_mid_n_) {
DtlsTransportInternal* dtls_transport =
transport_controller_->GetDtlsTransport(*sctp_mid_n_);
if (dtls_transport) {
media_types_by_transport_name[dtls_transport->transport_name()].insert(
MediaType::DATA);
}
}
for (constauto& entry : media_types_by_transport_name) {
TransportStats stats;
if (transport_controller_->GetStats(entry.first, &stats)) {
ReportBestConnectionState(stats);
ReportNegotiatedCiphers(dtls_enabled_, stats, entry.second);
}
}
}
// Walk through the ConnectionInfos to gather best connection usage // for IPv4 and IPv6. // static (no member state required) void PeerConnection::ReportBestConnectionState(const TransportStats& stats) {
for (const TransportChannelStats& channel_stats : stats.channel_stats) {
for (const ConnectionInfo& connection_info :
channel_stats.ice_transport_stats.connection_infos) {
if (!connection_info.best_connection) { continue;
}
const Candidate& local = connection_info.local_candidate; const Candidate& remote = connection_info.remote_candidate;
// Increment the counter for IceCandidatePairType.
if (local.protocol() == TCP_PROTOCOL_NAME ||
(local.is_relay() && local.relay_protocol() == TCP_PROTOCOL_NAME)) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_TCP",
GetIceCandidatePairType(local, remote),
kIceCandidatePairMax);
} else if (local.protocol() == UDP_PROTOCOL_NAME) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_UDP",
GetIceCandidatePairType(local, remote),
kIceCandidatePairMax);
} else {
RTC_LOG(LS_WARNING) << "ReportBestConnectionState: No histogram for "
<< local.protocol();
}
// Increment the counter for IP type.
if (local.address().family() == AF_INET) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics",
kBestConnections_IPv4,
kPeerConnectionAddressFamilyCounter_Max);
} else if (local.address().family() == AF_INET6) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics",
kBestConnections_IPv6,
kPeerConnectionAddressFamilyCounter_Max);
} else {
RTC_CHECK(!local.address().hostname().empty() &&
local.address().IsUnresolvedIP());
}
absl::AnyInvocable<void(const RtpPacketReceived& parsed_packet) const>
PeerConnection::InitializeUnDemuxablePacketHandler() { return [this](const RtpPacketReceived& parsed_packet) {
RTC_DCHECK_RUN_ON(network_thread()); // Deliver the packet anyway to Call to allow Call to do BWE. // Even if there is no media receiver, the packet has still // been received on the network and has been correctly parsed.
call_ptr_->Receiver()->DeliverRtpPacket(
MediaType::ANY, parsed_packet, /*undemuxable_packet_handler=*/
[](const RtpPacketReceived& packet) { return false; });
};
}
¤ 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.0.111Bemerkung:
¤
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.