| // Copyright 2018 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. |
| |
| #ifndef CHROMEOS_SERVICES_SECURE_CHANNEL_CONNECTION_ATTEMPT_BASE_H_ |
| #define CHROMEOS_SERVICES_SECURE_CHANNEL_CONNECTION_ATTEMPT_BASE_H_ |
| |
| #include "base/containers/flat_map.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/stl_util.h" |
| #include "base/time/default_clock.h" |
| #include "chromeos/components/multidevice/logging/logging.h" |
| #include "chromeos/services/secure_channel/authenticated_channel.h" |
| #include "chromeos/services/secure_channel/connect_to_device_operation.h" |
| #include "chromeos/services/secure_channel/connection_attempt.h" |
| #include "chromeos/services/secure_channel/connection_attempt_details.h" |
| #include "chromeos/services/secure_channel/connection_details.h" |
| #include "chromeos/services/secure_channel/pending_connection_request.h" |
| #include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h" |
| #include "chromeos/services/secure_channel/public/mojom/secure_channel.mojom.h" |
| |
| namespace chromeos { |
| |
| namespace secure_channel { |
| |
| class AuthenticatedChannel; |
| |
| // ConnectionAttempt implementation which stays active for as long as at least |
| // one of its requests has not yet completed. While a ConnectionAttemptBase is |
| // active, it starts one or more operations to connect to the device. If an |
| // operation succeeds in connecting, the ConnectionAttempt notifies its delegate |
| // of success. |
| // |
| // If an operation fails to connect, ConnectionAttemptBase alerts each of its |
| // PendingConnectionRequests of the failure to connect. Each request can |
| // decide to give up connecting due to the client canceling the request or |
| // due to handling too many failures of individual operations. A |
| // ConnectionAttemptBase alerts its delegate of a failure if all of its |
| // associated PendingConnectionRequests have given up trying to connect. |
| // |
| // When an operation fails but there still exist active requests, |
| // ConnectionAttempt simply starts up a new operation and retries the |
| // connection. |
| template <typename FailureDetailType> |
| class ConnectionAttemptBase : public ConnectionAttempt<FailureDetailType> { |
| protected: |
| ConnectionAttemptBase( |
| ConnectionAttemptDelegate* delegate, |
| const ConnectionAttemptDetails& connection_attempt_details, |
| base::Clock* clock = base::DefaultClock::GetInstance()) |
| : ConnectionAttempt<FailureDetailType>(delegate, |
| clock, |
| connection_attempt_details), |
| weak_ptr_factory_(this) {} |
| |
| ~ConnectionAttemptBase() override { |
| if (operation_) |
| operation_->Cancel(); |
| } |
| |
| virtual std::unique_ptr<ConnectToDeviceOperation<FailureDetailType>> |
| CreateConnectToDeviceOperation( |
| const DeviceIdPair& device_id_pair, |
| ConnectionPriority connection_priority, |
| typename ConnectToDeviceOperation< |
| FailureDetailType>::ConnectionSuccessCallback success_callback, |
| const typename ConnectToDeviceOperation< |
| FailureDetailType>::ConnectionFailedCallback& failure_callback) = 0; |
| |
| private: |
| // ConnectionAttempt<FailureDetailType>: |
| void ProcessAddingNewConnectionRequest( |
| std::unique_ptr<PendingConnectionRequest<FailureDetailType>> request) |
| override { |
| ConnectionPriority priority_before_add = |
| GetHighestRemainingConnectionPriority(); |
| |
| if (base::ContainsKey(id_to_request_map_, request->GetRequestId())) { |
| PA_LOG(ERROR) << "ConnectionAttemptBase::" |
| << "ProcessAddingNewConnectionRequest(): Processing " |
| << "request whose ID has already been processed."; |
| NOTREACHED(); |
| } |
| |
| bool was_empty = id_to_request_map_.empty(); |
| id_to_request_map_[request->GetRequestId()] = std::move(request); |
| |
| // In the case that this ConnectionAttempt was just created and had not yet |
| // received a request yet, start up its operation. |
| if (was_empty) { |
| operation_ = CreateConnectToDeviceOperation( |
| this->connection_attempt_details().device_id_pair(), |
| GetHighestRemainingConnectionPriority(), |
| base::BindOnce( |
| &ConnectionAttemptBase< |
| FailureDetailType>::OnConnectToDeviceOperationSuccess, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindRepeating( |
| &ConnectionAttemptBase< |
| FailureDetailType>::OnConnectToDeviceOperationFailure, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| ConnectionPriority priority_after_add = |
| GetHighestRemainingConnectionPriority(); |
| if (priority_before_add != priority_after_add) |
| operation_->UpdateConnectionPriority(priority_after_add); |
| } |
| |
| std::vector<std::unique_ptr<ClientConnectionParameters>> |
| ExtractClientConnectionParameters() override { |
| std::vector<std::unique_ptr<ClientConnectionParameters>> data_list; |
| for (auto& map_entry : id_to_request_map_) { |
| data_list.push_back( |
| PendingConnectionRequest<FailureDetailType>:: |
| ExtractClientConnectionParameters(std::move(map_entry.second))); |
| } |
| return data_list; |
| } |
| |
| // PendingConnectionRequestDelegate: |
| void OnRequestFinishedWithoutConnection( |
| const base::UnguessableToken& request_id, |
| PendingConnectionRequestDelegate::FailedConnectionReason reason) |
| override { |
| ConnectionPriority priority_before_removal = |
| GetHighestRemainingConnectionPriority(); |
| |
| size_t removed_element_count = id_to_request_map_.erase(request_id); |
| if (removed_element_count != 1) { |
| DCHECK(removed_element_count == 0); |
| PA_LOG(ERROR) << "ConnectionAttemptBase::" |
| << "OnRequestFinishedWithoutConnection(): Request " |
| << "finished, but it was missing from the map."; |
| } |
| |
| ConnectionPriority priority_after_removal = |
| GetHighestRemainingConnectionPriority(); |
| if (priority_before_removal != priority_after_removal) |
| operation_->UpdateConnectionPriority(priority_after_removal); |
| |
| // If there are no longer any active entries, this attempt is finished. |
| if (id_to_request_map_.empty()) |
| this->OnConnectionAttemptFinishedWithoutConnection(); |
| } |
| |
| void OnConnectToDeviceOperationSuccess( |
| std::unique_ptr<AuthenticatedChannel> authenticated_channel) { |
| DCHECK(operation_); |
| operation_.reset(); |
| this->OnConnectionAttemptSucceeded(std::move(authenticated_channel)); |
| } |
| |
| void OnConnectToDeviceOperationFailure(FailureDetailType failure_detail) { |
| for (auto& map_entry : id_to_request_map_) |
| map_entry.second->HandleConnectionFailure(failure_detail); |
| } |
| |
| ConnectionPriority GetHighestRemainingConnectionPriority() { |
| ConnectionPriority highest_priority = ConnectionPriority::kLow; |
| for (const auto& map_entry : id_to_request_map_) { |
| if (map_entry.second->connection_priority() > highest_priority) |
| highest_priority = map_entry.second->connection_priority(); |
| } |
| return highest_priority; |
| } |
| |
| std::unique_ptr<ConnectToDeviceOperation<FailureDetailType>> operation_; |
| base::flat_map<base::UnguessableToken, |
| std::unique_ptr<PendingConnectionRequest<FailureDetailType>>> |
| id_to_request_map_; |
| |
| base::WeakPtrFactory<ConnectionAttemptBase<FailureDetailType>> |
| weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ConnectionAttemptBase); |
| }; |
| |
| } // namespace secure_channel |
| |
| } // namespace chromeos |
| |
| #endif // CHROMEOS_SERVICES_SECURE_CHANNEL_CONNECTION_ATTEMPT_BASE_H_ |