blob: 708711edfde64dfb00120d81a81649f14b9b6ae9 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/signaling/muxing_signal_strategy.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/timer/timer.h"
#include "remoting/signaling/signaling_address.h"
#include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
namespace remoting {
namespace {
constexpr base::TimeDelta kWaitForAllStrategiesConnectedTimeout =
base::TimeDelta::FromSeconds(5);
} // namespace
class MuxingSignalStrategy::Core final : public SignalStrategy::Listener {
public:
Core(std::unique_ptr<SignalStrategy> ftl_signal_strategy,
std::unique_ptr<SignalStrategy> xmpp_signal_strategy);
~Core() override;
void Invalidate();
void Connect();
State GetState() const;
const SignalingAddress& GetLocalAddress() const;
void AddListener(SignalStrategy::Listener* listener);
void RemoveListener(SignalStrategy::Listener* listener);
bool SendStanza(std::unique_ptr<jingle_xmpp::XmlElement> stanza);
SignalStrategy* ftl_signal_strategy() { return ftl_signal_strategy_.get(); }
SignalStrategy* xmpp_signal_strategy() { return xmpp_signal_strategy_.get(); }
private:
enum class MuxingState {
ALL_DISCONNECTED,
SOME_CONNECTING,
ONLY_ONE_CONNECTED_BEFORE_TIMEOUT,
ALL_CONNECTED,
ONLY_ONE_CONNECTED_AFTER_TIMEOUT,
};
SignalStrategy* GetSignalStrategyForStanza(
const jingle_xmpp::XmlElement* stanza);
// Returns true if the state is updated.
bool UpdateState();
void OnWaitForAllStrategiesConnectedTimeout();
// SignalStrategy::Listener implementations.
void OnSignalStrategyStateChange(SignalStrategy::State state) override;
bool OnSignalStrategyIncomingStanza(
const jingle_xmpp::XmlElement* stanza) override;
bool IsAnyStrategyConnected() const;
bool IsEveryStrategyConnected() const;
bool IsEveryStrategyDisconnected() const;
base::ObserverList<SignalStrategy::Listener> listeners_;
std::unique_ptr<SignalStrategy> ftl_signal_strategy_;
std::unique_ptr<SignalStrategy> xmpp_signal_strategy_;
SignalingAddress current_local_address_;
MuxingState state_ = MuxingState::ALL_DISCONNECTED;
base::OneShotTimer wait_for_all_strategies_connected_timeout_timer_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<Core> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
MuxingSignalStrategy::Core::Core(
std::unique_ptr<SignalStrategy> ftl_signal_strategy,
std::unique_ptr<SignalStrategy> xmpp_signal_strategy)
: weak_factory_(this) {
ftl_signal_strategy_ = std::move(ftl_signal_strategy);
xmpp_signal_strategy_ = std::move(xmpp_signal_strategy);
DCHECK(ftl_signal_strategy_);
DCHECK(xmpp_signal_strategy_);
DCHECK_EQ(State::DISCONNECTED, ftl_signal_strategy_->GetState());
DCHECK_EQ(State::DISCONNECTED, xmpp_signal_strategy_->GetState());
ftl_signal_strategy_->AddListener(this);
xmpp_signal_strategy_->AddListener(this);
UpdateState();
}
MuxingSignalStrategy::Core::~Core() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!ftl_signal_strategy_);
DCHECK(!xmpp_signal_strategy_);
}
void MuxingSignalStrategy::Core::Invalidate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
wait_for_all_strategies_connected_timeout_timer_.AbandonAndStop();
ftl_signal_strategy_->RemoveListener(this);
xmpp_signal_strategy_->RemoveListener(this);
ftl_signal_strategy_.reset();
xmpp_signal_strategy_.reset();
}
void MuxingSignalStrategy::Core::Connect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ftl_signal_strategy_->Connect();
xmpp_signal_strategy_->Connect();
}
SignalStrategy::State MuxingSignalStrategy::Core::GetState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (state_) {
case MuxingState::ALL_DISCONNECTED:
return State::DISCONNECTED;
case MuxingState::SOME_CONNECTING:
case MuxingState::ONLY_ONE_CONNECTED_BEFORE_TIMEOUT:
return State::CONNECTING;
case MuxingState::ONLY_ONE_CONNECTED_AFTER_TIMEOUT:
case MuxingState::ALL_CONNECTED:
return State::CONNECTED;
default:
NOTREACHED();
return State::DISCONNECTED;
}
}
const SignalingAddress& MuxingSignalStrategy::Core::GetLocalAddress() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!current_local_address_.empty())
<< "GetLocalAddress() can only be called inside "
<< "OnSignalStrategyIncomingStanza().";
return current_local_address_;
}
void MuxingSignalStrategy::Core::AddListener(
SignalStrategy::Listener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
listeners_.AddObserver(listener);
}
void MuxingSignalStrategy::Core::RemoveListener(
SignalStrategy::Listener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
listeners_.RemoveObserver(listener);
}
bool MuxingSignalStrategy::Core::SendStanza(
std::unique_ptr<jingle_xmpp::XmlElement> stanza) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SignalStrategy* strategy = GetSignalStrategyForStanza(stanza.get());
if (!strategy) {
return false;
}
return strategy->SendStanza(std::move(stanza));
}
SignalStrategy* MuxingSignalStrategy::Core::GetSignalStrategyForStanza(
const jingle_xmpp::XmlElement* stanza) {
std::string error;
SignalingAddress receiver =
SignalingAddress::Parse(stanza, SignalingAddress::TO, &error);
if (!error.empty()) {
LOG(DFATAL) << "Failed to parse receiver address: " << error;
return nullptr;
}
if (receiver.channel() == SignalingAddress::Channel::FTL) {
DCHECK(ftl_signal_strategy_->GetLocalAddress().empty() ||
ftl_signal_strategy_->GetLocalAddress().channel() ==
SignalingAddress::Channel::FTL)
<< "|ftl_signal_strategy_|'s local address channel is not FTL. "
<< "You might have flipped the signal strategies. "
<< "Local address: " << ftl_signal_strategy_->GetLocalAddress().jid();
return ftl_signal_strategy_.get();
} else {
DCHECK(xmpp_signal_strategy_->GetLocalAddress().empty() ||
xmpp_signal_strategy_->GetLocalAddress().channel() !=
SignalingAddress::Channel::FTL)
<< "|xmpp_signal_strategy_|'s local address channel is FTL. "
<< "You might have flipped the signal strategies. "
<< "Local address: " << xmpp_signal_strategy_->GetLocalAddress().jid();
}
return xmpp_signal_strategy_.get();
}
bool MuxingSignalStrategy::Core::UpdateState() {
MuxingState new_state = state_;
if (IsEveryStrategyConnected()) {
wait_for_all_strategies_connected_timeout_timer_.AbandonAndStop();
new_state = MuxingState::ALL_CONNECTED;
} else if (IsEveryStrategyDisconnected()) {
wait_for_all_strategies_connected_timeout_timer_.AbandonAndStop();
new_state = MuxingState::ALL_DISCONNECTED;
} else if (IsAnyStrategyConnected()) {
if (state_ == MuxingState::ALL_CONNECTED // One connection is dropped
|| (state_ == MuxingState::ONLY_ONE_CONNECTED_BEFORE_TIMEOUT &&
!wait_for_all_strategies_connected_timeout_timer_.IsRunning())) {
new_state = MuxingState::ONLY_ONE_CONNECTED_AFTER_TIMEOUT;
} else if (state_ != MuxingState::ONLY_ONE_CONNECTED_AFTER_TIMEOUT) {
new_state = MuxingState::ONLY_ONE_CONNECTED_BEFORE_TIMEOUT;
if (!wait_for_all_strategies_connected_timeout_timer_.IsRunning()) {
wait_for_all_strategies_connected_timeout_timer_.Start(
FROM_HERE, kWaitForAllStrategiesConnectedTimeout, this,
&MuxingSignalStrategy::Core::
OnWaitForAllStrategiesConnectedTimeout);
}
}
// Otherwise we are not changing the state unless all strategies are
// connected or all strategies are disconnected.
} else {
new_state = MuxingState::SOME_CONNECTING;
}
if (state_ == new_state) {
return false;
}
state_ = new_state;
return true;
}
void MuxingSignalStrategy::Core::OnWaitForAllStrategiesConnectedTimeout() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!IsEveryStrategyConnected()) {
LOG(WARNING) << "Timeout waiting for all strategies to be connected.";
OnSignalStrategyStateChange(/* unused */ State::CONNECTED);
}
}
void MuxingSignalStrategy::Core::OnSignalStrategyStateChange(
SignalStrategy::State unused) {
bool is_state_changed = UpdateState();
if (is_state_changed) {
for (auto& listener : listeners_) {
listener.OnSignalStrategyStateChange(GetState());
}
}
}
bool MuxingSignalStrategy::Core::OnSignalStrategyIncomingStanza(
const jingle_xmpp::XmlElement* stanza) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SignalStrategy* strategy = GetSignalStrategyForStanza(stanza);
DCHECK(current_local_address_.empty());
current_local_address_ = strategy->GetLocalAddress();
bool message_handled = false;
for (auto& listener : listeners_) {
if (listener.OnSignalStrategyIncomingStanza(stanza)) {
message_handled = true;
break;
}
}
current_local_address_ = SignalingAddress();
return message_handled;
}
bool MuxingSignalStrategy::Core::IsAnyStrategyConnected() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return xmpp_signal_strategy_->GetState() == State::CONNECTED ||
ftl_signal_strategy_->GetState() == State::CONNECTED;
}
bool MuxingSignalStrategy::Core::IsEveryStrategyConnected() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return xmpp_signal_strategy_->GetState() == State::CONNECTED &&
ftl_signal_strategy_->GetState() == State::CONNECTED;
}
bool MuxingSignalStrategy::Core::IsEveryStrategyDisconnected() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return xmpp_signal_strategy_->GetState() == State::DISCONNECTED &&
ftl_signal_strategy_->GetState() == State::DISCONNECTED;
}
MuxingSignalStrategy::MuxingSignalStrategy(
std::unique_ptr<SignalStrategy> ftl_signal_strategy,
std::unique_ptr<SignalStrategy> xmpp_signal_strategy)
: ftl_signal_strategy_(std::move(ftl_signal_strategy)),
xmpp_signal_strategy_(std::move(xmpp_signal_strategy)) {}
MuxingSignalStrategy::~MuxingSignalStrategy() {
if (!core_) {
// The core has never been created. Just do nothing.
return;
}
GetCore()->Invalidate();
base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE,
std::move(core_));
}
void MuxingSignalStrategy::Connect() {
GetCore()->Connect();
}
SignalStrategy::State MuxingSignalStrategy::GetState() const {
return GetCore()->GetState();
}
const SignalingAddress& MuxingSignalStrategy::GetLocalAddress() const {
return GetCore()->GetLocalAddress();
}
void MuxingSignalStrategy::AddListener(SignalStrategy::Listener* listener) {
GetCore()->AddListener(listener);
}
void MuxingSignalStrategy::RemoveListener(SignalStrategy::Listener* listener) {
GetCore()->RemoveListener(listener);
}
bool MuxingSignalStrategy::SendStanza(
std::unique_ptr<jingle_xmpp::XmlElement> stanza) {
return GetCore()->SendStanza(std::move(stanza));
}
std::string MuxingSignalStrategy::GetNextId() {
return base::NumberToString(base::RandUint64());
}
SignalStrategy* MuxingSignalStrategy::ftl_signal_strategy() {
return GetCore()->ftl_signal_strategy();
}
SignalStrategy* MuxingSignalStrategy::xmpp_signal_strategy() {
return GetCore()->xmpp_signal_strategy();
}
void MuxingSignalStrategy::Disconnect() {
NOTREACHED();
}
SignalStrategy::Error MuxingSignalStrategy::GetError() const {
NOTREACHED();
return Error::NETWORK_ERROR;
}
MuxingSignalStrategy::Core* MuxingSignalStrategy::GetCore() {
return GetCoreImpl();
}
const MuxingSignalStrategy::Core* MuxingSignalStrategy::GetCore() const {
return const_cast<MuxingSignalStrategy*>(this)->GetCoreImpl();
}
MuxingSignalStrategy::Core* MuxingSignalStrategy::GetCoreImpl() {
if (!core_) {
core_ = std::make_unique<Core>(std::move(ftl_signal_strategy_),
std::move(xmpp_signal_strategy_));
}
return core_.get();
}
} // namespace remoting