blob: ce695e7bb87d2d6e20ed3febfeda0c79344a6159 [file] [log] [blame]
// Copyright 2016 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_host_files.h"
#include <memory>
#include <vector>
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/native_library.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "build/build_config.h"
#include "media/cdm/api/content_decryption_module_ext.h"
#include "media/cdm/cdm_paths.h"
// Needed for finding CDM path from CDM adapter path.
// TODO(crbug.com/403462): Remove this after CDM adapter is deprecated.
#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
namespace media {
namespace {
// TODO(xhwang): Move this to a common place if needed.
const base::FilePath::CharType kSignatureFileExtension[] =
FILE_PATH_LITERAL(".sig");
// Returns the signature file path given the |file_path|. This function should
// only be used when the signature file and the file are located in the same
// directory, which is the case for the CDM and CDM adapter.
base::FilePath GetSigFilePath(const base::FilePath& file_path) {
return file_path.AddExtension(kSignatureFileExtension);
}
// Returns the CDM library file name given the |cdm_adapter_file_name|. Returns
// nullptr if |cdm_adapter_file_name| does not correspond to a known CDM.
const char* GetCdmFileName(const base::FilePath& cdm_adapter_file_name) {
#if defined(WIDEVINE_CDM_AVAILABLE)
if (cdm_adapter_file_name ==
base::FilePath::FromUTF8Unsafe(kWidevineCdmAdapterFileName))
return kWidevineCdmLibraryName;
#endif
// Clear Key CDM. For test only.
if (cdm_adapter_file_name ==
base::FilePath::FromUTF8Unsafe(media::kClearKeyCdmAdapterFileName))
return media::kClearKeyCdmLibraryName;
return nullptr;
}
// Returns the path to the CDM binary given the |cdm_adapter_path|. Returns an
// empty path if |cdm_adapter_path| does not correspond to a known CDM.
base::FilePath GetCdmPath(const base::FilePath& cdm_adapter_path) {
const char* cdm_file_name = GetCdmFileName(cdm_adapter_path.BaseName());
if (!cdm_file_name)
return base::FilePath();
return cdm_adapter_path.DirName().AppendASCII(
base::GetNativeLibraryName(cdm_file_name));
}
// Gets the library where InitVerificationFunc pointer can be obtained.
// TODO(crbug.com/403462): Remove this after CDM adapter is deprecated.
base::NativeLibrary GetCdmLibrary(base::NativeLibrary cdm_adapter_library,
const base::FilePath& cdm_adapter_path) {
DCHECK(cdm_adapter_library);
base::NativeLibrary cdm_library = nullptr;
#if defined(OS_LINUX) || defined(OS_MACOSX)
// On POSIX, "the dlsym() function shall search for the named symbol in all
// objects loaded automatically as a result of loading the object referenced
// by handle". Since the CDM is loaded automatically as a result of loading
// the CDM adapter, we can just use the adapter to look for CDM symbols.
cdm_library = cdm_adapter_library;
#elif defined(OS_WIN)
// On Windows, we have manually load the CDM.
base::ScopedNativeLibrary scoped_cdm_library;
base::NativeLibraryLoadError error;
scoped_cdm_library.Reset(
base::LoadNativeLibrary(GetCdmPath(cdm_adapter_path), &error));
if (!scoped_cdm_library.is_valid())
LOG(ERROR) << "Failed to load CDM (error: " << error.ToString() << ")";
else
cdm_library = scoped_cdm_library.get();
#endif
return cdm_library;
}
} // namespace
CdmHostFiles::CdmHostFiles() {
DVLOG(1) << __func__;
}
CdmHostFiles::~CdmHostFiles() {
DVLOG(1) << __func__;
}
void CdmHostFiles::InitializeWithAdapter(
const base::FilePath& cdm_adapter_path,
const std::vector<CdmHostFilePath>& cdm_host_file_paths) {
OpenCdmFileWithAdapter(cdm_adapter_path);
OpenCommonFiles(cdm_host_file_paths);
}
void CdmHostFiles::Initialize(
const base::FilePath& cdm_path,
const std::vector<CdmHostFilePath>& cdm_host_file_paths) {
OpenCdmFile(cdm_path);
OpenCommonFiles(cdm_host_file_paths);
}
CdmHostFiles::Status CdmHostFiles::InitVerificationWithAdapter(
base::NativeLibrary cdm_adapter_library,
const base::FilePath& cdm_adapter_path) {
DVLOG(1) << __func__;
DCHECK(cdm_adapter_library);
base::NativeLibrary cdm_library =
GetCdmLibrary(cdm_adapter_library, cdm_adapter_path);
if (!cdm_library)
return Status::kCdmLoadFailed;
return InitVerification(cdm_library);
}
CdmHostFiles::Status CdmHostFiles::InitVerification(
base::NativeLibrary cdm_library) {
DVLOG(1) << __func__;
DCHECK(cdm_library);
// Get function pointer exported by the CDM.
// See media/cdm/api/content_decryption_module_ext.h.
using InitVerificationFunc =
bool (*)(const cdm::HostFile* cdm_host_files, uint32_t num_files);
static const char kInitVerificationFuncName[] = "VerifyCdmHost_0";
InitVerificationFunc init_verification_func =
reinterpret_cast<InitVerificationFunc>(
base::GetFunctionPointerFromNativeLibrary(cdm_library,
kInitVerificationFuncName));
if (!init_verification_func) {
LOG(ERROR) << "Function " << kInitVerificationFuncName << " not found.";
CloseAllFiles();
return Status::kGetFunctionFailed;
}
// Fills |cdm_host_files| with common and CDM specific files.
std::vector<cdm::HostFile> cdm_host_files;
TakePlatformFiles(&cdm_host_files);
// std::vector::data() is not guaranteed to be nullptr when empty().
const cdm::HostFile* cdm_host_files_ptr =
cdm_host_files.empty() ? nullptr : cdm_host_files.data();
// Call |init_verification_func| on the CDM with |cdm_host_files|. Note that
// the ownership of these files are transferred to the CDM, which will close
// the files immediately after use.
DVLOG(1) << __func__ << ": Calling " << kInitVerificationFuncName
<< "() with " << cdm_host_files.size() << " files.";
for (const auto& host_file : cdm_host_files) {
DVLOG(1) << " - File Path: " << host_file.file_path;
DVLOG(1) << " - File: " << host_file.file;
DVLOG(1) << " - Sig File: " << host_file.sig_file;
}
if (!init_verification_func(cdm_host_files_ptr, cdm_host_files.size())) {
DVLOG(1) << "Failed to verify CDM host.";
CloseAllFiles();
return Status::kInitVerificationFailed;
}
// Close all files not passed to the CDM.
CloseAllFiles();
return Status::kSuccess;
}
void CdmHostFiles::CloseAllFiles() {
common_files_.clear();
cdm_specific_files_.clear();
}
void CdmHostFiles::OpenCommonFiles(
const std::vector<CdmHostFilePath>& cdm_host_file_paths) {
DCHECK(common_files_.empty());
for (const auto& value : cdm_host_file_paths) {
common_files_.push_back(
CdmHostFile::Create(value.file_path, value.sig_file_path));
}
}
void CdmHostFiles::OpenCdmFileWithAdapter(
const base::FilePath& cdm_adapter_path) {
DCHECK(!cdm_adapter_path.empty());
cdm_specific_files_.push_back(
CdmHostFile::Create(cdm_adapter_path, GetSigFilePath(cdm_adapter_path)));
base::FilePath cdm_path = GetCdmPath(cdm_adapter_path);
if (cdm_path.empty()) {
LOG(ERROR) << "CDM path is empty.";
return;
}
OpenCdmFile(cdm_path);
}
void CdmHostFiles::OpenCdmFile(const base::FilePath& cdm_path) {
DCHECK(!cdm_path.empty());
cdm_specific_files_.push_back(
CdmHostFile::Create(cdm_path, GetSigFilePath(cdm_path)));
}
void CdmHostFiles::TakePlatformFiles(
std::vector<cdm::HostFile>* cdm_host_files) {
DCHECK(cdm_host_files->empty());
// Populate an array of cdm::HostFile.
for (const auto& file : common_files_)
cdm_host_files->push_back(file->TakePlatformFile());
for (const auto& file : cdm_specific_files_)
cdm_host_files->push_back(file->TakePlatformFile());
}
// Question(xhwang): Any better way to check whether a plugin is a CDM? Maybe
// when we register the plugin we can set some flag explicitly?
bool IsCdm(const base::FilePath& cdm_adapter_path) {
return !GetCdmPath(cdm_adapter_path).empty();
}
} // namespace media