| // Copyright 2017 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 "media/cdm/cdm_module.h" |
| |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "components/crash/core/common/crash_key.h" |
| |
| #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| #include "base/feature_list.h" |
| #include "media/base/media_switches.h" |
| #include "media/cdm/cdm_host_files.h" |
| #endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| |
| // INITIALIZE_CDM_MODULE is a macro in api/content_decryption_module.h. |
| // However, we need to pass it as a string to GetFunctionPointer(). The follow |
| // macro helps expanding it into a string. |
| #define STRINGIFY(X) #X |
| #define MAKE_STRING(X) STRINGIFY(X) |
| |
| namespace media { |
| |
| namespace { |
| |
| static CdmModule* g_cdm_module = nullptr; |
| |
| #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| void InitCdmHostVerification( |
| base::NativeLibrary cdm_library, |
| const base::FilePath& cdm_path, |
| const std::vector<CdmHostFilePath>& cdm_host_file_paths) { |
| DCHECK(cdm_library); |
| |
| CdmHostFiles cdm_host_files; |
| cdm_host_files.Initialize(cdm_path, cdm_host_file_paths); |
| |
| auto status = cdm_host_files.InitVerification(cdm_library); |
| |
| UMA_HISTOGRAM_ENUMERATION("Media.EME.CdmHostVerificationStatus", status, |
| CdmHostFiles::Status::kStatusCount); |
| } |
| #endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| |
| // These enums are reported to UMA so values should not be renumbered or reused. |
| enum class LoadResult { |
| kLoadSuccess, |
| kFileMissing, // The CDM does not exist. |
| kLoadFailed, // CDM exists but LoadNativeLibrary() failed. |
| kEntryPointMissing, // CDM loaded but somce required entry point missing. |
| // NOTE: Add new values only immediately above this line. |
| kLoadResultCount // Boundary value for UMA_HISTOGRAM_ENUMERATION. |
| }; |
| |
| void ReportLoadResult(LoadResult load_result) { |
| DCHECK_LT(load_result, LoadResult::kLoadResultCount); |
| UMA_HISTOGRAM_ENUMERATION("Media.EME.CdmLoadResult", load_result, |
| LoadResult::kLoadResultCount); |
| } |
| |
| void ReportLoadErrorCode(const base::NativeLibraryLoadError* error) { |
| // Only report load error code on Windows because that's the only platform that |
| // has a numerical error value. |
| #if defined(OS_WIN) |
| base::UmaHistogramSparse("Media.EME.CdmLoadErrorCode", error->code); |
| #endif |
| } |
| |
| void ReportLoadTime(const base::TimeDelta load_time) { |
| UMA_HISTOGRAM_TIMES("Media.EME.CdmLoadTime", load_time); |
| } |
| |
| } // namespace |
| |
| // static |
| CdmModule* CdmModule::GetInstance() { |
| // The |cdm_module| will be leaked and we will not be able to call |
| // |deinitialize_cdm_module_func_|. This is fine since it's never guaranteed |
| // to be called, e.g. in the fast shutdown case. |
| // TODO(xhwang): Find a better ownership model to make sure |cdm_module| is |
| // destructed properly whenever possible (e.g. in non-fast-shutdown case). |
| if (!g_cdm_module) |
| g_cdm_module = new CdmModule(); |
| |
| return g_cdm_module; |
| } |
| |
| // static |
| void CdmModule::ResetInstanceForTesting() { |
| if (!g_cdm_module) |
| return; |
| |
| delete g_cdm_module; |
| g_cdm_module = nullptr; |
| } |
| |
| CdmModule::CdmModule() = default; |
| |
| CdmModule::~CdmModule() { |
| if (deinitialize_cdm_module_func_) |
| deinitialize_cdm_module_func_(); |
| } |
| |
| CdmModule::CreateCdmFunc CdmModule::GetCreateCdmFunc() { |
| if (!initialized_) { |
| DLOG(ERROR) << __func__ << " called before CdmModule is initialized."; |
| return nullptr; |
| } |
| |
| // If initialization failed, nullptr will be returned. |
| return create_cdm_func_; |
| } |
| |
| #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| bool CdmModule::Initialize(const base::FilePath& cdm_path, |
| std::vector<CdmHostFilePath> cdm_host_file_paths) { |
| #else |
| bool CdmModule::Initialize(const base::FilePath& cdm_path) { |
| #endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| DVLOG(1) << __func__ << ": cdm_path = " << cdm_path.value(); |
| CHECK(!initialized_) << "CdmModule can only be initialized once!"; |
| |
| initialized_ = true; |
| cdm_path_ = cdm_path; |
| |
| // Load the CDM. |
| base::TimeTicks start = base::TimeTicks::Now(); |
| library_ = base::ScopedNativeLibrary(cdm_path); |
| base::TimeDelta load_time = base::TimeTicks::Now() - start; |
| if (!library_.is_valid()) { |
| LOG(ERROR) << "CDM at " << cdm_path.value() << " could not be loaded."; |
| LOG(ERROR) << "Error: " << library_.GetError()->ToString(); |
| ReportLoadResult(base::PathExists(cdm_path) ? LoadResult::kLoadFailed |
| : LoadResult::kFileMissing); |
| ReportLoadErrorCode(library_.GetError()); |
| return false; |
| } |
| |
| // Only report load time for success loads. |
| ReportLoadTime(load_time); |
| |
| // Get function pointers. |
| // TODO(xhwang): Define function names in macros to avoid typo errors. |
| initialize_cdm_module_func_ = reinterpret_cast<InitializeCdmModuleFunc>( |
| library_.GetFunctionPointer(MAKE_STRING(INITIALIZE_CDM_MODULE))); |
| deinitialize_cdm_module_func_ = reinterpret_cast<DeinitializeCdmModuleFunc>( |
| library_.GetFunctionPointer("DeinitializeCdmModule")); |
| create_cdm_func_ = reinterpret_cast<CreateCdmFunc>( |
| library_.GetFunctionPointer("CreateCdmInstance")); |
| get_cdm_version_func_ = reinterpret_cast<GetCdmVersionFunc>( |
| library_.GetFunctionPointer("GetCdmVersion")); |
| |
| if (!initialize_cdm_module_func_ || !deinitialize_cdm_module_func_ || |
| !create_cdm_func_ || !get_cdm_version_func_) { |
| LOG(ERROR) << "Missing entry function in CDM at " << cdm_path.value(); |
| initialize_cdm_module_func_ = nullptr; |
| deinitialize_cdm_module_func_ = nullptr; |
| create_cdm_func_ = nullptr; |
| get_cdm_version_func_ = nullptr; |
| library_.reset(); |
| ReportLoadResult(LoadResult::kEntryPointMissing); |
| return false; |
| } |
| |
| // In case of crashes, provide CDM version to facilitate investigation. |
| std::string cdm_version = get_cdm_version_func_(); |
| DVLOG(2) << __func__ << ": cdm_version = " << cdm_version; |
| |
| static crash_reporter::CrashKeyString<32> cdm_version_key("cdm-version"); |
| cdm_version_key.Set(cdm_version); |
| |
| #if defined(OS_WIN) |
| // Load DXVA before sandbox lockdown to give CDM access to Output Protection |
| // Manager (OPM). |
| LoadLibraryA("dxva2.dll"); |
| #endif // defined(OS_WIN) |
| |
| #if BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| if (base::FeatureList::IsEnabled(media::kCdmHostVerification)) |
| InitCdmHostVerification(library_.get(), cdm_path_, cdm_host_file_paths); |
| #endif // BUILDFLAG(ENABLE_CDM_HOST_VERIFICATION) |
| |
| ReportLoadResult(LoadResult::kLoadSuccess); |
| return true; |
| } |
| |
| void CdmModule::InitializeCdmModule() { |
| DCHECK(initialized_); |
| DCHECK(initialize_cdm_module_func_); |
| initialize_cdm_module_func_(); |
| } |
| |
| } // namespace media |