blob: 330643005581eb48ccad54e0c0790c5229b2fed6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cdm/common/cdm_manifest.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <string_view>
#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_split.h"
#include "base/values.h"
#include "base/version.h"
#include "content/public/common/cdm_info.h"
#include "media/base/cdm_capability.h"
#include "media/base/content_decryption_module.h"
#include "media/base/decrypt_config.h"
#include "media/base/video_codecs.h"
#include "media/cdm/supported_audio_codecs.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
// 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[] = ",";
// This field in the manifest is parsed by component updater code (e.g.
// `ComponentInstaller::FindPreinstallation()`) as well as in this file.
const char kCdmVersion[] = "version";
// 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 following strings are used to specify supported codecs in the
// parameter |kCdmCodecsListName|.
const char kCdmSupportedCodecVp8[] = "vp8";
// 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";
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::Dict& manifest,
const std::string version_name,
VersionCheckFunc version_check_func) {
auto* version_string = manifest.FindString(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 (std::string_view 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 |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::Dict& manifest,
base::flat_set<media::EncryptionScheme>* encryption_schemes) {
DCHECK(encryption_schemes);
const base::Value* value = manifest.Find(kCdmSupportedEncryptionSchemesName);
if (!value) {
// No manifest entry found, so assume only 'cenc' supported for backwards
// compatibility.
encryption_schemes->insert(media::EncryptionScheme::kCenc);
return true;
}
if (!value->is_list()) {
DLOG(ERROR) << "CDM manifest entry " << kCdmSupportedEncryptionSchemesName
<< " is not a list.";
return false;
}
base::flat_set<media::EncryptionScheme> result;
for (const auto& item : value->GetList()) {
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::EncryptionScheme::kCenc);
} else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
result.insert(media::EncryptionScheme::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 |audio_codecs| with the full set of audio
// codecs that support decryption.
bool GetAudioCodecs(const base::Value::Dict& manifest,
base::flat_set<media::AudioCodec>* audio_codecs) {
DCHECK(audio_codecs);
// Note that desktop CDMs only support decryption of audio content,
// no decoding. Manifest does not contain any audio codecs, so return
// the standard set of audio codecs supported only if there is at least
// one encryption scheme specified.
base::flat_set<media::EncryptionScheme> encryption_schemes;
if (!GetEncryptionSchemes(manifest, &encryption_schemes)) {
return false;
}
DCHECK(!encryption_schemes.empty());
*audio_codecs = media::GetCdmSupportedAudioCodecs();
return true;
}
// Returns true and updates |video_codecs| if the appropriate manifest entry is
// valid. Returns false and does not modify |video_codecs| if the manifest entry
// is incorrectly formatted.
bool GetVideoCodecs(const base::Value::Dict& manifest,
media::CdmCapability::VideoCodecMap* video_codecs) {
DCHECK(video_codecs);
const base::Value* value = manifest.Find(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;
}
const std::vector<std::string_view> supported_codecs =
base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// As the manifest string does not include profiles, specify {} to indicate
// that all relevant profiles should be considered supported.
media::CdmCapability::VideoCodecMap result;
const media::VideoCodecInfo kAllProfiles;
for (const auto& codec : supported_codecs) {
if (codec == kCdmSupportedCodecVp8) {
result.emplace(media::VideoCodec::kVP8, kAllProfiles);
} else if (codec == kCdmSupportedCodecVp9) {
result.emplace(media::VideoCodec::kVP9, kAllProfiles);
} else if (codec == kCdmSupportedCodecAv1) {
result.emplace(media::VideoCodec::kAV1, kAllProfiles);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
} else if (codec == kCdmSupportedCodecAvc1) {
result.emplace(media::VideoCodec::kH264, kAllProfiles);
#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::Dict& manifest,
base::flat_set<media::CdmSessionType>* session_types) {
DCHECK(session_types);
bool is_persistent_license_supported = false;
const base::Value* value = manifest.Find(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;
}
bool GetVersion(const base::Value::Dict& manifest, base::Version* version) {
auto* version_string = manifest.FindString(kCdmVersion);
if (!version_string) {
DLOG(ERROR) << "CDM manifest missing " << kCdmVersion;
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::Dict& manifest) {
return CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
media::IsSupportedCdmModuleVersion) &&
CheckForCompatibleVersion(
manifest, kCdmInterfaceVersionsName,
media::IsSupportedAndEnabledCdmInterfaceVersion) &&
CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
media::IsSupportedCdmHostVersion);
}
bool ParseCdmManifest(const base::Value::Dict& manifest,
media::CdmCapability* capability) {
return GetAudioCodecs(manifest, &capability->audio_codecs) &&
GetVideoCodecs(manifest, &capability->video_codecs) &&
GetEncryptionSchemes(manifest, &capability->encryption_schemes) &&
GetSessionTypes(manifest, &capability->session_types) &&
GetVersion(manifest, &capability->version);
}
bool ParseCdmManifestFromPath(const base::FilePath& manifest_path,
media::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;
}
base::Value::Dict& manifest_dict = manifest->GetDict();
return IsCdmManifestCompatibleWithChrome(manifest_dict) &&
ParseCdmManifest(manifest_dict, capability);
}