diff options
Diffstat (limited to 'chromium/chrome/common/media/cdm_manifest.cc')
-rw-r--r-- | chromium/chrome/common/media/cdm_manifest.cc | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/chromium/chrome/common/media/cdm_manifest.cc b/chromium/chrome/common/media/cdm_manifest.cc new file mode 100644 index 00000000000..e7147156dec --- /dev/null +++ b/chromium/chrome/common/media/cdm_manifest.cc @@ -0,0 +1,372 @@ +// 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 <memory> +#include <string> +#include <vector> + +#include "base/containers/flat_set.h" +#include "base/containers/span.h" +#include "base/files/file_path.h" +#include "base/json/json_file_value_serializer.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 "base/version.h" +#include "content/public/common/cdm_info.h" +#include "extensions/common/manifest_constants.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"; +#if BUILDFLAG(USE_PROPRIETARY_CODECS) +const char kCdmSupportedCodecAvc1[] = "avc1"; +#endif + +// 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); +#if BUILDFLAG(USE_PROPRIETARY_CODECS) + } else if (codec == kCdmSupportedCodecAvc1) { + result.push_back(media::VideoCodec::kCodecH264); +#endif + } + } + + 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; +} + +bool GetVersion(const base::Value& manifest, base::Version* version) { + DCHECK(manifest.is_dict()); + auto* version_string = + manifest.FindStringKey(extensions::manifest_keys::kVersion); + if (!version_string) { + DLOG(ERROR) << "CDM manifest missing " + << extensions::manifest_keys::kVersion; + return false; + } + + *version = base::Version(*version_string); + if (!version->IsValid()) { + DLOG(ERROR) << "CDM manifest version " << version_string << " is invalid."; + return false; + } + + 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); +} + +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); +} + +bool ParseCdmManifestFromPath(const base::FilePath& manifest_path, + base::Version* version, + content::CdmCapability* capability) { + JSONFileValueDeserializer deserializer(manifest_path); + int error_code; + std::string error_message; + std::unique_ptr<base::Value> manifest = + deserializer.Deserialize(&error_code, &error_message); + if (!manifest || !manifest->is_dict()) { + DLOG(ERROR) << "Could not deserialize CDM manifest from " << manifest_path + << ". Error: " << error_code << " / " << error_message; + return false; + } + + return IsCdmManifestCompatibleWithChrome(*manifest) && + GetVersion(*manifest, version) && + ParseCdmManifest(*manifest, capability); +} |