Commit 9c0c03af authored by John Rummell's avatar John Rummell Committed by Commit Bot

Move CDM manifest parsing into a separate file.

Moving the code that handles the CDM manifest into a separate file
in chrome/common to support manifest parsing outside of the component
updater.

Bug: 889561,971433
Test: new unit_tests pass
Change-Id: I1d6591834f01f1fd01daf5450f5cad08f4af2e26
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1764249Reviewed-by: default avatarXiaohan Wang <xhwang@chromium.org>
Reviewed-by: default avatarJoshua Pawlicki <waffles@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Commit-Queue: John Rummell <jrummell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#695246}
parent 3f4b744c
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <string.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
...@@ -14,21 +13,16 @@ ...@@ -14,21 +13,16 @@
#include "base/base_paths.h" #include "base/base_paths.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/native_library.h" #include "base/native_library.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/values.h" #include "base/values.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/common/media/cdm_manifest.h"
#include "components/component_updater/component_installer.h" #include "components/component_updater/component_installer.h"
#include "components/component_updater/component_updater_service.h" #include "components/component_updater/component_updater_service.h"
#include "components/version_info/version_info.h" #include "components/version_info/version_info.h"
...@@ -37,12 +31,6 @@ ...@@ -37,12 +31,6 @@
#include "content/public/browser/cdm_registry.h" #include "content/public/browser/cdm_registry.h"
#include "content/public/common/cdm_info.h" #include "content/public/common/cdm_info.h"
#include "crypto/sha2.h" #include "crypto/sha2.h"
// TODO(crbug.com/825041): Move EncryptionMode out of decrypt_config and
// rename it to EncryptionScheme.
#include "media/base/decrypt_config.h"
#include "media/base/video_codecs.h"
#include "media/cdm/cdm_proxy.h"
#include "media/cdm/supported_cdm_versions.h"
#include "third_party/widevine/cdm/buildflags.h" #include "third_party/widevine/cdm/buildflags.h"
#include "third_party/widevine/cdm/widevine_cdm_common.h" #include "third_party/widevine/cdm/widevine_cdm_common.h"
...@@ -85,54 +73,6 @@ const char kWidevineCdmArch[] = ...@@ -85,54 +73,6 @@ const char kWidevineCdmArch[] =
"???"; "???";
#endif #endif
// The CDM manifest includes several custom values, all beginning with "x-cdm-".
// All values are strings.
// All values that are lists are delimited by commas. No trailing commas.
// For example, "1,2,4".
const char kCdmValueDelimiter[] = ",";
// The following entries are required.
// Interface versions are lists of integers (e.g. "1" or "1,2,4").
// These are checked in this file before registering the CDM.
// All match the interface versions from content_decryption_module.h that the
// CDM supports.
// Matches CDM_MODULE_VERSION.
const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
// Matches supported ContentDecryptionModule_* version(s).
const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
// Matches supported Host_* version(s).
const char kCdmHostVersionsName[] = "x-cdm-host-versions";
// The codecs list is a list of simple codec names (e.g. "vp8,vorbis").
const char kCdmCodecsListName[] = "x-cdm-codecs";
// Whether persistent license is supported by the CDM: "true" or "false".
const char kCdmPersistentLicenseSupportName[] =
"x-cdm-persistent-license-support";
const char kCdmSupportedEncryptionSchemesName[] =
"x-cdm-supported-encryption-schemes";
const char kCdmSupportedCdmProxyProtocolsName[] =
"x-cdm-supported-cdm-proxy-protocols";
// The following strings are used to specify supported codecs in the
// parameter |kCdmCodecsListName|.
const char kCdmSupportedCodecVp8[] = "vp8";
// Legacy VP9, which is equivalent to VP9 profile 0.
// TODO(xhwang): Newer CDMs should support "vp09" below. Remove this after older
// CDMs are obsolete.
const char kCdmSupportedCodecLegacyVp9[] = "vp9.0";
// Supports at least VP9 profile 0 and profile 2.
const char kCdmSupportedCodecVp9[] = "vp09";
const char kCdmSupportedCodecAv1[] = "av01";
const char kCdmSupportedCodecAvc1[] = "avc1";
// The following strings are used to specify supported encryption schemes in
// the parameter |kCdmSupportedEncryptionSchemesName|.
const char kCdmSupportedEncryptionSchemeCenc[] = "cenc";
const char kCdmSupportedEncryptionSchemeCbcs[] = "cbcs";
// The following string(s) are used to specify supported CdmProxy protocols in
// the parameter |kCdmSupportedCdmProxyProtocolsName|.
const char kCdmSupportedCdmProxyProtocolIntel[] = "intel";
// Widevine CDM is packaged as a multi-CRX. Widevine CDM binaries are located in // Widevine CDM is packaged as a multi-CRX. Widevine CDM binaries are located in
// _platform_specific/<platform_arch> folder in the package. This function // _platform_specific/<platform_arch> folder in the package. This function
// returns the platform-specific subdirectory that is part of that multi-CRX. // returns the platform-specific subdirectory that is part of that multi-CRX.
...@@ -143,235 +83,6 @@ base::FilePath GetPlatformDirectory(const base::FilePath& base_path) { ...@@ -143,235 +83,6 @@ base::FilePath GetPlatformDirectory(const base::FilePath& base_path) {
return base_path.AppendASCII("_platform_specific").AppendASCII(platform_arch); return base_path.AppendASCII("_platform_specific").AppendASCII(platform_arch);
} }
typedef bool (*VersionCheckFunc)(int version);
bool CheckForCompatibleVersion(const base::DictionaryValue& manifest,
const std::string version_name,
VersionCheckFunc version_check_func) {
std::string versions_string;
if (!manifest.GetString(version_name, &versions_string)) {
DVLOG(1) << "Widevine CDM component manifest missing " << version_name;
return false;
}
DVLOG_IF(1, versions_string.empty())
<< "Widevine CDM component manifest has empty " << version_name;
for (const base::StringPiece& ver_str :
base::SplitStringPiece(versions_string, kCdmValueDelimiter,
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
int version = 0;
if (base::StringToInt(ver_str, &version))
if (version_check_func(version))
return true;
}
DVLOG(1) << "Widevine CDM component manifest has no supported "
<< version_name << " in '" << versions_string << "'";
return false;
}
// Returns whether the CDM's API versions, as specified in the manifest, are
// supported in this Chrome binary and not disabled at run time.
// Checks the module API, CDM interface API, and Host API.
// This should never fail except in rare cases where the component has not been
// updated recently or the user downgrades Chrome.
bool IsCompatibleWithChrome(const base::DictionaryValue& manifest) {
return CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
media::IsSupportedCdmModuleVersion) &&
CheckForCompatibleVersion(
manifest, kCdmInterfaceVersionsName,
media::IsSupportedAndEnabledCdmInterfaceVersion) &&
CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
media::IsSupportedCdmHostVersion);
}
// Returns true and updates |video_codecs| if the appropriate manifest entry is
// valid. When VP9 is supported, sets |supports_vp9_profile2| if profile 2 is
// supported. Older CDMs may only support profile 0. Returns false and does not
// modify |video_codecs| if the manifest entry is incorrectly formatted.
bool GetCodecs(const base::DictionaryValue& manifest,
std::vector<media::VideoCodec>* video_codecs,
bool* supports_vp9_profile2) {
DCHECK(video_codecs);
const base::Value* value = manifest.FindKey(kCdmCodecsListName);
if (!value) {
DLOG(WARNING) << "Widevine CDM component manifest is missing codecs.";
return true;
}
if (!value->is_string()) {
DLOG(ERROR) << "Manifest entry " << kCdmCodecsListName
<< " is not a string.";
return false;
}
const std::string& codecs = value->GetString();
if (codecs.empty()) {
DLOG(WARNING) << "Widevine CDM component manifest has empty codecs list.";
return true;
}
std::vector<media::VideoCodec> result;
const std::vector<base::StringPiece> supported_codecs =
base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// Assuming VP9 profile 2 is not supported by default. Will only be set when
// kCdmSupportedCodecVp9 is available below.
*supports_vp9_profile2 = false;
for (const auto& codec : supported_codecs) {
if (codec == kCdmSupportedCodecVp8) {
result.push_back(media::VideoCodec::kCodecVP8);
} else if (codec == kCdmSupportedCodecLegacyVp9) {
result.push_back(media::VideoCodec::kCodecVP9);
} else if (codec == kCdmSupportedCodecVp9) {
result.push_back(media::VideoCodec::kCodecVP9);
*supports_vp9_profile2 = true;
} else if (codec == kCdmSupportedCodecAv1) {
result.push_back(media::VideoCodec::kCodecAV1);
} else if (codec == kCdmSupportedCodecAvc1) {
result.push_back(media::VideoCodec::kCodecH264);
}
}
video_codecs->swap(result);
return true;
}
// Returns true and updates |session_types| if the appropriate manifest entry is
// valid. Returns false if the manifest entry is incorrectly formatted.
bool GetSessionTypes(const base::DictionaryValue& manifest,
base::flat_set<media::CdmSessionType>* session_types) {
DCHECK(session_types);
bool is_persistent_license_supported = false;
const base::Value* value = manifest.FindKey(kCdmPersistentLicenseSupportName);
if (value) {
if (!value->is_bool())
return false;
is_persistent_license_supported = value->GetBool();
}
// Temporary session is always supported.
session_types->insert(media::CdmSessionType::kTemporary);
if (is_persistent_license_supported)
session_types->insert(media::CdmSessionType::kPersistentLicense);
return true;
}
// Returns true and updates |encryption_schemes| if the appropriate manifest
// entry is valid. Returns false and does not modify |encryption_schemes| if the
// manifest entry is incorrectly formatted. It is assumed that all CDMs support
// 'cenc', so if the manifest entry is missing, the result will indicate support
// for 'cenc' only. Incorrect types in the manifest entry will log the error and
// fail. Unrecognized values will be reported but otherwise ignored.
bool GetEncryptionSchemes(
const base::DictionaryValue& manifest,
base::flat_set<media::EncryptionMode>* encryption_schemes) {
DCHECK(encryption_schemes);
const base::Value* value =
manifest.FindKey(kCdmSupportedEncryptionSchemesName);
if (!value) {
// No manifest entry found, so assume only 'cenc' supported for backwards
// compatibility.
encryption_schemes->insert(media::EncryptionMode::kCenc);
return true;
}
if (!value->is_list()) {
DLOG(ERROR) << "Manifest entry " << kCdmSupportedEncryptionSchemesName
<< " is not a list.";
return false;
}
base::span<const base::Value> list = value->GetList();
base::flat_set<media::EncryptionMode> result;
for (const auto& item : list) {
if (!item.is_string()) {
DLOG(ERROR) << "Unrecognized item type in manifest entry "
<< kCdmSupportedEncryptionSchemesName;
return false;
}
const std::string& scheme = item.GetString();
if (scheme == kCdmSupportedEncryptionSchemeCenc) {
result.insert(media::EncryptionMode::kCenc);
} else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
result.insert(media::EncryptionMode::kCbcs);
} else {
DLOG(WARNING) << "Unrecognized encryption scheme " << scheme
<< " in manifest entry "
<< kCdmSupportedEncryptionSchemesName;
}
}
// As the manifest entry exists, it must specify at least one valid value.
if (result.empty())
return false;
encryption_schemes->swap(result);
return true;
}
// Returns true and updates |cdm_proxy_protocols| if the appropriate manifest
// entry is valid. Returns false and does not modify |cdm_proxy_protocols| if
// the manifest entry is incorrectly formatted. Incorrect types in the manifest
// entry will log the error and fail. Unrecognized values will be reported but
// otherwise ignored.
bool GetCdmProxyProtocols(
const base::DictionaryValue& manifest,
base::flat_set<media::CdmProxy::Protocol>* cdm_proxy_protocols) {
const auto* value = manifest.FindKey(kCdmSupportedCdmProxyProtocolsName);
if (!value)
return true;
if (!value->is_list()) {
DLOG(ERROR) << "Manifest entry " << kCdmSupportedCdmProxyProtocolsName
<< " is not a list.";
return false;
}
base::span<const base::Value> list = value->GetList();
base::flat_set<media::CdmProxy::Protocol> result;
for (const auto& item : list) {
if (!item.is_string()) {
DLOG(ERROR) << "Unrecognized item type in manifest entry "
<< kCdmSupportedCdmProxyProtocolsName;
return false;
}
const std::string& protocol = item.GetString();
if (protocol == kCdmSupportedCdmProxyProtocolIntel) {
result.insert(media::CdmProxy::Protocol::kIntel);
} else {
DLOG(WARNING) << "Unrecognized CdmProxy protocol" << protocol
<< " in manifest entry "
<< kCdmSupportedCdmProxyProtocolsName;
}
}
cdm_proxy_protocols->swap(result);
return true;
}
// Returns true if the entries in the manifest can be parsed correctly,
// false otherwise. Updates |capability|, with the values obtained from the
// manifest, if they are provided. If this method returns false, |capability|
// may or may not be updated.
bool ParseManifest(const base::DictionaryValue& manifest,
content::CdmCapability* capability) {
return GetCodecs(manifest, &capability->video_codecs,
&capability->supports_vp9_profile2) &&
GetEncryptionSchemes(manifest, &capability->encryption_schemes) &&
GetSessionTypes(manifest, &capability->session_types) &&
GetCdmProxyProtocols(manifest, &capability->cdm_proxy_protocols);
}
void RegisterWidevineCdmWithChrome( void RegisterWidevineCdmWithChrome(
const base::Version& cdm_version, const base::Version& cdm_version,
const base::FilePath& cdm_install_dir, const base::FilePath& cdm_install_dir,
...@@ -382,13 +93,12 @@ void RegisterWidevineCdmWithChrome( ...@@ -382,13 +93,12 @@ void RegisterWidevineCdmWithChrome(
// avoid the case where the CDM is accepted by the component updater // avoid the case where the CDM is accepted by the component updater
// but not registered. // but not registered.
content::CdmCapability capability; content::CdmCapability capability;
if (!ParseManifest(*manifest, &capability)) { if (!ParseCdmManifest(*manifest, &capability)) {
VLOG(1) << "Not registering Widevine CDM due to malformed manifest."; VLOG(1) << "Not registering Widevine CDM due to malformed manifest.";
return; return;
} }
VLOG(1) << "Register Widevine CDM with Chrome"; VLOG(1) << "Register Widevine CDM with Chrome";
const base::FilePath cdm_path = const base::FilePath cdm_path =
GetPlatformDirectory(cdm_install_dir) GetPlatformDirectory(cdm_install_dir)
.AppendASCII(base::GetNativeLibraryName(kWidevineCdmLibraryName)); .AppendASCII(base::GetNativeLibraryName(kWidevineCdmLibraryName));
...@@ -457,7 +167,7 @@ void WidevineCdmComponentInstallerPolicy::ComponentReady( ...@@ -457,7 +167,7 @@ void WidevineCdmComponentInstallerPolicy::ComponentReady(
const base::Version& version, const base::Version& version,
const base::FilePath& path, const base::FilePath& path,
std::unique_ptr<base::DictionaryValue> manifest) { std::unique_ptr<base::DictionaryValue> manifest) {
if (!IsCompatibleWithChrome(*manifest)) { if (!IsCdmManifestCompatibleWithChrome(*manifest)) {
VLOG(1) << "Installed Widevine CDM component is incompatible."; VLOG(1) << "Installed Widevine CDM component is incompatible.";
return; return;
} }
...@@ -475,12 +185,12 @@ void WidevineCdmComponentInstallerPolicy::ComponentReady( ...@@ -475,12 +185,12 @@ void WidevineCdmComponentInstallerPolicy::ComponentReady(
bool WidevineCdmComponentInstallerPolicy::VerifyInstallation( bool WidevineCdmComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest, const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const { const base::FilePath& install_dir) const {
const base::FilePath cdm_path =
GetPlatformDirectory(install_dir)
.AppendASCII(base::GetNativeLibraryName(kWidevineCdmLibraryName));
content::CdmCapability capability; content::CdmCapability capability;
return IsCompatibleWithChrome(manifest) && return IsCdmManifestCompatibleWithChrome(manifest) &&
base::PathExists(GetPlatformDirectory(install_dir) base::PathExists(cdm_path) && ParseCdmManifest(manifest, &capability);
.AppendASCII(base::GetNativeLibraryName(
kWidevineCdmLibraryName))) &&
ParseManifest(manifest, &capability);
} }
// The base directory on Windows looks like: // The base directory on Windows looks like:
......
...@@ -491,8 +491,15 @@ static_library("common") { ...@@ -491,8 +491,15 @@ static_library("common") {
] ]
} }
if (enable_widevine && enable_library_cdms) { if (enable_library_cdms) {
deps += [ "//third_party/widevine/cdm:headers" ] sources += [
"media/cdm_manifest.cc",
"media/cdm_manifest.h",
]
if (enable_widevine) {
# Needed by chrome_content_client.cc.
deps += [ "//third_party/widevine/cdm:headers" ]
}
} }
if (safe_browsing_mode != 0) { if (safe_browsing_mode != 0) {
......
// 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 "chrome/common/media/cdm_manifest.h"
#include <stddef.h>
#include <string>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/values.h"
#include "content/public/common/cdm_info.h"
#include "media/base/content_decryption_module.h"
#include "media/base/decrypt_config.h"
#include "media/base/video_codecs.h"
#include "media/cdm/cdm_proxy.h"
#include "media/cdm/supported_cdm_versions.h"
#include "media/media_buildflags.h"
#if !BUILDFLAG(ENABLE_LIBRARY_CDMS)
#error This file should only be compiled when library CDMs are enabled
#endif
namespace {
// The CDM manifest includes several custom values, all beginning with "x-cdm-".
// They are:
// x-cdm-module-versions
// x-cdm-interface-versions
// x-cdm-host-versions
// x-cdm-codecs
// x-cdm-persistent-license-support
// x-cdm-supported-encryption-schemes
// x-cdm-supported-cdm-proxy-protocols
// What they represent is listed below. They should never have non-backwards
// compatible changes. All values are strings. All values that are lists are
// delimited by commas. No trailing commas. For example, "1,2,4".
const char kCdmValueDelimiter[] = ",";
// The following entries are required.
// Interface versions are lists of integers (e.g. "1" or "1,2,4").
// All match the interface versions from content_decryption_module.h that the
// CDM supports.
// Matches CDM_MODULE_VERSION.
const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
// Matches supported ContentDecryptionModule_* version(s).
const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
// Matches supported Host_* version(s).
const char kCdmHostVersionsName[] = "x-cdm-host-versions";
// The codecs list is a list of simple codec names (e.g. "vp8,vorbis").
const char kCdmCodecsListName[] = "x-cdm-codecs";
// Whether persistent license is supported by the CDM: "true" or "false".
const char kCdmPersistentLicenseSupportName[] =
"x-cdm-persistent-license-support";
// The list of supported encryption schemes (e.g. ["cenc","cbcs"]).
const char kCdmSupportedEncryptionSchemesName[] =
"x-cdm-supported-encryption-schemes";
// The list of supported proxy protocols (e.g. ["intel"]).
const char kCdmSupportedCdmProxyProtocolsName[] =
"x-cdm-supported-cdm-proxy-protocols";
// The following strings are used to specify supported codecs in the
// parameter |kCdmCodecsListName|.
const char kCdmSupportedCodecVp8[] = "vp8";
// Legacy VP9, which is equivalent to VP9 profile 0.
// TODO(xhwang): Newer CDMs should support "vp09" below. Remove this after older
// CDMs are obsolete.
const char kCdmSupportedCodecLegacyVp9[] = "vp9.0";
// Supports at least VP9 profile 0 and profile 2.
const char kCdmSupportedCodecVp9[] = "vp09";
const char kCdmSupportedCodecAv1[] = "av01";
const char kCdmSupportedCodecAvc1[] = "avc1";
// The following strings are used to specify supported encryption schemes in
// the parameter |kCdmSupportedEncryptionSchemesName|.
const char kCdmSupportedEncryptionSchemeCenc[] = "cenc";
const char kCdmSupportedEncryptionSchemeCbcs[] = "cbcs";
// The following string(s) are used to specify supported CdmProxy protocols in
// the parameter |kCdmSupportedCdmProxyProtocolsName|.
const char kCdmSupportedCdmProxyProtocolIntel[] = "intel";
typedef bool (*VersionCheckFunc)(int version);
// Returns whether the CDM's API version, as specified in the manifest by
// |version_name|, is supported in this Chrome binary and not disabled at run
// time by calling |version_check_func|. If the manifest entry contains multiple
// values, each one is checked sequentially, and if any one is supported, this
// function returns true. If all values in the manifest entry are not supported,
// then return false.
bool CheckForCompatibleVersion(const base::Value& manifest,
const std::string version_name,
VersionCheckFunc version_check_func) {
DCHECK(manifest.is_dict());
auto* version_string = manifest.FindStringKey(version_name);
if (!version_string) {
DVLOG(1) << "CDM manifest missing " << version_name;
return false;
}
DVLOG_IF(1, version_string->empty())
<< "CDM manifest has empty " << version_name;
for (const base::StringPiece& ver_str :
base::SplitStringPiece(*version_string, kCdmValueDelimiter,
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
int version = 0;
if (base::StringToInt(ver_str, &version) && version_check_func(version))
return true;
}
DVLOG(1) << "CDM manifest has no supported " << version_name << " in '"
<< *version_string << "'";
return false;
}
// Returns true and updates |video_codecs| if the appropriate manifest entry is
// valid. When VP9 is supported, sets |supports_vp9_profile2| if profile 2 is
// supported. Older CDMs may only support profile 0. Returns false and does not
// modify |video_codecs| if the manifest entry is incorrectly formatted.
bool GetCodecs(const base::Value& manifest,
std::vector<media::VideoCodec>* video_codecs,
bool* supports_vp9_profile2) {
DCHECK(manifest.is_dict());
DCHECK(video_codecs);
const base::Value* value = manifest.FindKey(kCdmCodecsListName);
if (!value) {
DLOG(WARNING) << "CDM manifest is missing codecs.";
return true;
}
if (!value->is_string()) {
DLOG(ERROR) << "CDM manifest entry " << kCdmCodecsListName
<< " is not a string.";
return false;
}
const std::string& codecs = value->GetString();
if (codecs.empty()) {
DLOG(WARNING) << "CDM manifest has empty codecs list.";
return true;
}
std::vector<media::VideoCodec> result;
const std::vector<base::StringPiece> supported_codecs =
base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// Assuming VP9 profile 2 is not supported by default. Will only be set when
// kCdmSupportedCodecVp9 is available below.
*supports_vp9_profile2 = false;
for (const auto& codec : supported_codecs) {
if (codec == kCdmSupportedCodecVp8) {
result.push_back(media::VideoCodec::kCodecVP8);
} else if (codec == kCdmSupportedCodecLegacyVp9) {
result.push_back(media::VideoCodec::kCodecVP9);
} else if (codec == kCdmSupportedCodecVp9) {
result.push_back(media::VideoCodec::kCodecVP9);
*supports_vp9_profile2 = true;
} else if (codec == kCdmSupportedCodecAv1) {
result.push_back(media::VideoCodec::kCodecAV1);
} else if (codec == kCdmSupportedCodecAvc1) {
result.push_back(media::VideoCodec::kCodecH264);
}
}
video_codecs->swap(result);
return true;
}
// Returns true and updates |session_types| if the appropriate manifest entry is
// valid. Returns false if the manifest entry is incorrectly formatted.
bool GetSessionTypes(const base::Value& manifest,
base::flat_set<media::CdmSessionType>* session_types) {
DCHECK(manifest.is_dict());
DCHECK(session_types);
bool is_persistent_license_supported = false;
const base::Value* value = manifest.FindKey(kCdmPersistentLicenseSupportName);
if (value) {
if (!value->is_bool())
return false;
is_persistent_license_supported = value->GetBool();
}
// Temporary session is always supported.
session_types->insert(media::CdmSessionType::kTemporary);
if (is_persistent_license_supported)
session_types->insert(media::CdmSessionType::kPersistentLicense);
return true;
}
// Returns true and updates |encryption_schemes| if the appropriate manifest
// entry is valid. Returns false and does not modify |encryption_schemes| if the
// manifest entry is incorrectly formatted. It is assumed that all CDMs support
// 'cenc', so if the manifest entry is missing, the result will indicate support
// for 'cenc' only. Incorrect types in the manifest entry will log the error and
// fail. Unrecognized values will be reported but otherwise ignored.
bool GetEncryptionSchemes(
const base::Value& manifest,
base::flat_set<media::EncryptionMode>* encryption_schemes) {
DCHECK(manifest.is_dict());
DCHECK(encryption_schemes);
const base::Value* value =
manifest.FindKey(kCdmSupportedEncryptionSchemesName);
if (!value) {
// No manifest entry found, so assume only 'cenc' supported for backwards
// compatibility.
encryption_schemes->insert(media::EncryptionMode::kCenc);
return true;
}
if (!value->is_list()) {
DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedEncryptionSchemesName
<< " is not a list.";
return false;
}
base::span<const base::Value> list = value->GetList();
base::flat_set<media::EncryptionMode> result;
for (const auto& item : list) {
if (!item.is_string()) {
DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
<< kCdmSupportedEncryptionSchemesName;
return false;
}
const std::string& scheme = item.GetString();
if (scheme == kCdmSupportedEncryptionSchemeCenc) {
result.insert(media::EncryptionMode::kCenc);
} else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
result.insert(media::EncryptionMode::kCbcs);
} else {
DLOG(WARNING) << "Unrecognized encryption scheme '" << scheme
<< "' in CDM manifest entry "
<< kCdmSupportedEncryptionSchemesName;
}
}
// As the manifest entry exists, it must specify at least one valid value.
if (result.empty())
return false;
encryption_schemes->swap(result);
return true;
}
// Returns true and updates |cdm_proxy_protocols| if the appropriate manifest
// entry is valid. Returns false and does not modify |cdm_proxy_protocols| if
// the manifest entry is incorrectly formatted. Incorrect types in the manifest
// entry will log the error and fail. Unrecognized values will be reported but
// otherwise ignored.
bool GetCdmProxyProtocols(
const base::Value& manifest,
base::flat_set<media::CdmProxy::Protocol>* cdm_proxy_protocols) {
DCHECK(manifest.is_dict());
const auto* value = manifest.FindKey(kCdmSupportedCdmProxyProtocolsName);
if (!value)
return true;
if (!value->is_list()) {
DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedCdmProxyProtocolsName
<< " is not a list.";
return false;
}
base::span<const base::Value> list = value->GetList();
base::flat_set<media::CdmProxy::Protocol> result;
for (const auto& item : list) {
if (!item.is_string()) {
DLOG(ERROR) << "Unrecognized item type in CDM manifest entry "
<< kCdmSupportedCdmProxyProtocolsName;
return false;
}
const std::string& protocol = item.GetString();
if (protocol == kCdmSupportedCdmProxyProtocolIntel) {
result.insert(media::CdmProxy::Protocol::kIntel);
} else {
DLOG(WARNING) << "Unrecognized CdmProxy protocol '" << protocol
<< "' in CDM manifest entry "
<< kCdmSupportedCdmProxyProtocolsName;
}
}
cdm_proxy_protocols->swap(result);
return true;
}
} // namespace
bool IsCdmManifestCompatibleWithChrome(const base::Value& manifest) {
DCHECK(manifest.is_dict());
return CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
media::IsSupportedCdmModuleVersion) &&
CheckForCompatibleVersion(
manifest, kCdmInterfaceVersionsName,
media::IsSupportedAndEnabledCdmInterfaceVersion) &&
CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
media::IsSupportedCdmHostVersion);
}
// Returns true if the entries in the manifest can be parsed correctly,
// false otherwise. Updates |version| and |capability| with the values obtained
// from the manifest, if they are provided. If this method returns false,
// |version| and |capability| may or may not be updated.
bool ParseCdmManifest(const base::Value& manifest,
content::CdmCapability* capability) {
DCHECK(manifest.is_dict());
return GetCodecs(manifest, &capability->video_codecs,
&capability->supports_vp9_profile2) &&
GetEncryptionSchemes(manifest, &capability->encryption_schemes) &&
GetSessionTypes(manifest, &capability->session_types) &&
GetCdmProxyProtocols(manifest, &capability->cdm_proxy_protocols);
}
// 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.
#ifndef CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
#define CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
namespace base {
class Value;
}
namespace content {
struct CdmCapability;
}
// Returns whether the CDM's API versions, as specified in the manifest, are
// supported in this Chrome binary and not disabled at run time.
// Checks the module API, CDM interface API, and Host API.
// This should never fail except in rare cases where the component has not been
// updated recently or the user downgrades Chrome.
bool IsCdmManifestCompatibleWithChrome(const base::Value& manifest);
// Extracts the necessary information from |manifest| and updates |capability|.
// Returns true on success, false if there are errors in the manifest.
// If this method returns false, |capability| may or may not be updated.
bool ParseCdmManifest(const base::Value& manifest,
content::CdmCapability* capability);
#endif // CHROME_COMMON_MEDIA_CDM_MANIFEST_H_
// 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 "chrome/common/media/cdm_manifest.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "content/public/common/cdm_info.h"
#include "media/cdm/api/content_decryption_module.h"
#include "media/cdm/supported_cdm_versions.h"
#include "testing/gtest/include/gtest/gtest.h"
using content::CdmCapability;
namespace {
// These names must match what is used in cdm_manifest.cc.
const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
const char kCdmHostVersionsName[] = "x-cdm-host-versions";
const char kCdmCodecsListName[] = "x-cdm-codecs";
const char kCdmPersistentLicenseSupportName[] =
"x-cdm-persistent-license-support";
const char kCdmSupportedEncryptionSchemesName[] =
"x-cdm-supported-encryption-schemes";
const char kCdmSupportedCdmProxyProtocolsName[] =
"x-cdm-supported-cdm-proxy-protocols";
// Version checking does change over time. Deriving these values from constants
// in the code to ensure they change when the CDM interface changes.
// |kSupportedCdmInterfaceVersion| and |kSupportedCdmHostVersion| are the
// minimum versions supported. There may be versions after them that are also
// supported.
constexpr int kSupportedCdmModuleVersion = CDM_MODULE_VERSION;
constexpr int kSupportedCdmInterfaceVersion =
media::kSupportedCdmInterfaceVersions[0].version;
static_assert(media::kSupportedCdmInterfaceVersions[0].enabled,
"kSupportedCdmInterfaceVersion is not enabled by default.");
constexpr int kSupportedCdmHostVersion = media::kMinSupportedCdmHostVersion;
// Make a string of the values from 0 up to and including |item|.
std::string MakeStringList(int item) {
DCHECK_GT(item, 0);
std::vector<std::string> parts;
for (int i = 0; i <= item; ++i) {
parts.push_back(base::NumberToString(i));
}
return base::JoinString(parts, ",");
}
base::Value MakeListValue(const std::string& item) {
base::Value list(base::Value::Type::LIST);
list.GetList().push_back(base::Value(item));
return list;
}
base::Value MakeListValue(const std::string& item1, const std::string& item2) {
base::Value list(base::Value::Type::LIST);
list.GetList().push_back(base::Value(item1));
list.GetList().push_back(base::Value(item2));
return list;
}
// Create a default manifest with valid values for all entries.
base::Value DefaultManifest() {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetStringKey(kCdmCodecsListName, "vp8,vp9.0,avc1");
dict.SetBoolKey(kCdmPersistentLicenseSupportName, true);
dict.SetKey(kCdmSupportedEncryptionSchemesName,
MakeListValue("cenc", "cbcs"));
dict.SetKey(kCdmSupportedCdmProxyProtocolsName, MakeListValue("intel"));
// The following are dependent on what the current code supports.
EXPECT_TRUE(media::IsSupportedCdmModuleVersion(kSupportedCdmModuleVersion));
EXPECT_TRUE(media::IsSupportedAndEnabledCdmInterfaceVersion(
kSupportedCdmInterfaceVersion));
EXPECT_TRUE(media::IsSupportedCdmHostVersion(kSupportedCdmHostVersion));
dict.SetStringKey(kCdmModuleVersionsName,
base::NumberToString(kSupportedCdmModuleVersion));
dict.SetStringKey(kCdmInterfaceVersionsName,
base::NumberToString(kSupportedCdmInterfaceVersion));
dict.SetStringKey(kCdmHostVersionsName,
base::NumberToString(kSupportedCdmHostVersion));
return dict;
}
void CheckCodecs(const std::vector<media::VideoCodec>& actual,
const std::vector<media::VideoCodec>& expected) {
EXPECT_EQ(expected.size(), actual.size());
for (const auto& codec : expected) {
EXPECT_TRUE(base::Contains(actual, codec));
}
}
} // namespace
TEST(CdmManifestTest, IsCompatibleWithChrome) {
base::Value manifest(DefaultManifest());
EXPECT_TRUE(IsCdmManifestCompatibleWithChrome(manifest));
}
TEST(CdmManifestTest, InCompatibleModuleVersion) {
const int kUnsupportedModuleVersion = 0;
EXPECT_FALSE(media::IsSupportedCdmModuleVersion(kUnsupportedModuleVersion));
auto manifest = DefaultManifest();
manifest.SetStringKey(kCdmModuleVersionsName,
base::NumberToString(kUnsupportedModuleVersion));
EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
}
TEST(CdmManifestTest, InCompatibleInterfaceVersion) {
const int kUnsupportedInterfaceVersion = kSupportedCdmInterfaceVersion - 1;
EXPECT_FALSE(media::IsSupportedAndEnabledCdmInterfaceVersion(
kUnsupportedInterfaceVersion));
auto manifest = DefaultManifest();
manifest.SetStringKey(kCdmInterfaceVersionsName,
base::NumberToString(kUnsupportedInterfaceVersion));
EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
}
TEST(CdmManifestTest, InCompatibleHostVersion) {
const int kUnsupportedHostVersion = kSupportedCdmHostVersion - 1;
EXPECT_FALSE(media::IsSupportedCdmHostVersion(kUnsupportedHostVersion));
auto manifest = DefaultManifest();
manifest.SetStringKey(kCdmHostVersionsName,
base::NumberToString(kUnsupportedHostVersion));
EXPECT_FALSE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
}
TEST(CdmManifestTest, IsCompatibleWithMultipleValues) {
auto manifest = DefaultManifest();
manifest.SetStringKey(kCdmModuleVersionsName,
MakeStringList(kSupportedCdmModuleVersion));
manifest.SetStringKey(kCdmInterfaceVersionsName,
MakeStringList(kSupportedCdmInterfaceVersion));
manifest.SetStringKey(kCdmHostVersionsName,
MakeStringList(kSupportedCdmHostVersion));
EXPECT_TRUE(IsCdmManifestCompatibleWithChrome(std::move(manifest)));
}
TEST(CdmManifestTest, ValidManifest) {
auto manifest = DefaultManifest();
CdmCapability capability;
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
}
TEST(CdmManifestTest, ManifestCodecs) {
auto manifest = DefaultManifest();
// Try each valid value individually.
{
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "vp8");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP8});
}
{
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "vp9.0");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP9});
EXPECT_FALSE(capability.supports_vp9_profile2);
}
{
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "vp09");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecVP9});
EXPECT_TRUE(capability.supports_vp9_profile2);
}
{
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "av01");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecAV1});
}
{
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "avc1");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecH264});
}
{
// Try list of everything.
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "vp8,vp9.0,vp09,av01,avc1");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
// Note that kCodecVP9 is returned twice in the list.
CheckCodecs(capability.video_codecs,
{media::VideoCodec::kCodecVP8, media::VideoCodec::kCodecVP9,
media::VideoCodec::kCodecVP9, media::VideoCodec::kCodecAV1,
media::VideoCodec::kCodecH264});
EXPECT_TRUE(capability.supports_vp9_profile2);
}
{
// Note that invalid codec values are simply skipped.
CdmCapability capability;
manifest.SetStringKey(kCdmCodecsListName, "invalid,avc1");
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {media::VideoCodec::kCodecH264});
}
{
// Wrong types are an error.
CdmCapability capability;
manifest.SetBoolKey(kCdmCodecsListName, true);
EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
}
{
// Missing entry is OK, but list is empty.
CdmCapability capability;
EXPECT_TRUE(manifest.RemoveKey(kCdmCodecsListName));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
CheckCodecs(capability.video_codecs, {});
}
}
TEST(CdmManifestTest, ManifestEncryptionSchemes) {
auto manifest = DefaultManifest();
// Try each valid value individually.
{
CdmCapability capability;
manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cenc"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.encryption_schemes.size(), 1u);
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCenc));
EXPECT_FALSE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCbcs));
}
{
CdmCapability capability;
manifest.SetKey(kCdmSupportedEncryptionSchemesName, MakeListValue("cbcs"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.encryption_schemes.size(), 1u);
EXPECT_FALSE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCenc));
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCbcs));
}
{
// Try multiple valid entries.
CdmCapability capability;
manifest.SetKey(kCdmSupportedEncryptionSchemesName,
MakeListValue("cenc", "cbcs"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.encryption_schemes.size(), 2u);
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCenc));
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCbcs));
}
{
// Invalid encryption schemes are ignored. However, if value specified then
// there must be at least 1 valid value.
CdmCapability capability;
manifest.SetKey(kCdmSupportedEncryptionSchemesName,
MakeListValue("invalid"));
EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
}
{
CdmCapability capability;
manifest.SetKey(kCdmSupportedEncryptionSchemesName,
MakeListValue("invalid", "cenc"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCenc));
EXPECT_FALSE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCbcs));
}
{
// Wrong types are an error.
CdmCapability capability;
manifest.SetBoolKey(kCdmSupportedEncryptionSchemesName, true);
EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
}
{
// Missing values default to "cenc".
CdmCapability capability;
EXPECT_TRUE(manifest.RemoveKey(kCdmSupportedEncryptionSchemesName));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_TRUE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCenc));
EXPECT_FALSE(base::Contains(capability.encryption_schemes,
media::EncryptionMode::kCbcs));
}
}
TEST(CdmManifestTest, ManifestSessionTypes) {
auto manifest = DefaultManifest();
{
// Try false (persistent license not supported).
CdmCapability capability;
manifest.SetBoolKey(kCdmPersistentLicenseSupportName, false);
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.session_types.size(), 1u);
EXPECT_TRUE(base::Contains(capability.session_types,
media::CdmSessionType::kTemporary));
EXPECT_FALSE(base::Contains(capability.session_types,
media::CdmSessionType::kPersistentLicense));
}
{
// Try true (persistent license is supported).
CdmCapability capability;
manifest.SetBoolKey(kCdmPersistentLicenseSupportName, true);
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.session_types.size(), 2u);
EXPECT_TRUE(base::Contains(capability.session_types,
media::CdmSessionType::kTemporary));
EXPECT_TRUE(base::Contains(capability.session_types,
media::CdmSessionType::kPersistentLicense));
}
{
// Wrong types are an error.
CdmCapability capability;
manifest.SetStringKey(kCdmPersistentLicenseSupportName, "true");
EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
}
{
// Missing values default to "temporary".
CdmCapability capability;
EXPECT_TRUE(manifest.RemoveKey(kCdmPersistentLicenseSupportName));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_TRUE(base::Contains(capability.session_types,
media::CdmSessionType::kTemporary));
EXPECT_FALSE(base::Contains(capability.session_types,
media::CdmSessionType::kPersistentLicense));
}
}
TEST(CdmManifestTest, ManifestProxyProtocols) {
auto manifest = DefaultManifest();
{
// Try only supported value.
CdmCapability capability;
manifest.SetKey(kCdmSupportedCdmProxyProtocolsName, MakeListValue("intel"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.cdm_proxy_protocols.size(), 1u);
EXPECT_TRUE(base::Contains(capability.cdm_proxy_protocols,
media::CdmProxy::Protocol::kIntel));
}
{
// Unrecognized values are ignored.
CdmCapability capability;
manifest.SetKey(kCdmSupportedCdmProxyProtocolsName,
MakeListValue("unknown", "intel"));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.cdm_proxy_protocols.size(), 1u);
EXPECT_TRUE(base::Contains(capability.cdm_proxy_protocols,
media::CdmProxy::Protocol::kIntel));
}
{
// Wrong types are an error.
CdmCapability capability;
manifest.SetStringKey(kCdmSupportedCdmProxyProtocolsName, "intel");
EXPECT_FALSE(ParseCdmManifest(manifest, &capability));
}
{
// Missing values are OK.
CdmCapability capability;
EXPECT_TRUE(manifest.RemoveKey(kCdmSupportedCdmProxyProtocolsName));
EXPECT_TRUE(ParseCdmManifest(manifest, &capability));
EXPECT_EQ(capability.cdm_proxy_protocols.size(), 0u);
}
}
...@@ -3333,6 +3333,10 @@ test("unit_tests") { ...@@ -3333,6 +3333,10 @@ test("unit_tests") {
} }
} }
if (enable_library_cdms) {
sources += [ "../common/media/cdm_manifest_unittest.cc" ]
}
if (!is_android) { if (!is_android) {
sources += [ sources += [
# CRLSets are not supported on Android or iOS, but available on all other # CRLSets are not supported on Android or iOS, but available on all other
......
...@@ -32,7 +32,7 @@ struct SupportedVersion { ...@@ -32,7 +32,7 @@ struct SupportedVersion {
bool enabled; bool enabled;
}; };
constexpr std::array<SupportedVersion, 3> kSupportedCdmInterfaceVersions = {{ constexpr std::array<SupportedVersion, 2> kSupportedCdmInterfaceVersions = {{
{10, true}, {10, true},
{11, false}, {11, false},
}}; }};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment