Source release 19.3.0

This commit is contained in:
John W. Bruce
2024-09-05 07:02:36 +00:00
parent cd8256726f
commit 11c108a8da
122 changed files with 2259 additions and 1082 deletions

View File

@@ -19,6 +19,9 @@
'device_files_target': 'cdm.gyp:device_files',
# The path to the prebuilt CDM to test against. (iOS only)
'prebuilt_cdm_path%': '',
# The path to the include directory.
'test_include_dir%': 'include',
'expected_cdm_version%': '',
'supports_dynamic_perf_test%': 0,
'json_dir': '../third_party/nlohmann-json',
'jsmn_dir': '../third_party/jsmn',
@@ -150,6 +153,21 @@
],
}],
}], # condition: supports_dynamic_perf_test
['prebuilt_cdm_path!=""', {
'targets': [{
'target_name': 'widevine_check_version',
'type': 'executable',
'sources': [
'test/test_version.cpp',
],
'include_dirs': [
'<(test_include_dir)',
],
'libraries': [
'<(prebuilt_cdm_path)',
],
}],
}],
['OS=="ios"', {
'targets': [{
'toolsets' : [ 'target' ],
@@ -165,6 +183,7 @@
],
'xcode_settings': {
'INFOPLIST_FILE': 'test/info.plist',
'PROVISIONING_PROFILE_SPECIFIER': 'Google Development',
},
'conditions': [
['prebuilt_cdm_path!=""', {
@@ -187,6 +206,7 @@
'type': 'loadable_module',
'mac_xctest_bundle': '1',
'defines': [
'EXPECTED_CDM_VERSION="<(expected_cdm_version)"',
'GTEST_FILTER="<(gtest_filter)"',
],
'xcode_settings': {
@@ -210,22 +230,27 @@
'../core/test/license_request.cpp',
'../core/test/url_request.cpp',
'../util/src/string_conversions.cpp',
'../util/src/string_format.cpp',
'../util/test/test_clock.cpp',
'../util/test/test_sleep.cpp',
'src/log.cpp',
'src/logger_global.cpp',
'test/perf_test.cpp',
'test/perf_test_xctest.mm',
'test/test_host.cpp',
'test/test_version_xctest.mm',
],
'includes': [ '../util/libssl_dependency.gypi' ],
'include_dirs': [
'<(test_include_dir)',
'../core/include',
'../core/test',
'../oemcrypto/include',
'../util/include',
'../util/test',
'include',
],
'dependencies': [
'../cdm/cdm.gyp:device_files',
'../third_party/googletest.gyp:gmock',
'../third_party/googletest.gyp:gtest',
'dummy_app',

View File

@@ -15,17 +15,24 @@
'toolsets' : [ 'target' ],
'target_name': 'extract_bcc_tool',
'sources': [
'../factory_upload_tool/ce/log.cpp',
'../factory_upload_tool/ce/properties_ce.cpp',
'../factory_upload_tool/ce/wv_factory_extractor.cpp',
'../factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp',
'../oemcrypto/test/extract_bcc_tool.cpp',
'../util/src/string_conversions.cpp',
],
'include_dirs': [
'../cdm/include',
'../core/include',
'../metrics/include',
'../factory_upload_tool/ce',
'../factory_upload_tool/common/include',
'../oemcrypto/include',
'../util/include',
],
'dependencies': [
'cdm.gyp:widevine_ce_cdm_static',
# The liboemcrypto.so is dynamically loaded, causing control flow
# integrity check failed during indirect function call.
'cflags/': [
['exclude', '^-fsanitize'],
['exclude', '^-fno-sanitize'],
],
'msvs_settings': {
'VCLinkerTool': {

View File

@@ -10,7 +10,7 @@
# define CDM_VERSION_MAJOR 19
#endif
#ifndef CDM_VERSION_MINOR
# define CDM_VERSION_MINOR 2
# define CDM_VERSION_MINOR 3
#endif
#ifndef CDM_VERSION_PATCH
# define CDM_VERSION_PATCH 0

View File

@@ -8,6 +8,7 @@
#include <time.h>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
@@ -2219,6 +2220,208 @@ TEST_F(CdmTest, GetMetrics) {
EXPECT_NE(metrics.length(), 0u);
}
// These test reverse-compatibility of offline licenses. These tests are
// disabled by default as they require multiple runs using different builds.
// First run the *Setup* test with the old build to save the files into the test
// data path. Then copy the *.dat files into the new build directory. Finally
// run the remaining tests on the new build.
// You can set GTEST_ALSO_RUN_DISABLED_TESTS variable to run these tests too.
class OfflineReverseCompatTest : public CdmTest {
public:
void SaveToFiles(const std::string& session_id) {
auto save = [](const std::string& data, const std::string& path) {
std::ofstream os(path);
os.write(data.data(), data.size());
ASSERT_TRUE(os);
};
ASSERT_NO_FATAL_FAILURE(save(session_id, kSessionIdFile));
std::string data;
ASSERT_TRUE(g_host->global_storage().SaveToString(&data));
ASSERT_NO_FATAL_FAILURE(save(data, kGlobalStorageFile));
ASSERT_TRUE(g_host->per_origin_storage().SaveToString(&data));
ASSERT_NO_FATAL_FAILURE(save(data, kPerOriginStorageFile));
}
void LoadFromFiles(std::string* session_id) {
auto load = [](const std::string& path, std::string* data) {
std::ifstream file(path);
ASSERT_TRUE(file);
data->assign(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
};
ASSERT_NO_FATAL_FAILURE(load(kSessionIdFile, session_id));
std::string data;
ASSERT_NO_FATAL_FAILURE(load(kGlobalStorageFile, &data));
ASSERT_TRUE(g_host->global_storage().LoadFromString(data));
ASSERT_NO_FATAL_FAILURE(load(kPerOriginStorageFile, &data));
ASSERT_TRUE(g_host->per_origin_storage().LoadFromString(data));
}
private:
static constexpr const char* kSessionIdFile = "session_id.dat";
static constexpr const char* kGlobalStorageFile = "global.dat";
static constexpr const char* kPerOriginStorageFile = "per_origin.dat";
};
TEST_F(OfflineReverseCompatTest, DISABLED_Setup) {
constexpr const size_t kNumSessions = 20;
std::string first_session_id;
std::string session_id;
std::string response;
EnsureProvisioned();
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentLicense, Cdm::kCenc, &first_session_id, &response));
ASSERT_EQ(Cdm::kSuccess, updateWithRetry(first_session_id, response));
ASSERT_EQ(Cdm::kSuccess, cdm_->close(first_session_id));
for (size_t i = 0; i < kNumSessions; i++) {
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response));
ASSERT_EQ(Cdm::kSuccess, updateWithRetry(session_id, response));
ASSERT_EQ(Cdm::kSuccess, cdm_->close(session_id));
}
ASSERT_NO_FATAL_FAILURE(SaveToFiles(first_session_id));
}
TEST_F(OfflineReverseCompatTest, DISABLED_LoadAndDecrypt) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id));
ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned);
Cdm::Status status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Cdm::Subsample subsample;
subsample.protected_bytes = kInputSize;
// Set up sample
Cdm::Sample sample;
sample.input.iv = kIvCenc;
sample.input.iv_length = kIvCencSize;
sample.input.data = kInput;
sample.input.data_length = kInputSize;
sample.input.subsamples = &subsample;
sample.input.subsamples_length = 1;
std::vector<uint8_t> output_buffer(sample.input.data_length);
sample.output.data = output_buffer.data();
sample.output.data_length = static_cast<uint32_t>(output_buffer.size());
// Set up batch to decrypt
Cdm::DecryptionBatch batch;
batch.samples = &sample;
batch.samples_length = 1;
batch.key_id = kKeyIdCtr.data();
batch.key_id_length = static_cast<uint32_t>(kKeyIdCtr.size());
batch.pattern = kPatternNone;
batch.encryption_scheme = Cdm::kAesCtr;
std::vector<uint8_t> expected_output(kOutputCenc,
kOutputCenc + kOutputCencSize);
status = cdm_->decrypt(session_id, batch);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(expected_output, output_buffer);
}
TEST_F(OfflineReverseCompatTest, DISABLED_CreateSessionThenLoad) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id));
ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned);
// This will cause us to re-provision.
EnsureProvisioned();
// Create a new streaming license to use the new CDM/OEMCrypto.
std::string new_session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response));
EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true));
Cdm::Status status = updateWithRetry(new_session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Loading the offline license should still succeed.
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
}
TEST_F(OfflineReverseCompatTest, DISABLED_LoadThenCreateSession) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id));
ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned);
// Loading the offline license should not break new sessions.
Cdm::Status status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(cdm_->close(session_id), Cdm::kSuccess);
// This will cause us to re-provision.
EnsureProvisioned();
std::string new_session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response));
EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true));
status = updateWithRetry(new_session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
TEST_F(OfflineReverseCompatTest, DISABLED_LoadThenCreateOfflineSession) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id));
ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned);
// Loading the offline license should not break new sessions.
Cdm::Status status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(cdm_->close(session_id), Cdm::kSuccess);
// This will cause us to re-provision.
EnsureProvisioned();
std::string new_session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentLicense, Cdm::kCenc, &new_session_id, &response));
EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true));
status = updateWithRetry(new_session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
TEST_F(OfflineReverseCompatTest, DISABLED_NewCdmInstance) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(LoadFromFiles(&session_id));
ASSERT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned);
// This will cause us to re-provision.
EnsureProvisioned();
std::string new_session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kTemporary, Cdm::kCenc, &new_session_id, &response));
EXPECT_CALL(*this, onKeyStatusesChange(new_session_id, true));
Cdm::Status status = updateWithRetry(new_session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Loading should still work with an entirely new CDM instance.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EnsureProvisioned();
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
}
TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) {
EnsureProvisioned();
DecryptParam param = GetParam();

View File

@@ -7,11 +7,14 @@
#include <string.h>
#include <time.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "cdm.h"
#include "log.h"
#include "oec_device_features.h"
#include "stderr_logger.h"
#include "test_base.h"
#include "test_host.h"
@@ -22,13 +25,56 @@ std::string g_sandbox_id;
namespace widevine {
namespace {
constexpr char kSandboxIdParam[] = "--sandbox_id=";
constexpr char kCertPathParam[] = "--cert_path=";
constexpr char kCertKeyPathParam[] = "--cert_key_path=";
// Following the pattern established by help text in test_base.cpp
constexpr char kExtraHelpText[] =
" --sandbox_id=<sandbox_id>\n"
" Specifies the Sandbox ID that should be sent to OEMCrypto via\n"
" OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n"
" in use, this parameter should be omitted.\n";
" in use, this parameter should be omitted.\n"
" --cert_path=<cert_path>\n"
" Path to a preloaded DRM certificate. This may speed up some tests\n"
" by skipping the provisioning step. On most platforms, this parameter\n"
" parameter should be omitted.\n"
" --cert_key_path=<cert_key_path>\n"
" Path to a key in preloaded DRM certificate. This should only be used\n"
" if the device has a baked in cert.\n";
bool ReadFile(const std::string& path, std::string* output) {
output->clear();
std::ifstream fs(path, std::ios::in | std::ios::binary);
if (!fs) {
LOGE("Failed to open %s: %s", path.c_str(), strerror(errno));
return false;
}
std::stringstream buffer;
buffer << fs.rdbuf();
*output = buffer.str();
return true;
}
// Reads a file from the command line arguments.
// The file path is expected to be the first argument after the flag.
// For example, if the flag is "--cert_path=" and the command line is
// cdm_test_runner --cert_path=/path/to/cert
// then the file at /path/to/cert will be read.
// The flag will be removed from the command line arguments.
bool ReadFileFromArg(const char* path_flag, std::vector<std::string>& args,
std::string* data) {
auto path_iter = std::find_if(std::begin(args) + 1, std::end(args),
[path_flag](const std::string& elem) -> bool {
return elem.find(path_flag) == 0;
});
if (path_iter != std::end(args)) {
const std::string path = path_iter->substr(strlen(path_flag));
args.erase(path_iter);
return ReadFile(path, data);
}
return false;
}
} // namespace
int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer,
@@ -50,6 +96,15 @@ int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer,
(void)status; // status is now used when assertions are turned off.
assert(status == Cdm::kSuccess);
std::string data;
if (ReadFileFromArg(kCertPathParam, args, &data)) {
g_host->set_baked_in_cert(data);
}
if (ReadFileFromArg(kCertKeyPathParam, args, &data)) {
wvoec::global_features.set_rsa_test_key(
std::vector<uint8_t>(data.begin(), data.end()));
}
std::vector<const char*> new_argv(args.size());
std::transform(
std::begin(args), std::end(args), std::begin(new_argv),

View File

@@ -6,6 +6,7 @@
#include "test_host.h"
wvutil::FileSystem* CreateTestFileSystem() {
return new wvutil::FileSystem("", &g_host->per_origin_storage());
std::unique_ptr<wvutil::FileSystem> CreateTestFileSystem() {
return std::make_unique<wvutil::FileSystem>("",
&g_host->per_origin_storage());
}

View File

@@ -169,7 +169,7 @@ bool SendPost(const std::string& message, std::string* response) {
std::unique_ptr<Cdm> CreateCdm(EventListener* event_listener) {
std::unique_ptr<Cdm> ret(
create_func(event_listener, &g_host->per_origin_storage(), true));
create_func(event_listener, &g_host->per_origin_storage(), true, false));
if (ret) {
EXPECT_SUCCESS(ret->setServiceCertificate(
Cdm::kProvisioningService,
@@ -182,8 +182,7 @@ std::unique_ptr<Cdm> CreateCdm(EventListener* event_listener) {
class GlobalEnv : public testing::Environment {
public:
GlobalEnv(InitFuncType init_func, const std::string& cert)
: init_func_(init_func), cert_(cert) {}
GlobalEnv(InitFuncType init_func) : init_func_(init_func) {}
void SetUp() override {
// Manually set the logger because `TestHost` makes logging calls before
@@ -191,7 +190,6 @@ class GlobalEnv : public testing::Environment {
g_logger = &g_stderr_logger;
g_host = new TestHost;
if (!cert_.empty()) g_host->per_origin_storage().write("cert.bin", cert_);
Cdm::LogLevel log_level = Cdm::kErrors;
if (const char* verbose = getenv("VERBOSE_OUTPUT")) {
@@ -203,7 +201,6 @@ class GlobalEnv : public testing::Environment {
private:
const InitFuncType init_func_;
const std::string cert_;
};
} // namespace
@@ -331,8 +328,7 @@ std::string PrintDecryptParam(const testing::TestParamInfo<bool>& info) {
INSTANTIATE_TEST_SUITE_P(Decrypt, DecryptPerfTest, testing::Bool(),
PrintDecryptParam);
int PerfTestMain(InitFuncType init_func, CreateFuncType create,
const std::string& cert) {
int PerfTestMain(InitFuncType init_func, CreateFuncType create) {
#ifdef _DEBUG
// Don't use #error since we build all targets and we don't want to fail the
// debug build (and we can't have configuration-specific targets).
@@ -340,7 +336,7 @@ int PerfTestMain(InitFuncType init_func, CreateFuncType create,
return 1;
#else
create_func = create;
testing::AddGlobalTestEnvironment(new GlobalEnv(init_func, cert));
testing::AddGlobalTestEnvironment(new GlobalEnv(init_func));
return RUN_ALL_TESTS();
#endif

View File

@@ -14,10 +14,10 @@ namespace widevine {
using InitFuncType = Cdm::Status (*)(Cdm::SecureOutputType, Cdm::IStorage*,
Cdm::IClock*, Cdm::ITimer*, Cdm::ILogger*,
Cdm::LogLevel);
using CreateFuncType = Cdm* (*)(Cdm::IEventListener*, Cdm::IStorage*, bool);
using CreateFuncType =
Cdm* (*)(Cdm::IEventListener*, Cdm::IStorage*, bool, bool);
int PerfTestMain(InitFuncType init_func, CreateFuncType create_func,
const std::string& cert);
int PerfTestMain(InitFuncType init_func, CreateFuncType create_func);
} // namespace widevine

View File

@@ -24,24 +24,6 @@ constexpr char kInitName[] =
constexpr char kCreateName[] =
"_ZN8widevine3Cdm6createEPNS0_14IEventListenerEPNS0_8IStorageEbb";
bool ReadFile(const std::string& path, std::string* output) {
constexpr size_t kReadSize = 8 * 1024;
std::ifstream fs(path, std::ios::in | std::ios::binary);
if (!fs) return false;
while (true) {
const size_t offset = output->size();
output->resize(output->size() + kReadSize);
fs.read(&output->at(offset), kReadSize);
if (fs.eof()) {
output->resize(offset + fs.gcount());
return true;
} else if (!fs) {
fprintf(stderr, "Error reading from cert file\n");
return false;
}
}
}
std::tuple<InitFuncType, CreateFuncType> LoadCdm(const char* path) {
// Note we will leak the library object; but this is just for tests and will
// be unloaded when we exit anyway.
@@ -70,15 +52,11 @@ int64_t Clock::GetCurrentTime() { return g_host->now() / 1000; }
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
if (argc != 3) {
fprintf(stderr, "Usage: %s <CDM_SO_PATH> <CERT_PATH>\n", argv[0]);
if (argc != 2) {
fprintf(stderr, "Usage: %s <CDM_SO_PATH>\n", argv[0]);
return 1;
}
std::string cert;
if (!widevine::ReadFile(argv[2], &cert))
return 1;
auto funcs = widevine::LoadCdm(argv[1]);
return widevine::PerfTestMain(std::get<0>(funcs), std::get<1>(funcs), cert);
return widevine::PerfTestMain(std::get<0>(funcs), std::get<1>(funcs));
}

View File

@@ -20,7 +20,7 @@
int argc = 1;
testing::InitGoogleTest(&argc, argv);
XCTAssertEqual(widevine::PerfTestMain(&widevine::Cdm::initialize, &widevine::Cdm::create, ""), 0);
XCTAssertEqual(widevine::PerfTestMain(&widevine::Cdm::initialize, &widevine::Cdm::create), 0);
}
@end

View File

@@ -137,6 +137,11 @@ bool TestHost::Storage::SaveToString(std::string* data) const {
}
bool TestHost::Storage::read(const std::string& name, std::string* data) {
if (wvutil::kLegacyCertificateFileName == name &&
!g_host->baked_in_cert().empty()) {
*data = g_host->baked_in_cert();
return true;
}
StorageMap::iterator it = files_.find(name);
bool ok = it != files_.end();
LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail");
@@ -148,12 +153,21 @@ bool TestHost::Storage::read(const std::string& name, std::string* data) {
bool TestHost::Storage::write(const std::string& name,
const std::string& data) {
LOGV("write file: %s", name.c_str());
if (wvutil::kLegacyCertificateFileName == name &&
!g_host->baked_in_cert().empty()) {
return false;
}
if (!CheckFilename(name)) return false;
files_[name] = data;
return true;
}
bool TestHost::Storage::exists(const std::string& name) {
if (wvutil::kLegacyCertificateFileName == name &&
!g_host->baked_in_cert().empty()) {
LOGV("exists? %s: always", name.c_str());
return true;
}
StorageMap::iterator it = files_.find(name);
bool ok = it != files_.end();
LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false");
@@ -174,6 +188,11 @@ bool TestHost::Storage::remove(const std::string& name) {
}
int32_t TestHost::Storage::size(const std::string& name) {
if (wvutil::kLegacyCertificateFileName == name &&
!g_host->baked_in_cert().empty()) {
LOGV("size? %s: always", name.c_str());
return static_cast<int32_t>(g_host->baked_in_cert().size());
}
StorageMap::iterator it = files_.find(name);
bool ok = (it != files_.end());
LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail");

View File

@@ -69,6 +69,13 @@ class TestHost : public widevine::Cdm::IClock,
void setTimeout(int64_t delay_ms, IClient* client, void* context) override;
void cancel(IClient* client) override;
// If this is set, then the storage will return this as a baked in cert.
// Trying to write a new cert will generate an error.
const std::string& baked_in_cert() const { return baked_in_cert_; };
void set_baked_in_cert(const std::string& baked_in_cert) {
baked_in_cert_ = baked_in_cert;
};
private:
struct Timer {
Timer(int64_t expiry_time, IClient* client, void* context)
@@ -95,6 +102,7 @@ class TestHost : public widevine::Cdm::IClock,
Storage global_storage_;
Storage per_origin_storage_;
std::string baked_in_cert_;
};
// Owned and managed by the test runner.

26
cdm/test/test_version.cpp Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include <stdio.h>
#include "cdm.h"
#include "cdm_version.h"
int main(int argc, char** argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <VERSION>\n", argv[0]);
return 1;
}
const char* bundle_version = argv[1];
fprintf(stderr, "Header: " CDM_VERSION "\n");
fprintf(stderr, "Library: %s\n", widevine::Cdm::version());
fprintf(stderr, "Bundle: %s\n", bundle_version);
if (strcmp(CDM_VERSION, widevine::Cdm::version()) ||
strcmp(CDM_VERSION, bundle_version)) {
fprintf(stderr, "ERROR: Mismatched version\n");
return 1;
}
return 0;
}

View File

@@ -0,0 +1,28 @@
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include <stdio.h>
#import <XCTest/XCTest.h>
#include "cdm.h"
#include "cdm_version.h"
#define STR(s) [NSString stringWithUTF8String:s]
@interface VersionTests : XCTestCase
@end
@implementation VersionTests
- (void)testVersion {
fprintf(stderr, "Header: " CDM_VERSION "\n");
fprintf(stderr, "Library: %s\n", widevine::Cdm::version());
fprintf(stderr, "Bundle: " EXPECTED_CDM_VERSION "\n");
XCTAssertEqual(STR(CDM_VERSION), STR(widevine::Cdm::version()));
XCTAssertEqual(STR(CDM_VERSION), STR(EXPECTED_CDM_VERSION));
}
@end