chromium/ui/display/manager/content_protection_key_manager.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/manager/content_protection_key_manager.h"

#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "ui/display/display_features.h"
#include "ui/display/manager/util/display_manager_util.h"

namespace display {

namespace {

// Length of the key as expected to come from the server.
// This is the same length that the kernel also expects.
constexpr size_t kHdcpKeySize = 285;

display::DisplaySnapshot* GetDisplayWithIdIfHdcpCapableAndKeyNeeded(
    const std::vector<raw_ptr<display::DisplaySnapshot, VectorExperimental>>&
        displays_states,
    int64_t display_id) {
  for (display::DisplaySnapshot* display : displays_states) {
    if (display->display_id() == display_id) {
      uint32_t protection_mask;
      bool is_hdcp_capable =
          GetContentProtectionMethods(display->type(), &protection_mask) &&
          protection_mask & display::kContentProtectionMethodHdcpAll;
      if (is_hdcp_capable && display->has_content_protection_key()) {
        return display;
      }
      break;
    }
  }

  return nullptr;
}

}  // namespace

ContentProtectionKeyManager::ContentProtectionKeyManager() = default;
ContentProtectionKeyManager::~ContentProtectionKeyManager() = default;

void ContentProtectionKeyManager::SetKeyIfRequired(
    const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
        displays_states,
    int64_t display_id,
    KeySetCallback on_key_set) {
  DCHECK(!on_key_set.is_null());

  // TODO(markyacoub): Remove this flag once the feature is fully launched.
  if (!features::IsHdcpKeyProvisioningRequired()) {
    std::move(on_key_set).Run(false);
    return;
  }

  if (!GetDisplayWithIdIfHdcpCapableAndKeyNeeded(displays_states, display_id)) {
    std::move(on_key_set).Run(false);
    return;
  }

  pending_display_callbacks_[display_id] = std::move(on_key_set);

  // If we already learnt that we need a key and already fetched the key, go
  // ahead and inject it into the kernel.
  if (!cached_provisioned_key_.empty()) {
    InjectKeyToKernel(display_id);
    return;
  }

  // If there are no pending displays nor we have the key, it means we haven't
  // fetched the key from the server yet.
  if (displays_pending_set_key_.empty()) {
    displays_pending_set_key_.insert(display_id);
    FetchKeyFromServer();
    // If the list isn't empty, it means we're in the process of fetching the
    // key from the server already. Just add the pending display to the list and
    // we'll set it later when the key is fetched.
  } else {
    displays_pending_set_key_.insert(display_id);
  }
}

void ContentProtectionKeyManager::FetchKeyFromServer() {
  DCHECK(!provisioned_key_request_.is_null());
  provisioned_key_request_.Run(
      base::BindOnce(&ContentProtectionKeyManager::OnKeyFetchedFromServer,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ContentProtectionKeyManager::OnKeyFetchedFromServer(
    const std::string& key) {
  if (key.size()) {
    // This is the size of the key that we expect from the server as of now.
    DCHECK_EQ(key.size(), kHdcpKeySize);
    cached_provisioned_key_ = key;
    for (int64_t display_id : displays_pending_set_key_) {
      InjectKeyToKernel(display_id);
    }
  } else {
    LOG(ERROR) << "Fetched an empty HDCP key from widevine server";
    for (int64_t display_id : displays_pending_set_key_) {
      TriggerPendingCallbacks(display_id, false);
    }
  }
  displays_pending_set_key_.clear();
}

void ContentProtectionKeyManager::InjectKeyToKernel(int64_t display_id) {
  DCHECK(native_display_delegate_);
  native_display_delegate_->SetHdcpKeyProp(
      display_id, cached_provisioned_key_,
      base::BindOnce(&ContentProtectionKeyManager::OnKeyInjectedToKernel,
                     weak_ptr_factory_.GetWeakPtr(), display_id));
}

void ContentProtectionKeyManager::OnKeyInjectedToKernel(int64_t display_id,
                                                        bool success) {
  LOG_IF(ERROR, !success) << "Failed to Inject the HDCP Key to Display #"
                          << display_id;
  TriggerPendingCallbacks(display_id, success);
}

void ContentProtectionKeyManager::TriggerPendingCallbacks(int64_t display_id,
                                                          bool is_key_set) {
  CHECK(base::Contains(pending_display_callbacks_, display_id));
  KeySetCallback callback = std::move(pending_display_callbacks_[display_id]);
  DCHECK(!callback.is_null());
  std::move(callback).Run(is_key_set);

  pending_display_callbacks_.erase(display_id);
}

}  // namespace display