| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/components/cdm_factory_daemon/output_protection_impl.h" |
| |
| #include <utility> |
| |
| #include "ash/shell.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "ui/display/manager/display_configurator.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/types/display_constants.h" |
| |
| namespace chromeos { |
| namespace { |
| // Make sure the mapping between the Mojo enums and the Chrome enums do not |
| // fall out of sync. |
| #define VALIDATE_ENUM(mojo_type, chrome_type, name) \ |
| static_assert( \ |
| static_cast<uint32_t>(cdm::mojom::OutputProtection::mojo_type::name) == \ |
| display::chrome_type##_##name, \ |
| #chrome_type "_" #name "value doesn't match") |
| |
| VALIDATE_ENUM(ProtectionType, CONTENT_PROTECTION_METHOD, NONE); |
| VALIDATE_ENUM(ProtectionType, CONTENT_PROTECTION_METHOD, HDCP_TYPE_0); |
| VALIDATE_ENUM(ProtectionType, CONTENT_PROTECTION_METHOD, HDCP_TYPE_1); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, NONE); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, UNKNOWN); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, INTERNAL); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, VGA); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, HDMI); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, DVI); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, DISPLAYPORT); |
| VALIDATE_ENUM(LinkType, DISPLAY_CONNECTION_TYPE, NETWORK); |
| |
| static_assert(display::DISPLAY_CONNECTION_TYPE_LAST == |
| display::DISPLAY_CONNECTION_TYPE_NETWORK, |
| "DISPLAY_CONNECTION_TYPE_LAST value doesn't match"); |
| |
| constexpr uint32_t kUnprotectableConnectionTypes = |
| display::DISPLAY_CONNECTION_TYPE_UNKNOWN | |
| display::DISPLAY_CONNECTION_TYPE_VGA | |
| display::DISPLAY_CONNECTION_TYPE_NETWORK; |
| |
| constexpr uint32_t kProtectableConnectionTypes = |
| display::DISPLAY_CONNECTION_TYPE_HDMI | |
| display::DISPLAY_CONNECTION_TYPE_DVI | |
| display::DISPLAY_CONNECTION_TYPE_DISPLAYPORT; |
| |
| std::vector<int64_t> GetDisplayIdsFromSnapshots( |
| const std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>>& |
| snapshots) { |
| std::vector<int64_t> display_ids; |
| for (display::DisplaySnapshot* ds : snapshots) { |
| display_ids.push_back(ds->display_id()); |
| } |
| return display_ids; |
| } |
| |
| cdm::mojom::OutputProtection::ProtectionType ConvertProtection( |
| uint32_t protection_mask) { |
| // Only return Type 1 if that is the only type active since we want to reflect |
| // the overall output security. |
| if ((protection_mask & display::kContentProtectionMethodHdcpAll) == |
| display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_1) { |
| return cdm::mojom::OutputProtection::ProtectionType::HDCP_TYPE_1; |
| } else if (protection_mask & display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_0) { |
| return cdm::mojom::OutputProtection::ProtectionType::HDCP_TYPE_0; |
| } else { |
| return cdm::mojom::OutputProtection::ProtectionType::NONE; |
| } |
| } |
| |
| class DisplaySystemDelegateImpl |
| : public OutputProtectionImpl::DisplaySystemDelegate { |
| public: |
| DisplaySystemDelegateImpl() { |
| display_configurator_ = |
| ash::Shell::Get()->display_manager()->configurator(); |
| DCHECK(display_configurator_); |
| content_protection_manager_ = |
| display_configurator_->content_protection_manager(); |
| DCHECK(content_protection_manager_); |
| } |
| ~DisplaySystemDelegateImpl() override = default; |
| |
| void ApplyContentProtection( |
| display::ContentProtectionManager::ClientId client_id, |
| int64_t display_id, |
| uint32_t protection_mask, |
| display::ContentProtectionManager::ApplyContentProtectionCallback |
| callback) override { |
| content_protection_manager_->ApplyContentProtection( |
| client_id, display_id, protection_mask, std::move(callback)); |
| } |
| void QueryContentProtection( |
| display::ContentProtectionManager::ClientId client_id, |
| int64_t display_id, |
| display::ContentProtectionManager::QueryContentProtectionCallback |
| callback) override { |
| content_protection_manager_->QueryContentProtection(client_id, display_id, |
| std::move(callback)); |
| } |
| display::ContentProtectionManager::ClientId RegisterClient() override { |
| return content_protection_manager_->RegisterClient(); |
| } |
| void UnregisterClient( |
| display::ContentProtectionManager::ClientId client_id) override { |
| content_protection_manager_->UnregisterClient(client_id); |
| } |
| const std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>>& |
| cached_displays() const override { |
| return display_configurator_->cached_displays(); |
| } |
| |
| private: |
| raw_ptr<display::ContentProtectionManager> |
| content_protection_manager_; // Not owned. |
| raw_ptr<display::DisplayConfigurator> display_configurator_; // Not owned. |
| }; |
| |
| // These are reported to UMA server. Do not renumber or reuse values. |
| enum class OutputProtectionStatus { |
| kQueried = 0, |
| kNoExternalLink = 1, |
| kAllExternalLinksProtected = 2, |
| // Note: Only add new values immediately before this line. |
| kMaxValue = kAllExternalLinksProtected, |
| }; |
| |
| void ReportOutputProtectionUMA(OutputProtectionStatus status) { |
| UMA_HISTOGRAM_ENUMERATION("Media.EME.OutputProtection.PlatformCdm", status); |
| } |
| |
| } // namespace |
| |
| // static |
| void OutputProtectionImpl::Create( |
| mojo::PendingReceiver<cdm::mojom::OutputProtection> receiver, |
| std::unique_ptr<DisplaySystemDelegate> delegate) { |
| // This needs to run on the UI thread for its interactions with the display |
| // system. |
| if (!content::GetUIThreadTaskRunner({})->RunsTasksInCurrentSequence()) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&OutputProtectionImpl::Create, |
| std::move(receiver), std::move(delegate))); |
| return; |
| } |
| // This object should destruct when the mojo connection is lost. |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<OutputProtectionImpl>(std::move(delegate)), |
| std::move(receiver)); |
| } |
| |
| OutputProtectionImpl::OutputProtectionImpl( |
| std::unique_ptr<DisplaySystemDelegate> delegate) |
| : delegate_(std::move(delegate)) { |
| if (!delegate_) |
| delegate_ = std::make_unique<DisplaySystemDelegateImpl>(); |
| } |
| |
| OutputProtectionImpl::~OutputProtectionImpl() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (client_id_) |
| delegate_->UnregisterClient(client_id_); |
| } |
| |
| void OutputProtectionImpl::QueryStatus(QueryStatusCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!client_id_) |
| Initialize(); |
| if (display_id_list_.empty()) { |
| std::move(callback).Run(true, display::DISPLAY_CONNECTION_TYPE_NONE, |
| ProtectionType::NONE); |
| return; |
| } |
| |
| ReportOutputProtectionQuery(); |
| |
| // We want to copy this since we will manipulate it. |
| std::vector<int64_t> remaining_displays = display_id_list_; |
| int64_t curr_display_id = remaining_displays.back(); |
| remaining_displays.pop_back(); |
| delegate_->QueryContentProtection( |
| client_id_, curr_display_id, |
| base::BindOnce(&OutputProtectionImpl::QueryStatusCallbackAggregator, |
| weak_factory_.GetWeakPtr(), std::move(remaining_displays), |
| std::move(callback), true, |
| display::DISPLAY_CONNECTION_TYPE_NONE, |
| display::CONTENT_PROTECTION_METHOD_NONE, |
| display::CONTENT_PROTECTION_METHOD_NONE)); |
| } |
| |
| void OutputProtectionImpl::EnableProtection(ProtectionType desired_protection, |
| EnableProtectionCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!client_id_) |
| Initialize(); |
| |
| if (display_id_list_.empty()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| // We just pass through what the client requests. |
| switch (desired_protection) { |
| case ProtectionType::HDCP_TYPE_0: |
| desired_protection_mask_ = display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_0; |
| break; |
| case ProtectionType::HDCP_TYPE_1: |
| desired_protection_mask_ = display::CONTENT_PROTECTION_METHOD_HDCP_TYPE_1; |
| break; |
| case ProtectionType::NONE: |
| desired_protection_mask_ = display::CONTENT_PROTECTION_METHOD_NONE; |
| break; |
| } |
| |
| // We want to copy this since we will manipulate it. |
| std::vector<int64_t> remaining_displays = display_id_list_; |
| int64_t curr_display_id = remaining_displays.back(); |
| remaining_displays.pop_back(); |
| delegate_->ApplyContentProtection( |
| client_id_, curr_display_id, desired_protection_mask_, |
| base::BindOnce(&OutputProtectionImpl::EnableProtectionCallbackAggregator, |
| weak_factory_.GetWeakPtr(), std::move(remaining_displays), |
| std::move(callback), true)); |
| } |
| |
| void OutputProtectionImpl::Initialize() { |
| DCHECK(!client_id_); |
| // This needs to be setup on the browser thread, so wait to do it until we |
| // are on that thread (i.e. don't do it in the constructor). |
| client_id_ = delegate_->RegisterClient(); |
| DCHECK(client_id_); |
| display_observer_.emplace(this); |
| display_id_list_ = GetDisplayIdsFromSnapshots(delegate_->cached_displays()); |
| } |
| |
| void OutputProtectionImpl::EnableProtectionCallbackAggregator( |
| std::vector<int64_t> remaining_displays, |
| EnableProtectionCallback callback, |
| bool aggregate_success, |
| bool success) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| aggregate_success &= success; |
| if (remaining_displays.empty()) { |
| std::move(callback).Run(aggregate_success); |
| return; |
| } |
| int64_t curr_display_id = remaining_displays.back(); |
| remaining_displays.pop_back(); |
| delegate_->ApplyContentProtection( |
| client_id_, curr_display_id, desired_protection_mask_, |
| base::BindOnce(&OutputProtectionImpl::EnableProtectionCallbackAggregator, |
| weak_factory_.GetWeakPtr(), std::move(remaining_displays), |
| std::move(callback), aggregate_success)); |
| } |
| |
| void OutputProtectionImpl::QueryStatusCallbackAggregator( |
| std::vector<int64_t> remaining_displays, |
| QueryStatusCallback callback, |
| bool aggregate_success, |
| uint32_t aggregate_link_mask, |
| uint32_t aggregate_protection_mask, |
| uint32_t aggregate_no_protection_mask, |
| bool success, |
| uint32_t link_mask, |
| uint32_t protection_mask) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| aggregate_success &= success; |
| aggregate_link_mask |= link_mask; |
| if (link_mask & kUnprotectableConnectionTypes) { |
| aggregate_no_protection_mask |= display::kContentProtectionMethodHdcpAll; |
| } |
| if (link_mask & kProtectableConnectionTypes) { |
| aggregate_protection_mask |= protection_mask; |
| } |
| if (!remaining_displays.empty()) { |
| int64_t curr_display_id = remaining_displays.back(); |
| remaining_displays.pop_back(); |
| delegate_->QueryContentProtection( |
| client_id_, curr_display_id, |
| base::BindOnce( |
| &OutputProtectionImpl::QueryStatusCallbackAggregator, |
| weak_factory_.GetWeakPtr(), std::move(remaining_displays), |
| std::move(callback), aggregate_success, aggregate_link_mask, |
| aggregate_protection_mask, aggregate_no_protection_mask)); |
| return; |
| } |
| |
| if (aggregate_success) { |
| ReportOutputProtectionQueryResult(aggregate_link_mask, |
| aggregate_protection_mask); |
| } |
| |
| aggregate_protection_mask &= ~aggregate_no_protection_mask; |
| std::move(callback).Run(aggregate_success, aggregate_link_mask, |
| ConvertProtection(aggregate_protection_mask)); |
| } |
| |
| void OutputProtectionImpl::HandleDisplayChange() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| display_id_list_ = GetDisplayIdsFromSnapshots(delegate_->cached_displays()); |
| if (desired_protection_mask_) { |
| // We always reapply content protection on display changes since we affect |
| // all displays. |
| EnableProtection(ConvertProtection(desired_protection_mask_), |
| base::DoNothing()); |
| } |
| } |
| |
| void OutputProtectionImpl::OnDisplayAdded(const display::Display& display) { |
| HandleDisplayChange(); |
| } |
| |
| void OutputProtectionImpl::OnDisplayMetricsChanged( |
| const display::Display& display, |
| uint32_t changed_metrics) { |
| HandleDisplayChange(); |
| } |
| |
| void OutputProtectionImpl::OnDisplaysRemoved( |
| const display::Displays& removed_displays) { |
| HandleDisplayChange(); |
| } |
| |
| void OutputProtectionImpl::ReportOutputProtectionQuery() { |
| if (uma_for_output_protection_query_reported_) |
| return; |
| |
| ReportOutputProtectionUMA(OutputProtectionStatus::kQueried); |
| uma_for_output_protection_query_reported_ = true; |
| } |
| |
| void OutputProtectionImpl::ReportOutputProtectionQueryResult( |
| uint32_t link_mask, |
| uint32_t protection_mask) { |
| DCHECK(uma_for_output_protection_query_reported_); |
| |
| if (uma_for_output_protection_positive_result_reported_) |
| return; |
| |
| // Report UMAs for output protection query result. |
| uint32_t external_links = |
| (link_mask & ~display::DISPLAY_CONNECTION_TYPE_INTERNAL); |
| |
| if (!external_links) { |
| ReportOutputProtectionUMA(OutputProtectionStatus::kNoExternalLink); |
| uma_for_output_protection_positive_result_reported_ = true; |
| return; |
| } |
| |
| bool is_unprotectable_link_connected = |
| (external_links & ~kProtectableConnectionTypes) != 0; |
| bool is_hdcp_enabled_on_all_protectable_links = |
| (protection_mask & desired_protection_mask_) != 0; |
| |
| if (!is_unprotectable_link_connected && |
| is_hdcp_enabled_on_all_protectable_links) { |
| ReportOutputProtectionUMA( |
| OutputProtectionStatus::kAllExternalLinksProtected); |
| uma_for_output_protection_positive_result_reported_ = true; |
| return; |
| } |
| |
| // Do not report a negative result because it could be a false negative. |
| // Instead, we will calculate number of negatives using the total number of |
| // queries and positive results. |
| } |
| |
| } // namespace chromeos |