diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e23347..849f8114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,60 @@ [TOC] +## 16.3.0 (2020-07-24) + +Features: + - CE CDM 16.3.0 updates the included version of OEMCrypto and its tests to + v16.3. CE CDM 16.3.0 *requires* OEMCrypto v16.3 or later. Widevine will not + be supporting OEMCrypto v16.2 any longer. Upgrading to CE CDM 16.3.0 and + OEMCrypto v16.3 is required for all partners using the 16.x release series. + - OEMCrypto v16.3 includes several updates to the ODK code. Don't forget to + update your OEMCrypto integrations. + - The algorithms that drive the usage tables in the CE CDM are more robust, + particularly in cases involving deleting entries and/or the table becoming + fragmented. + +Bugfixes: + - Fixed a `validate_nonce` error when using `load_refresh_keys` with certain + license services. + - Fixed an issue where clear subsamples that don't make up a full sample might + be accepted when the later encrypted subsamples would be rejected. + - Fixed an issue preventing `device_files.cpp` from compiling with certain C++ + STL implementations. + - Fixed an issue where nonce-free offline licenses (such as those used by + ATSC 3.0) would fail to load in the v16 ODK. + - Fixed issues where compiling with recent GCC releases and with stringent + warning checks enabled would trigger warnings that were treated as errors, + failing compilation. + - Fixed an issue where the OEMCrypto tests were deriving keys too eagerly, + causing OEMCrypto implementations with very strict state-progression checks + to fail. + - Fixed an issue that was causing the following tests to fail when used with + recent license service builds: + - `CdmTest.RemoveUsageRecord` + - `CdmTest.RemoveThreeUsageRecords` + - `CdmTest.RemoveIncomplete` + - `CdmTest.RemoveUsageRecordIncomplete` + - `CdmRemoveTest/CdmTestWithRemoveParam.Remove/false, where GetParam() = false` + - `CdmRemoveTest/CdmTestWithRemoveParam.Remove/true, where GetParam() = true` + - Fixed an issue with accessing the usage table when OEMCrypto had reached the + maximum number of open sessions. + - Fixed an error that could occur if an offline license's file persisted after + its usage entry had been removed. + - Fixed a buffer overrun in the test code. + - Fixed a memory leak in the test code. + - Fixed a buffer overrun in the OEMCrypto Reference implementation. We will again + remind you that the OEMCrypto Reference implementation is *not* intended for production use. + - The test `DecryptNoAnalogToClearAPI13` was no longer valid and has been + removed. + - Fixed an issue where offline licenses with a rental duration and no PST + would instantly expire because they were treated as having been rented + in 1970. + - Fixed a rare issue that could occur with Device IDs between 33 and 64 bytes + long, inclusive. + - The CE CDM now correctly handles the case when OEMCrypto reports an + unlimited usage table capacity. + ## 16.2.0 (2020-04-10) **Note:** CE CDM 16.2.0 is the first release of the CE CDM 16 series. It is diff --git a/README.md b/README.md index 28a0e9fd..3311bd59 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ -# Widevine CE CDM 16.2.0 +# Widevine CE CDM 16.3.0 -Released 2020-04-10 +Released 2020-07-24 ## Getting started This project contains the sources for building a Widevine CDM module. Read the following to learn more about the contents of this project and how to use them: -[Widevine_CE_CDM_IntegrationGuide_16.2.0.pdf][integration-guide]\ +[Widevine_CE_CDM_IntegrationGuide_16.3.0.pdf][integration-guide]\ Documents the CDM API and describes how to integrate the CDM into a system. [CHANGELOG.md][changelog]\ Lists the major changes for each release. -[integration-guide]: ./Widevine_CE_CDM_IntegrationGuide_16.2.0.pdf +[integration-guide]: ./Widevine_CE_CDM_IntegrationGuide_16.3.0.pdf [changelog]: ./CHANGELOG.md ## Reference OEMCrypto Implementation diff --git a/Widevine_CE_CDM_IntegrationGuide_16.2.0.pdf b/Widevine_CE_CDM_IntegrationGuide_16.3.0.pdf similarity index 85% rename from Widevine_CE_CDM_IntegrationGuide_16.2.0.pdf rename to Widevine_CE_CDM_IntegrationGuide_16.3.0.pdf index 18954d32..e3730627 100644 Binary files a/Widevine_CE_CDM_IntegrationGuide_16.2.0.pdf and b/Widevine_CE_CDM_IntegrationGuide_16.3.0.pdf differ diff --git a/build.py b/build.py index c282828e..3c95b3be 100755 --- a/build.py +++ b/build.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/python3 -B # Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary # source code may only be used and distributed under the Widevine Master # License Agreement. @@ -247,6 +247,9 @@ def main(args): 'jobs will spawn without limit.') parser.add_argument('-D', action='append', default=[], help='Pass variable definitions to gyp.') + parser.add_argument('-e', '--extra_gyp', action='append', default=[], + help='External gyp file that are processed after the ' + 'standard gyp files. (Maybe be specified multiple times)') parser.add_argument('-ft', '--fuzz_tests', default=False, action='store_true', help='Set this flag if you want to build fuzz tests.') @@ -270,6 +273,9 @@ def main(args): os.path.join(OEMCRYPTO_FUZZTEST_DIR_PATH, 'oemcrypto_fuzztests.gyp')) else: gyp_args.append(os.path.join(CDM_TOP_PATH, 'cdm', 'cdm_unittests.gyp')) + for var in options.extra_gyp: + gyp_args.append(var) + for var in options.D: gyp_args.append('-D' + var) diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index b3c7cba8..0e788716 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -52,6 +52,14 @@ 'proto_in_dir': '../metrics/src', }, }, + { + 'target_name': 'widevine_utils', + 'type': 'static_library', + 'include_dirs': [ + '../util/include', + ], + 'sources': [ '<@(wvutil_sources)'], + }, { 'target_name': 'widevine_cdm_core', 'type': 'static_library', @@ -60,6 +68,7 @@ 'device_files', 'license_protocol', 'metrics_proto', + 'widevine_utils', ], 'include_dirs': [ '../core/include', @@ -82,8 +91,6 @@ }, 'sources': [ '<@(wvcdm_sources)', - '<@(wvutil_sources)', - '../core/src/oemcrypto_adapter_static.cpp', '../third_party/jsmn/jsmn.h', '../third_party/jsmn/jsmn.c', ], @@ -98,9 +105,29 @@ '../core/src/privacy_crypto_<(privacy_crypto_impl).cpp', ], }], # end else + ['oemcrypto_adapter_type=="dynamic"', { + 'sources': [ + '../core/src/oemcrypto_adapter_dynamic.cpp', + ], + }], + ['oemcrypto_adapter_type=="static"', { + 'sources': [ + '../core/src/oemcrypto_adapter_static.cpp', + ], + }], + # TODO(b/139814713): For testing and internal use only. + ['oemcrypto_adapter_type=="static_v15"', { + 'sources': [ + '../core/src/oemcrypto_adapter_static.cpp', + '../core/src/oemcrypto_adapter_static_v16.cpp', + ], + }], ], }, # widevine_cdm_core target { + # This is the widevine_ce_cdm built as a static library. This name does + # not mean that it uses oemcrypto's static adapter. Control over which + # adapter is used comes from the settings file for the platform. 'target_name': 'widevine_ce_cdm_static', 'type': 'static_library', 'standalone_static_library': 1, diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index 678f0f42..42dd7c13 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -11,8 +11,6 @@ 'variables': { # Directory where OEMCrypto header, test, and reference files lives. 'oemcrypto_dir': '../oemcrypto', - # Label as 'static' or 'dynamic' to use the respective OEMCrypto adapter. - 'oemcrypto_adapter': 'static', # Directory where widevine utilities live. 'util_dir': '../util', 'metrics_target': 'cdm.gyp:metrics_proto', @@ -66,27 +64,28 @@ 'oec_ref', ], }], - ['oemcrypto_lib=="level3"', { + # TODO(b/139814713): For testing and internal use only. + ['oemcrypto_adapter_type=="static_v15"', { 'defines': [ # This is used by the unit tests to use some v15 functions. 'TEST_OEMCRYPTO_V15', ], + }], + ['oemcrypto_lib=="level3"', { 'sources': [ # The test impl of OEMCrypto_Level3FileSystem and its factory. 'test/level3_file_system_ce_test.h', 'test/level3_file_system_ce_test.cpp', 'test/level3_file_system_ce_test_factory.cpp', - # TODO(139814713): Remove once Level 3 supports version 16. - '../core/src/oemcrypto_adapter_static_v16.cpp', ], 'conditions': [ - ['oemcrypto_adapter=="static"', { + ['oemcrypto_adapter_type=="dynamic" ', { 'dependencies': [ - '../oemcrypto/level3/oec_level3.gyp:oec_level3_static', + '../oemcrypto/level3/oec_level3.gyp:oec_level3_dynamic', ], }, { 'dependencies': [ - '../oemcrypto/level3/oec_level3.gyp:oec_level3_dynamic', + '../oemcrypto/level3/oec_level3.gyp:oec_level3_static', ], }], ], @@ -114,6 +113,5 @@ 'oec_ref' ], }, - ], } diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 725e49d4..b0a71283 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -1,5 +1,5 @@ // Widevine CE CDM Version #ifndef CDM_VERSION -# define CDM_VERSION "16.2.0" +# define CDM_VERSION "16.3.0" #endif #define EME_VERSION "https://www.w3.org/TR/2017/REC-encrypted-media-20170918" diff --git a/cdm/platform_properties.gypi b/cdm/platform_properties.gypi index b4d694f1..2c1adf60 100644 --- a/cdm/platform_properties.gypi +++ b/cdm/platform_properties.gypi @@ -41,6 +41,11 @@ # You only need to set this value if you set 'oemcrypto_lib' to 'target' # above. 'oemcrypto_gyp_target%': '', + # Choose the oemcrypto adapter type. Valid values are: + # + # 'static' - (default). Statically link oemcrypto into the tests. + # other values - for internal testing. + 'oemcrypto_adapter_type%': 'static', # Override this to indicate what CPU architecture's assembly-language files # should be used when building assembly language files. Or, set it to diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index 5b04cc67..e388462e 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -102,6 +102,8 @@ class PropertySet final : public CdmClientPropertySet { return empty_string_; } + bool use_atsc_mode() const override { return false; } + private: bool use_privacy_mode_; std::string licensing_service_certificate_; @@ -498,7 +500,7 @@ Cdm::Status CdmImpl::getProvisioningRequest(std::string* request) { std::string ignored_base_url; CdmResponseType result = cdm_engine_->GetProvisioningRequest( kCertificateWidevine, empty_authority, provisioning_service_certificate_, - request, &ignored_base_url); + kLevelDefault, request, &ignored_base_url); if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) { LOGE("Nonce quota exceeded"); return kResourceContention; @@ -517,7 +519,7 @@ Cdm::Status CdmImpl::handleProvisioningResponse(const std::string& response) { std::string ignored_wrapped_key; CdmResponseType result = cdm_engine_->HandleProvisioningResponse( - response, &ignored_cert, &ignored_wrapped_key); + response, kLevelDefault, &ignored_cert, &ignored_wrapped_key); if (result == SYSTEM_INVALIDATED_ERROR) { LOGE("System invalidated"); return kSystemStateLost; diff --git a/cdm/src/properties_ce.cpp b/cdm/src/properties_ce.cpp index 60d22e44..f6ab1481 100644 --- a/cdm/src/properties_ce.cpp +++ b/cdm/src/properties_ce.cpp @@ -149,9 +149,10 @@ bool Properties::GetFactoryKeyboxPath(std::string*) { } // static -bool Properties::GetOEMCryptoPath(std::string*) { - // Unused on CE devices. - return false; +bool Properties::GetOEMCryptoPath(std::string* path) { + if (path == nullptr) return false; + *path = "liboemcrypto.so"; + return true; } // static diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index e9cb515e..8cd047b3 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -26,6 +26,7 @@ #include "test_base.h" #include "test_host.h" #include "test_printers.h" +#include "test_sleep.h" #include "url_request.h" using namespace testing; @@ -58,7 +59,7 @@ const std::string kCencInitData = a2bs_hex( "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id "00000022" // pssh data size // pssh data: - "08011a0d7769646576696e655f746573" + "08011a0d7769646576696e655f746573" // "streaming_clip9" "74220f73747265616d696e675f636c69" "7039"); const std::string kCencPersistentInitData = a2bs_hex( @@ -68,7 +69,7 @@ const std::string kCencPersistentInitData = a2bs_hex( "edef8ba979d64acea3c827dcd51d21ed" // Widevine system id "00000020" // pssh data size // pssh data: - "08011a0d7769646576696e655f746573" + "08011a0d7769646576696e655f746573" // "offline_clip6" "74220d6f66666c696e655f636c697036"); const std::string kInvalidCencInitData = a2bs_hex( "0000000c" // blob size @@ -83,7 +84,7 @@ const std::string kNonWidevineCencInitData = a2bs_hex( const std::string kWebMInitData = a2bs_hex("deadbeefdeadbeefdeadbeefdeadbeef"); const std::string kKeyIdsInitData = "{\"kids\":[\"67ef0gd8pvfd0\",\"77ef0gd8pvfd0\"]}"; -const std::string kHlsInitData = +const std::string kHlsInitData = // content_id = "bigbuckbunny" "#EXT-X-KEY:METHOD=SAMPLE-AES,KEYFORMAT=\"com.widevine\",KEYFORMATVERSIONS=" "\"1\",URI=\"data:text/plain;base64,ew0KICAgInByb3ZpZGVyIjogIndpZGV2aW5lX3R" "lc3QiLA0KICAgImNvbnRlbnRfaWQiOiAiWW1sblluVmphMkoxYm01NSIsDQogICAia2V5X2lkc" @@ -331,19 +332,27 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { Cdm::Status status = cdm_->createSession(session_type, session_id); ASSERT_EQ(Cdm::kSuccess, status); + std::string init_data_type_name; std::string init_data; if (session_type == Cdm::kTemporary) { if (init_data_type == Cdm::kCenc) { + init_data_type_name = CENC_INIT_DATA_FORMAT; init_data = kCencInitData; } else if (init_data_type == Cdm::kHls) { + init_data_type_name = HLS_INIT_DATA_FORMAT; init_data = kHlsInitData; } } else if (session_type == Cdm::kPersistentLicense || session_type == Cdm::kPersistentUsageRecord) { if (init_data_type == Cdm::kCenc) { init_data = kCencPersistentInitData; + init_data_type_name = CENC_INIT_DATA_FORMAT; } } + if (g_cutoff >= LOG_DEBUG) { + InitializationData parsed_init_data(init_data_type_name, init_data); + parsed_init_data.DumpToLogs(); + } ASSERT_FALSE(init_data.empty()); EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)) @@ -397,26 +406,19 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { Mock::VerifyAndClear(this); } - std::string GetProvisioningResponse(const std::string& message, - size_t max_attempts = 10) { + std::string GetProvisioningResponse(const std::string& message) { std::string uri = config_.provisioning_server(); - LOGV("GetProvisioningResponse: URI: %s", uri.c_str()); LOGV("GetProvisioningResponse: message:\n%s\n", b2a_hex(message).c_str()); - std::string reply; uri += "&signedRequest=" + message; - // TODO(b/139361531): Remove loop once provisioning service is stable. - for (size_t attempt = 1; attempt <= max_attempts; attempt++) { - FetchCertificate(uri, &reply); - if (HasFatalFailure()) { - LOGE("Failed to get provisioning response: attempt = %zu", attempt); - reply.clear(); - continue; - } else { - LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str()); - break; - } + std::string reply; + FetchCertificate(uri, &reply); + if (HasFatalFailure()) { + LOGE("Failed to get provisioning response"); + reply.clear(); + } else { + LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str()); } return reply; } @@ -443,6 +445,10 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { Cdm::InitDataType init_data_type, const std::string& init_data) { const int num_retries = 5; + if (init_data_type == Cdm::kCenc && g_cutoff >= LOG_DEBUG) { + InitializationData parsed_init_data(CENC_INIT_DATA_FORMAT, init_data); + parsed_init_data.DumpToLogs(); + } for (int i = 0; i < num_retries; i++) { LOGD("attempt %d", i); Cdm::Status status = @@ -512,18 +518,19 @@ class MockTimerClient : public Cdm::ITimer::IClient { MOCK_METHOD1(onTimerExpired, void(void*)); }; -} // namespace - TEST_F(CdmTest, TestHostTimer) { - // Validate that the TestHost timers are processed in the correct order. + // Validate that the TestHost timers are processed in the correct order and + // on the correct timeouts. const int64_t kTimerDelayMs = 1000; void* kCtx1 = reinterpret_cast(0x1); void* kCtx2 = reinterpret_cast(0x2); + void* kCtx4 = reinterpret_cast(0x4); MockTimerClient client; g_host->setTimeout(kTimerDelayMs * 1, &client, kCtx1); g_host->setTimeout(kTimerDelayMs * 2, &client, kCtx2); + g_host->setTimeout(kTimerDelayMs * 4, &client, kCtx4); EXPECT_CALL(client, onTimerExpired(kCtx1)); g_host->ElapseTime(kTimerDelayMs); @@ -536,6 +543,14 @@ TEST_F(CdmTest, TestHostTimer) { EXPECT_CALL(client, onTimerExpired(_)).Times(0); g_host->ElapseTime(kTimerDelayMs); Mock::VerifyAndClear(&client); + + EXPECT_CALL(client, onTimerExpired(kCtx4)); + g_host->ElapseTime(kTimerDelayMs); + Mock::VerifyAndClear(&client); + + EXPECT_CALL(client, onTimerExpired(_)).Times(0); + g_host->ElapseTime(kTimerDelayMs); + Mock::VerifyAndClear(&client); } TEST_F(CdmTest, Initialize) { @@ -1649,6 +1664,65 @@ TEST_F(CdmTest, RemoveThreeUsageRecords) { ASSERT_EQ(Cdm::kSessionNotFound, status); } +// Reload an offline license that does not have a usage entry. +TEST_F(CdmTest, LoadPersistentNoNonce) { + EnsureProvisioned(); + + std::string session_id; + ASSERT_EQ(Cdm::kSuccess, + cdm_->createSession(Cdm::kPersistentLicense, &session_id)); + + video_widevine::WidevinePsshData pssh; + // offline_clip1 does not have a provider session token, so it will not + // generate a usage table entry. + pssh.set_content_id("offline_clip1"); + const std::string init_data = MakePSSH(pssh); + std::string license_request; + { + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)) + .WillOnce(SaveArg<2>(&license_request)); + ASSERT_EQ(Cdm::kSuccess, + generateRequestWithRetry(session_id, Cdm::kCenc, init_data)); + Mock::VerifyAndClear(this); + } + + // Send the request to the license server and receive the license response. + std::string license_response; + FetchLicense(config_.license_server(), license_request, &license_response); + + // Update the session with the new keys. + { + EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)); + ASSERT_EQ(Cdm::kSuccess, updateWithRetry(session_id, license_response)); + Mock::VerifyAndClear(this); + } + + // Should be able to load the session again after closing it. + Cdm::Status status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Should be able to load the session again after recreating the CDM. + ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */)); + EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSuccess, status); + Mock::VerifyAndClear(this); + + // Should not be able to load the session again after clearing storage. + status = cdm_->close(session_id); + ASSERT_EQ(Cdm::kSuccess, status); + g_host->Reset(); + EnsureProvisioned(); + EXPECT_CALL(*this, onKeyStatusesChange(session_id, _)).Times(0); + status = cdm_->load(session_id); + EXPECT_EQ(Cdm::kSessionNotFound, status); + Mock::VerifyAndClear(this); +} + TEST_F(CdmTest, RemoveIncomplete) { EnsureProvisioned(); std::string session_id; @@ -2414,4 +2488,6 @@ TEST_F(CdmIndividualizationTest, NoLoadWithoutProvisioning) { EXPECT_EQ(Cdm::kNeedsDeviceCertificate, cdm_->load(kBogusSessionId)); } +} // namespace + } // namespace widevine diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 16bf835f..2d25db4b 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -13,7 +13,7 @@ using namespace widevine; namespace { -const std::string kCertificateFilename = "cert.bin"; +constexpr char kCertificateFilename[] = "cert.bin"; } // namespace @@ -33,7 +33,7 @@ void TestHost::Reset() { } files_.clear(); - files_[kCertificateFilename.c_str()] = + files_[kCertificateFilename] = (device_cert_.size() > 0) ? device_cert_ : std::string((const char*)kDeviceCert, kDeviceCertSize); @@ -46,10 +46,14 @@ void TestHost::ElapseTime(int64_t milliseconds) { now_ = goal_time; } else { Timer t = timers_.top(); - timers_.pop(); ASSERT_GE(t.expiry_time(), now_); - now_ = t.expiry_time(); - t.client()->onTimerExpired(t.context()); + if (t.expiry_time() <= goal_time) { + timers_.pop(); + now_ = t.expiry_time(); + t.client()->onTimerExpired(t.context()); + } else { + now_ = goal_time; + } } } } @@ -68,7 +72,7 @@ bool TestHost::read(const std::string& name, std::string* data) { bool TestHost::write(const std::string& name, const std::string& data) { LOGV("write file: %s", name.c_str()); files_[name] = data; - if (save_device_cert_ && kCertificateFilename.compare(name) == 0) { + if (save_device_cert_ && name.compare(kCertificateFilename) == 0) { device_cert_ = data; save_device_cert_ = false; } @@ -87,10 +91,10 @@ bool TestHost::remove(const std::string& name) { if (name.empty()) { // If no name, delete all files (see DeviceFiles::DeleteAllFiles()) files_.clear(); - } else { - files_.erase(name); + return true; } - return true; + + return files_.erase(name) > 0; } int32_t TestHost::size(const std::string& name) { diff --git a/cdm/test/test_host.h b/cdm/test/test_host.h index f9d005de..ff7316ff 100644 --- a/cdm/test/test_host.h +++ b/cdm/test/test_host.h @@ -4,12 +4,17 @@ #ifndef WVCDM_CDM_TEST_TEST_HOST_H_ #define WVCDM_CDM_TEST_TEST_HOST_H_ +#include #include +#include #include #include "cdm.h" #include "test_sleep.h" +// This provides a host environment for running CDM tests. It implements the +// IStorage, IClock and ITimer interfaces that a host would normally implement, +// while allowing them to be manipulated by the test. class TestHost : public widevine::Cdm::IStorage, public widevine::Cdm::IClock, public widevine::Cdm::ITimer, @@ -19,11 +24,15 @@ class TestHost : public widevine::Cdm::IStorage, ~TestHost(); void Reset(); + // Used for manipulating and inspecting timer states during testing. void ElapseTime(int64_t milliseconds) override; int NumTimers() const; + // This should be called before trying to write the cert.bin file. This is + // used when testing device provisioning. void SaveProvisioningInformation() { save_device_cert_ = true; } + // widevine::Cdm::IStorage bool read(const std::string& name, std::string* data) override; bool write(const std::string& name, const std::string& data) override; bool exists(const std::string& name) override; @@ -31,8 +40,10 @@ class TestHost : public widevine::Cdm::IStorage, int32_t size(const std::string& name) override; bool list(std::vector* names) override; + // widevine::Cdm::IClock int64_t now() override; + // widevine::Cdm::ITimer void setTimeout(int64_t delay_ms, IClient* client, void* context) override; void cancel(IClient* client) override; diff --git a/cdm/util_unittests.gypi b/cdm/util_unittests.gypi index 1c9f60d3..d480cc9d 100644 --- a/cdm/util_unittests.gypi +++ b/cdm/util_unittests.gypi @@ -4,7 +4,9 @@ { 'sources': [ '../util/test/base64_test.cpp', - '../util/test/cdm_random_unittest.cpp' + '../util/test/cdm_random_unittest.cpp', + # TODO(b/119200528): Needs test vectors + # '../util/test/file_store_unittest.cpp', ], 'include_dirs': [ '../util/include' diff --git a/core/include/cdm_client_property_set.h b/core/include/cdm_client_property_set.h index 38398ebe..e6c7ba4c 100644 --- a/core/include/cdm_client_property_set.h +++ b/core/include/cdm_client_property_set.h @@ -24,6 +24,7 @@ class CdmClientPropertySet { virtual uint32_t session_sharing_id() const = 0; virtual void set_session_sharing_id(uint32_t id) = 0; virtual const std::string& app_id() const = 0; + virtual bool use_atsc_mode() const = 0; }; } // namespace wvcdm diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 963fa74a..16a4e1f1 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -179,12 +179,14 @@ class CdmEngine { // Generate and return a valid provisioning request. virtual CdmResponseType GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& service_certificate, CdmProvisioningRequest* request, + const std::string& service_certificate, + SecurityLevel requested_security_level, CdmProvisioningRequest* request, std::string* default_url); // Verify and process a provisioning response. virtual CdmResponseType HandleProvisioningResponse( - const CdmProvisioningResponse& response, std::string* cert, + const CdmProvisioningResponse& response, + SecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key); // Return true if there is a device certificate on the current @@ -388,7 +390,6 @@ class CdmEngine { CdmSessionMap session_map_; CdmReleaseKeySetMap release_key_sets_; std::unique_ptr cert_provisioning_; - SecurityLevel cert_provisioning_requested_security_level_; FileSystem* file_system_; Clock clock_; std::string spoid_; diff --git a/core/include/cdm_engine_metrics_decorator.h b/core/include/cdm_engine_metrics_decorator.h index befbd9f6..5eb7d9a0 100644 --- a/core/include/cdm_engine_metrics_decorator.h +++ b/core/include/cdm_engine_metrics_decorator.h @@ -156,21 +156,24 @@ class CdmEngineMetricsImpl : public T { CdmResponseType GetProvisioningRequest(CdmCertificateType cert_type, const std::string& cert_authority, const std::string& service_certificate, + SecurityLevel requested_security_level, CdmProvisioningRequest* request, std::string* default_url) override { CdmResponseType sts; - M_TIME(sts = T::GetProvisioningRequest(cert_type, cert_authority, - service_certificate, request, - default_url), + M_TIME(sts = T::GetProvisioningRequest( + cert_type, cert_authority, service_certificate, + requested_security_level, request, default_url), metrics_, cdm_engine_get_provisioning_request_, sts); return sts; } CdmResponseType HandleProvisioningResponse( - const CdmProvisioningResponse& response, std::string* cert, + const CdmProvisioningResponse& response, + SecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) override { CdmResponseType sts; - M_TIME(sts = T::HandleProvisioningResponse(response, cert, wrapped_key), + M_TIME(sts = T::HandleProvisioningResponse( + response, requested_security_level, cert, wrapped_key), metrics_, cdm_engine_handle_provisioning_response_, sts); return sts; } diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 8859093a..6325e49e 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -219,7 +219,7 @@ class CdmSession { private: friend class CdmSessionTest; - bool GenerateKeySetId(CdmKeySetId* key_set_id); + bool GenerateKeySetId(bool atsc_mode_enabled, CdmKeySetId* key_set_id); CdmResponseType StoreLicense(); @@ -233,6 +233,12 @@ class CdmSession { virtual CdmResponseType AddKeyInternal(const CdmKeyResponse& key_response); void UpdateRequestLatencyTiming(CdmResponseType sts); + // Checks that the usage entry in the usage table header matches the + // information of the currently loaded license for this session. + // Returns false if there is any unexpected mismatch of information, + // true otherwise. + bool VerifyOfflineUsageEntry(); + // These setters are for testing only. Takes ownership of the pointers. void set_license_parser(CdmLicense* license_parser); void set_crypto_session(CryptoSession* crypto_session); diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index 1fecdd25..1e44b94a 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -89,7 +89,20 @@ class CryptoSession { virtual bool GetApiMinorVersion(SecurityLevel requested_level, uint32_t* minor_version); + // This method will return, for devices with a + // * keybox: the 32 byte device ID from the keybox. + // * OEM certificate: + // - that implements |OEMCrypto_GetDeviceID|: the (1 to 64 byte) device ID. + // - that does not implement |OEMCrypto_GetDeviceID|: the OEM public + // certificate. virtual CdmResponseType GetInternalDeviceUniqueId(std::string* device_id); + + // This method will return, for devices with a + // * keybox: the 32 byte device ID from the keybox. + // * OEM certificate: + // - that implements |OEMCrypto_GetDeviceID|: the (1 to 64 byte) device ID. + // - that does not implement |OEMCrypto_GetDeviceID|: the 32 byte hash + // of the OEM public certificate. virtual CdmResponseType GetExternalDeviceUniqueId(std::string* device_id); virtual bool GetSystemId(uint32_t* system_id); virtual CdmResponseType GetProvisioningId(std::string* provisioning_id); @@ -242,11 +255,17 @@ class CryptoSession { virtual UsageTableHeader* GetUsageTableHeader() { return usage_table_header_; } - + // The following crypto methods do not require an open session to + // complete the operations. virtual CdmResponseType CreateUsageTableHeader( + SecurityLevel requested_security_level, CdmUsageTableHeader* usage_table_header); virtual CdmResponseType LoadUsageTableHeader( + SecurityLevel requested_security_level, const CdmUsageTableHeader& usage_table_header); + virtual CdmResponseType ShrinkUsageTableHeader( + SecurityLevel requested_security_level, uint32_t new_entry_count, + CdmUsageTableHeader* usage_table_header); // Usage entry. virtual CdmResponseType CreateUsageEntry(uint32_t* entry_number); @@ -256,8 +275,6 @@ class CryptoSession { CdmUsageTableHeader* usage_table_header, CdmUsageEntry* usage_entry); // Adjust usage entries in usage table header. - virtual CdmResponseType ShrinkUsageTableHeader( - uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header); virtual CdmResponseType MoveUsageEntry(uint32_t new_entry_number); virtual bool GetAnalogOutputCapabilities(bool* can_support_output, diff --git a/core/include/device_files.h b/core/include/device_files.h index 5de50572..70f55e36 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -95,13 +95,16 @@ class DeviceFiles { return Init(security_level); } + // ATSC certificates are installed by the ATSC service. They can be read + // and used but not written or removed. virtual bool StoreCertificate(const std::string& certificate, const std::string& wrapped_private_key); - virtual bool RetrieveCertificate(std::string* certificate, + virtual bool RetrieveCertificate(bool atsc_mode_enabled, + std::string* certificate, std::string* wrapped_private_key, std::string* serial_number, uint32_t* system_id); - virtual bool HasCertificate(); + virtual bool HasCertificate(bool atsc_mode_enabled); virtual bool RemoveCertificate(); virtual bool StoreLicense(const CdmLicenseData& license_data, @@ -256,7 +259,7 @@ class DeviceFiles { bool RemoveFile(const std::string& name); ssize_t GetFileSize(const std::string& name); - static std::string GetCertificateFileName(); + static std::string GetCertificateFileName(bool atsc_mode_enabled); static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageTableFileName(); @@ -264,8 +267,8 @@ class DeviceFiles { #if defined(UNIT_TEST) FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); - FRIEND_TEST(DeviceCertificateStoreTest, StoreCertificate); - FRIEND_TEST(DeviceCertificateTest, DISABLED_ReadCertificate); + FRIEND_TEST(DeviceCertificateTest, StoreCertificate); + FRIEND_TEST(DeviceCertificateTest, ReadCertificate); FRIEND_TEST(DeviceCertificateTest, HasCertificate); FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete); diff --git a/core/include/license.h b/core/include/license.h index 95c307e4..9d566803 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -176,6 +176,9 @@ class CdmLicense { // HandleKeyResponse VersionInfo latest_service_version_; + // The nonce used in the original license request. + uint32_t license_nonce_; + #if defined(UNIT_TEST) friend class CdmLicenseTestPeer; #endif diff --git a/core/include/usage_table_header.h b/core/include/usage_table_header.h index 931048a7..d63bdcb1 100644 --- a/core/include/usage_table_header.h +++ b/core/include/usage_table_header.h @@ -73,23 +73,37 @@ class UsageTableHeader { // The licenses or usage info records specified by |usage_entry_number| // should not be in use by any open CryptoSession objects when calls - // to DeleteEntry and MoveEntry are made. - virtual CdmResponseType DeleteEntry(uint32_t usage_entry_number, - DeviceFiles* handle, - metrics::CryptoMetrics* metrics); + // to InvalidateEntry and MoveEntry are made. + // If |defrag_table| is true, the table will be defragmented after + // the entry has been invalidated. + virtual CdmResponseType InvalidateEntry(uint32_t usage_entry_number, + bool defrag_table, + DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); - // Test only method. This method emulates the behavior of DeleteEntry + // Test only method. This method emulates the behavior of InvalidateEntry // without actually invoking OEMCrypto (through CryptoSession) // or storage (through DeviceFiles). It modifies internal data structures - // when DeleteEntry is mocked. This allows one to test methods that are - // dependent on DeleteEntry without having to set expectations - // for the objects that DeleteEntry depends on. - void DeleteEntryForTest(uint32_t usage_entry_number); + // when InvalidateEntry is mocked. This allows one to test methods that are + // dependent on InvalidateEntry without having to set expectations + // for the objects that InvalidateEntry depends on. + void InvalidateEntryForTest(uint32_t usage_entry_number); size_t size() { return usage_entry_info_.size(); } size_t potential_table_capacity() const { return potential_table_capacity_; } + bool HasUnlimitedTableCapacity() const { + return potential_table_capacity_ == 0; + } + + // Returns the number of entries currently tracked by the CDM that + // are related to usage info (streaming licenses). + size_t UsageInfoCount() const; + // Returns the number of entries currently tracked by the CDM that + // are related to offline licenses. + size_t OfflineEntryCount() const; + const std::vector& usage_entry_info() const { return usage_entry_info_; } @@ -104,39 +118,58 @@ class UsageTableHeader { static bool DetermineLicenseToRemoveForTesting( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates) { + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove) { return DetermineLicenseToRemove(usage_entry_info_list, current_time, - unexpired_threshold, removal_count, - removal_candidates); + unexpired_threshold, entry_to_remove); } private: CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, uint32_t to /* usage entry number */, - DeviceFiles* handle, + DeviceFiles* device_files, metrics::CryptoMetrics* metrics); - CdmResponseType GetEntry(uint32_t usage_entry_number, DeviceFiles* handle, + CdmResponseType GetEntry(uint32_t usage_entry_number, + DeviceFiles* device_files, CdmUsageEntry* usage_entry); - CdmResponseType StoreEntry(uint32_t usage_entry_number, DeviceFiles* handle, + CdmResponseType StoreEntry(uint32_t usage_entry_number, + DeviceFiles* device_files, const CdmUsageEntry& usage_entry); + // Stores the usage table and it's info. This will increment + // |store_table_counter_| if successful. + bool StoreTable(DeviceFiles* device_files); + CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); + // Must lock table before calling. + CdmResponseType DefragTable(DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); + + // This will use the LRU algorithm to decide which entry is to be + // evicted. + CdmResponseType ReleaseOldestEntry(metrics::CryptoMetrics* metrics); + virtual bool is_inited() { return is_inited_; } // Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a // device file that had not yet been upgraded to use the LRU data. virtual bool LruUpgradeAllUsageEntries(); - virtual bool GetRemovalCandidates(std::vector* removal_candidates); + virtual bool GetRemovalCandidate(uint32_t* entry_to_remove); int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); } - // Uses an LRU-base algorithm to determine which licenses should be + // Sets LRU related metrics based on the provided |staleness| (in + // seconds) and |storage_type| of the entry removed. + void RecordLruEventMetrics(metrics::CryptoMetrics* metrics, + uint64_t staleness, + CdmUsageEntryStorageType storage_type); + + // Uses an LRU-base algorithm to determine which license should be // removed. This is intended to be used if the usage table is full // and a new entry needs to be added. // @@ -151,8 +184,6 @@ class UsageTableHeader { // 2) Unexpired offline licenses will only be considered for // removal if the number of unexpired offline licenses exceeds // |unexpired_threshold|. - // The number of licenses to be considered will be less than or - // equal to the requested |removal_count|. // // Unknown storage types will be considered above all other entry // types. @@ -165,26 +196,22 @@ class UsageTableHeader { // [in] unexpired_threshold: The maximum number of unexpired // offline licenses that are present, before offline // licenses would be considered for removal. - // [in] removal_count: The desired number of removal candidate to - // find. Note that the actual number will be anywhere - // between 1 and |removal_count|. Must be greater than or - // equal to 1. - // [out] removal_candidates: List of usage entry numbers of the - // entries to be removed. Assume to be unaffected if the + // [out] entry_to_remove: Usage entry index of the entry selected + // to be removed. Assume to be unaffected if the // function returns |false|. // // Returns: - // |true| if at least one removal candidate can be determined. + // |true| if an entry has been determined to be removed. // Otherwise returns |false|. static bool DetermineLicenseToRemove( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates); + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove); // This handle and file system is only to be used when accessing // usage_table_header. Usage entries should use the file system provided // by CdmSession. - std::unique_ptr file_handle_; + std::unique_ptr device_files_; std::unique_ptr file_system_; CdmSecurityLevel security_level_; SecurityLevel requested_security_level_; @@ -199,7 +226,7 @@ class UsageTableHeader { // Synchonizes access to the Usage Table Header and bookkeeping // data-structures - std::mutex usage_table_header_lock_; + mutable std::mutex usage_table_header_lock_; metrics::CryptoMetrics alternate_crypto_metrics_; @@ -217,6 +244,11 @@ class UsageTableHeader { // assumed to be |kMinimumUsageTableEntriesSupported|. size_t potential_table_capacity_ = 0u; + // Counts the number of successful calls to |StoreTable()|. Used + // to reduce the number of calls to device files for certain + // table operations. + uint32_t store_table_counter_ = 0u; + #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; @@ -228,7 +260,7 @@ class UsageTableHeader { // These setters are for testing only. Takes ownership of the pointers. void SetDeviceFiles(DeviceFiles* device_files) { - file_handle_.reset(device_files); + device_files_.reset(device_files); } void SetCryptoSession(CryptoSession* crypto_session) { test_crypto_session_.reset(crypto_session); diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index 5d427e20..3db2a051 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -43,8 +43,10 @@ static const uint32_t RESOURCE_RATING_TIER_MAX = RESOURCE_RATING_TIER_VERY_HIGH; static const uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER = 15; static const char SESSION_ID_PREFIX[] = "sid"; +static const char ATSC_KEY_SET_ID_PREFIX[] = "atscksid"; static const char KEY_SET_ID_PREFIX[] = "ksid"; static const char KEY_SYSTEM[] = "com.widevine"; +static const char ATSC_APP_PACKAGE_NAME[] = "org.atsc"; // define query keys, values here static const std::string QUERY_KEY_LICENSE_TYPE = diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index a6ce404e..3517005f 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -253,7 +253,7 @@ enum CdmResponseType { INVALID_SESSION_1 = 199, NO_DEVICE_KEY_1 = 200, NO_CONTENT_KEY_2 = 201, - INSUFFICIENT_CRYPTO_RESOURCES_2 = 202, + /* previously INSUFFICIENT_CRYPTO_RESOURCES_2 = 202, */ INVALID_PARAMETERS_ENG_13 = 203, INVALID_PARAMETERS_ENG_14 = 204, INVALID_PARAMETERS_ENG_15 = 205, @@ -272,7 +272,7 @@ enum CdmResponseType { LOAD_USAGE_HEADER_UNKNOWN_ERROR = 218, /* previously INVALID_PARAMETERS_ENG_17 = 219, */ /* preivously INVALID_PARAMETERS_ENG_18 = 220, */ - INSUFFICIENT_CRYPTO_RESOURCES_3 = 221, + /* previously INSUFFICIENT_CRYPTO_RESOURCES_3 = 221, */ CREATE_USAGE_ENTRY_UNKNOWN_ERROR = 222, LOAD_USAGE_ENTRY_GENERATION_SKEW = 223, LOAD_USAGE_ENTRY_SIGNATURE_FAILURE = 224, @@ -281,7 +281,7 @@ enum CdmResponseType { /* previsouly INVALID_PARAMETERS_ENG_20 = 227, */ UPDATE_USAGE_ENTRY_UNKNOWN_ERROR = 228, /* previously INVALID_PARAMETERS_ENG_21 = 229, */ - SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR = 230, + SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR = 230, MOVE_USAGE_ENTRY_UNKNOWN_ERROR = 231, COPY_OLD_USAGE_ENTRY_UNKNOWN_ERROR = 232, INVALID_PARAMETERS_ENG_22 = 233, @@ -331,12 +331,12 @@ enum CdmResponseType { /* previously LICENSE_REQUEST_INVALID_SUBLICENSE = 277, */ CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE = 278, LOAD_SYSTEM_ID_ERROR = 279, - INSUFFICIENT_CRYPTO_RESOURCES_4 = 280, - INSUFFICIENT_CRYPTO_RESOURCES_5 = 281, + /* previously INSUFFICIENT_CRYPTO_RESOURCES_4 = 280, */ + /* previously INSUFFICIENT_CRYPTO_RESOURCES_5 = 281, */ REMOVE_USAGE_INFO_ERROR_1 = 282, REMOVE_USAGE_INFO_ERROR_2 = 283, REMOVE_USAGE_INFO_ERROR_3 = 284, - INSUFFICIENT_CRYPTO_RESOURCES_6 = 285, + /* previously INSUFFICIENT_CRYPTO_RESOURCES_6 = 285, */ NOT_AN_ENTITLEMENT_SESSION = 286, NO_MATCHING_ENTITLEMENT_KEY = 287, LOAD_ENTITLED_CONTENT_KEYS_ERROR = 288, @@ -408,6 +408,12 @@ enum CdmResponseType { CANNOT_DECRYPT_ZERO_SUBSAMPLES = 354, SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH = 355, INVALID_IV_SIZE = 356, + PROVISIONING_NOT_ALLOWED_FOR_ATSC = 357, + // 357 was |LOAD_USAGE_ENTRY_INVALID_SESSION| in early R builds + MOVE_USAGE_ENTRY_DESTINATION_IN_USE = 358, + SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE = 359, + LICENSE_USAGE_ENTRY_MISSING = 360, + LOAD_USAGE_ENTRY_INVALID_SESSION = 361, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h @@ -529,6 +535,14 @@ struct CdmUsageEntryInfo { // else storage_type == kStorageTypeUnknown return true; } + + void Clear() { + storage_type = kStorageTypeUnknown; + key_set_id.clear(); + usage_info_file_name.clear(); + last_use_time = 0; + offline_license_expiry_time = 0; + } }; enum CdmKeySecurityLevel { diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 0317f6d5..e9f4bd09 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -62,6 +63,7 @@ class UsagePropertySet : public CdmClientPropertySet { void set_session_sharing_id(uint32_t /* id */) override {} const std::string& app_id() const override { return app_id_; } void set_app_id(const std::string& appId) { app_id_ = appId; } + bool use_atsc_mode() const override { return false; } private: std::string app_id_; @@ -73,7 +75,6 @@ CdmEngine::CdmEngine(FileSystem* file_system, std::shared_ptr metrics) : metrics_(metrics), cert_provisioning_(), - cert_provisioning_requested_security_level_(kLevelDefault), file_system_(file_system), spoid_(EMPTY_SPOID), usage_session_(), @@ -136,8 +137,6 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, new_session->Init(property_set, forced_session_id, event_listener); if (sts != NO_ERROR) { if (sts == NEED_PROVISIONING) { - cert_provisioning_requested_security_level_ = - new_session->GetRequestedSecurityLevel(); // Reserve a session ID so the CDM can return success. if (session_id) *session_id = new_session->GenerateSessionId(); } else { @@ -172,10 +171,13 @@ CdmResponseType CdmEngine::OpenKeySetSession( key_set_in_use = release_key_sets_.find(key_set_id) != release_key_sets_.end(); } - if (key_set_in_use) CloseKeySetSession(key_set_id); + if (key_set_in_use) { + LOGD("Reopening existing key session"); + CloseKeySetSession(key_set_id); + } CdmSessionId session_id; - CdmResponseType sts = + const CdmResponseType sts = OpenSession(KEY_SYSTEM, property_set, event_listener, nullptr /* forced_session_id */, &session_id); @@ -294,10 +296,6 @@ CdmResponseType CdmEngine::GenerateKeyRequest( if (KEY_ADDED == sts) { return sts; } else if (KEY_MESSAGE != sts) { - if (sts == NEED_PROVISIONING) { - cert_provisioning_requested_security_level_ = - session->GetRequestedSecurityLevel(); - } LOGE("Key request generation failed, status = %d", static_cast(sts)); return sts; } @@ -414,10 +412,6 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, &error_detail); session->GetMetrics()->cdm_session_restore_offline_session_.Increment( sts, error_detail); - if (sts == NEED_PROVISIONING) { - cert_provisioning_requested_security_level_ = - session->GetRequestedSecurityLevel(); - } if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) { LOGE("Restore offline session failed: status = %d", static_cast(sts)); } @@ -703,6 +697,12 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, LOGW("GetMaxUsageTableEntries failed"); return UNKNOWN_ERROR; } + if (max_number_of_usage_entries == 0) { + // Zero indicates that the table is dynamically allocated and does + // not have a defined limit. Setting to max value of int32_t to + // be able to fit into a Java int. + max_number_of_usage_entries = std::numeric_limits::max(); + } *query_response = std::to_string(max_number_of_usage_entries); return NO_ERROR; } else if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) { @@ -895,7 +895,8 @@ bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { */ CdmResponseType CdmEngine::GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, - const std::string& service_certificate, CdmProvisioningRequest* request, + const std::string& service_certificate, + SecurityLevel requested_security_level, CdmProvisioningRequest* request, std::string* default_url) { LOGI("Getting provisioning request"); if (!request) { @@ -915,7 +916,7 @@ CdmResponseType CdmEngine::GetProvisioningRequest( if (status != NO_ERROR) return status; } CdmResponseType ret = cert_provisioning_->GetProvisioningRequest( - cert_provisioning_requested_security_level_, cert_type, cert_authority, + requested_security_level, cert_type, cert_authority, file_system_->origin(), spoid_, request, default_url); if (ret != NO_ERROR) { cert_provisioning_.reset(); // Release resources. @@ -931,7 +932,8 @@ CdmResponseType CdmEngine::GetProvisioningRequest( * Returns NO_ERROR for success and CdmResponseType error code if fails. */ CdmResponseType CdmEngine::HandleProvisioningResponse( - const CdmProvisioningResponse& response, std::string* cert, + const CdmProvisioningResponse& response, + SecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) { LOGI("Handling provision request"); if (response.empty()) { @@ -955,10 +957,9 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); CdmResponseType status; - M_TIME(status = crypto_session->Open( - cert_provisioning_requested_security_level_), + M_TIME(status = crypto_session->Open(requested_security_level), metrics_->GetCryptoMetrics(), crypto_session_open_, status, - cert_provisioning_requested_security_level_); + requested_security_level); if (NO_ERROR != status) { LOGE("Provisioning object missing and crypto session open failed"); return EMPTY_PROVISIONING_CERTIFICATE_2; @@ -1124,14 +1125,19 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); DeviceFiles handle(file_system_); + + if (!handle.Init(security_level)) { + LOGE("Cannot initialize device files: security_level = %s", + security_level == kSecurityLevelL3 ? "L3" : "Default"); + return REMOVE_OFFLINE_LICENSE_ERROR_1; + } + CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set, nullptr /* event listener */); if (sts != NO_ERROR) { - if (!handle.Init(security_level)) { - LOGE("Cannot initialize device files"); - } + LOGE("Failed to open key set session: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); - return REMOVE_OFFLINE_LICENSE_ERROR_1; + return sts; } CdmSessionId session_id; @@ -1151,6 +1157,14 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( session_id = iter->second.first; sts = RemoveLicense(session_id); } + } else if (sts == LICENSE_USAGE_ENTRY_MISSING) { + // It is possible that the CDM is tracking a key set ID, but has + // removed the usage information associated with it. In this case, + // it will no longer be possible to load the license for release; + // and the file should simply be deleted. + LOGW("License usage entry is missing, deleting license file"); + handle.DeleteLicense(key_set_id); + sts = NO_ERROR; } if (sts != NO_ERROR) { diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 33b066b4..29a9f2f0 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -159,8 +159,12 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, // License server client ID token is a stored certificate. Stage it or // indicate that provisioning is needed. Get token from stored certificate std::string wrapped_key; - if (!file_handle_->RetrieveCertificate(&client_token, &wrapped_key, - &serial_number, nullptr)) { + bool atsc_mode_enabled = false; + if (cdm_client_property_set != nullptr) + atsc_mode_enabled = cdm_client_property_set->use_atsc_mode(); + if (!file_handle_->RetrieveCertificate(atsc_mode_enabled, &client_token, + &wrapped_key, &serial_number, + nullptr)) { return NEED_PROVISIONING; } CdmResponseType load_cert_sts; @@ -186,7 +190,7 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, if (forced_session_id) { key_set_id_ = *forced_session_id; } else { - bool ok = GenerateKeySetId(&key_set_id_); + bool ok = GenerateKeySetId(atsc_mode_enabled, &key_set_id_); (void)ok; // ok is now used when assertions are turned off. assert(ok); } @@ -282,6 +286,17 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, key_response_, &provider_session_token) || usage_table_header_ == nullptr) { provider_session_token.clear(); + std::string fake_message("empty message"); + std::string core_message; + std::string license_request_signature; + // Sign a fake message so that OEMCrypto will start the rental clock. The + // signature and generated core message are ignored. + CdmResponseType status = crypto_session_->PrepareAndSignLicenseRequest( + fake_message, &core_message, &license_request_signature); + if (status != NO_ERROR) return status; + } else if (!VerifyOfflineUsageEntry()) { + LOGE("License usage entry is invalid, cannot restore"); + return LICENSE_USAGE_ENTRY_MISSING; } else { CdmResponseType sts = usage_table_header_->LoadEntry( crypto_session_.get(), usage_entry_, usage_entry_number_); @@ -529,16 +544,18 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { metrics_->license_sdk_version_.Record( version_info.license_service_version()); - // Update or delete entry if usage table header+entries are supported + // Update or invalidate entry if usage table header+entries are supported if (usage_support_type_ == kUsageEntrySupport && !provider_session_token.empty() && usage_table_header_ != nullptr) { if (sts != KEY_ADDED) { - CdmResponseType delete_sts = usage_table_header_->DeleteEntry( - usage_entry_number_, file_handle_.get(), crypto_metrics_); - crypto_metrics_->usage_table_header_delete_entry_.Increment(delete_sts); - if (delete_sts != NO_ERROR) { - LOGW("Delete usage entry failed: status = %d", - static_cast(delete_sts)); + const CdmResponseType invalidate_sts = + usage_table_header_->InvalidateEntry( + usage_entry_number_, true, file_handle_.get(), crypto_metrics_); + crypto_metrics_->usage_table_header_delete_entry_.Increment( + invalidate_sts); + if (invalidate_sts != NO_ERROR) { + LOGW("Invalidate usage entry failed: status = %d", + static_cast(invalidate_sts)); } } } @@ -824,8 +841,8 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { return INCORRECT_USAGE_SUPPORT_TYPE_1; } - sts = usage_table_header_->DeleteEntry(usage_entry_number, file_handle_.get(), - crypto_metrics_); + sts = usage_table_header_->InvalidateEntry( + usage_entry_number, true, file_handle_.get(), crypto_metrics_); crypto_metrics_->usage_table_header_delete_entry_.Increment(sts); return sts; } @@ -844,7 +861,8 @@ CdmSessionId CdmSession::GenerateSessionId() { return SESSION_ID_PREFIX + IntToString(++session_num); } -bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { +bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled, + CdmKeySetId* key_set_id) { RETURN_FALSE_IF_NULL(key_set_id); std::vector random_data( @@ -856,7 +874,10 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { return false; } - *key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data); + if (atsc_mode_enabled) + *key_set_id = ATSC_KEY_SET_ID_PREFIX + b2a_hex(random_data); + else + *key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data); // key set collision if (file_handle_->LicenseExists(*key_set_id)) { @@ -960,11 +981,10 @@ CdmResponseType CdmSession::RemoveKeys() { } CdmResponseType CdmSession::RemoveLicense() { - CdmResponseType sts = NO_ERROR; if (is_offline_ || has_provider_session_token()) { if (usage_support_type_ == kUsageEntrySupport && has_provider_session_token()) { - sts = DeleteUsageEntry(usage_entry_number_); + DeleteUsageEntry(usage_entry_number_); } DeleteLicenseFile(); } @@ -1131,6 +1151,25 @@ void CdmSession::UpdateRequestLatencyTiming(CdmResponseType sts) { license_request_latency_.Clear(); } +bool CdmSession::VerifyOfflineUsageEntry() { + // Check that the current license is the same as the expected + // entry in the usage table. It is possible that the license has + // been removed from the usage table but the license file remains. + if (usage_entry_number_ >= usage_table_header_->size()) { + LOGD("License usage entry does not exist: entry_number = %u, size = %zu", + usage_entry_number_, usage_table_header_->size()); + return false; + } + const CdmUsageEntryInfo& usage_entry_info = + usage_table_header_->usage_entry_info().at(usage_entry_number_); + if (usage_entry_info.storage_type != kStorageLicense || + usage_entry_info.key_set_id != key_set_id_) { + LOGD("License usage entry does not match"); + return false; + } + return true; +} + // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index c8f39394..9d72bce3 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -708,12 +708,10 @@ uint8_t CryptoSession::GetSecurityPatchLevel() { } CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { - LOGD( - "Opening crypto session: requested_security_level: " - "requested_security_level = %s", - requested_security_level == kLevel3 - ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() - : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); + LOGD("Opening crypto session: requested_security_level = %s", + requested_security_level == kLevel3 + ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() + : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR); if (open_) return NO_ERROR; @@ -750,12 +748,13 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { open_ = true; // Get System ID and save it. - if (GetSystemIdInternal(&system_id_) == NO_ERROR) { + result = GetSystemIdInternal(&system_id_); + if (result == NO_ERROR) { metrics_->crypto_session_system_id_.Record(system_id_); } else { LOGE("Failed to fetch system ID"); - metrics_->crypto_session_system_id_.SetError(LOAD_SYSTEM_ID_ERROR); - return LOAD_SYSTEM_ID_ERROR; + metrics_->crypto_session_system_id_.SetError(result); + return result; } // Set up request ID @@ -802,35 +801,33 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { CdmSecurityLevel security_level = GetSecurityLevel(); if (security_level == kSecurityLevelL1 || security_level == kSecurityLevelL3) { - { - // This block cannot use |WithStaticFieldWriteLock| because it needs - // to unlock the lock partway through. - LOGV("Static field write lock: Open() initializing usage table"); - std::unique_lock auto_lock(static_field_mutex_); + // This block cannot use |WithStaticFieldWriteLock| because it needs + // to unlock the lock partway through. + LOGV("Static field write lock: Open() initializing usage table"); + std::unique_lock auto_lock(static_field_mutex_); - UsageTableHeader** header = security_level == kSecurityLevelL1 - ? &usage_table_header_l1_ - : &usage_table_header_l3_; - if (*header == nullptr) { - *header = new UsageTableHeader(); - // Ignore errors since we do not know when a session is opened, - // if it is intended to be used for offline/usage session related - // or otherwise. - auto_lock.unlock(); - bool is_usage_table_header_inited = - (*header)->Init(security_level, this); - auto_lock.lock(); - if (!is_usage_table_header_inited) { - delete *header; - *header = nullptr; - usage_table_header_ = nullptr; - return NO_ERROR; - } + UsageTableHeader** header = security_level == kSecurityLevelL1 + ? &usage_table_header_l1_ + : &usage_table_header_l3_; + if (*header == nullptr) { + *header = new UsageTableHeader(); + // Ignore errors since we do not know when a session is opened, + // if it is intended to be used for offline/usage session related + // or otherwise. + auto_lock.unlock(); + bool is_usage_table_header_inited = + (*header)->Init(security_level, this); + auto_lock.lock(); + if (!is_usage_table_header_inited) { + delete *header; + *header = nullptr; + usage_table_header_ = nullptr; + return NO_ERROR; } - usage_table_header_ = *header; - metrics_->usage_table_header_initial_size_.Record((*header)->size()); } - } + usage_table_header_ = *header; + metrics_->usage_table_header_initial_size_.Record((*header)->size()); + } // End |static_field_mutex_| block. } } else { metrics_->oemcrypto_usage_table_support_.SetError(result); @@ -943,7 +940,7 @@ CdmResponseType CryptoSession::LoadKeys( update_usage_table_after_close_session_ = true; return KEY_ADDED; case OEMCrypto_ERROR_TOO_MANY_KEYS: - return INSUFFICIENT_CRYPTO_RESOURCES_4; + return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE: // Handle vendor specific error return NEED_PROVISIONING; @@ -983,7 +980,7 @@ CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message, return LOAD_LICENSE_ERROR; case OEMCrypto_ERROR_TOO_MANY_KEYS: LOGE("Too many keys in license"); - return INSUFFICIENT_CRYPTO_RESOURCES_4; + return INSUFFICIENT_CRYPTO_RESOURCES; default: break; } @@ -1185,16 +1182,15 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( CdmResponseType CryptoSession::LoadEntitledContentKeys( const std::vector& key_array) { - OEMCryptoResult sts; - WithOecSessionLock("LoadEntitledContentKeys", [&] { - sts = key_session_->LoadEntitledContentKeys(key_array); - }); + const OEMCryptoResult sts = WithOecSessionLock( + "LoadEntitledContentKeys", + [&] { return key_session_->LoadEntitledContentKeys(key_array); }); switch (sts) { case OEMCrypto_SUCCESS: return KEY_ADDED; case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: - return INSUFFICIENT_CRYPTO_RESOURCES_6; + return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_INVALID_CONTEXT: return NOT_AN_ENTITLEMENT_SESSION; case OEMCrypto_KEY_NOT_ENTITLED: @@ -1243,39 +1239,46 @@ CdmResponseType CryptoSession::LoadCertificatePrivateKey( // Private. CdmResponseType CryptoSession::SelectKey(const std::string& key_id, CdmCipherMode cipher_mode) { - OEMCryptoResult sts; - WithOecSessionLock( - "SelectKey", [&] { sts = key_session_->SelectKey(key_id, cipher_mode); }); + const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] { + return key_session_->SelectKey(key_id, cipher_mode); + }); switch (sts) { + // SelectKey errors. case OEMCrypto_SUCCESS: return NO_ERROR; case OEMCrypto_ERROR_KEY_EXPIRED: return NEED_KEY; - case OEMCrypto_ERROR_INSUFFICIENT_HDCP: - return INSUFFICIENT_OUTPUT_PROTECTION; - case OEMCrypto_ERROR_ANALOG_OUTPUT: - return ANALOG_OUTPUT_ERROR; case OEMCrypto_ERROR_INVALID_SESSION: return INVALID_SESSION_1; case OEMCrypto_ERROR_NO_DEVICE_KEY: return NO_DEVICE_KEY_1; case OEMCrypto_ERROR_NO_CONTENT_KEY: return NO_CONTENT_KEY_2; - case OEMCrypto_KEY_NOT_LOADED: // obsolete. - return NO_CONTENT_KEY_3; - case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: - return INSUFFICIENT_CRYPTO_RESOURCES_2; - case OEMCrypto_ERROR_UNKNOWN_FAILURE: - return UNKNOWN_SELECT_KEY_ERROR_1; - case OEMCrypto_ERROR_SESSION_LOST_STATE: - return SESSION_LOST_STATE_ERROR; - case OEMCrypto_ERROR_SYSTEM_INVALIDATED: - return SYSTEM_INVALIDATED_ERROR; case OEMCrypto_ERROR_CONTROL_INVALID: case OEMCrypto_ERROR_KEYBOX_INVALID: - default: return UNKNOWN_SELECT_KEY_ERROR_2; + case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: + return INSUFFICIENT_CRYPTO_RESOURCES; + case OEMCrypto_ERROR_UNKNOWN_FAILURE: + return UNKNOWN_SELECT_KEY_ERROR_1; + case OEMCrypto_ERROR_ANALOG_OUTPUT: + return ANALOG_OUTPUT_ERROR; + case OEMCrypto_ERROR_INSUFFICIENT_HDCP: + return INSUFFICIENT_OUTPUT_PROTECTION; + // LoadEntitledContentKeys errors. + // |key_session_| may make calls to OEMCrypto_LoadEntitledContentKeys + // if the key selected has not yet been loaded. + case OEMCrypto_ERROR_INVALID_CONTEXT: + return NOT_AN_ENTITLEMENT_SESSION; + case OEMCrypto_KEY_NOT_ENTITLED: + return NO_MATCHING_ENTITLEMENT_KEY; + // Obsolete errors. + case OEMCrypto_KEY_NOT_LOADED: + return NO_CONTENT_KEY_3; + // Catch all else. + default: + return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey"); } } @@ -1530,7 +1533,7 @@ CdmResponseType CryptoSession::Decrypt( case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION: return NO_ERROR; case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: - return INSUFFICIENT_CRYPTO_RESOURCES_5; + return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_KEY_EXPIRED: return NEED_KEY; case OEMCrypto_ERROR_INVALID_SESSION: @@ -2005,6 +2008,11 @@ bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, metrics_->oemcrypto_maximum_usage_table_header_size_.Record( *number_of_entries); + if (*number_of_entries == 0) { + // Special value, indicating that the table size is not directly + // limited. + return true; + } return *number_of_entries >= kMinimumUsageTableEntriesSupported; } @@ -2349,18 +2357,20 @@ CdmResponseType CryptoSession::GetUsageSupportType( } CdmResponseType CryptoSession::CreateUsageTableHeader( + SecurityLevel requested_security_level, CdmUsageTableHeader* usage_table_header) { - LOGV("Creating usage table header: id = %u", oec_session_id_); - + LOGV("Creating usage table header: requested_security_level = %s", + requested_security_level == kLevel3 + ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() + : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); usage_table_header->resize(kEstimatedInitialUsageTableHeader); - size_t usage_table_header_size = usage_table_header->size(); OEMCryptoResult result; WithOecWriteLock("CreateUsageTableHeader Attempt 1", [&] { result = OEMCrypto_CreateUsageTableHeader( - requested_security_level_, + requested_security_level, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_size); @@ -2371,7 +2381,7 @@ CdmResponseType CryptoSession::CreateUsageTableHeader( usage_table_header->resize(usage_table_header_size); WithOecWriteLock("CreateUsageTableHeader Attempt 2", [&] { result = OEMCrypto_CreateUsageTableHeader( - requested_security_level_, + requested_security_level, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_size); @@ -2379,22 +2389,28 @@ CdmResponseType CryptoSession::CreateUsageTableHeader( }); } - if (result == OEMCrypto_SUCCESS) { - usage_table_header->resize(usage_table_header_size); + switch (result) { + case OEMCrypto_SUCCESS: + usage_table_header->resize(usage_table_header_size); + return NO_ERROR; + default: + return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR, + "CreateUsageTableHeader"); } - - return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR, - "CreateUsageTableHeader"); } CdmResponseType CryptoSession::LoadUsageTableHeader( + SecurityLevel requested_security_level, const CdmUsageTableHeader& usage_table_header) { - LOGV("Loading usage table header: id = %u", oec_session_id_); + LOGV("Loading usage table header: requested_security_level = %s", + requested_security_level == kLevel3 + ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() + : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); OEMCryptoResult result; WithOecWriteLock("LoadUsageTableHeader", [&] { result = OEMCrypto_LoadUsageTableHeader( - requested_security_level_, + requested_security_level, reinterpret_cast(usage_table_header.data()), usage_table_header.size()); metrics_->oemcrypto_load_usage_table_header_.Increment(result); @@ -2427,6 +2443,48 @@ CdmResponseType CryptoSession::LoadUsageTableHeader( } } +CdmResponseType CryptoSession::ShrinkUsageTableHeader( + SecurityLevel requested_security_level, uint32_t new_entry_count, + CdmUsageTableHeader* usage_table_header) { + LOGV("Shrinking usage table header: requested_security_level = %s", + requested_security_level == kLevel3 + ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() + : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); + RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); + + size_t usage_table_header_len = 0; + OEMCryptoResult result; + WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] { + result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level, + new_entry_count, nullptr, + &usage_table_header_len); + metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); + }); + + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + usage_table_header->resize(usage_table_header_len); + WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] { + result = OEMCrypto_ShrinkUsageTableHeader( + requested_security_level, new_entry_count, + reinterpret_cast( + const_cast(usage_table_header->data())), + &usage_table_header_len); + metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); + }); + } + + switch (result) { + case OEMCrypto_SUCCESS: + usage_table_header->resize(usage_table_header_len); + return NO_ERROR; + case OEMCrypto_ERROR_ENTRY_IN_USE: + return SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE; + default: + return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR, + "ShrinkUsageTableHeader"); + } +} + CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) { LOGV("Creating usage entry: id = %u", oec_session_id_); @@ -2447,7 +2505,7 @@ CdmResponseType CryptoSession::CreateUsageEntry(uint32_t* entry_number) { case OEMCrypto_SUCCESS: return NO_ERROR; case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: - return INSUFFICIENT_CRYPTO_RESOURCES_3; + return INSUFFICIENT_CRYPTO_RESOURCES; case OEMCrypto_ERROR_SESSION_LOST_STATE: return SESSION_LOST_STATE_ERROR; case OEMCrypto_ERROR_SYSTEM_INVALIDATED: @@ -2483,18 +2541,19 @@ CdmResponseType CryptoSession::LoadUsageEntry( case OEMCrypto_SUCCESS: case OEMCrypto_WARNING_GENERATION_SKEW: return NO_ERROR; + case OEMCrypto_ERROR_INVALID_SESSION: + // This case is special, as it could imply that the provided + // session ID is invalid (CDM internal bug), or that the entry + // being loaded is already in use in a different session. + // It is up to the caller to handle this. + return LOAD_USAGE_ENTRY_INVALID_SESSION; case OEMCrypto_ERROR_GENERATION_SKEW: return LOAD_USAGE_ENTRY_GENERATION_SKEW; case OEMCrypto_ERROR_SIGNATURE_FAILURE: return LOAD_USAGE_ENTRY_SIGNATURE_FAILURE; - case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: - return INSUFFICIENT_CRYPTO_RESOURCES_3; - case OEMCrypto_ERROR_SESSION_LOST_STATE: - return SESSION_LOST_STATE_ERROR; - case OEMCrypto_ERROR_SYSTEM_INVALIDATED: - return SYSTEM_INVALIDATED_ERROR; default: - return LOAD_USAGE_ENTRY_UNKNOWN_ERROR; + return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR, + "LoadUsageEntry"); } } @@ -2540,42 +2599,6 @@ CdmResponseType CryptoSession::UpdateUsageEntry( "UpdateUsageEntry"); } -CdmResponseType CryptoSession::ShrinkUsageTableHeader( - uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header) { - LOGV("Shrinking usage table header: id = %u", oec_session_id_); - - RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); - - size_t usage_table_header_len = 0; - OEMCryptoResult result; - WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] { - result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level_, - new_entry_count, nullptr, - &usage_table_header_len); - metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); - }); - - if (result == OEMCrypto_ERROR_SHORT_BUFFER) { - usage_table_header->resize(usage_table_header_len); - - WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] { - result = OEMCrypto_ShrinkUsageTableHeader( - requested_security_level_, new_entry_count, - reinterpret_cast( - const_cast(usage_table_header->data())), - &usage_table_header_len); - metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); - }); - } - - if (result == OEMCrypto_SUCCESS) { - usage_table_header->resize(usage_table_header_len); - } - - return MapOEMCryptoResult(result, SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR, - "ShrinkUsageTableHeader"); -} - CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) { LOGV("Moving usage entry: id = %u", oec_session_id_); @@ -2585,8 +2608,15 @@ CdmResponseType CryptoSession::MoveUsageEntry(uint32_t new_entry_number) { metrics_->oemcrypto_move_entry_.Increment(result); }); - return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR, - "MoveUsageEntry"); + switch (result) { + case OEMCrypto_ERROR_ENTRY_IN_USE: + LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u", + new_entry_number); + return MOVE_USAGE_ENTRY_DESTINATION_IN_USE; + default: + return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR, + "MoveUsageEntry"); + } } bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output, diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index c49c34db..923de336 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -6,6 +6,7 @@ #include +#include #include #include "certificate_provisioning.h" @@ -70,6 +71,7 @@ using video_widevine_client::sdk:: namespace { +const char kAtscCertificateFileName[] = "atsccert.bin"; const char kCertificateFileName[] = "cert.bin"; const char kHlsAttributesFileNameExt[] = ".hal"; const char kUsageInfoFileNamePrefix[] = "usage"; @@ -126,19 +128,25 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate, std::string serialized_file; file.SerializeToString(&serialized_file); - return StoreFileWithHash(GetCertificateFileName(), serialized_file) == + return StoreFileWithHash(GetCertificateFileName(false), serialized_file) == kNoError; } -bool DeviceFiles::RetrieveCertificate(std::string* certificate, +bool DeviceFiles::RetrieveCertificate(bool atsc_mode_enabled, + std::string* certificate, std::string* wrapped_private_key, std::string* serial_number, uint32_t* system_id) { RETURN_FALSE_IF_UNINITIALIZED(); + if (!HasCertificate(atsc_mode_enabled)) { + return false; + } + video_widevine_client::sdk::File file; - if (RetrieveHashedFile(GetCertificateFileName(), &file) != kNoError) { - LOGE("Unable to retrieve certificate file"); + if (RetrieveHashedFile(GetCertificateFileName(atsc_mode_enabled), &file) != + kNoError) { + LOGW("Unable to retrieve certificate file"); return false; } @@ -166,14 +174,16 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate, device_certificate.certificate(), serial_number, system_id); } -bool DeviceFiles::HasCertificate() { +bool DeviceFiles::HasCertificate(bool atsc_mode_enabled) { RETURN_FALSE_IF_UNINITIALIZED(); - return FileExists(GetCertificateFileName()); + + return FileExists(GetCertificateFileName(atsc_mode_enabled)); } bool DeviceFiles::RemoveCertificate() { RETURN_FALSE_IF_UNINITIALIZED() - return RemoveFile(GetCertificateFileName()); + + return RemoveFile(GetCertificateFileName(false)); } bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, @@ -1103,7 +1113,7 @@ DeviceFiles::ResponseType DeviceFiles::RetrieveHashedFile( path += name; if (!file_system_->Exists(path)) { - LOGE("File does not exist: path = %s", path.c_str()); + LOGW("File does not exist: path = %s", path.c_str()); return kFileNotFound; } @@ -1214,8 +1224,8 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) { return file_system_->FileSize(path); } -std::string DeviceFiles::GetCertificateFileName() { - return kCertificateFileName; +std::string DeviceFiles::GetCertificateFileName(bool atsc_mode_enabled) { + return atsc_mode_enabled ? kAtscCertificateFileName : kCertificateFileName; } std::string DeviceFiles::GetUsageTableFileName() { return kUsageTableFileName; } diff --git a/core/src/license.cpp b/core/src/license.cpp index 901d04f1..a934571c 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -237,6 +237,7 @@ bool CdmLicense::Init(const std::string& client_token, crypto_session_ = session; policy_engine_ = policy_engine; use_privacy_mode_ = use_privacy_mode; + license_nonce_ = 0; initialized_ = true; return true; } @@ -313,8 +314,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( // Get/set the nonce. This value will be reflected in the Key Control Block // of the license response. - uint32_t nonce; - status = crypto_session_->GenerateNonce(&nonce); + status = crypto_session_->GenerateNonce(&license_nonce_); switch (status) { case NO_ERROR: @@ -325,9 +325,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( default: return LICENSE_REQUEST_NONCE_GENERATION_ERROR; } - license_request.set_key_control_nonce(nonce); - LOGD("nonce = %u", nonce); - + license_request.set_key_control_nonce(license_nonce_); license_request.set_protocol_version(video_widevine::VERSION_2_1); // License request is complete. Serialize it. @@ -462,11 +460,11 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( LOGW("Unknown API Version"); api_version = 15; } - uint32_t nonce = 0; if (api_version < 16) { // For a pre-v16 license, get/set the nonce. This value will be reflected // in the Key Control Block of the license response. - const CdmResponseType status = crypto_session_->GenerateNonce(&nonce); + const CdmResponseType status = + crypto_session_->GenerateNonce(&license_nonce_); switch (status) { case NO_ERROR: break; @@ -477,8 +475,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( return LICENSE_RENEWAL_NONCE_GENERATION_ERROR; } } - license_request.set_key_control_nonce(nonce); - LOGD("nonce = %u", nonce); + license_request.set_key_control_nonce(license_nonce_); license_request.set_protocol_version(video_widevine::VERSION_2_1); // License request is complete. Serialize it. @@ -705,12 +702,13 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( return INVALID_LICENSE_TYPE; } - // At this point of the license life-cycle (handling a renewal or - // release), we should already know if the license is v15 or not. - // If license is v16, then there should be a |core_message| - // present; otherwise there might have beeen some tampering with the - // request or response. - if (supports_core_messages() && + // At this point of the license life-cycle (handling a renewal), we should + // already know if the license is v15 or not. If license is v16, then a + // renewal should have a |core_message| present; otherwise there might have + // been some tampering with the request or response. On the other hand, a + // release is processed without loading the license, so OEMCrypto does not + // know if it is v15 or v16, and will not add a core message. + if (is_renewal && supports_core_messages() && (!signed_response.has_oemcrypto_core_message() || signed_response.oemcrypto_core_message().empty())) { LOGE("Renewal response is missing |core_message| field"); @@ -723,8 +721,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( } const std::string& signed_message = signed_response.msg(); const std::string core_message = - supports_core_messages() ? signed_response.oemcrypto_core_message() - : std::string(); + signed_response.has_oemcrypto_core_message() + ? signed_response.oemcrypto_core_message() + : std::string(); const std::string& signature = signed_response.signature(); License license; @@ -811,6 +810,13 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( } key_request_ = signed_request.msg(); + LicenseRequest original_license_request; + if (!original_license_request.ParseFromString(key_request_)) { + LOGW("Could not parse original request."); + } else { + license_nonce_ = original_license_request.key_control_nonce(); + } + CdmResponseType sts = HandleKeyResponse(license_response); if (sts != KEY_ADDED) return sts; diff --git a/core/src/usage_table_header.cpp b/core/src/usage_table_header.cpp index 74b5d08e..9842622b 100644 --- a/core/src/usage_table_header.cpp +++ b/core/src/usage_table_header.cpp @@ -5,6 +5,7 @@ #include "usage_table_header.h" #include +#include #include "cdm_random.h" #include "crypto_session.h" @@ -16,15 +17,12 @@ namespace wvcdm { namespace { std::string kEmptyString; -size_t kMaxCryptoRetries = 3; wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); std::string kOldUsageEntryPoviderSessionToken = "nahZ6achSheiqua3TohQuei0ahwohv"; constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days -// Number of elements to consider for removal using the LRU algorithm. -constexpr size_t kLruRemovalSetSize = 3; // Fraction of table capacity of number of unexpired offline licenses // before they are considered to be removed. This could occur if // there are not enough expired offline or streaming licenses to @@ -34,6 +32,11 @@ constexpr size_t kLruRemovalSetSize = 3; // nears the capacity of the usage table). constexpr double kLruUnexpiredThresholdFraction = 0.75; +// Maximum number of entries to be moved during a defrag operation. +// This is to prevent the system from stalling too long if the defrag +// occurs during an active application session. +constexpr size_t kMaxDefragEntryMoves = 5; + // Convert |license_message| -> SignedMessage -> License. bool ParseLicenseFromLicenseMessage(const CdmKeyResponse& license_message, video_widevine::License* license) { @@ -128,6 +131,15 @@ bool RetrieveUsageInfoLicense(DeviceFiles* device_files, return true; } +bool EntryIsUsageInfo(const CdmUsageEntryInfo& info) { + // Used for stl filters. + return info.storage_type == kStorageUsageInfo; +} + +bool EntryIsOfflineLicense(const CdmUsageEntryInfo& info) { + // Used for stl filters. + return info.storage_type == kStorageLicense; +} } // namespace UsageTableHeader::UsageTableHeader() @@ -136,7 +148,7 @@ UsageTableHeader::UsageTableHeader() is_inited_(false), clock_ref_(&clock_) { file_system_.reset(new FileSystem()); - file_handle_.reset(new DeviceFiles(file_system_.get())); + device_files_.reset(new DeviceFiles(file_system_.get())); } bool UsageTableHeader::Init(CdmSecurityLevel security_level, @@ -164,16 +176,26 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (!crypto_session->GetMaximumUsageTableEntries( requested_security_level_, &potential_table_capacity_)) { + LOGW( + "Could not determine usage table capacity, assuming default: " + "default = %zu", + kMinimumUsageTableEntriesSupported); potential_table_capacity_ = kMinimumUsageTableEntriesSupported; + } else if (potential_table_capacity_ == 0) { + LOGD("Usage table capacity is unlimited: security_level = %d", + static_cast(security_level)); } else if (potential_table_capacity_ < kMinimumUsageTableEntriesSupported) { LOGW( "Reported usage table capacity is smaller than minimally required: " "capacity = %zu, minimum = %zu", potential_table_capacity_, kMinimumUsageTableEntriesSupported); potential_table_capacity_ = kMinimumUsageTableEntriesSupported; + } else { + LOGD("Usage table capacity: %zu, security_level = %d", + potential_table_capacity_, static_cast(security_level)); } - if (!file_handle_->Init(security_level)) { + if (!device_files_->Init(security_level)) { LOGE("Failed to initialize device files"); return false; } @@ -183,10 +205,11 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (metrics == nullptr) metrics = &alternate_crypto_metrics_; bool run_lru_upgrade = false; - if (file_handle_->RetrieveUsageTableInfo( + if (device_files_->RetrieveUsageTableInfo( &usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) { LOGI("Number of usage entries: %zu", usage_entry_info_.size()); - status = crypto_session->LoadUsageTableHeader(usage_table_header_); + status = crypto_session->LoadUsageTableHeader(requested_security_level_, + usage_table_header_); bool lru_success = true; if (status == NO_ERROR && run_lru_upgrade) { @@ -200,11 +223,14 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } } - // If the usage table header has been successfully loaded, and is at - // minimum capacity (>200), we need to make sure we can still add and - // remove entries. If not, clear files/data and recreate usage header table. + // If the usage table header has been successfully loaded, and is + // at minimum capacity (>200) or the table size does not have a + // hard limit, we need to make sure we can still add and remove + // entries. If not, clear files/data and recreate usage header + // table. if (status == NO_ERROR && lru_success) { - if (usage_entry_info_.size() > potential_table_capacity()) { + if (HasUnlimitedTableCapacity() || + usage_entry_info_.size() > potential_table_capacity()) { uint32_t temporary_usage_entry_number; // Create a new temporary usage entry, close the session and then @@ -229,8 +255,16 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, } } if (result == NO_ERROR) { - result = DeleteEntry(temporary_usage_entry_number, file_handle_.get(), - metrics); + result = InvalidateEntry(temporary_usage_entry_number, + /* defrag_table = */ true, + device_files_.get(), metrics); + if (usage_entry_info_.size() > temporary_usage_entry_number) { + // The entry should have been deleted from the usage table, + // not just marked as type unknown. Failure to call + // Shrink() may be an indicator of other issues. + LOGE("Temporary entry was not deleted"); + result = UNKNOWN_ERROR; + } } if (result != NO_ERROR) { LOGE( @@ -245,19 +279,21 @@ bool UsageTableHeader::Init(CdmSecurityLevel security_level, if (status != NO_ERROR || !lru_success) { LOGE("Failed to load usage table: security_level = %d, status = %d", static_cast(security_level), static_cast(status)); - file_handle_->DeleteAllLicenses(); - file_handle_->DeleteAllUsageInfo(); - file_handle_->DeleteUsageTableInfo(); + device_files_->DeleteAllLicenses(); + device_files_->DeleteAllUsageInfo(); + device_files_->DeleteUsageTableInfo(); usage_entry_info_.clear(); usage_table_header_.clear(); - status = crypto_session->CreateUsageTableHeader(&usage_table_header_); + status = crypto_session->CreateUsageTableHeader(requested_security_level_, + &usage_table_header_); if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); } } else { - status = crypto_session->CreateUsageTableHeader(&usage_table_header_); + status = crypto_session->CreateUsageTableHeader(requested_security_level_, + &usage_table_header_); if (status != NO_ERROR) return false; - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); } is_inited_ = true; @@ -275,66 +311,12 @@ CdmResponseType UsageTableHeader::AddEntry( CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); - if (status == INSUFFICIENT_CRYPTO_RESOURCES_3) { - // If usage entry creation fails due to insufficient resources, release an - // entry based on LRU. - std::vector removal_candidates; - if (!GetRemovalCandidates(&removal_candidates)) { - LOGE("Could not determine which license to remove"); - return status; - } - - // Variables for metrics. - const size_t usage_info_count = - std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), - [](const CdmUsageEntryInfo& usage_entry) { - return usage_entry.storage_type == kStorageUsageInfo; - }); - const size_t license_count = - std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), - [](const CdmUsageEntryInfo& usage_entry) { - return usage_entry.storage_type == kStorageLicense; - }); - int64_t staleness_of_removed; - CdmUsageEntryStorageType storage_type_of_removed; - const int64_t current_time = GetCurrentTime(); - - for (size_t i = 0; i < removal_candidates.size() && - status == INSUFFICIENT_CRYPTO_RESOURCES_3; - ++i) { - const uint32_t entry_number_to_delete = removal_candidates[i]; - // Calculate metric values. - staleness_of_removed = - current_time - - usage_entry_info_[entry_number_to_delete].last_use_time; - storage_type_of_removed = - usage_entry_info_[entry_number_to_delete].storage_type; - - if (DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics) == - NO_ERROR) { - // If the entry was deleted, it is still possible for the create new - // entry to fail. If so, we must ensure that the previously last - // entry was not in the |removal_candidates| as it has now been swapped - // with the deleted entry. - for (uint32_t& entry_number : removal_candidates) { - if (entry_number == usage_entry_info_.size()) { - entry_number = entry_number_to_delete; - } - } - } + if (status == INSUFFICIENT_CRYPTO_RESOURCES) { + LOGW("Usage table may be full, releasing oldest entry: size = %zu", + usage_entry_info_.size()); + status = ReleaseOldestEntry(metrics); + if (status == NO_ERROR) { status = crypto_session->CreateUsageEntry(usage_entry_number); - - // Record metrics on success. - if (status == NO_ERROR) { - metrics->usage_table_header_lru_usage_info_count_.Record( - usage_info_count); - metrics->usage_table_header_lru_offline_license_count_.Record( - license_count); - metrics->usage_table_header_lru_evicted_entry_staleness_.Record( - staleness_of_removed); - metrics->usage_table_header_lru_evicted_entry_type_.Record( - static_cast(storage_type_of_removed)); - } } } @@ -358,9 +340,7 @@ CdmResponseType UsageTableHeader::AddEntry( const size_t number_of_entries = usage_entry_info_.size(); usage_entry_info_.resize(*usage_entry_number + 1); for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { - usage_entry_info_[i].storage_type = kStorageTypeUnknown; - usage_entry_info_[i].key_set_id.clear(); - usage_entry_info_[i].usage_info_file_name.clear(); + usage_entry_info_[i].Clear(); } } else /* *usage_entry_number == usage_entry_info_.size() */ { usage_entry_info_.resize(*usage_entry_number + 1); @@ -392,8 +372,20 @@ CdmResponseType UsageTableHeader::AddEntry( } } + // Call to update the usage table header, but don't store the usage + // entry. If the entry is used by the CDM, the CDM session will make + // subsequent calls to update the usage entry and store that entry. + std::string usage_entry; + status = crypto_session->UpdateUsageEntry(&usage_table_header_, &usage_entry); + if (status != NO_ERROR) { + LOGE("Failed to update new usage entry: usage_entry_number = %u", + *usage_entry_number); + usage_entry_info_[*usage_entry_number].Clear(); + return status; + } + LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number); - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); return NO_ERROR; } @@ -415,27 +407,9 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); if (metrics == nullptr) metrics = &alternate_crypto_metrics_; - CdmResponseType status = + const CdmResponseType status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); - // If loading a usage entry fails due to insufficient resources, release a - // random entry different from |usage_entry_number| and try again. If there - // are no more entries to release, we fail. - for (uint32_t retry_count = 0; retry_count < kMaxCryptoRetries && - status == INSUFFICIENT_CRYPTO_RESOURCES_3; - ++retry_count) { - if (usage_entry_info_.size() <= 1) break; - // Get a random entry from the other entries. - uint32_t entry_number_to_delete = - CdmRandom::RandomInRange(usage_entry_info_.size() - 2); - if (entry_number_to_delete >= usage_entry_number) { - // Exclude |usage_entry_number|. - ++entry_number_to_delete; - } - DeleteEntry(entry_number_to_delete, file_handle_.get(), metrics); - status = crypto_session->LoadUsageEntry(usage_entry_number, usage_entry); - } - if (status == NO_ERROR) { usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); } @@ -460,15 +434,19 @@ CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number, if (status != NO_ERROR) return status; usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + StoreTable(device_files_.get()); return NO_ERROR; } -CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, - DeviceFiles* handle, - metrics::CryptoMetrics* metrics) { - LOGI("Locking to delete entry: usage_entry_number = %u", usage_entry_number); +CdmResponseType UsageTableHeader::InvalidateEntry( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { + LOGI("Locking to invalidate entry: usage_entry_number = %u", + usage_entry_number); std::unique_lock auto_lock(usage_table_header_lock_); + // OEMCrypto does not have any concept of "deleting" an entry. + // Instead, the CDM marks the entry's meta data as invalid (storage + // type unknown) and then performs a "defrag" of the OEMCrypto table. if (usage_entry_number >= usage_entry_info_.size()) { LOGE( "Usage entry number is larger than table size: " @@ -477,55 +455,52 @@ CdmResponseType UsageTableHeader::DeleteEntry(uint32_t usage_entry_number, return USAGE_INVALID_PARAMETERS_1; } - // Find the last valid entry number, in order to swap - size_t swap_entry_number = usage_entry_info_.size() - 1; - CdmUsageEntry swap_usage_entry; - bool swap_usage_entry_valid = false; + usage_entry_info_[usage_entry_number].Clear(); - while (!swap_usage_entry_valid && swap_entry_number > usage_entry_number) { - switch (usage_entry_info_[swap_entry_number].storage_type) { - case kStorageLicense: - case kStorageUsageInfo: { - CdmResponseType status = - GetEntry(swap_entry_number, handle, &swap_usage_entry); - if (status == NO_ERROR) swap_usage_entry_valid = true; - break; - } - case kStorageTypeUnknown: - default: - break; + if (defrag_table) { + // The defrag operation calls many OEMCrypto functions that are + // unrelated to the caller, the only error that will be returned is + // a SYSTEM_INVALIDATED_ERROR. As long as the storage type is + // properly set to unknown, the operation is considered successful. + // SYSTEM_INVALIDATED_ERROR is a special type of error that must be + // sent back to the caller for the CDM as a whole to handle. + const uint32_t pre_defrag_store_counter = store_table_counter_; + const CdmResponseType status = DefragTable(device_files, metrics); + if (pre_defrag_store_counter == store_table_counter_) { + // It is possible that DefragTable() does not result in any + // changes to the table, and as a result, it will not store the + // invalidated entry. + LOGD("Table was not stored during defrag, storing now"); + StoreTable(device_files); } - if (!swap_usage_entry_valid) --swap_entry_number; + if (status == SYSTEM_INVALIDATED_ERROR) { + LOGE("Invalidate entry failed due to system invalidation error"); + return SYSTEM_INVALIDATED_ERROR; + } + } else { + StoreTable(device_files); } - uint32_t number_of_entries_to_be_deleted = - usage_entry_info_.size() - usage_entry_number; + return NO_ERROR; +} - if (swap_usage_entry_valid) { - CdmResponseType status = MoveEntry(swap_entry_number, swap_usage_entry, - usage_entry_number, handle, metrics); - // If unable to move entry, unset storage type of entry to be deleted and - // resize |usage_entry_info_| so that swap usage entry is the last entry. - if (status != NO_ERROR) { - usage_entry_info_[usage_entry_number].storage_type = kStorageTypeUnknown; - usage_entry_info_[usage_entry_number].key_set_id.clear(); - if (usage_entry_info_.size() - 1 == swap_entry_number) { - file_handle_->StoreUsageTableInfo(usage_table_header_, - usage_entry_info_); - } else { - Shrink(metrics, usage_entry_info_.size() - swap_entry_number - 1); - } - return NO_ERROR; - } - number_of_entries_to_be_deleted = - usage_entry_info_.size() - swap_entry_number; - } - return Shrink(metrics, number_of_entries_to_be_deleted); +size_t UsageTableHeader::UsageInfoCount() const { + LOGI("Locking to count usage info (streaming license) entries"); + std::unique_lock auto_lock(usage_table_header_lock_); + return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), + EntryIsUsageInfo); +} + +size_t UsageTableHeader::OfflineEntryCount() const { + LOGI("Locking to count offline license entries"); + std::unique_lock auto_lock(usage_table_header_lock_); + return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), + EntryIsOfflineLicense); } CdmResponseType UsageTableHeader::MoveEntry( uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry, - uint32_t to_usage_entry_number, DeviceFiles* handle, + uint32_t to_usage_entry_number, DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { LOGI( "Moving usage entry: " @@ -537,13 +512,18 @@ CdmResponseType UsageTableHeader::MoveEntry( std::unique_ptr scoped_crypto_session; CryptoSession* crypto_session = test_crypto_session_.get(); if (crypto_session == nullptr) { - scoped_crypto_session.reset((CryptoSession::MakeCryptoSession(metrics))); + scoped_crypto_session.reset(CryptoSession::MakeCryptoSession(metrics)); crypto_session = scoped_crypto_session.get(); } - crypto_session->Open(requested_security_level_); + CdmResponseType status = crypto_session->Open(requested_security_level_); + if (status != NO_ERROR) { + LOGE("Cannot open session for move: usage_entry_number = %u", + from_usage_entry_number); + return status; + } - CdmResponseType status = + status = crypto_session->LoadUsageEntry(from_usage_entry_number, from_usage_entry); if (status != NO_ERROR) { @@ -564,6 +544,7 @@ CdmResponseType UsageTableHeader::MoveEntry( usage_entry_info_[to_usage_entry_number] = usage_entry_info_[from_usage_entry_number]; + usage_entry_info_[from_usage_entry_number].Clear(); CdmUsageEntry usage_entry; status = crypto_session->UpdateUsageEntry(&usage_table_header_, &usage_entry); @@ -574,15 +555,15 @@ CdmResponseType UsageTableHeader::MoveEntry( return status; } - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); - - StoreEntry(to_usage_entry_number, handle, usage_entry); + // Store the usage table and usage entry after successful move. + StoreTable(device_files); + StoreEntry(to_usage_entry_number, device_files, usage_entry); return NO_ERROR; } CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, - DeviceFiles* handle, + DeviceFiles* device_files, CdmUsageEntry* usage_entry) { LOGI("Getting usage entry: usage_entry_number = %u, storage_type: %d", usage_entry_number, @@ -594,7 +575,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, case kStorageLicense: { DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - if (!handle->RetrieveLicense( + if (!device_files->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license: status = %d", @@ -611,7 +592,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, CdmKeyMessage license_request; CdmKeyResponse license; - if (!handle->RetrieveUsageInfoByKeySetId( + if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &license_request, &license, usage_entry, @@ -642,7 +623,7 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, } CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, - DeviceFiles* handle, + DeviceFiles* device_files, const CdmUsageEntry& usage_entry) { LOGI("Storing usage entry: usage_entry_number = %u, storage type: %d", usage_entry_number, @@ -655,7 +636,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - if (!handle->RetrieveLicense( + if (!device_files->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license: status = %d", @@ -667,7 +648,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, license_data.usage_entry = usage_entry; license_data.usage_entry_number = usage_entry_number; - if (!handle->StoreLicense(license_data, &sub_error_code)) { + if (!device_files->StoreLicense(license_data, &sub_error_code)) { LOGE("Failed to store license: status = %d", static_cast(sub_error_code)); return USAGE_STORE_LICENSE_FAILED; @@ -679,7 +660,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, uint32_t entry_number; std::string provider_session_token, init_data, key_request, key_response, key_renewal_request; - if (!handle->RetrieveUsageInfoByKeySetId( + if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &key_request, &key_response, &entry, @@ -687,10 +668,10 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, LOGE("Failed to retrieve usage information"); return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } - handle->DeleteUsageInfo( + device_files->DeleteUsageInfo( usage_entry_info_[usage_entry_number].usage_info_file_name, provider_session_token); - if (!handle->StoreUsageInfo( + if (!device_files->StoreUsageInfo( provider_session_token, key_request, key_response, usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, usage_entry, @@ -711,6 +692,18 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, return NO_ERROR; } +bool UsageTableHeader::StoreTable(DeviceFiles* device_files) { + LOGV("Storing usage table information"); + const bool result = + device_files->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + if (result) { + ++store_table_counter_; + } else { + LOGW("Failed to store usage table info"); + } + return result; +} + CdmResponseType UsageTableHeader::Shrink( metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete) { @@ -731,9 +724,6 @@ CdmResponseType UsageTableHeader::Shrink( if (number_of_usage_entries_to_delete == 0) return NO_ERROR; - usage_entry_info_.resize(usage_entry_info_.size() - - number_of_usage_entries_to_delete); - // crypto_session points to an object whose scope is this method or a test // object whose scope is the lifetime of this class std::unique_ptr scoped_crypto_session; @@ -743,19 +733,273 @@ CdmResponseType UsageTableHeader::Shrink( crypto_session = scoped_crypto_session.get(); } - CdmResponseType status = crypto_session->Open(requested_security_level_); - if (status != NO_ERROR) return status; + const size_t new_size = + usage_entry_info_.size() - number_of_usage_entries_to_delete; + const CdmResponseType status = crypto_session->ShrinkUsageTableHeader( + requested_security_level_, new_size, &usage_table_header_); - status = crypto_session->ShrinkUsageTableHeader(usage_entry_info_.size(), - &usage_table_header_); - if (status != NO_ERROR) return status; + if (status == NO_ERROR) { + usage_entry_info_.resize(new_size); + StoreTable(device_files_.get()); + } + return status; +} - file_handle_->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); +CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { + LOGV("Defragging table: current_size = %zu", usage_entry_info_.size()); + // Defragging the usage table involves moving valid entries near the + // end of the usage table to the position of invalid entries near the + // front of the table. After the entries are moved, the CDM shrinks + // the table to cut off all trailing invalid entries at the end of + // the table. + + // Special case 0: Empty table, do nothing. + if (usage_entry_info_.empty()) { + LOGD("Table empty, nothing to defrag"); + return NO_ERROR; + } + + // Step 1: Create a list of entries to be removed from the table. + // Priority is given to the entries near the beginning of the table. + // To avoid large delays from the swapping process, we limit the + // quantity of entries to remove to |kMaxDefragEntryMoves| or fewer. + std::vector entries_to_remove; + for (uint32_t i = 0; i < usage_entry_info_.size() && + entries_to_remove.size() < kMaxDefragEntryMoves; + ++i) { + if (usage_entry_info_[i].storage_type == kStorageTypeUnknown) { + entries_to_remove.push_back(i); + } + } + + // Special case 1: There are no entries that are invalid; nothing + // needs to be done. + if (entries_to_remove.empty()) { + LOGD("No entries are invalid"); + return NO_ERROR; + } + + // Step 2: Create a list of entries to be moved from the end of the + // table to the positions identified for removal. + std::vector entries_to_move; + for (uint32_t i = 0; i < usage_entry_info_.size() && + entries_to_move.size() < entries_to_remove.size(); + ++i) { + // Search from the end of the table. + const uint32_t entry_index = usage_entry_info_.size() - i - 1; + if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { + entries_to_move.push_back(entry_index); + } + } + + // Special case 2: There are no valid entries in the table. In this case, + // the whole table can be removed. + if (entries_to_move.empty()) { + LOGD("No valid entries found, shrinking entire table: size = %zu", + usage_entry_info_.size()); + return Shrink(metrics, usage_entry_info_.size()); + } + + // Step 3: Ignore invalid entries that are after the last valid + // entry. No entry is to be moved to a greater index than it already + // has, and entries after the last valid entry will be removed when + // the shrink operation is applied to the table. + // Note: Special case 4 will handle any non-trivial cases related to + // interweaving of valid and invalid entries. + const uint32_t last_valid_entry = entries_to_move.front(); + while (!entries_to_remove.empty() && + entries_to_remove.back() > last_valid_entry) { + entries_to_remove.pop_back(); + } + + // Special case 3: All of the invalid entries are after the last valid + // entry. In this case, no movement is required and the table can just + // be shrunk to the last valid entry. + if (entries_to_remove.empty()) { + const size_t to_remove = usage_entry_info_.size() - last_valid_entry - 1; + LOGD("Removing all entries after the last valid entry: count = %zu", + to_remove); + return Shrink(metrics, to_remove); + } + + // Step 4: Move the valid entries to overwrite the invalid entries. + // Moving the highest numbered valid entry to the lowest numbered + // invalid entry. + + // Reversing vectors to make accessing and popping easier. This + // will put the lowest number invalid entry and the highest number + // valid entry at the back of their respective vectors. + std::reverse(entries_to_remove.begin(), entries_to_remove.end()); + std::reverse(entries_to_move.begin(), entries_to_move.end()); + while (!entries_to_remove.empty() && !entries_to_move.empty()) { + // Entries are popped after use only. + const uint32_t to_entry_number = entries_to_remove.back(); + const uint32_t from_entry_number = entries_to_move.back(); + + // Special case 4: We don't want to move any entries to a higher + // index than their current. Once this occurs, we can stop the + // loop. + if (to_entry_number > from_entry_number) { + LOGD("Entries will not be moved further down the table"); + break; + } + + CdmUsageEntry from_entry; + CdmResponseType status = + GetEntry(from_entry_number, device_files, &from_entry); + if (status != NO_ERROR) { + LOGW("Could not get entry: entry_number = %u", from_entry_number); + // It is unlikely that an unretrievable entry will suddenly + // become retrievable later on when it is needed. + usage_entry_info_[from_entry_number].Clear(); + entries_to_move.pop_back(); + continue; + } + + status = MoveEntry(from_entry_number, from_entry, to_entry_number, + device_files, metrics); + switch (status) { + case NO_ERROR: { + entries_to_remove.pop_back(); + entries_to_move.pop_back(); + break; + } + // Handle errors associated with the valid "from" entry. + case LOAD_USAGE_ENTRY_INVALID_SESSION: { + // This is a special error code when returned from LoadEntry() + // indicating that the entry is already in use in a different + // session. In this case, skip the entry and move on. + LOGD("From entry already in use: from_entry_number = %u", + from_entry_number); + entries_to_move.pop_back(); + break; + } + case LOAD_USAGE_ENTRY_GENERATION_SKEW: + case LOAD_USAGE_ENTRY_SIGNATURE_FAILURE: + case LOAD_USAGE_ENTRY_UNKNOWN_ERROR: { + // The entry (from the CDM's point of view) is invalid and + // can no longer be used. Safe to continue loop. + // TODO(b/152256186): Remove local files associated with this + // entry. + usage_entry_info_[from_entry_number].Clear(); + LOGW("From entry was corrupted: from_entry_number = %u", + from_entry_number); + entries_to_move.pop_back(); + break; + } + // Handle errors associated with the invalid "to" entry. + case MOVE_USAGE_ENTRY_DESTINATION_IN_USE: { + // The usage entry specified by |to_entry_number| is currently + // being used by another session. This is unlikely, but still + // possible. Given that this entry is already marked as unknown + // storage type, it will likely be removed at a later time. + LOGD("To entry already in use: to_entry_number = %u", to_entry_number); + entries_to_remove.pop_back(); + break; + } + case MOVE_USAGE_ENTRY_UNKNOWN_ERROR: { + // Something else wrong occurred when moving to the destination + // entry. This could be a problem with from entry or the to + // entry. Both should be skipped on the next iteration. + LOGW( + "Move failed, skipping both to entry and from entry: " + "to_entry_number = %u, from_entry_number = %u", + to_entry_number, from_entry_number); + entries_to_remove.pop_back(); + entries_to_move.pop_back(); + break; + } + // Handle other possible errors from the operations. + case INSUFFICIENT_CRYPTO_RESOURCES: { + // Cannot open any new sessions. The loop should end, but + // an attempt to shrink the table should still be made. + LOGW("Cannot open new session for table clean up"); + entries_to_remove.clear(); + entries_to_move.clear(); + break; + } + default: { + // For all other cases, it may not be safe to proceed, even to + // shrink the table. + LOGE("Unrecoverable error occurred while defragging table: status = %d", + static_cast(status)); + return status; + } + } // End switch case. + } // End while loop. + + // Step 5: Find the new last valid entry. + uint32_t new_last_valid_entry = usage_entry_info_.size(); + for (uint32_t i = 0; i < usage_entry_info_.size(); ++i) { + const uint32_t entry_index = usage_entry_info_.size() - i - 1; + if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { + new_last_valid_entry = entry_index; + break; + } + } + + // Special case 5: No entries in the table are valid. This could + // have occurred if entries during the move process were found to be + // invalid. In this case, remove the whole table. + if (new_last_valid_entry == usage_entry_info_.size()) { + LOGD( + "All entries have been invalidated, shrinking entire table: size = %zu", + usage_entry_info_.size()); + return Shrink(metrics, usage_entry_info_.size()); + } + + const size_t to_remove = usage_entry_info_.size() - new_last_valid_entry - 1; + + // Special case 6: It is possible that the last entry in the table + // is valid and currently loaded in the table by another session. + // The loop above would have tried to move it but had failed. In + // this case, nothing more to do. + if (to_remove == 0) { + LOGD("Defrag completed without shrinking table"); + StoreTable(device_files); + return NO_ERROR; + } + + // Step 6: Shrink table to the new size. + LOGD("Clean up complete, shrinking table: count = %zu", to_remove); + return Shrink(metrics, to_remove); +} // End Defrag(). + +CdmResponseType UsageTableHeader::ReleaseOldestEntry( + metrics::CryptoMetrics* metrics) { + LOGV("Releasing oldest entry"); + uint32_t entry_number_to_delete; + if (!GetRemovalCandidate(&entry_number_to_delete)) { + LOGE("Could not determine which license to remove"); + return UNKNOWN_ERROR; + } + const CdmUsageEntryInfo& usage_entry_info = + usage_entry_info_[entry_number_to_delete]; + + const int64_t current_time = GetCurrentTime(); + // Capture metric values now, as the |usage_entry_info| reference will + // change after the call to invalidate. + const int64_t staleness = current_time - usage_entry_info.last_use_time; + const CdmUsageEntryStorageType storage_type = usage_entry_info.storage_type; + + const CdmResponseType status = + InvalidateEntry(entry_number_to_delete, /* defrag_table = */ true, + device_files_.get(), metrics); + + if (status != NO_ERROR) { + LOGE("Failed to invalidate oldest entry: status = %d", + static_cast(status)); + return status; + } + + // Record metrics on success. + RecordLruEventMetrics(metrics, staleness, storage_type); return NO_ERROR; } // Test only method. -void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { +void UsageTableHeader::InvalidateEntryForTest(uint32_t usage_entry_number) { LOGV("Deleting entry for test: usage_entry_number = %u", usage_entry_number); if (usage_entry_number >= usage_entry_info_.size()) { LOGE( @@ -764,7 +1008,8 @@ void UsageTableHeader::DeleteEntryForTest(uint32_t usage_entry_number) { usage_entry_number, usage_entry_info_.size()); return; } - // Move last entry into deleted location and shrink usage entries + // Move last entry into invalidated entry location and shrink usage + // entries. usage_entry_info_[usage_entry_number] = usage_entry_info_[usage_entry_info_.size() - 1]; usage_entry_info_.resize(usage_entry_info_.size() - 1); @@ -788,13 +1033,13 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { switch (usage_entry_info.storage_type) { case kStorageLicense: { retrieve_response = RetrieveOfflineLicense( - file_handle_.get(), usage_entry_info.key_set_id, &license_message, + device_files_.get(), usage_entry_info.key_set_id, &license_message, &retrieved_entry_number); break; } case kStorageUsageInfo: { retrieve_response = RetrieveUsageInfoLicense( - file_handle_.get(), usage_entry_info.usage_info_file_name, + device_files_.get(), usage_entry_info.usage_info_file_name, usage_entry_info.key_set_id, &license_message, &retrieved_entry_number); break; @@ -868,7 +1113,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { for (size_t usage_entry_number : bad_license_file_entries) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; if (usage_entry_info.storage_type == kStorageLicense) { - file_handle_->DeleteLicense(usage_entry_info.key_set_id); + device_files_->DeleteLicense(usage_entry_info.key_set_id); } else if (usage_entry_info.storage_type == kStorageUsageInfo) { // To reduce write cycles, the deletion of usage info will be done // in bulk. @@ -881,148 +1126,139 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { } it->second.push_back(usage_entry_info.key_set_id); } // else kStorageUnknown { Nothing special }. - usage_entry_info.storage_type = kStorageTypeUnknown; - usage_entry_info.key_set_id.clear(); - usage_entry_info.usage_info_file_name.clear(); + usage_entry_info.Clear(); } for (const auto& p : usage_info_clean_up) { - file_handle_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); + device_files_->DeleteMultipleUsageInfoByKeySetIds(p.first, p.second); } return true; } -bool UsageTableHeader::GetRemovalCandidates( - std::vector* removal_candidates) { +bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) { LOGI("Locking to determine removal candidates"); std::unique_lock auto_lock(usage_table_header_lock_); const size_t lru_unexpired_threshold = - kLruUnexpiredThresholdFraction * potential_table_capacity(); + HasUnlimitedTableCapacity() + ? kLruUnexpiredThresholdFraction * size() + : kLruUnexpiredThresholdFraction * potential_table_capacity(); return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(), - lru_unexpired_threshold, kLruRemovalSetSize, - removal_candidates); + lru_unexpired_threshold, entry_to_remove); +} + +void UsageTableHeader::RecordLruEventMetrics( + metrics::CryptoMetrics* metrics, uint64_t staleness, + CdmUsageEntryStorageType storage_type) { + if (metrics == nullptr) return; + metrics->usage_table_header_lru_usage_info_count_.Record(UsageInfoCount()); + metrics->usage_table_header_lru_offline_license_count_.Record( + OfflineEntryCount()); + metrics->usage_table_header_lru_evicted_entry_staleness_.Record(staleness); + metrics->usage_table_header_lru_evicted_entry_type_.Record( + static_cast(storage_type)); } // Static. bool UsageTableHeader::DetermineLicenseToRemove( const std::vector& usage_entry_info_list, - int64_t current_time, size_t unexpired_threshold, size_t removal_count, - std::vector* removal_candidates) { - if (removal_candidates == nullptr) { - LOGE("Output parameter |removal_candidates| is null"); - return false; - } - if (removal_count == 0) { - LOGE("|removal_count| cannot be zero"); + int64_t current_time, size_t unexpired_threshold, + uint32_t* entry_to_remove) { + if (entry_to_remove == nullptr) { + LOGE("Output parameter |entry_to_remove| is null"); return false; } if (usage_entry_info_list.empty()) { return false; } - removal_candidates->clear(); - std::vector unknown_storage_entry_numbers; - // |entry_numbers| contains expired offline and streaming license. - std::vector entry_numbers; - std::vector unexpired_offline_license_entry_numbers; - - // Separate the entries based on their priority properties. - for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); - ++entry_number) { - const CdmUsageEntryInfo& usage_entry_info = - usage_entry_info_list[entry_number]; - if (usage_entry_info.storage_type == kStorageLicense) { - if (usage_entry_info.offline_license_expiry_time > current_time) { - // Unexpired offline. - unexpired_offline_license_entry_numbers.push_back(entry_number); - } else { - // Expired offline. - entry_numbers.push_back(entry_number); - } - } else if (usage_entry_info.storage_type == kStorageUsageInfo) { - // Streaming. - entry_numbers.push_back(entry_number); - } else { - // Unknown entries. - unknown_storage_entry_numbers.push_back(entry_number); - } - } - - // Select any entries of unknown storage type. - if (!unknown_storage_entry_numbers.empty()) { - if (unknown_storage_entry_numbers.size() >= removal_count) { - // Case: There are enough entries with unknown storage types to - // fill the removal set. - removal_candidates->insert( - removal_candidates->begin(), unknown_storage_entry_numbers.begin(), - unknown_storage_entry_numbers.begin() + removal_count); - return true; - } - // Fill whatever are available, and check for more. - *removal_candidates = std::move(unknown_storage_entry_numbers); - } - - // Sort licenses based on last used time. - const auto compare_last_used = [&](uint32_t i, uint32_t j) { + // Returns true if entry of first index is more stale than the + // entry of the second index. + const auto is_more_stale = [&](uint32_t i, uint32_t j) -> bool { return usage_entry_info_list[i].last_use_time < usage_entry_info_list[j].last_use_time; }; - // Check if unexpired licenses should be considered too. - if (unexpired_offline_license_entry_numbers.size() > unexpired_threshold) { - std::sort(unexpired_offline_license_entry_numbers.begin(), - unexpired_offline_license_entry_numbers.end(), compare_last_used); - if (unexpired_offline_license_entry_numbers.size() > removal_count) { - unexpired_offline_license_entry_numbers.resize(removal_count); + // Find the most stale expired offline / streaming license and the + // most stale unexpired offline entry. Count the number of unexpired + // entries. If any entry is of storage type unknown, then it should + // be removed. + constexpr uint32_t kNoEntry = std::numeric_limits::max(); + uint32_t stalest_expired_offline_license = kNoEntry; + uint32_t stalest_unexpired_offline_license = kNoEntry; + uint32_t stalest_streaming_license = kNoEntry; + size_t unexpired_license_count = 0; + + for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size(); + ++entry_number) { + const CdmUsageEntryInfo& usage_entry_info = + usage_entry_info_list[entry_number]; + + if (usage_entry_info.storage_type != kStorageLicense && + usage_entry_info.storage_type != kStorageUsageInfo) { + // Unknown storage type entries. Remove this entry. + *entry_to_remove = entry_number; + return true; + } + if (usage_entry_info.storage_type == kStorageLicense && + usage_entry_info.offline_license_expiry_time > current_time) { + // Unexpired offline. + ++unexpired_license_count; + if (stalest_unexpired_offline_license == kNoEntry || + is_more_stale(entry_number, stalest_unexpired_offline_license)) { + stalest_unexpired_offline_license = entry_number; + } + } else if (usage_entry_info.storage_type == kStorageLicense) { + // Expired offline. + if (stalest_expired_offline_license == kNoEntry || + is_more_stale(entry_number, stalest_expired_offline_license)) { + stalest_expired_offline_license = entry_number; + } + } else { + // Streaming. + if (stalest_streaming_license == kNoEntry || + is_more_stale(entry_number, stalest_streaming_license)) { + stalest_streaming_license = entry_number; + } } - // Merge the sets. - entry_numbers.insert(entry_numbers.end(), - unexpired_offline_license_entry_numbers.begin(), - unexpired_offline_license_entry_numbers.end()); } - // Sort expired offline and streaming license based on last used time. - std::sort(entry_numbers.begin(), entry_numbers.end(), compare_last_used); - - if ((entry_numbers.size() + removal_candidates->size()) <= removal_count) { - // Under testing conditions, it is possible for there to be fewer usage - // entries than there are being requested. - - // Move whatever values are available to the removal candidates. - removal_candidates->insert(removal_candidates->end(), entry_numbers.begin(), - entry_numbers.end()); - return removal_candidates->size() > 0; + if (stalest_expired_offline_license == kNoEntry && + stalest_streaming_license == kNoEntry && + unexpired_license_count <= unexpired_threshold) { + // Unexpected situation, could be an issue with the threshold. + LOGW( + "Table only contains unexpired offline licenses, " + "but threshold not met: size = %zu, count = %zu, threshold = %zu", + usage_entry_info_list.size(), unexpired_license_count, + unexpired_threshold); + *entry_to_remove = stalest_unexpired_offline_license; + return true; } - const size_t remaining_removal_count = - removal_count - removal_candidates->size(); - - // Based on the last use time the |remaining_removal_count|-th - // least recently used entry, filter out all elements which have - // been used more recently than it. This might result in a set - // which is larger than the desired size. - const int64_t cutoff_last_use_time = - usage_entry_info_list[entry_numbers[remaining_removal_count - 1]] - .last_use_time; - const auto equal_to_cutoff = [&](uint32_t entry_number) { - return usage_entry_info_list[entry_number].last_use_time == - cutoff_last_use_time; + const auto select_most_stale = [&](uint32_t a, uint32_t b) -> uint32_t { + if (a == kNoEntry) return b; + if (b == kNoEntry) return a; + return is_more_stale(a, b) ? a : b; }; - const auto first_cutoff_it = - std::find_if(entry_numbers.begin(), entry_numbers.end(), equal_to_cutoff); + // Only consider an unexpired entry if the threshold is reached. + if (unexpired_license_count > unexpired_threshold) { + const uint32_t temp = select_most_stale(stalest_unexpired_offline_license, + stalest_streaming_license); + *entry_to_remove = select_most_stale(temp, stalest_expired_offline_license); + } else { + *entry_to_remove = select_most_stale(stalest_streaming_license, + stalest_expired_offline_license); + } - const auto after_last_cutoff_it = - std::find_if_not(first_cutoff_it, entry_numbers.end(), equal_to_cutoff); - - // To avoid always selecting the greatest entry number (due to the - // sort & reverse), we randomize the set. - std::shuffle(first_cutoff_it, after_last_cutoff_it, - std::default_random_engine(CdmRandom::Rand())); - - removal_candidates->insert(removal_candidates->end(), entry_numbers.cbegin(), - entry_numbers.cbegin() + remaining_removal_count); + if (*entry_to_remove == kNoEntry) { + // Illegal state check. The loop above should have found at least + // one entry given that |usage_entry_info_list| is not empty. + LOGE("No entry could be used for removal: size = %zu", + usage_entry_info_list.size()); + return false; + } return true; } diff --git a/core/test/cdm_engine_metrics_decorator_unittest.cpp b/core/test/cdm_engine_metrics_decorator_unittest.cpp index 2ca27d89..4b9d2d4b 100644 --- a/core/test/cdm_engine_metrics_decorator_unittest.cpp +++ b/core/test/cdm_engine_metrics_decorator_unittest.cpp @@ -38,6 +38,8 @@ class MockCdmClientPropertySet : public CdmClientPropertySet { MOCK_CONST_METHOD0(is_session_sharing_enabled, bool()); MOCK_CONST_METHOD0(session_sharing_id, uint32_t()); MOCK_METHOD1(set_session_sharing_id, void(uint32_t)); + MOCK_CONST_METHOD0(use_atsc_mode, bool()); + MOCK_METHOD1(set_use_atsc_mode, void(bool)); MOCK_CONST_METHOD0(app_id, const std::string&()); }; @@ -76,13 +78,13 @@ class MockCdmEngineImpl : public CdmEngine { MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&)); MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); - MOCK_METHOD5(GetProvisioningRequest, + MOCK_METHOD6(GetProvisioningRequest, CdmResponseType(CdmCertificateType, const std::string&, - const std::string&, CdmProvisioningRequest*, - std::string*)); - MOCK_METHOD3(HandleProvisioningResponse, - CdmResponseType(const CdmProvisioningResponse&, std::string*, - std::string*)); + const std::string&, SecurityLevel, + CdmProvisioningRequest*, std::string*)); + MOCK_METHOD4(HandleProvisioningResponse, + CdmResponseType(const CdmProvisioningResponse&, SecurityLevel, + std::string*, std::string*)); MOCK_METHOD1(Unprovision, CdmResponseType(CdmSecurityLevel)); MOCK_METHOD4(ListUsageIds, CdmResponseType(const std::string&, CdmSecurityLevel, @@ -311,16 +313,17 @@ TEST_F(WvCdmEngineMetricsImplTest, GetProvisioningRequest) { std::string default_url; EXPECT_CALL(*test_cdm_metrics_engine_, - GetProvisioningRequest(Eq(kCertificateX509), - Eq("fake certificate authority"), - Eq("fake service certificate"), - Eq(&request), Eq(&default_url))) + GetProvisioningRequest( + Eq(kCertificateX509), Eq("fake certificate authority"), + Eq("fake service certificate"), Eq(wvcdm::kLevelDefault), + Eq(&request), Eq(&default_url))) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); ASSERT_EQ(wvcdm::UNKNOWN_ERROR, test_cdm_metrics_engine_->GetProvisioningRequest( kCertificateX509, "fake certificate authority", - "fake service certificate", &request, &default_url)); + "fake service certificate", wvcdm::kLevelDefault, &request, + &default_url)); drm_metrics::WvCdmMetrics metrics_proto; test_cdm_metrics_engine_->GetMetricsSnapshot(&metrics_proto); @@ -340,12 +343,14 @@ TEST_F(WvCdmEngineMetricsImplTest, HandleProvisioningResponse) { EXPECT_CALL(*test_cdm_metrics_engine_, HandleProvisioningResponse(Eq("fake provisioning response"), - Eq(&cert), Eq(&wrapped_key))) + Eq(wvcdm::kLevelDefault), Eq(&cert), + Eq(&wrapped_key))) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); ASSERT_EQ(wvcdm::UNKNOWN_ERROR, test_cdm_metrics_engine_->HandleProvisioningResponse( - "fake provisioning response", &cert, &wrapped_key)); + "fake provisioning response", wvcdm::kLevelDefault, &cert, + &wrapped_key)); drm_metrics::WvCdmMetrics metrics_proto; test_cdm_metrics_engine_->GetMetricsSnapshot(&metrics_proto); diff --git a/core/test/cdm_session_unittest.cpp b/core/test/cdm_session_unittest.cpp index f0ac0731..674939e8 100644 --- a/core/test/cdm_session_unittest.cpp +++ b/core/test/cdm_session_unittest.cpp @@ -114,8 +114,8 @@ class MockDeviceFiles : public DeviceFiles { MockDeviceFiles() : DeviceFiles(nullptr) {} MOCK_METHOD1(Init, bool(CdmSecurityLevel)); - MOCK_METHOD4(RetrieveCertificate, - bool(std::string*, std::string*, std::string*, uint32_t*)); + MOCK_METHOD5(RetrieveCertificate, + bool(bool, std::string*, std::string*, std::string*, uint32_t*)); }; class MockUsageTableHeader : public UsageTableHeader { @@ -217,8 +217,8 @@ TEST_F(CdmSessionTest, InitWithBuiltInCertificate) { EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) .WillOnce(Return(kClientTokenDrmCert)); EXPECT_CALL(*file_handle_, - RetrieveCertificate(NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -245,8 +245,8 @@ TEST_F(CdmSessionTest, InitWithCertificate) { .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, - RetrieveCertificate(NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -272,8 +272,8 @@ TEST_F(CdmSessionTest, ReInitFail) { .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, - RetrieveCertificate(NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) @@ -307,7 +307,7 @@ TEST_F(CdmSessionTest, InitNeedsProvisioning) { .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, - RetrieveCertificate(NotNull(), NotNull(), NotNull(), _)) + RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) .WillOnce(Return(false)); ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(nullptr)); @@ -327,8 +327,8 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); EXPECT_CALL(*file_handle_, - RetrieveCertificate(NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey), + RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), Return(true))); EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) .InSequence(crypto_session_seq) diff --git a/core/test/certificate_provisioning_unittest.cpp b/core/test/certificate_provisioning_unittest.cpp index 13ff2464..6c7d0b28 100644 --- a/core/test/certificate_provisioning_unittest.cpp +++ b/core/test/certificate_provisioning_unittest.cpp @@ -40,16 +40,19 @@ class MockCryptoSession : public TestCryptoSession { MockCryptoSession(metrics::CryptoMetrics* metrics) : TestCryptoSession(metrics) {} MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); - MOCK_METHOD1(LoadUsageTableHeader, - CdmResponseType(const CdmUsageTableHeader&)); - MOCK_METHOD1(CreateUsageTableHeader, CdmResponseType(CdmUsageTableHeader*)); + // Usage Table Header. + MOCK_METHOD2(CreateUsageTableHeader, + CdmResponseType(SecurityLevel, CdmUsageTableHeader*)); + MOCK_METHOD2(LoadUsageTableHeader, + CdmResponseType(SecurityLevel, const CdmUsageTableHeader&)); + MOCK_METHOD3(ShrinkUsageTableHeader, + CdmResponseType(SecurityLevel, uint32_t, CdmUsageTableHeader*)); + // Usage Entry. MOCK_METHOD1(CreateUsageEntry, CdmResponseType(uint32_t*)); MOCK_METHOD2(LoadUsageEntry, CdmResponseType(uint32_t, const CdmUsageEntry&)); MOCK_METHOD2(UpdateUsageEntry, CdmResponseType(CdmUsageTableHeader*, CdmUsageEntry*)); MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t)); - MOCK_METHOD2(ShrinkUsageTableHeader, - CdmResponseType(uint32_t, CdmUsageTableHeader*)); }; class TestStubCryptoSessionFactory : public CryptoSessionFactory { @@ -62,18 +65,19 @@ class TestStubCryptoSessionFactory : public CryptoSessionFactory { using ::testing::_; class CertificateProvisioningTest : public WvCdmTestBase { - public: protected: void SetUp() override { WvCdmTestBase::SetUp(); CryptoSession::SetCryptoSessionFactory(new TestStubCryptoSessionFactory()); + metrics_.reset(new metrics::CryptoMetrics()); certificate_provisioning_.reset( - new CertificateProvisioning(new metrics::CryptoMetrics())); + new CertificateProvisioning(metrics_.get())); } void TearDown() override {} + std::unique_ptr metrics_; std::unique_ptr certificate_provisioning_; }; diff --git a/core/test/crypto_session_unittest.cpp b/core/test/crypto_session_unittest.cpp index 4cf703db..646b0b62 100644 --- a/core/test/crypto_session_unittest.cpp +++ b/core/test/crypto_session_unittest.cpp @@ -316,8 +316,6 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) { EXPECT_EQ(OEMCrypto_Keybox, metrics_proto.oemcrypto_provisioning_method().int_value()); EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); - EXPECT_EQ( - 1u, metrics_proto.oemcrypto_get_key_data_time_us(0).operation_count()); } else if (token_type == kClientTokenOemCert) { // Recent devices all have a system id between 1k and 6 or 7k. Errors // we are trying to catch are 0, byte swapped 32 bit numbers, or @@ -365,8 +363,6 @@ TEST_F(CryptoSessionMetricsTest, GetProvisioningTokenValidMetrics) { uint32_t system_id = FindKeyboxSystemID(); EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); - EXPECT_EQ( - 2u, metrics_proto.oemcrypto_get_key_data_time_us(0).operation_count()); } else if (token_type == kClientTokenOemCert) { // Recent devices all have a system id between 1k and 6 or 7k. Errors // we are trying to catch are 0, byte swapped 32 bit numbers, or diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 6dc0ec11..1c8d6ed2 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "arraysize.h" #include "cdm_random.h" @@ -25,34 +26,31 @@ namespace { const uint32_t kCertificateLen = 700; const uint32_t kWrappedKeyLen = 500; -const uint32_t kProtobufEstimatedOverhead = 200; - const std::string kEmptyString; // Structurally valid test certificate. // The data elements in this module are used to test the storage and // retrieval of certificates and licenses -const std::string kTestCertificate = - "124B035F3D256A656F0E505A085E7A6C482B61035E0C4A540F7803137F4C3B45206B7F33" - "347F4D7A005E56400F0955011F4E07072D0D46781817460974326A516E3944385760280E" - "4F166B380F033D045231201E6146041C3A6F01345C59300D32592732192C0F2310586306" - "7B31467B1477010D6F1D1944272509572A26217E1E6F7B666F46153E7749106E48760468" - "19467E164A731773155B3236537D5128682014174D125063380E48356A370B5015416A7F" - "672F132E37364E154B41540F440E47092775531508495F1E55576F363C0C190C3A332179" - "415B343905563E37645E68007053315A1A20286E7C3B4320424A5F7F36635558686C3565" - "762122237D344A411C0F00342135776753461D105C21111E5024434E5E0F275D12061658" - "4435410F210E5228532D214F505D0F0B3C34032C7C597F6159665E664C682C5A6C03212E" - "71333C3A642D796A65642E151827086E2D671C130B172C43192C792D294440630163526D" - "0658537A073E0F32231E7426593230692A4468386D3511542F1A6F71440128466E510445" - "294F4465113D1B1A711D4D67691363093B680854322B041C2F72524A513E5F0E407C6233" - "1728520E6C0C09107C26737B78287231661952283619647A6241391940297D2067036D44" - "3C64766918236C51175A636F000A2E5A4C5B725D5500652B1C39283037723F0255092976" - "6F2D204F0E616F1233206B75661B0F755E1E3807491079663A191C0B2D5E363B3768663A" - "4E222A1D32015D3D783E5148313F05713B140347231C59243648313C23770F554E012715" - "3350597775274A580306202E65265957291F490F642A2E7C6700716400617C7E6A303266" - "523B102906195E003C2D111A7D4740122C6941003726602B59263B5C09473D4E025E3541" - "701B122D340A3D145436137002687E4C470D2F6F4C357A3245384D737B734E2274301179" - "402473486311156E5A0C78644C593273"; +const std::string kTestCertificate = a2bs_hex( + "0A98030802120D73657269616C5F6E756D62657218B4B2CDE00422E8024D49494243674B43" + "415145412B78475A2F77637A39756746705030374E73706F365531376C3059684669467078" + "78553470546B334C69667A3952337A734973754552777461372B66574966784F6F32303865" + "74742F6A68736B69566F645345743351424768345842697079576F704B775A393348486144" + "565A41414C692F32412B785442745764456F37584755756A4B447643322F615A4B756B666A" + "704F6955493841684C41666A6D6C63442F555A31515068306D4873676C524E436D7043776D" + "7753584139564E6D687A2B5069422B446D6C3457576E4B572F56486F32756A54587871372B" + "65664D55344832666E79335365334B594F73465046475A31544E5153596C46755368577248" + "5074694C6D5564506F50364356326D4D4C31746B2B6C3744494971587251684C554B444143" + "654D35726F4D78306B4C6855574238502B30756A31434E6C4E4E344A525A6C433778466671" + "694D62465255395A344E3659774944415141422899203A11746573742E7769646576696E65" + "2E636F6D128202307836353063396632653637303165336665373364333035343930346139" + "61346262646239363733336631633463373433656635373361643661633134633561336266" + "38613437333166366536323736666165613532343733303336373766623864626466323466" + "66373865353363323530353263646361383765656366656538353437366263623861303563" + "62396131656665663763623837646436383232336531313763653830306163343631373731" + "37323534343735376134383762653332663561623866653038373966613861646437386265" + "34363565613866386435616366393737653966316165333664346434373831366561366564" + "343133373262"); // A Wrapped Private Key // The data elements in this module are used to test the storage and @@ -76,42 +74,54 @@ const std::string kTestWrappedPrivateKey = // The test certificate in file storage format. // The data elements in this module are used to test the storage and // retrieval of certificates and licenses -const std::string kTestCertificateFileData = - "0ABD09080110011AB6090ABC05124B035F3D256A656F0E505A085E7A6C482B61035E0C4A" - "540F7803137F4C3B45206B7F33347F4D7A005E56400F0955011F4E07072D0D4678181746" - "0974326A516E3944385760280E4F166B380F033D045231201E6146041C3A6F01345C5930" - "0D32592732192C0F23105863067B31467B1477010D6F1D1944272509572A26217E1E6F7B" - "666F46153E7749106E4876046819467E164A731773155B3236537D5128682014174D1250" - "63380E48356A370B5015416A7F672F132E37364E154B41540F440E47092775531508495F" - "1E55576F363C0C190C3A332179415B343905563E37645E68007053315A1A20286E7C3B43" - "20424A5F7F36635558686C3565762122237D344A411C0F00342135776753461D105C2111" - "1E5024434E5E0F275D120616584435410F210E5228532D214F505D0F0B3C34032C7C597F" - "6159665E664C682C5A6C03212E71333C3A642D796A65642E151827086E2D671C130B172C" - "43192C792D294440630163526D0658537A073E0F32231E7426593230692A4468386D3511" - "542F1A6F71440128466E510445294F4465113D1B1A711D4D67691363093B680854322B04" - "1C2F72524A513E5F0E407C62331728520E6C0C09107C26737B7828723166195228361964" - "7A6241391940297D2067036D443C64766918236C51175A636F000A2E5A4C5B725D550065" - "2B1C39283037723F02550929766F2D204F0E616F1233206B75661B0F755E1E3807491079" - "663A191C0B2D5E363B3768663A4E222A1D32015D3D783E5148313F05713B140347231C59" - "243648313C23770F554E0127153350597775274A580306202E65265957291F490F642A2E" - "7C6700716400617C7E6A303266523B102906195E003C2D111A7D4740122C694100372660" - "2B59263B5C09473D4E025E3541701B122D340A3D145436137002687E4C470D2F6F4C357A" - "3245384D737B734E2274301179402473486311156E5A0C78644C59327312F4034F724B06" - "5326371A2F5F6F51467C2E26555C453B5C7C1B4F2738454B782E3E7B5340435A66374D06" - "12052C521A233D7A67194871751C78575E5177070130264C4F037633320E667B1A491929" - "24491338693D106E6113014A733A241A1A033E28352178146B4F543D38104A5919120325" - "502C31365506096D59585E08774B5B567A7B5D03451E6B11633E52672C226103104B3E4C" - "031A6403050F3A574D2C501711773802741F7F3A0D364757101D02181C7D4D3520716750" - "6A424C094E4A72316F791F162D76657D2B5D3C2D7B273A2869277175613165187E552824" - "30491467086425432347701C3116446D21645C756B2D3D0F797C3220322D622A254D0B7D" - "4F1D5D0C0A36755D1246741A34783C45157247091C78232B7D2E0E1F637A2A3739085D76" - "166747034350613969072F5B5C5B21657E470C7E513B3F091D74455A3A0737057B7E3B53" - "37191D4E7536087C334B6028530F3F5B23380B6A076031294501003D6D1F240F63053D5D" - "0B271B6A0F26185650731308660B0447566041684F584C22216E567D3B7755695F7F3D6B" - "64525E7227165948101540243C19495C4C702F37490F2661335379782562414326304302" - "0E1E6760123D51056F2F1E482F2E3D021B27677D3E7E3C0C11757C3448275E08382E1112" - "63644C6D224714706D760A054A586E17505C3429575A41043F1842091220F8D0A23D4B1B" - "C7B23A38B921BC1EA8938D1FD22FF9A389B58DA856A3E2625F27"; +const std::string kTestCertificateFileData = a2bs_hex( + "0A950D080110011A8E0D0AA0050A98030802120D73657269616C5F6E756D62657218B4B2CD" + "E00422E8024D49494243674B43415145412B78475A2F77637A39756746705030374E73706F" + "365531376C305968466946707878553470546B334C69667A3952337A734973754552777461" + "372B66574966784F6F3230386574742F6A68736B69566F6453457433514247683458426970" + "79576F704B775A393348486144565A41414C692F32412B785442745764456F37584755756A" + "4B447643322F615A4B756B666A704F6955493841684C41666A6D6C63442F555A3151506830" + "6D4873676C524E436D7043776D7753584139564E6D687A2B5069422B446D6C3457576E4B57" + "2F56486F32756A54587871372B65664D55344832666E79335365334B594F73465046475A31" + "544E5153596C467553685772485074694C6D5564506F50364356326D4D4C31746B2B6C3744" + "494971587251684C554B444143654D35726F4D78306B4C6855574238502B30756A31434E6C" + "4E4E344A525A6C433778466671694D62465255395A344E3659774944415141422899203A11" + "746573742E7769646576696E652E636F6D1282023078363530633966326536373031653366" + "65373364333035343930346139613462626462393637333366316334633734336566353733" + "61643661633134633561336266386134373331663665363237366661656135323437333033" + "36373766623864626466323466663738653533633235303532636463613837656563666565" + "38353437366263623861303563623961316566656637636238376464363832323365313137" + "63653830306163343631373731373235343437353761343837626533326635616238666530" + "38373966613861646437386265343635656138663864356163663937376539663161653336" + "6434643437383136656136656434313337326212E807344637323442303635333236333731" + "41324635463646353134363743324532363535354334353342354337433142344632373338" + "34353442373832453345374235333430343335413636333734443036313230353243353231" + "41323333443741363731393438373137353143373835373545353137373037303133303236" + "34433446303337363333333230453636374231413439313932393234343931333338363933" + "44313036453631313330313441373333413234314131413033334532383335323137383134" + "36423446353433443338313034413539313931323033323535303243333133363535303630" + "39364435393538354530383737344235423536374137423544303334353145364231313633" + "33453532363732433232363130333130344233453443303331413634303330353046334135" + "37344432433530313731313737333830323734314637463341304433363437353731303144" + "30323138314337443444333532303731363735303641343234433039344534413732333136" + "46373931463136324437363635374432423544334332443742323733413238363932373731" + "37353631333136353138374535353238323433303439313436373038363432353433323334" + "37373031433331313634343644323136343543373536423244334430463739374333323230" + "33323244363232413235344430423744344631443544304330413336373535443132343637" + "34314133343738334334353135373234373039314337383233324237443245304531463633" + "37413241333733393038354437363136363734373033343335303631333936393037324635" + "42354335423231363537453437304337453531334233463039314437343435354133413037" + "33373035374237453342353333373139314434453735333630383743333334423630323835" + "33304633463542323333383042364130373630333132393435303130303344364431463234" + "30463633303533443544304232373142364130463236313835363530373331333038363630" + "42303434373536363034313638344635383443323232313645353637443342373735353639" + "35463746334436423634353235453732323731363539343831303135343032343343313934" + "39354334433730324633373439304632363631333335333739373832353632343134333236" + "33303433303230453145363736303132334435313035364632463145343832463245334430" + "32314232373637374433453745334330433131373537433334343832373545303833383245" + "31313132363336343443364432323437313437303644373630413035344135383645313735" + "303543333432393537354134313034334631383432303912205C6993E9656F73A41739773A" + "0FCBA8AE232CD8856ACE585FF6BFB2A09C20061E"); struct LicenseInfo { std::string key_set_id; @@ -2014,6 +2024,7 @@ class MockFileSystem : public FileSystem { // gmock methods using ::testing::_; +using ::testing::AllArgs; using ::testing::AllOf; using ::testing::DoAll; using ::testing::Eq; @@ -2075,9 +2086,9 @@ class DeviceFilesTest : public ::testing::Test { class DeviceFilesStoreTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -class DeviceCertificateStoreTest : public DeviceFilesTest {}; - -class DeviceCertificateTest : public DeviceFilesTest {}; +class DeviceCertificateTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; class DeviceFilesSecurityLevelTest : public DeviceFilesTest, @@ -2102,103 +2113,35 @@ class DeviceFilesDeleteMultipleUsageInfoTest public ::testing::WithParamInterface {}; MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; } -MATCHER_P(IsStrEq, str, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - return memcmp(arg, str.c_str(), str.size()) == 0; +MATCHER_P(StrAndLenEq, str, "") { + const std::string data(std::get<0>(arg), std::get<1>(arg)); + return data == str; } -MATCHER_P(ContainsAllElementsInVector, str_vector, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - size_t str_length = 0; - for (size_t i = 0; i < str_vector.size(); ++i) { - str_length += str_vector[i].size(); - } - std::string data(arg, str_length + kProtobufEstimatedOverhead); - bool all_entries_found = true; - for (size_t i = 0; i < str_vector.size(); ++i) { - if (data.find(str_vector[i]) == std::string::npos) { - all_entries_found = false; +MATCHER_P(StrAndLenContains, str_vector, "") { + const std::string data(std::get<0>(arg), std::get<1>(arg)); + for (const std::string& str : str_vector) { + if (data.find(str) == std::string::npos) { + return false; } } - return all_entries_found; -} -MATCHER_P2(Contains, str1, size, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - std::string data(arg, size + str1.size() + kProtobufEstimatedOverhead); - return (data.find(str1) != std::string::npos); -} -MATCHER_P3(Contains, str1, str2, size, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - std::string data( - arg, size + str1.size() + str2.size() + kProtobufEstimatedOverhead); - return (data.find(str1) != std::string::npos && - data.find(str2) != std::string::npos); -} -MATCHER_P4(Contains, str1, str2, str3, size, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - std::string data(arg, size + str1.size() + str2.size() + str3.size() + - kProtobufEstimatedOverhead); - return (data.find(str1) != std::string::npos && - data.find(str2) != std::string::npos && - data.find(str3) != std::string::npos); -} -MATCHER_P6(Contains, str1, str2, str3, str4, str5, size, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - std::string data(arg, size + str1.size() + str2.size() + str3.size() + - str4.size() + str5.size() + - kProtobufEstimatedOverhead); - return (data.find(str1) != std::string::npos && - data.find(str2) != std::string::npos && - data.find(str3) != std::string::npos && - data.find(str4) != std::string::npos && - data.find(str5) != std::string::npos); -} -MATCHER_P8(Contains, str1, str2, str3, str4, str5, str6, map7, str8, "") { - // Estimating the length of data. We can have gmock provide length - // as well as pointer to data but that will introduce a dependency on tr1 - size_t map7_len = 0; - CdmAppParameterMap::const_iterator itr = map7.begin(); - for (itr = map7.begin(); itr != map7.end(); ++itr) { - map7_len += itr->first.length(); - map7_len += itr->second.length(); - } - std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() + - str5.size() + str6.size() + map7_len + str8.size() + - kProtobufEstimatedOverhead); - bool map7_entries_present = true; - for (itr = map7.begin(); itr != map7.end(); ++itr) { - map7_entries_present = map7_entries_present && - data.find(itr->first) != std::string::npos && - data.find(itr->second) != std::string::npos; - } - return (data.find(str1) != std::string::npos && - data.find(str2) != std::string::npos && - data.find(str3) != std::string::npos && - data.find(str4) != std::string::npos && - data.find(str5) != std::string::npos && - data.find(str6) != std::string::npos && map7_entries_present && - data.find(str8) != std::string::npos); + return true; } -TEST_F(DeviceCertificateStoreTest, StoreCertificate) { +TEST_F(DeviceCertificateTest, StoreCertificate) { MockFileSystem file_system; std::string certificate(CdmRandom::RandomData(kCertificateLen)); std::string wrapped_private_key(CdmRandom::RandomData(kWrappedKeyLen)); std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(); + device_base_path_ + DeviceFiles::GetCertificateFileName(false); // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, DoOpen(StrEq(device_certificate_path), IsCreateFileFlagSet())) .WillOnce(Return(file)); - EXPECT_CALL(*file, Write(Contains(certificate, wrapped_private_key, 0), - Gt(certificate.size() + wrapped_private_key.size()))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains( + std::vector{certificate, wrapped_private_key}))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); @@ -2207,17 +2150,18 @@ TEST_F(DeviceCertificateStoreTest, StoreCertificate) { EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); } -// TODO(tinskip): Fix. kTestCertificateFileData appears to be incorect. -TEST_F(DeviceCertificateTest, DISABLED_ReadCertificate) { +TEST_P(DeviceCertificateTest, ReadCertificate) { MockFileSystem file_system; + const bool atsc_mode = GetParam(); std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(); - std::string data = a2bs_hex(kTestCertificateFileData); + device_base_path_ + DeviceFiles::GetCertificateFileName(atsc_mode); + std::string data = kTestCertificateFileData; // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) - .WillOnce(Return(true)); + .Times(2) + .WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) .WillOnce(Return(data.size())); EXPECT_CALL(file_system, DoOpen(StrEq(device_certificate_path), _)) @@ -2233,16 +2177,18 @@ TEST_F(DeviceCertificateTest, DISABLED_ReadCertificate) { std::string certificate, wrapped_private_key; std::string serial_number; uint32_t system_id = 0; - ASSERT_TRUE(device_files.RetrieveCertificate( - &certificate, &wrapped_private_key, &serial_number, &system_id)); - EXPECT_EQ(kTestCertificate, b2a_hex(certificate)); - EXPECT_EQ(kTestWrappedPrivateKey, b2a_hex(wrapped_private_key)); + ASSERT_TRUE(device_files.RetrieveCertificate(atsc_mode, &certificate, + &wrapped_private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificate, certificate); + EXPECT_EQ(kTestWrappedPrivateKey, wrapped_private_key); } -TEST_F(DeviceCertificateTest, HasCertificate) { +TEST_P(DeviceCertificateTest, HasCertificate) { MockFileSystem file_system; + bool atsc_mode = GetParam(); std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(); + device_base_path_ + DeviceFiles::GetCertificateFileName(atsc_mode); EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(false)) @@ -2253,11 +2199,14 @@ TEST_F(DeviceCertificateTest, HasCertificate) { ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); // MockFile returns false. - EXPECT_FALSE(device_files.HasCertificate()); + EXPECT_FALSE(device_files.HasCertificate(atsc_mode)); // MockFile returns true. - EXPECT_TRUE(device_files.HasCertificate()); + EXPECT_TRUE(device_files.HasCertificate(atsc_mode)); } +INSTANTIATE_TEST_CASE_P(AtscMode, DeviceCertificateTest, + ::testing::Values(false, true)); + TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { MockFileSystem file_system; std::string certificate(CdmRandom::RandomData(kCertificateLen)); @@ -2268,15 +2217,16 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { ASSERT_TRUE( Properties::GetDeviceFilesBasePath(security_level, &device_base_path)); std::string device_certificate_path = - device_base_path + DeviceFiles::GetCertificateFileName(); + device_base_path + DeviceFiles::GetCertificateFileName(false); // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, DoOpen(StrEq(device_certificate_path), IsCreateFileFlagSet())) .WillOnce(Return(file)); - EXPECT_CALL(*file, Write(Contains(certificate, wrapped_private_key, 0), - Gt(certificate.size() + wrapped_private_key.size()))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains( + std::vector{certificate, wrapped_private_key}))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); @@ -2298,20 +2248,26 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { CdmAppParameterMap app_parameters = GetAppParameters(kLicenseTestData[license_num].app_parameters); + std::vector expected_substrings{ + kLicenseTestData[license_num].pssh_data, + kLicenseTestData[license_num].key_request, + kLicenseTestData[license_num].key_response, + kLicenseTestData[license_num].key_renewal_request, + kLicenseTestData[license_num].key_renewal_response, + kLicenseTestData[license_num].key_release_url, + kLicenseTestData[license_num].usage_entry, + }; + for (const auto& iter : app_parameters) { + expected_substrings.push_back(iter.first); + expected_substrings.push_back(iter.second); + } + // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) .WillOnce(Return(file)); - EXPECT_CALL( - *file, - Write(Contains(kLicenseTestData[license_num].pssh_data, - kLicenseTestData[license_num].key_request, - kLicenseTestData[license_num].key_response, - kLicenseTestData[license_num].key_renewal_request, - kLicenseTestData[license_num].key_renewal_response, - kLicenseTestData[license_num].key_release_url, - app_parameters, kLicenseTestData[license_num].usage_entry), - Gt(GetLicenseDataSize(kLicenseTestData[license_num])))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains(expected_substrings))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); @@ -2350,20 +2306,27 @@ TEST_F(DeviceFilesTest, StoreLicenses) { CdmAppParameterMap app_parameters = GetAppParameters(kLicenseTestData[i].app_parameters); + std::vector expected_substrings{ + kLicenseTestData[i].pssh_data, + kLicenseTestData[i].key_request, + kLicenseTestData[i].key_response, + kLicenseTestData[i].key_renewal_request, + kLicenseTestData[i].key_renewal_response, + kLicenseTestData[i].key_release_url, + kLicenseTestData[i].usage_entry, + }; + for (const auto& iter : app_parameters) { + expected_substrings.push_back(iter.first); + expected_substrings.push_back(iter.second); + } + // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) .WillOnce(Return(file)); - EXPECT_CALL(*file, - Write(Contains(kLicenseTestData[i].pssh_data, - kLicenseTestData[i].key_request, - kLicenseTestData[i].key_response, - kLicenseTestData[i].key_renewal_request, - kLicenseTestData[i].key_renewal_response, - kLicenseTestData[i].key_release_url, - app_parameters, kLicenseTestData[i].usage_entry), - Gt(GetLicenseDataSize(kLicenseTestData[i])))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains(expected_substrings))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); } @@ -2523,8 +2486,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { MockFile* file = new MockFile(); EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) .WillOnce(Return(file)); - EXPECT_CALL(*file, Write(IsStrEq(kLicenseUpdateTestData[i].file_data), - Eq(kLicenseUpdateTestData[i].file_data.size()))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenEq(kLicenseUpdateTestData[i].file_data))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); DeviceFiles::CdmLicenseData license_data{ @@ -2955,7 +2918,6 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { std::string file_name = DeviceFiles::GetUsageInfoFileName(app_id); std::string path = device_base_path_ + file_name; - size_t usage_data_fields_length = 0; std::vector usage_data_fields; std::vector usage_data_list; @@ -2969,18 +2931,12 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.license); usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.key_set_id); usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.usage_entry); - usage_data_fields_length += - kUsageInfoTestData[i].usage_data.provider_session_token.size() + - kUsageInfoTestData[i].usage_data.license_request.size() + - kUsageInfoTestData[i].usage_data.license.size() + - kUsageInfoTestData[i].usage_data.key_set_id.size() + - kUsageInfoTestData[i].usage_data.usage_entry.size(); } } EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); - EXPECT_CALL(*file, Write(ContainsAllElementsInVector(usage_data_fields), - Gt(usage_data_fields_length))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains(usage_data_fields))) .WillOnce(ReturnArg<1>()); DeviceFiles device_files(&file_system); @@ -3222,7 +3178,6 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { std::string file_name = DeviceFiles::GetUsageInfoFileName(app_id); std::string path = device_base_path_ + file_name; - size_t usage_data_fields_length = 0; std::vector usage_data_fields; size_t max_index_by_app_id = 0; @@ -3240,12 +3195,6 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { kUsageInfoTestData[i].usage_data.key_set_id); usage_data_fields.push_back( kUsageInfoTestData[i].usage_data.usage_entry); - usage_data_fields_length += - kUsageInfoTestData[i].usage_data.provider_session_token.size() + - kUsageInfoTestData[i].usage_data.license_request.size() + - kUsageInfoTestData[i].usage_data.license.size() + - kUsageInfoTestData[i].usage_data.key_set_id.size() + - kUsageInfoTestData[i].usage_data.usage_entry.size(); } } } @@ -3257,12 +3206,6 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { usage_data_fields.push_back(kUsageInfoUpdateTestData.license); usage_data_fields.push_back(kUsageInfoUpdateTestData.key_set_id); usage_data_fields.push_back(kUsageInfoUpdateTestData.usage_entry); - usage_data_fields_length += - kUsageInfoTestData[index].usage_data.provider_session_token.size() + - kUsageInfoUpdateTestData.license_request.size() + - kUsageInfoUpdateTestData.license.size() + - kUsageInfoUpdateTestData.key_set_id.size() + - kUsageInfoUpdateTestData.usage_entry.size(); } std::string file_data = @@ -3289,14 +3232,14 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { .Times(2) .WillOnce(Return(file)) .WillOnce(Return(next_file)); - ON_CALL(*file, Write(ContainsAllElementsInVector(usage_data_fields), - Gt(usage_data_fields_length))) + ON_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains(usage_data_fields))) .WillByDefault(DoAll(InvokeWithoutArgs([&write_called]() -> void { write_called = true; }), ReturnArg<1>())); - ON_CALL(*next_file, Write(ContainsAllElementsInVector(usage_data_fields), - Gt(usage_data_fields_length))) + ON_CALL(*next_file, Write(_, _)) + .With(AllArgs(StrAndLenContains(usage_data_fields))) .WillByDefault(DoAll(InvokeWithoutArgs([&write_called]() -> void { write_called = true; }), @@ -3358,8 +3301,9 @@ TEST_P(DeviceFilesHlsAttributesTest, Store) { EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); - EXPECT_CALL(*file, Write(Contains(param->media_segment_iv, 0), - Gt(param->media_segment_iv.size()))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs( + StrAndLenContains(std::vector{param->media_segment_iv}))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); @@ -3394,7 +3338,6 @@ TEST_P(DeviceFilesUsageTableTest, Store) { MockFile* file = new MockFile(); int index = GetParam(); - size_t entry_data_length = 0; std::vector entry_data; std::vector usage_entry_info; usage_entry_info.resize(index + 1); @@ -3402,18 +3345,15 @@ TEST_P(DeviceFilesUsageTableTest, Store) { usage_entry_info[i] = kUsageEntriesTestData[i]; entry_data.push_back(kUsageEntriesTestData[i].key_set_id); entry_data.push_back(kUsageEntriesTestData[i].usage_info_file_name); - entry_data_length += kUsageEntriesTestData[i].key_set_id.size() + - kUsageEntriesTestData[i].usage_info_file_name.size(); } entry_data.push_back(kUsageTableInfoTestData[index].usage_table_header); - entry_data_length += kUsageTableInfoTestData[index].usage_table_header.size(); std::string path = device_base_path_ + DeviceFiles::GetUsageTableFileName(); EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); - EXPECT_CALL(*file, Write(ContainsAllElementsInVector(entry_data), - Gt(entry_data_length))) + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains(entry_data))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index 38610791..6769ebb9 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -8,7 +8,8 @@ #include #include #include -#include + +#include #ifdef _WIN32 # include "winsock2.h" @@ -33,6 +34,11 @@ namespace wvcdm { namespace { +// Number of attempts to identify an Internet host and a service should the +// host's nameserver be temporarily unavailable. See getaddrinfo(3) for +// more info. +constexpr size_t kMaxNameserverAttempts = 2; + // Helper function to tokenize a string. This makes it easier to avoid silly // parsing bugs that creep in easily when each part of the string is parsed // with its own piece of code. @@ -71,6 +77,17 @@ bool IsRetryableSslError(int ssl_error) { ssl_error != SSL_ERROR_SSL; } +// Ensures that the SSL library is only initialized once. +void InitSslLibrary() { + static bool ssl_initialized = false; + static std::mutex ssl_init_mutex; + std::lock_guard guard(ssl_init_mutex); + if (!ssl_initialized) { + SSL_library_init(); + ssl_initialized = true; + } +} + #if 0 // unused, may be useful for debugging SSL-related issues. void ShowServerCertificate(const SSL* ssl) { @@ -211,7 +228,7 @@ HttpSocket::HttpSocket(const std::string& url) : socket_fd_(-1), ssl_(nullptr), ssl_ctx_(nullptr) { valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_, &resource_path_); - SSL_library_init(); + InitSslLibrary(); } HttpSocket::~HttpSocket() { CloseSocket(); } @@ -237,6 +254,12 @@ void HttpSocket::CloseSocket() { bool HttpSocket::Connect(int timeout_in_ms) { if (!valid_url_) { + LOGE("URL is invalid"); + return false; + } + + if (socket_fd_ != -1) { + LOGE("Socket already connected"); return false; } @@ -259,24 +282,42 @@ bool HttpSocket::Connect(int timeout_in_ms) { hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG; - struct addrinfo* addr_info = nullptr; - int ret = - getaddrinfo(domain_name_.c_str(), port_.c_str(), &hints, &addr_info); + + int ret = EAI_AGAIN; + for (size_t attempt = 1; + attempt <= kMaxNameserverAttempts && ret == EAI_AGAIN; ++attempt) { + if (attempt > 1) { + LOGW( + "Nameserver is temporarily unavailable, waiting to try again: " + "attempt = %zu", + attempt); + sleep(1); + } + ret = getaddrinfo(domain_name_.c_str(), port_.c_str(), &hints, &addr_info); + } + if (ret != 0) { - LOGE("getaddrinfo failed, errno = %d", ret); + if (ret == EAI_SYSTEM) { + // EAI_SYSTEM implies an underlying system issue. Error is + // specified by |errno|. + LOGE("getaddrinfo failed due to system error: errno = %d", GetError()); + } else { + // Error is specified by return value. + LOGE("getaddrinfo failed: ret = %d", ret); + } return false; } - // get a socket + // Open a socket. socket_fd_ = socket(addr_info->ai_family, addr_info->ai_socktype, addr_info->ai_protocol); if (socket_fd_ < 0) { - LOGE("cannot open socket, errno = %d", GetError()); + LOGE("Cannot open socket: errno = %d", GetError()); return false; } - // set the socket in non-blocking mode + // Set the socket in non-blocking mode. #ifdef _WIN32 u_long mode = 1; // Non-blocking mode. if (ioctlsocket(socket_fd_, FIONBIO, &mode) != 0) { @@ -285,7 +326,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { return false; } #else - int original_flags = fcntl(socket_fd_, F_GETFL, 0); + const int original_flags = fcntl(socket_fd_, F_GETFL, 0); if (original_flags == -1) { LOGE("fcntl error, errno = %d", errno); CloseSocket(); @@ -301,9 +342,10 @@ bool HttpSocket::Connect(int timeout_in_ms) { // connect to the server ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen); freeaddrinfo(addr_info); + addr_info = nullptr; if (ret == 0) { - // connected right away. + // Connected right away. } else { if (GetError() != ERROR_ASYNC_COMPLETE) { // failed right away. @@ -336,6 +378,8 @@ bool HttpSocket::Connect(int timeout_in_ms) { return false; } + // |BIO_NOCLOSE| prevents closing the socket from being closed when + // the BIO is freed. BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE); if (!a_bio) { LOGE("BIO_new_socket error"); @@ -354,9 +398,9 @@ bool HttpSocket::Connect(int timeout_in_ms) { CloseSocket(); return false; } - bool for_read = ssl_err == SSL_ERROR_WANT_READ; + const bool for_read = (ssl_err == SSL_ERROR_WANT_READ); if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) { - LOGE("cannot connect to %s", domain_name_.c_str()); + LOGE("Cannot connect securely to %s", domain_name_.c_str()); CloseSocket(); return false; } diff --git a/core/test/license_request.h b/core/test/license_request.h index 93254c1e..6188d8e4 100644 --- a/core/test/license_request.h +++ b/core/test/license_request.h @@ -15,8 +15,8 @@ namespace wvcdm { // Google license servers. class LicenseRequest { public: - LicenseRequest() {}; - ~LicenseRequest() {}; + LicenseRequest() {} + ~LicenseRequest() {} void GetDrmMessage(const std::string& response, std::string& drm_msg); diff --git a/core/test/parallel_operations_test.cpp b/core/test/parallel_operations_test.cpp index 458a17d3..c0632639 100644 --- a/core/test/parallel_operations_test.cpp +++ b/core/test/parallel_operations_test.cpp @@ -6,7 +6,6 @@ // would, but in parallel, attempting to create as many collisions in the CDM // code as possible. -#include #include #include #include @@ -41,6 +40,12 @@ constexpr const auto kMinimumWait = std::chrono::nanoseconds(1); constexpr const int kRepetitions = 10; constexpr const int kThreadCount = 24; +// Number of attempts to request a license key from the license server +// before failing. +constexpr size_t kMaxKeyRequestAttempts = 5; +// Wait time between failed key requests. +constexpr const auto kRequestRetryWait = std::chrono::milliseconds(10); + const std::vector kKeyId = a2b_hex("371ea35e1a985d75d198a7f41020dc23"); const std::vector kIv = a2b_hex("cedc47cccd6cb437af41325953c2e5e0"); const std::vector kEncryptedData = a2b_hex( @@ -110,17 +115,27 @@ class ParallelCdmTest : public WvCdmTestBase, ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); } - void GetKeyResponse(const std::string& url, const CdmKeyRequest& key_request, + void GetKeyResponse(const CdmSessionId& session_id, const std::string& url, + const CdmKeyRequest& key_request, std::string* key_response) { - UrlRequest url_request(url); - ASSERT_TRUE(url_request.is_connected()); - + bool request_ok = false; std::string http_response; - url_request.PostRequest(key_request.message); - ASSERT_TRUE(url_request.GetResponse(&http_response)); - int status_code = url_request.GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code); - + for (size_t attempt = 1; attempt <= kMaxKeyRequestAttempts; ++attempt) { + UrlRequest url_request(url); + ASSERT_TRUE(url_request.is_connected()); + url_request.PostRequest(key_request.message); + if (url_request.GetResponse(&http_response)) { + int status_code = url_request.GetStatusCode(http_response); + ASSERT_EQ(kHttpOk, status_code); + request_ok = true; + break; + } else { + LOGW("License request failed: sid = %s, attempt = %zu", + session_id.c_str(), attempt); + std::this_thread::sleep_for(kRequestRetryWait); + } + } + ASSERT_TRUE(request_ok); LicenseRequest license_request; license_request.GetDrmMessage(http_response, *key_response); } @@ -184,8 +199,10 @@ class ParallelCdmTest : public WvCdmTestBase, template void RunSessionThreadsSimultaneously(Function do_work) { std::atomic threads_waiting(0); - - const int session_count = std::min(kThreadCount, GetMaxNumberOfSessions()); + // The OEMCrypto V16 adapter makes use of one of the sessions, + // reducing the maximum number of sessions available for the test. + const int session_count = + std::min(kThreadCount, GetMaxNumberOfSessions() - 1); LOGI("Running %d Threads", session_count); std::vector sessions(session_count); std::vector> threads; @@ -231,7 +248,10 @@ TEST_P(ParallelCdmTest, ParallelLicenseRequests) { GenerateKeyRequest(session_id, init_data, &key_request)); std::string key_response; - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(url, key_request, &key_response)); + LOGD("Getting license request: sid = %s", session_id.c_str()); + ASSERT_NO_FATAL_FAILURE( + GetKeyResponse(session_id, url, key_request, &key_response)) + << "SID: " << session_id; ASSERT_NO_FATAL_FAILURE(AddKey(session_id, key_response)); }); @@ -248,7 +268,8 @@ TEST_P(ParallelCdmTest, ParallelDecryptSessions) { GenerateKeyRequest(session_id, init_data, &key_request)); std::string key_response; - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(url, key_request, &key_response)); + ASSERT_NO_FATAL_FAILURE( + GetKeyResponse(session_id, url, key_request, &key_response)); ASSERT_NO_FATAL_FAILURE(AddKey(session_id, key_response)); @@ -273,7 +294,8 @@ TEST_P(ParallelCdmTest, ParallelDecryptsInSameSession) { GenerateKeyRequest(session_id, init_data, &key_request)); std::string key_response; - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(url, key_request, &key_response)); + ASSERT_NO_FATAL_FAILURE( + GetKeyResponse(session_id, url, key_request, &key_response)); ASSERT_NO_FATAL_FAILURE(AddKey(session_id, key_response)); diff --git a/core/test/service_certificate_unittest.cpp b/core/test/service_certificate_unittest.cpp index aecbc92a..6958d307 100644 --- a/core/test/service_certificate_unittest.cpp +++ b/core/test/service_certificate_unittest.cpp @@ -79,6 +79,7 @@ class StubCdmClientPropertySet : public CdmClientPropertySet { } uint32_t session_sharing_id() const override { return session_sharing_id_; } + virtual bool use_atsc_mode() const { return false; } void set_session_sharing_id(uint32_t id) override { session_sharing_id_ = id; diff --git a/core/test/test_base.cpp b/core/test/test_base.cpp index 572cef79..b527645b 100644 --- a/core/test/test_base.cpp +++ b/core/test/test_base.cpp @@ -294,8 +294,8 @@ void WvCdmTestBase::Provision() { std::shared_ptr(new EngineMetrics)); FakeProvisioningServer server; CdmResponseType result = cdm_engine.GetProvisioningRequest( - cert_type, cert_authority, server.service_certificate(), &prov_request, - &provisioning_server_url); + cert_type, cert_authority, server.service_certificate(), kLevelDefault, + &prov_request, &provisioning_server_url); ASSERT_EQ(NO_ERROR, result); if (!binary_provisioning_) { std::vector prov_request_v = Base64SafeDecode(prov_request); @@ -304,8 +304,8 @@ void WvCdmTestBase::Provision() { std::string response; ASSERT_TRUE(server.MakeResponse(prov_request, &response)) << "Fake provisioning server could not provision"; - result = - cdm_engine.HandleProvisioningResponse(response, &cert, &wrapped_key); + result = cdm_engine.HandleProvisioningResponse(response, kLevelDefault, + &cert, &wrapped_key); EXPECT_EQ(NO_ERROR, result); } else { // TODO(fredgc): provision for different SPOIDs. @@ -314,7 +314,7 @@ void WvCdmTestBase::Provision() { CdmResponseType result = cdm_engine.GetProvisioningRequest( cert_type, cert_authority, config_.provisioning_service_certificate(), - &prov_request, &provisioning_server_url); + kLevelDefault, &prov_request, &provisioning_server_url); ASSERT_EQ(NO_ERROR, result); if (binary_provisioning_) { @@ -329,84 +329,54 @@ void WvCdmTestBase::Provision() { // for test vs. production server. provisioning_server_url.assign(config_.provisioning_server()); - // TODO(b/139361531): Remove loop once provisioning service is stable. - std::string http_message; - size_t attempt_num = 0; - bool provision_success = false; - do { - if (attempt_num > 0) { - // Sleep between attempts. - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - ++attempt_num; - - // Make request. - UrlRequest url_request(provisioning_server_url); - if (!url_request.is_connected()) { - LOGE("Failed to connect to provisioning server: url = %s", - provisioning_server_url.c_str()); - continue; - } - url_request.PostCertRequestInQueryString(prov_request); - - // Receive and parse response. - if (!url_request.GetResponse(&http_message)) { - LOGE("Failed to get provisioning response"); - continue; - } - - LOGV("http_message: \n%s\n", http_message.c_str()); - - if (binary_provisioning_) { - // extract provisioning response from received message - // Extracts signed response from JSON string, result is serialized - // protobuf. - static const std::string kMessageStart = "\"signedResponse\": \""; - static const std::string kMessageEnd = "\""; - std::string protobuf_response; - if (!ExtractSignedMessage(http_message, kMessageStart, kMessageEnd, - &protobuf_response)) { - LOGE( - "Failed to extract signed serialized response from JSON " - "response"); - continue; - } - - LOGV("Extracted response message: \n%s\n", protobuf_response.c_str()); - - // base64 decode response to yield binary protobuf - std::vector response_vec(Base64SafeDecode(protobuf_response)); - if (response_vec.empty() && !protobuf_response.empty()) { - LOGE("Failed to decode base64 of response: response = %s", - protobuf_response.c_str()); - continue; - } - - std::string binary_protobuf_response(response_vec.begin(), - response_vec.end()); - - if (cdm_engine.HandleProvisioningResponse( - binary_protobuf_response, &cert, &wrapped_key) != NO_ERROR) { - LOGE("Failed to handle provisioning response"); - continue; - } - } else { - if (cdm_engine.HandleProvisioningResponse(http_message, &cert, - &wrapped_key) != NO_ERROR) { - LOGE("Failed to handle binary provisioning response"); - continue; - } - } - provision_success = true; - } while (attempt_num <= kDefaultMaxProvisioningAttempts && - !provision_success); - - if (attempt_num > 1) { - LOGW("Provisioning request failed at least once: attempts = %zu", - attempt_num); + // Make request. + UrlRequest url_request(provisioning_server_url); + if (!url_request.is_connected()) { + LOGE("Failed to connect to provisioning server: url = %s", + provisioning_server_url.c_str()); + } + url_request.PostCertRequestInQueryString(prov_request); + + // Receive and parse response. + std::string http_message; + ASSERT_TRUE(url_request.GetResponse(&http_message)) + << "Failed to get provisioning response"; + LOGV("http_message: \n%s\n", http_message.c_str()); + + if (binary_provisioning_) { + // extract provisioning response from received message + // Extracts signed response from JSON string, result is serialized + // protobuf. + static const std::string kMessageStart = "\"signedResponse\": \""; + static const std::string kMessageEnd = "\""; + std::string protobuf_response; + const bool extract_ok = ExtractSignedMessage( + http_message, kMessageStart, kMessageEnd, &protobuf_response); + ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized " + "response from JSON response"; + LOGV("Extracted response message: \n%s\n", protobuf_response.c_str()); + + ASSERT_FALSE(protobuf_response.empty()) + << "Protobuf response is unexpectedly empty"; + + // base64 decode response to yield binary protobuf + const std::vector response_vec( + Base64SafeDecode(protobuf_response)); + ASSERT_FALSE(response_vec.empty()) + << "Failed to decode base64 of response: response = " + << protobuf_response; + + const std::string binary_protobuf_response(response_vec.begin(), + response_vec.end()); + + ASSERT_EQ(NO_ERROR, cdm_engine.HandleProvisioningResponse( + binary_protobuf_response, kLevelDefault, &cert, + &wrapped_key)); + } else { + ASSERT_EQ(NO_ERROR, + cdm_engine.HandleProvisioningResponse( + http_message, kLevelDefault, &cert, &wrapped_key)); } - ASSERT_TRUE(provision_success) - << "Failed to provision: message = " << http_message; } } @@ -561,6 +531,7 @@ void TestLicenseHolder::GenerateKeyRequest( CdmAppParameterMap app_parameters; CdmKeySetId key_set_id; InitializationData init_data(init_data_type_string, key_id); + if (g_cutoff >= LOG_DEBUG) init_data.DumpToLogs(); CdmKeyRequest key_request; CdmResponseType result = cdm_engine_->GenerateKeyRequest( session_id_, key_set_id, init_data, kLicenseTypeStreaming, app_parameters, diff --git a/core/test/test_base.h b/core/test/test_base.h index e4c75584..c6ba927f 100644 --- a/core/test/test_base.h +++ b/core/test/test_base.h @@ -22,9 +22,6 @@ namespace wvcdm { // to configure OEMCrypto to use a test keybox. class WvCdmTestBase : public ::testing::Test { public: - // Default number of provisioning try attempts. - constexpr static size_t kDefaultMaxProvisioningAttempts = 10; - WvCdmTestBase(); ~WvCdmTestBase() override {} void SetUp() override; diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp index fb6b7fb0..b88b8910 100644 --- a/core/test/test_printers.cpp +++ b/core/test/test_printers.cpp @@ -284,21 +284,6 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case INSUFFICIENT_CRYPTO_RESOURCES: *os << "INSUFFICIENT_CRYPTO_RESOURCES"; break; - case INSUFFICIENT_CRYPTO_RESOURCES_2: - *os << "INSUFFICIENT_CRYPTO_RESOURCES_2"; - break; - case INSUFFICIENT_CRYPTO_RESOURCES_3: - *os << "INSUFFICIENT_CRYPTO_RESOURCES_3"; - break; - case INSUFFICIENT_CRYPTO_RESOURCES_4: - *os << "INSUFFICIENT_CRYPTO_RESOURCES_4"; - break; - case INSUFFICIENT_CRYPTO_RESOURCES_5: - *os << "INSUFFICIENT_CRYPTO_RESOURCES_5"; - break; - case INSUFFICIENT_CRYPTO_RESOURCES_6: - *os << "INSUFFICIENT_CRYPTO_RESOURCES_6"; - break; case INSUFFICIENT_OUTPUT_PROTECTION: *os << "INSUFFICIENT_OUTPUT_PROTECTION"; break; @@ -521,6 +506,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LICENSE_RESPONSE_PARSE_ERROR_5: *os << "LICENSE_RESPONSE_PARSE_ERROR_5"; break; + case LICENSE_USAGE_ENTRY_MISSING: + *os << "LICENSE_USAGE_ENTRY_MISSING"; + break; case LIST_LICENSE_ERROR_1: *os << "LIST_LICENSE_ERROR_1"; break; @@ -560,6 +548,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LOAD_USAGE_ENTRY_GENERATION_SKEW: *os << "LOAD_USAGE_ENTRY_GENERATION_SKEW"; break; + case LOAD_USAGE_ENTRY_INVALID_SESSION: + *os << "LOAD_USAGE_ENTRY_INVALID_SESSION"; + break; case LOAD_USAGE_ENTRY_SIGNATURE_FAILURE: *os << "LOAD_USAGE_ENTRY_SIGNATURE_FAILURE"; break; @@ -578,6 +569,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case LOAD_USAGE_HEADER_UNKNOWN_ERROR: *os << "LOAD_USAGE_HEADER_UNKNOWN_ERROR"; break; + case MOVE_USAGE_ENTRY_DESTINATION_IN_USE: + *os << "MOVE_USAGE_ENTRY_DESTINATION_IN_USE"; + break; case MOVE_USAGE_ENTRY_UNKNOWN_ERROR: *os << "MOVE_USAGE_ENTRY_UNKNOWN_ERROR"; break; @@ -818,8 +812,11 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case SET_DECRYPT_HASH_ERROR: *os << "SET_DECRYPT_HASH_ERROR"; break; - case SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR: - *os << "SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR"; + case SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE: + *os << "SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE"; + break; + case SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR: + *os << "SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR"; break; case SIGNATURE_NOT_FOUND: *os << "SIGNATURE_NOT_FOUND"; @@ -923,6 +920,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case WEBM_INIT_DATA_UNAVAILABLE: *os << "WEBM_INIT_DATA_UNAVAILABLE"; break; + case PROVISIONING_NOT_ALLOWED_FOR_ATSC: + *os << "PROVISIONING_NOT_ALLOWED_FOR_ATSC"; + break; default: *os << "Unknown CdmResponseType"; break; diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp index 456eba49..2f89e07a 100644 --- a/core/test/url_request.cpp +++ b/core/test/url_request.cpp @@ -87,8 +87,8 @@ void UrlRequest::Reconnect() { if (socket_.Connect(kConnectTimeoutMs)) { is_connected_ = true; } else { - LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(), - socket_.port()); + LOGE("Failed to connect: url = %s, port = %d, attempt = %u", + socket_.domain_name().c_str(), socket_.port(), i); } } } @@ -148,7 +148,7 @@ bool UrlRequest::PostRequestWithPath(const std::string& path, // buffer to store length of data as a string char data_size_buffer[32] = {0}; - snprintf(data_size_buffer, sizeof(data_size_buffer), "%zd", data.size()); + snprintf(data_size_buffer, sizeof(data_size_buffer), "%zu", data.size()); request.append("Content-Length: "); request.append(data_size_buffer); // appends size of data @@ -158,7 +158,8 @@ bool UrlRequest::PostRequestWithPath(const std::string& path, request.append(data); - int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs); + const int ret = + socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs); LOGV("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str()); return ret != -1; } diff --git a/core/test/usage_table_header_unittest.cpp b/core/test/usage_table_header_unittest.cpp index 8a5c6f32..a0ac01ee 100644 --- a/core/test/usage_table_header_unittest.cpp +++ b/core/test/usage_table_header_unittest.cpp @@ -21,12 +21,36 @@ #include "wv_cdm_constants.h" #include "wv_cdm_types.h" +// gmock methods +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtMost; +using ::testing::ContainerEq; +using ::testing::Contains; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Ge; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::Lt; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::SizeIs; +using ::testing::StrEq; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; + namespace wvcdm { namespace { const std::string kEmptyString; +constexpr size_t kDefaultTableCapacity = 300; + constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days const CdmUsageTableHeader kEmptyUsageTableHeader; @@ -172,7 +196,7 @@ const std::vector kEmptyUsageInfoUsageDataList; const std::vector kEmptyUsageEntryInfoVector; std::vector kUsageEntryInfoVector; std::vector k10UsageEntryInfoVector; -std::vector k201UsageEntryInfoVector; +std::vector kOverFullUsageEntryInfoVector; const DeviceFiles::LicenseState kActiveLicenseState = DeviceFiles::kLicenseStateActive; @@ -189,7 +213,8 @@ int64_t kPlaybackDuration = 300; int64_t kGracePeriodEndTime = 60; // ==== LRU Upgrade Data ==== -const CdmUsageTableHeader kUpgradableUsageTableHeader = {0}; +const CdmUsageTableHeader kUpgradableUsageTableHeader = + "Upgradable Table Header"; // Usage entries. const CdmUsageEntryInfo kUpgradableUsageEntryInfo1 = { @@ -251,7 +276,7 @@ const int64_t kUpgradedUsageEntryInfo2ExpireTime = 0; // Unset const int64_t kUpgradedUsageEntryInfo3LastUsedTime = kLruBaseTime; const int64_t kUpgradedUsageEntryInfo3ExpireTime = kLruBaseTime + 604800 + 86400; -const CdmUsageTableHeader kUpgradedUsageTableHeader = {0}; +const CdmUsageTableHeader kUpgradedUsageTableHeader = "Upgraded Table Header"; std::vector kUpgradedUsageEntryInfoList; namespace { @@ -274,23 +299,24 @@ void InitVectorConstants() { k10UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense5); k10UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop5); - k201UsageEntryInfoVector.clear(); - for (size_t i = 0; i < 201; ++i) { + kOverFullUsageEntryInfoVector.clear(); + for (size_t i = 0; i < (kDefaultTableCapacity + 1); ++i) { switch (i % 4) { case 0: - k201UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense1); + kOverFullUsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense1); break; case 1: - k201UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop1); + kOverFullUsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop1); break; case 2: - k201UsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense2); + kOverFullUsageEntryInfoVector.push_back(kUsageEntryInfoOfflineLicense2); break; case 3: - k201UsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop2); + kOverFullUsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop2); break; default: - k201UsageEntryInfoVector.push_back(kUsageEntryInfoStorageTypeUnknown); + kOverFullUsageEntryInfoVector.push_back( + kUsageEntryInfoStorageTypeUnknown); break; } } @@ -349,28 +375,6 @@ void ToVector(std::vector& vec, } } -// Used to quickly populate a vector of CdmUsageEntryInfo structs with LRU -// information. This is intended to allow tests which are not concerned with -// the LRU replacement policy of the UsageTableHeader, but are affected by its -// presents. -void GenericLruUpgrade(std::vector* usage_entry_info_list, - int64_t last_use_time = kLruBaseTime, - int64_t offline_license_expiry_time = - kLruBaseTime + kDefaultExpireDuration) { - if (usage_entry_info_list == nullptr) { - return; - } - for (auto& usage_entry_info : *usage_entry_info_list) { - usage_entry_info.last_use_time = last_use_time; - if (usage_entry_info.storage_type == kStorageLicense) { - usage_entry_info.offline_license_expiry_time = - offline_license_expiry_time; - } else { - usage_entry_info.offline_license_expiry_time = 0; - } - } -} - }; // namespace class MockDeviceFiles : public DeviceFiles { @@ -400,6 +404,7 @@ class MockDeviceFiles : public DeviceFiles { bool(const std::string&, const std::string&, std::string*, CdmKeyMessage*, CdmKeyResponse*, CdmUsageEntry*, uint32_t*)); + MOCK_METHOD2(StoreLicense, bool(const CdmLicenseData&, ResponseType*)); MOCK_METHOD1(DeleteLicense, bool(const std::string&)); MOCK_METHOD0(DeleteAllLicenses, bool()); MOCK_METHOD0(DeleteAllUsageInfo, bool()); @@ -424,16 +429,19 @@ class MockCryptoSession : public TestCryptoSession { MockCryptoSession(metrics::CryptoMetrics* metrics) : TestCryptoSession(metrics) {} MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); - MOCK_METHOD1(LoadUsageTableHeader, - CdmResponseType(const CdmUsageTableHeader&)); - MOCK_METHOD1(CreateUsageTableHeader, CdmResponseType(CdmUsageTableHeader*)); + // Usage Table Header. + MOCK_METHOD2(CreateUsageTableHeader, + CdmResponseType(SecurityLevel, CdmUsageTableHeader*)); + MOCK_METHOD2(LoadUsageTableHeader, + CdmResponseType(SecurityLevel, const CdmUsageTableHeader&)); + MOCK_METHOD3(ShrinkUsageTableHeader, + CdmResponseType(SecurityLevel, uint32_t, CdmUsageTableHeader*)); + // Usage Entry. MOCK_METHOD1(CreateUsageEntry, CdmResponseType(uint32_t*)); MOCK_METHOD2(LoadUsageEntry, CdmResponseType(uint32_t, const CdmUsageEntry&)); MOCK_METHOD2(UpdateUsageEntry, CdmResponseType(CdmUsageTableHeader*, CdmUsageEntry*)); MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t)); - MOCK_METHOD2(ShrinkUsageTableHeader, - CdmResponseType(uint32_t, CdmUsageTableHeader*)); // Fake method for testing. Having an EXPECT_CALL causes complexities // for getting table capacity during initialization. @@ -453,8 +461,8 @@ class MockCryptoSession : public TestCryptoSession { } private: - size_t maximum_usage_table_entries_ = 0; - bool maximum_usage_table_entries_set_ = false; + size_t maximum_usage_table_entries_ = kDefaultTableCapacity; + bool maximum_usage_table_entries_set_ = true; }; // Partial mock of the UsageTableHeader. This is to test when dependency @@ -462,32 +470,27 @@ class MockCryptoSession : public TestCryptoSession { class MockUsageTableHeader : public UsageTableHeader { public: MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD3(DeleteEntry, CdmResponseType(uint32_t, DeviceFiles*, - metrics::CryptoMetrics*)); + MOCK_METHOD4(InvalidateEntry, CdmResponseType(uint32_t, bool, DeviceFiles*, + metrics::CryptoMetrics*)); + MOCK_METHOD6(AddEntry, + CdmResponseType(CryptoSession*, bool, const CdmKeySetId&, + const std::string&, const CdmKeyResponse&, + uint32_t*)); + + CdmResponseType SuperAddEntry(CryptoSession* crypto_session, + bool persistent_license, + const CdmKeySetId& key_set_id, + const std::string& usage_info_filename, + const CdmKeyResponse& license_message, + uint32_t* usage_entry_number) { + return UsageTableHeader::AddEntry(crypto_session, persistent_license, + key_set_id, usage_info_filename, + license_message, usage_entry_number); + } }; } // namespace -// gmock methods -using ::testing::_; -using ::testing::AllOf; -using ::testing::AtMost; -using ::testing::ContainerEq; -using ::testing::Contains; -using ::testing::DoAll; -using ::testing::ElementsAreArray; -using ::testing::Ge; -using ::testing::Invoke; -using ::testing::Lt; -using ::testing::NotNull; -using ::testing::Return; -using ::testing::SaveArg; -using ::testing::SetArgPointee; -using ::testing::SizeIs; -using ::testing::StrEq; -using ::testing::UnorderedElementsAre; -using ::testing::UnorderedElementsAreArray; - class UsageTableHeaderTest : public WvCdmTestBase { public: static void SetUpTestCase() { @@ -495,9 +498,9 @@ class UsageTableHeaderTest : public WvCdmTestBase { } // Useful when UsageTableHeader is mocked - void DeleteEntry(uint32_t usage_entry_number, DeviceFiles*, - metrics::CryptoMetrics*) { - usage_table_header_->DeleteEntryForTest(usage_entry_number); + void InvalidateEntry(uint32_t usage_entry_number, bool, DeviceFiles*, + metrics::CryptoMetrics*) { + usage_table_header_->InvalidateEntryForTest(usage_entry_number); } protected: @@ -538,6 +541,7 @@ class UsageTableHeaderTest : public WvCdmTestBase { // Create new mock objects if using MockUsageTableHeader device_files_ = new MockDeviceFiles(); crypto_session_ = new MockCryptoSession(&crypto_metrics_); + MockUsageTableHeader* mock_usage_table_header = new MockUsageTableHeader(); // mock_usage_table_header_ object takes ownership of these objects @@ -561,7 +565,7 @@ class UsageTableHeaderTest : public WvCdmTestBase { .WillOnce(DoAll(SetArgPointee<0>(usage_table_header), SetArgPointee<1>(usage_entry_info_vector), SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(usage_table_header)) + EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(_, usage_table_header)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(security_level, crypto_session_)); } @@ -594,14 +598,17 @@ class UsageTableHeaderInitializationTest }; TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(security_level, NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); @@ -610,14 +617,17 @@ TEST_P(UsageTableHeaderInitializationTest, CreateUsageTableHeader) { } TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(security_level, NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); // TODO: Why not being called? //EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, @@ -628,14 +638,17 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveLicenses) { } TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), SetArgPointee<1>(kEmptyUsageEntryInfoVector), SetArgPointee<2>(false), Return(false))); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(security_level, NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); @@ -644,48 +657,100 @@ TEST_P(UsageTableHeaderInitializationTest, Upgrade_UnableToRetrieveUsageInfo) { } TEST_P(UsageTableHeaderInitializationTest, UsageTableHeaderExists) { + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), SetArgPointee<1>(kUsageEntryInfoVector), SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } -TEST_P(UsageTableHeaderInitializationTest, 200UsageEntries) { - std::vector usage_entries_200 = k201UsageEntryInfoVector; - usage_entries_200.resize(200); +TEST_P(UsageTableHeaderInitializationTest, UsageEntriesAtCapacity) { + std::vector usage_entries = kOverFullUsageEntryInfoVector; + usage_entries.resize(kDefaultTableCapacity); + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(usage_entries_200), - SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + SetArgPointee<1>(usage_entries), SetArgPointee<2>(false), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } +TEST_P(UsageTableHeaderInitializationTest, UsageEntries_NoCapacity) { + crypto_session_->SetMaximumUsageTableEntries(0); // Unlimited. + std::vector usage_entries = kOverFullUsageEntryInfoVector; + usage_entries.resize(kDefaultTableCapacity); + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; + EXPECT_CALL(*device_files_, + RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), + SetArgPointee<1>(usage_entries), SetArgPointee<2>(false), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) + .WillOnce(Return(NO_ERROR)); + + // Expect an attempt to create a new entry. + EXPECT_CALL(*crypto_session_, Open(security_level)) + .WillOnce(Return(NO_ERROR)); + const uint32_t expect_usage_entry_number = kDefaultTableCapacity; + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + SizeIs(kDefaultTableCapacity + 1))) + .WillOnce(Return(true)); + + // Delete the entry after. + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(security_level, kDefaultTableCapacity, NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + SizeIs(kDefaultTableCapacity))) + .WillOnce(Return(true)); + + EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); +} + TEST_P(UsageTableHeaderInitializationTest, - 201UsageEntries_AddEntryFails_UsageTableHeaderRecreated) { + UsageEntriesOverCapacity_AddEntryFails_UsageTableHeaderRecreated) { EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(k201UsageEntryInfoVector), + SetArgPointee<1>(kOverFullUsageEntryInfoVector), SetArgPointee<2>(false), Return(true))); const SecurityLevel security_level = (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*crypto_session_, Open(security_level)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(security_level, NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteUsageTableInfo()).WillOnce(Return(true)); @@ -694,7 +759,8 @@ TEST_P(UsageTableHeaderInitializationTest, .WillOnce(Return(true)); // Expectations for AddEntry - const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); + const uint32_t expect_usage_entry_number = + kOverFullUsageEntryInfoVector.size(); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR))); @@ -703,90 +769,83 @@ TEST_P(UsageTableHeaderInitializationTest, } TEST_P(UsageTableHeaderInitializationTest, - 201UsageEntries_DeleteEntryFails_UsageTableHeaderRecreated) { - std::vector usage_entries_202 = k201UsageEntryInfoVector; - usage_entries_202.push_back(kDummyUsageEntryInfo); - + UsageEntries_NoCapacity_AddEntryFails_UsageTableHeaderRecreated) { + crypto_session_->SetMaximumUsageTableEntries(0); // Unlimited. + std::vector usage_entries = kOverFullUsageEntryInfoVector; + usage_entries.resize(kDefaultTableCapacity); + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(k201UsageEntryInfoVector), - SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + SetArgPointee<1>(usage_entries), SetArgPointee<2>(false), + Return(true))); + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) - .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + // Try to create a new entry, and fail. + EXPECT_CALL(*crypto_session_, Open(security_level)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) + .WillOnce(Return(CREATE_USAGE_ENTRY_UNKNOWN_ERROR)); + // Expect clean up. EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteUsageTableInfo()).WillOnce(Return(true)); + // Expect recreation of usage table. + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(security_level, NotNull())) + .WillOnce( + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kEmptyUsageTableHeader, kEmptyUsageEntryInfoVector)) .WillOnce(Return(true)); - // Expectations for AddEntry - const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); - EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), - Return(NO_ERROR))); - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, - usage_entries_202)) - .WillOnce(Return(true)); - - // Expectations for DeleteEntry - const SecurityLevel security_level = - (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - EXPECT_CALL(*crypto_session_, - Open(security_level)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) - .WillOnce(Return(SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR)); - EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); } TEST_P(UsageTableHeaderInitializationTest, - 201UsageEntries_AddDeleteEntrySucceeds) { - std::vector usage_entries_202 = k201UsageEntryInfoVector; - usage_entries_202.push_back(kDummyUsageEntryInfo); + UsageEntriesOverCapacity_AddInvalidateEntrySucceeds) { + // Capacity +2. + std::vector usage_entries = kOverFullUsageEntryInfoVector; + usage_entries.push_back(kDummyUsageEntryInfo); + + const SecurityLevel security_level = + (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(k201UsageEntryInfoVector), + SetArgPointee<1>(kOverFullUsageEntryInfoVector), SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(NO_ERROR)); // Expectations for AddEntry - const uint32_t expect_usage_entry_number = k201UsageEntryInfoVector.size(); + const uint32_t expect_usage_entry_number = + kOverFullUsageEntryInfoVector.size(); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, - usage_entries_202)) + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, usage_entries)) .WillOnce(Return(true)); - // Expectations for DeleteEntry - const SecurityLevel security_level = - (GetParam() == kSecurityLevelL3) ? kLevel3 : kLevelDefault; - + // Expectations for InvalidateEntry, assumes no entry other entry is invalid. + EXPECT_CALL(*crypto_session_, Open(security_level)) + .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*crypto_session_, - Open(security_level)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entries_202.size() - 1, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + ShrinkUsageTableHeader(security_level, usage_entries.size() - 1, + NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kYetAnotherUsageTableHeader), + Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - SizeIs(k201UsageEntryInfoVector.size()))) + StoreUsageTableInfo(kYetAnotherUsageTableHeader, + SizeIs(kOverFullUsageEntryInfoVector.size()))) .WillOnce(Return(true)); EXPECT_TRUE(usage_table_header_->Init(GetParam(), crypto_session_)); @@ -845,10 +904,12 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveOfflineUsageEntry) { EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo( - kUsageTableHeader, + kAnotherUsageTableHeader, UnorderedElementsAreArray(expect_usage_entry_info_vector))) .WillOnce(Return(true)); @@ -876,10 +937,12 @@ TEST_F(UsageTableHeaderTest, AddEntry_NextConsecutiveSecureStopUsageEntry) { EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, StoreUsageTableInfo( - kUsageTableHeader, + kAnotherUsageTableHeader, UnorderedElementsAreArray(expect_usage_entry_info_vector))) .WillOnce(Return(true)); @@ -904,11 +967,13 @@ TEST_F(UsageTableHeaderTest, AddEntry_SkipUsageEntries) { EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(expect_usage_entry_number), Return(NO_ERROR))); - + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL( *device_files_, StoreUsageTableInfo( - kUsageTableHeader, + kAnotherUsageTableHeader, UnorderedElementsAre( kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, @@ -938,28 +1003,31 @@ TEST_F(UsageTableHeaderTest, uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen std::vector final_usage_entries; - uint32_t expected_usage_entry_number = k10UsageEntryInfoVector.size() - 1; + const uint32_t expected_usage_entry_number = + k10UsageEntryInfoVector.size() - 1; // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) + InvalidateEntry(_, true, device_files_, NotNull())) .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) + .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES)) + .WillOnce(DoAll(SetArgPointee<0>(expected_usage_entry_number), + Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(expected_usage_entry_number), - Return(NO_ERROR))); + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, _)) + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) .WillOnce(DoAll(SaveArg<1>(&final_usage_entries), Return(true))); // Now invoke the method under test uint32_t usage_entry_number; EXPECT_EQ(NO_ERROR, - mock_usage_table_header->AddEntry( + mock_usage_table_header->SuperAddEntry( crypto_session_, kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, kUsageEntryInfoOfflineLicense6.key_set_id, @@ -983,123 +1051,50 @@ TEST_F(UsageTableHeaderTest, EXPECT_EQ(expected_usage_entries, final_usage_entries); } -TEST_F(UsageTableHeaderTest, - AddEntry_CreateUsageEntryFailsTwice_SucceedsThirdTime) { - // Initialize and setup - MockUsageTableHeader* mock_usage_table_header = SetUpMock(); - std::vector usage_entry_info_vector_at_start = - k10UsageEntryInfoVector; - GenericLruUpgrade(&usage_entry_info_vector_at_start); - Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector_at_start); - - uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen - uint32_t usage_entry_number_second_to_be_deleted; // randomly chosen - std::vector final_usage_entries; - - uint32_t expected_usage_entry_number = k10UsageEntryInfoVector.size() - 2; - - // Setup expectations - EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_second_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); - - EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce( - DoAll(SetArgPointee<0>(expected_usage_entry_number), - Return(NO_ERROR))); - - EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, _)) - .WillOnce(DoAll(SaveArg<1>(&final_usage_entries), Return(true))); - - // Now invoke the method under test - uint32_t usage_entry_number; - EXPECT_EQ(NO_ERROR, - mock_usage_table_header->AddEntry( - crypto_session_, - kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, - kUsageEntryInfoOfflineLicense6.key_set_id, - kUsageEntryInfoOfflineLicense6.usage_info_file_name, - kEmptyString /* license */, &usage_entry_number)); - - // Verify added/deleted usage entry number and entries - EXPECT_EQ(expected_usage_entry_number, usage_entry_number); - - EXPECT_LE(0u, usage_entry_number_first_to_be_deleted); - EXPECT_LE(usage_entry_number_first_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); - EXPECT_LE(0u, usage_entry_number_second_to_be_deleted); - EXPECT_LE(usage_entry_number_second_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); - - std::vector expected_usage_entries = - usage_entry_info_vector_at_start; - expected_usage_entries[usage_entry_number_first_to_be_deleted] = - expected_usage_entries[expected_usage_entries.size() - 1]; - expected_usage_entries.resize(expected_usage_entries.size() - 1); - expected_usage_entries[usage_entry_number_second_to_be_deleted] = - expected_usage_entries[expected_usage_entries.size() - 1]; - expected_usage_entries.resize(expected_usage_entries.size() - 1); - expected_usage_entries.push_back(kUsageEntryInfoOfflineLicense6); - - EXPECT_EQ(expected_usage_entries, final_usage_entries); -} - -TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsThrice) { +// The usage table should only delete/invalidate a single entry. +// After which, it should fail. +TEST_F(UsageTableHeaderTest, AddEntry_CreateUsageEntryFailsEveryTime) { // Initialize and setup MockUsageTableHeader* mock_usage_table_header = SetUpMock(); Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - std::vector usage_entry_info_vector_at_start = - k10UsageEntryInfoVector; - - uint32_t usage_entry_number_first_to_be_deleted; // randomly chosen - uint32_t usage_entry_number_second_to_be_deleted; // randomly chosen - uint32_t usage_entry_number_third_to_be_deleted; // randomly chosen - std::vector final_usage_entries; // Setup expectations EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_first_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_second_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))) - .WillOnce(DoAll(SaveArg<0>(&usage_entry_number_third_to_be_deleted), - Invoke(this, &UsageTableHeaderTest::DeleteEntry), + InvalidateEntry(_, true, device_files_, NotNull())) + .WillOnce(DoAll(Invoke(this, &UsageTableHeaderTest::InvalidateEntry), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, CreateUsageEntry(NotNull())) - .Times(4) - .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)); + .Times(2) + .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES)); // Now invoke the method under test uint32_t usage_entry_number; - EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES_3, - mock_usage_table_header->AddEntry( - crypto_session_, - kUsageEntryInfoOfflineLicense6.storage_type == kStorageLicense, + EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES, + mock_usage_table_header->SuperAddEntry( + crypto_session_, true /* persistent */, kUsageEntryInfoOfflineLicense6.key_set_id, kUsageEntryInfoOfflineLicense6.usage_info_file_name, kEmptyString /* license */, &usage_entry_number)); - // Verify deleted usage entry number and entries - EXPECT_LE(0u, usage_entry_number_first_to_be_deleted); - EXPECT_LE(usage_entry_number_first_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); - EXPECT_LE(0u, usage_entry_number_second_to_be_deleted); - EXPECT_LE(usage_entry_number_second_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); - EXPECT_LE(0u, usage_entry_number_third_to_be_deleted); - EXPECT_LE(usage_entry_number_third_to_be_deleted, - usage_entry_info_vector_at_start.size() - 1); + // Verify the number of entries deleted. + constexpr uint32_t kExpectedEntriesDeleted = 1; + + const std::vector& final_usage_entries = + mock_usage_table_header->usage_entry_info(); + uint32_t invalid_entries = 0; + for (const CdmUsageEntryInfo& usage_entry_info : final_usage_entries) { + if (usage_entry_info.storage_type == kStorageTypeUnknown) { + ++invalid_entries; + } + } + // Number of entries deleted is equal to the number of entries + // marked as invalid plus the number of fewer entries in the table + // at the end of the call. + const uint32_t entries_deleted = + invalid_entries + + (k10UsageEntryInfoVector.size() - final_usage_entries.size()); + EXPECT_EQ(kExpectedEntriesDeleted, entries_deleted); } TEST_F(UsageTableHeaderTest, LoadEntry_InvalidEntryNumber) { @@ -1163,348 +1158,272 @@ TEST_F(UsageTableHeaderTest, UpdateEntry) { usage_table_header_->UpdateEntry(0, crypto_session_, &usage_entry)); } -TEST_F(UsageTableHeaderTest, - LoadEntry_LoadUsageEntryFailsOnce_SucceedsSecondTime) { - // Initialize and setup - MockUsageTableHeader* mock_usage_table_header = SetUpMock(); - Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - - // We try to load a usage entry from the first 9 entries, since DeleteEntry - // can't delete an entry if the last one is in use. - - uint32_t usage_entry_number_to_load = - CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 2); - CdmUsageEntry usage_entry_to_load = kUsageEntry; - - // Setup expectations - EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) - .Times(1) - .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); - - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(Return(NO_ERROR)); - - // Now invoke the method under test - EXPECT_EQ(NO_ERROR, - mock_usage_table_header->LoadEntry( - crypto_session_, - usage_entry_to_load, - usage_entry_number_to_load)); -} - -TEST_F(UsageTableHeaderTest, - LoadEntry_LoadUsageEntryFailsTwice_SucceedsThirdTime) { - // Initialize and setup - MockUsageTableHeader* mock_usage_table_header = SetUpMock(); - Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - - // We try to load a usage entry from the first 8 entries, since DeleteEntry - // can't delete an entry if the last one is in use. - uint32_t usage_entry_number_to_load = - CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 3); - CdmUsageEntry usage_entry_to_load = kUsageEntry; - - // Setup expectations - EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) - .Times(2) - .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); - - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)) - .WillOnce(Return(NO_ERROR)); - - // Now invoke the method under test - EXPECT_EQ(NO_ERROR, - mock_usage_table_header->LoadEntry( - crypto_session_, - usage_entry_to_load, - usage_entry_number_to_load)); -} - -TEST_F(UsageTableHeaderTest, LoadEntry_LoadUsageEntryFailsThrice) { - // Initialize and setup - MockUsageTableHeader* mock_usage_table_header = SetUpMock(); - Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); - - // We try to load a usage entry from the first 7 entries, since DeleteEntry - // can't delete an entry if the last one is in use. - uint32_t usage_entry_number_to_load = - CdmRandom::RandomInRange(k10UsageEntryInfoVector.size() - 4); - CdmUsageEntry usage_entry_to_load = kUsageEntry; - - // Setup expectations - EXPECT_CALL(*mock_usage_table_header, - DeleteEntry(_, device_files_, NotNull())) - .Times(3) - .WillRepeatedly( - DoAll(Invoke(this, &UsageTableHeaderTest::DeleteEntry), - Return(NO_ERROR))); - - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(usage_entry_number_to_load, usage_entry_to_load)) - .Times(4) - .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES_3)); - - // Now invoke the method under test - EXPECT_EQ(INSUFFICIENT_CRYPTO_RESOURCES_3, - mock_usage_table_header->LoadEntry( - crypto_session_, - usage_entry_to_load, - usage_entry_number_to_load)); -} - -TEST_F(UsageTableHeaderTest, DeleteEntry_InvalidUsageEntryNumber) { +TEST_F(UsageTableHeaderTest, InvalidateEntry_InvalidUsageEntryNumber) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); uint32_t usage_entry_number = kUsageEntryInfoVector.size(); metrics::CryptoMetrics metrics; - EXPECT_NE(NO_ERROR, usage_table_header_->DeleteEntry( - usage_entry_number, device_files_, &metrics)); + EXPECT_NE(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number, true, device_files_, &metrics)); } // Initial Test state: -// 1. Entry to be delete is the last entry and is an Offline license. +// 1. Entry to be deleted is the last entry and is an Offline license. // When attempting to delete the entry a crypto session error // will occur. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will not be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. The usage table will be requested to shrink both the last entry +// and the unknown entry before it. +// c. OEMCrypto error will cause the internal entries to remain the +// same. +// d. InvalidateEntry() will return NO_ERROR as the storage type is +// changed. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 -// Offline License 2 3 3 +// Storage Type Unknown 2 2 +// Offline License 2 3 3 (Storage Type Unknown) // // # of usage entries 4 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_CryptoSessionError) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F(UsageTableHeaderTest, InvalidateEntry_CryptoSessionError) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoOfflineLicense2 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_info_vector.size() - 1, NotNull())) - .WillOnce(Return(SHRINK_USAGE_TABLER_HEADER_UNKNOWN_ERROR)); + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) + .WillOnce(Return(SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR)); - EXPECT_NE(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL( + *device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check that the list is unchanged. + constexpr size_t expected_size = 4; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Entry to be delete is the last entry and is an Offline license. +// 1. Entry to be deleted is the last entry and is an offline license. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. Usage table will be resized to remove the last two entries. +// c. Updated table will be saved. +// d. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 +// Storage Type Unknown 2 Deleted // Offline License 2 3 Deleted // -// # of usage entries 4 3 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastOfflineEntry) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntry_OfflineEntry) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoOfflineLicense2 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_info_vector.size() - 1, NotNull())) + // Expectations for call to shrink. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Entry to be delete is the last entry and is a secure stop. +// 1. Entry to be deleted is the last entry and is a secure stop. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. Usage table will be resized to remove the last two entries. +// c. Updated table will be saved. +// d. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Secure Stop 1 1 1 -// Storage Type unknown 2 2 +// Storage Type Unknown 2 Deleted // Secure Stop 2 3 Deleted // -// # of usage entries 4 3 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntry) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntry_SecureStopEntry) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop2 + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoSecureStop2 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_info_vector.size() - 1, NotNull())) + // Expectation when shrinking table. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are offline licenses, but have license files // missing from persistent storage. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Offline entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail and +// result in them being set as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 Deleted -// Offline License 3 4 Deleted +// Offline License 2 3 Deleted (because missing) +// Offline License 3 4 Deleted (because missing) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastOfflineEntriesHaveMissingLicenses) { - std::vector usage_entry_info_vector = { + InvalidateEntry_LastOfflineEntriesHaveMissingLicenses) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) - .WillOnce(Return(true)); + // Offline license 2 and 3 cannot be retrieved. EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, - NotNull(), NotNull())); + NotNull(), NotNull())) + .WillOnce(Return(false)); EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - NotNull(), NotNull())); + NotNull(), NotNull())) + .WillOnce(Return(false)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are secure stops, but have entries // missing from usage info file in persistent storage. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Secure stops in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail and +// result in them being set as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 Deleted -// Secure stop 3 4 Deleted +// Secure stop 2 3 Deleted (because missing) +// Secure stop 3 4 Deleted (because missing) // -// # of usage entries 5 2 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntriesAreMissing) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 1 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastSecureStopEntriesAreMissing) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Streaming license 2 and 3 cannot be retrieved. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop2.usage_info_file_name, @@ -1518,51 +1437,66 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastSecureStopEntriesAreMissing) { NotNull(), NotNull(), NotNull())) .WillOnce(Return(false)); + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are offline licenses, but have incorrect usage // entry number stored in persistent file store. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Offline entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail due +// to a mismatch in usage entry number and result in them being set +// as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 Deleted -// Offline License 3 4 Deleted +// Offline License 2 3 Deleted (because incorrect #) +// Offline License 3 4 Deleted (because incorrect #) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastOfflineEntriesHaveIncorrectUsageEntryNumber) { - std::vector usage_entry_info_vector = { + InvalidateEntry_LastOfflineEntriesHaveIncorrectUsageEntryNumber) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); const uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - DeviceFiles::ResponseType sub_error_code; + // Set offline license file data with mismatched usage entry numbers. const DeviceFiles::CdmLicenseData offline_license_3_data{ - usage_entry_info_vector[usage_entry_info_vector.size() - 1].key_set_id, + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1575,13 +1509,14 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - static_cast(usage_entry_info_vector.size() - 2)}; - EXPECT_TRUE( - device_files_->StoreLicense(offline_license_3_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + static_cast(3) /* Mismatch */}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); const DeviceFiles::CdmLicenseData offline_license_2_data{ - usage_entry_info_vector[usage_entry_info_vector.size() - 2].key_set_id, + kUsageEntryInfoOfflineLicense2.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1594,86 +1529,80 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - static_cast(usage_entry_info_vector.size() - 3)}; - EXPECT_TRUE( - device_files_->StoreLicense(offline_license_2_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + static_cast(2) /* Mismatch */}; EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoStorageTypeUnknown))) - .WillOnce(Return(true)); - EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_3_data.key_set_id, - NotNull(), NotNull())); - EXPECT_CALL(*device_files_, RetrieveLicense(offline_license_2_data.key_set_id, - NotNull(), NotNull())); + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are secure stops, but have incorrect usage // entry number stored in persistent file store. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Secure stops entries in (1) will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, the last two entries will be selected to +// move. +// c. Getting the usage entry for the selected entries will fail due +// to a mismatch in usage entry number and result in them being set +// as kStorageTypeUnknown. +// d. No entries will be moved due to (c). +// e. Usage table will be resized to have only one entry. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 Deleted -// Secure stop 3 4 Deleted +// Secure stop 2 3 Deleted (because incorrect #) +// Secure stop 3 4 Deleted (because incorrect #) // -// # of usage entries 5 2 +// # of usage entries 5 1 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastSecureStopEntriesHaveIncorrectUsageEntryNumber) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + InvalidateEntry_LastSecureStopEntriesHaveIncorrectUsageEntryNumber) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t usage_entry_number_after_deleted_entry = - usage_entry_number_to_be_deleted + 1; + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Set streaming license file data with mismatched usage entry numbers. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop2.usage_info_file_name, kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll( - SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(usage_entry_number_to_be_deleted), Return(true))); + .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), + SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), + SetArgPointee<6>(2) /* Mismatch */, Return(true))); EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( @@ -1684,73 +1613,122 @@ TEST_F(UsageTableHeaderTest, SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(usage_entry_number_after_deleted_entry), - Return(true))); + SetArgPointee<6>(3) /* Mismatch */, Return(true))); + + // Shrink to contain only the one valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 1; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Last few entries are of storage type unknown. -// 2. Usage entry to be deleted preceeds those in (1). +// 2. Usage entry to be deleted precedes those in (1). // // Attempting to delete the entry in (2) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The usage entry requested to be deleted will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline license 2 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The selected license will be moved to the entry position near the +// front of the table. +// e. The moved entry will be updated in device files. +// f. Usage table will be resized to have only one entry. +// g. Updated table will be saved. +// h. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 2 -// Offline License 2 3 3 +// Offline License 2 3 1 (Moved) // Offline License 3 4 Deleted -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // -// # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreStorageTypeUnknown) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoSecureStop1, kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); +// # of usage entries 7 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntriesAreStorageTypeUnknown) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown}; Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 4; // kUsageEntryInfoOfflineLicense3 metrics::CryptoMetrics metrics; + // Expect calls for moving offline license 2 (position 3) to position 1. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + // Calls during Move(). EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(usage_entry_number_to_be_deleted, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo(kAnotherUsageTableHeader, - UnorderedElementsAre(kUsageEntryInfoSecureStop1, - kUsageEntryInfoOfflineLicense1, - kUsageEntryInfoOfflineLicense2))) + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kYetAnotherUsageEntry), + SetArgPointee<1>(kUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kYetAnotherUsageEntry, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: @@ -1759,40 +1737,43 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreStorageTypeUnknown) { // OEMCrypto_MoveUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// b. The last offline usage entry will not be deleted/moved if the -// OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline license 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move process will fail due to the entry being busy, leaving +// the entry inplace. +// e. Usage table will not be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 -// Offline License 1 2 Deleted/storage type unknown +// Secure Stop 2 1 1 +// Offline License 1 2 2 (storage type unknown) // Offline License 2 3 3 // Offline License 3 4 4 // // # of usage entries 5 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntryIsOffline_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, + InvalidateEntry_LastEntryIsOffline_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData license_data{ - usage_entry_info_vector[last_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1805,34 +1786,36 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE(device_files_->StoreLicense(license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); + /* usage_entry_number = */ 4}; EXPECT_CALL(*device_files_, - RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Calls during Move(). + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // No calls to shrink are expected. + + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check that the table has been updated as expected. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: @@ -1841,117 +1824,126 @@ TEST_F(UsageTableHeaderTest, // OEMCrypto_MoveUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// b. The last secure stop usage entry will not be deleted/moved if the -// OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 3 will be selected to be moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move process will fail due to the entry being busy, leaving +// the entry inplace. +// e. Usage table will not be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 -// Secure stop 1 2 Deleted/storage type unknown -// Secure stop 2 3 3 -// Secure stop 3 4 4 +// Offline License 2 1 1 +// Secure Stop 1 2 2 (storage type unknown) +// Secure Stop 2 3 3 +// Secure Stop 3 4 4 // // # of usage entries 5 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntryIsSecureStop_MoveSecureStopEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, + InvalidateEntry_LastEntryIsSecureStop_MoveSecureStopEntryFailed) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - + // Expect calls for moving secure stop 3 (position 4), but + // failure to move will not result in any calls for updating. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), - SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_usage_entry_number), Return(true))); + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) + // Calls during Move(). + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // No calls to shrink are expected. + + // Regardless, the usage table should be updated to reflect the changes + // to the usage entry marked as storage type unknown. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last +// 1. Usage entry to be deleted is not last (Offline license 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license and calling -// OEMCrypto_MoveUsageEntry on it will fail. +// 3. Entry that precedes those in (2) are offline license and calling +// OEMCrypto_LoadUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The offline usage entry that preceeds the entries in (a) will -// not be deleted/moved if the OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move processes will fail due to the entries being busy, leaving +// the entries inplace. +// e. Usage table will be resized to remove the last two entries. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 -// Offline License 1 2 Deleted/storage type unknown +// Storage Type Unknown 1 1 +// Offline License 1 2 2 (storage type unknown) // Offline License 2 3 3 // Offline License 3 4 4 -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // // # of usage entries 7 5 TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntriesAreOfflineAndUnknown_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { + InvalidateEntry_LastEntriesAreOfflineAndUnknown_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData license_data{ - usage_entry_info_vector[last_valid_usage_entry_number].key_set_id, + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + + // Expect calls for moving offline license 3 (position 4), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -1964,163 +1956,203 @@ TEST_F(UsageTableHeaderTest, kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_valid_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE(device_files_->StoreLicense(license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(last_valid_usage_entry_number + 1, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); + 4}; EXPECT_CALL(*device_files_, - RetrieveLicense(license_data.key_set_id, NotNull(), NotNull())); + RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect calls for moving offline license 2 (position 3), but + // failure to move will not result in any calls for updating. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + 3}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Expect a call to shrink table to cut off only the unknown entries + // at the end of the table. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 5, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + // Update table for the entry now marked storage type unknown and + // the entries that were cut off. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense2, + kUsageEntryInfoOfflineLicense3))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last +// 1. Usage entry to be deleted is not last (Secure stop 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license and calling -// OEMCrypto_MoveUsageEntry on it will fail. +// 3. Entry that precedes those in (2) are secure stops and calling +// OEMCrypto_LoadUsageEntry on it will fail. // // Attempting to delete the entry in (1) will result in: -// a. Entries of storage type unknown at the end will be deleted. -// b. The offline usage entry that preceeds the entries in (a) will -// not be deleted/moved if the OEMCrypto_MoveUsageEntry operation fails. -// c. The usage entry requested to be deleted will be marked as -// storage type unknown. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entry will have the usage entry loaded from device +// files. +// d. The move processes will fail due to the entries being busy, leaving +// the entries inplace. +// e. Usage table will be resized to remove the last two entries. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 // Storage Type unknown 1 1 -// Secure stop 1 2 Deleted/storage type unknown +// Secure stop 1 2 2 (storage type unknown) // Secure stop 2 3 3 // Secure stop 3 4 4 // Storage Type unknown 5 Deleted // Storage Type unknown 6 Deleted // // # of usage entries 7 5 -TEST_F(UsageTableHeaderTest, - DeleteEntry_LastEntriesAreSecureStopAndUnknown_MoveOfflineEntryFailed) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F( + UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreSecureStopAndUnknown_MoveOfflineEntryFailed) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) .Times(2) .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(MOVE_USAGE_ENTRY_UNKNOWN_ERROR)); + // Expect calls for moving streaming license 3 (position 4), but + // failure to move will not result in any calls for updating. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillOnce( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_valid_usage_entry_number), Return(true))); - EXPECT_CALL( - *crypto_session_, - ShrinkUsageTableHeader(last_valid_usage_entry_number + 1, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) + // Expect calls for moving streaming license 2 (position 3), but + // failure to move will not result in any calls for updating. + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .WillOnce(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(LOAD_USAGE_ENTRY_INVALID_SESSION)); + + // Expect a call to shrink table to cut off only the unknown entries + // at the end of the table. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 5, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + // Update table for the entry now marked storage type unknown and + // the entries that were cut off. + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoSecureStop2, + kUsageEntryInfoSecureStop3))) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 5; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. -// 2. Last entry is an offline license. +// 2. Last entries are valid offline licenses. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be replaced with the last -// entry. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 3 -// Offline License 3 4 2 +// Offline License 2 3 2 (moved) +// Offline License 3 4 1 (moved) // -// # of usage entries 5 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsOffline) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsOffline) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData stored_license_data{ - usage_entry_info_vector[last_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4) to position 1. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -2133,222 +2165,236 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsOffline) { kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE( - device_files_->StoreLicense(stored_license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(last_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2))) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3))) - .WillOnce(Return(true)); - // Expecting three calls to RetrieveLicense(), twice by usage table when - // swapping (probing for swap, and the actual swap_, then by test case - // to verify data is still there. + static_cast(4)}; EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, NotNull(), NotNull())) - .Times(3); + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect calls for moving offline license 2 (position 3) to position 2. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)).WillOnce(Return(NO_ERROR)); - DeviceFiles::CdmLicenseData retrieved_license_data; - EXPECT_TRUE( - device_files_->RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - &retrieved_license_data, &sub_error_code)); + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); - EXPECT_EQ(kActiveLicenseState, retrieved_license_data.state); - EXPECT_EQ(kPsshData, retrieved_license_data.pssh_data); - EXPECT_EQ(kKeyRequest, retrieved_license_data.license_request); - EXPECT_EQ(kKeyResponse, retrieved_license_data.license); - EXPECT_EQ(kKeyRenewalRequest, retrieved_license_data.license_renewal_request); - EXPECT_EQ(kKeyRenewalResponse, retrieved_license_data.license_renewal); - EXPECT_EQ(kReleaseServerUrl, retrieved_license_data.release_server_url); - EXPECT_EQ(kPlaybackStartTime, retrieved_license_data.playback_start_time); - EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, - retrieved_license_data.last_playback_time); - EXPECT_EQ(kGracePeriodEndTime, retrieved_license_data.grace_period_end_time); - EXPECT_EQ(kEmptyAppParameters.size(), - retrieved_license_data.app_parameters.size()); - EXPECT_EQ(kAnotherUsageEntry, retrieved_license_data.usage_entry); - EXPECT_EQ(usage_entry_number_to_be_deleted, - retrieved_license_data.usage_entry_number); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense3, + kUsageEntryInfoOfflineLicense2))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. -// 2. Last entry is a secure stop. +// 2. Last entries are valid streaming licenses. // // Attempting to delete the entry in (1) will result in: -// a. The usage entry requested to be deleted will be replaced with the last -// entry. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 3 -// Secure stop 3 4 2 +// Secure stop 2 3 2 (moved) +// Secure stop 3 4 1 (moved) // -// # of usage entries 5 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntryIsSecureStop) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 5 3 +TEST_F(UsageTableHeaderTest, InvalidateEntry_LastEntryIsSecureStop) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop1 - uint32_t last_usage_entry_number = - usage_entry_info_vector.size() - 1; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(last_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Expect calls for moving streaming license 3 (position 4) to position 1. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillRepeatedly( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_usage_entry_number), Return(true))); + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, _, 1)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)); + // Expect calls for moving streaming license 2 (position 3) to position 2. EXPECT_CALL(*device_files_, - DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, - kProviderSessionToken)) - .WillOnce(Return(true)); - + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); EXPECT_CALL( *device_files_, - StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, - kUsageEntryInfoSecureStop3.usage_info_file_name, - kUsageEntryInfoSecureStop3.key_set_id, kAnotherUsageEntry, - usage_entry_number_to_be_deleted)) + DeleteUsageInfo(kUsageEntryInfoSecureStop2.usage_info_file_name, _)) .WillOnce(Return(true)); - EXPECT_CALL( *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2))) + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, _, 2)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)); + + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); + + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) .WillOnce(Return(true)); - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3))) - .WillOnce(Return(true)); - - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: // 1. Usage entry to be deleted is not last. // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is an offline license. +// 3. Entry that precedes those in (2) is an offline license. // // Attempting to delete the entry in (1) will result in: -// a. The entry being deleted and replaced with the offline entry in (3). -// b. The entries with unknown storage type in (2) will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, offline licenses 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Secure Stop 1 0 0 -// Storage Type unknown 1 1 +// Storage Type Unknown 1 Deleted // Offline License 1 2 Deleted -// Offline License 2 3 3 -// Offline License 3 4 2 -// Storage Type unknown 5 Deleted -// Storage Type unknown 6 Deleted +// Offline License 2 3 2 (moved) +// Offline License 3 4 1 (moved) +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted // // # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreOfflineAndUnknknown) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreOfflineAndUnknknown) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, kUsageEntryInfoOfflineLicense2, kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoOfflineLicense1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoOfflineLicense3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoOfflineLicense1 metrics::CryptoMetrics metrics; - const DeviceFiles::CdmLicenseData stored_license_data{ - usage_entry_info_vector[last_valid_usage_entry_number].key_set_id, + // Expect calls for moving offline license 3 (position 4) to position 1. + const DeviceFiles::CdmLicenseData offline_license_3_data{ + kUsageEntryInfoOfflineLicense3.key_set_id, kActiveLicenseState, kPsshData, kKeyRequest, @@ -2361,186 +2407,719 @@ TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreOfflineAndUnknknown) { kGracePeriodEndTime, kEmptyAppParameters, kUsageEntry, - last_valid_usage_entry_number}; - DeviceFiles::ResponseType sub_error_code; - EXPECT_TRUE( - device_files_->StoreLicense(stored_license_data, &sub_error_code)); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); - - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(last_valid_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2))) - .WillOnce(Return(true)); - - EXPECT_CALL( - *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoOfflineLicense2, - kUsageEntryInfoOfflineLicense3, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown))) - .WillOnce(Return(true)); - // Expecting three calls to RetrieveLicense(), twice by usage table when - // swapping (probing for swap, and the actual swap_, then by test case - // to verify data is still there. + static_cast(4)}; EXPECT_CALL(*device_files_, RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, NotNull(), NotNull())) - .Times(3); + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_3_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)).WillOnce(Return(NO_ERROR)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect calls for moving offline license 2 (position 3) to position 2. + const DeviceFiles::CdmLicenseData offline_license_2_data{ + kUsageEntryInfoOfflineLicense2.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(3)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense2.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_2_data), Return(true))); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)).WillOnce(Return(NO_ERROR)); - DeviceFiles::CdmLicenseData retrieved_license_data; - EXPECT_TRUE( - device_files_->RetrieveLicense(kUsageEntryInfoOfflineLicense3.key_set_id, - &retrieved_license_data, &sub_error_code)); + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); - EXPECT_EQ(kActiveLicenseState, retrieved_license_data.state); - EXPECT_EQ(kPsshData, retrieved_license_data.pssh_data); - EXPECT_EQ(kKeyRequest, retrieved_license_data.license_request); - EXPECT_EQ(kKeyResponse, retrieved_license_data.license); - EXPECT_EQ(kKeyRenewalRequest, retrieved_license_data.license_renewal_request); - EXPECT_EQ(kKeyRenewalResponse, retrieved_license_data.license_renewal); - EXPECT_EQ(kReleaseServerUrl, retrieved_license_data.release_server_url); - EXPECT_EQ(kPlaybackStartTime, retrieved_license_data.playback_start_time); - EXPECT_EQ(kPlaybackStartTime + kPlaybackDuration, - retrieved_license_data.last_playback_time); - EXPECT_EQ(kGracePeriodEndTime, retrieved_license_data.grace_period_end_time); - EXPECT_EQ(kEmptyAppParameters.size(), - retrieved_license_data.app_parameters.size()); - EXPECT_EQ(kAnotherUsageEntry, retrieved_license_data.usage_entry); - EXPECT_EQ(usage_entry_number_to_be_deleted, - retrieved_license_data.usage_entry_number); - EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoSecureStop1, + kUsageEntryInfoOfflineLicense3, + kUsageEntryInfoOfflineLicense2))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // Initial Test state: -// 1. Usage entry to be deleted is not last. +// 1. Usage entry to be deleted is not last (Secure stop 1) // 2. Last few entries are of storage type unknown. -// 3. Entry that preceeds those in (2) is a secure stop. // // Attempting to delete the entry in (1) will result in: -// a. The entry being deleted and replaced with the secure stop entry in (3). -// b. The entries with unknown storage type in (2) will be deleted. +// a. The entry will be marked as kStorageTypeUnknown. +// b. While defragging, secure stop 2 and 3 will be selected to be +// moved. +// c. The selected entries will be moved. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. // // Storage type Usage entries // at start at end // ============= ======== ====== // Offline License 1 0 0 -// Storage Type unknown 1 1 +// Storage Type unknown 1 Deleted // Secure stop 1 2 Deleted -// Secure stop 2 3 3 -// Secure stop 3 4 2 +// Secure stop 2 3 2 (Moved) +// Secure stop 3 4 1 (Moved) // Storage Type unknown 5 Deleted // Storage Type unknown 6 Deleted // -// # of usage entries 7 4 -TEST_F(UsageTableHeaderTest, DeleteEntry_LastEntriesAreSecureStopAndUnknknown) { - std::vector usage_entry_info_vector; - const CdmUsageEntryInfo usage_entry_info_array[] = { +// # of usage entries 7 3 +TEST_F(UsageTableHeaderTest, + InvalidateEntry_LastEntriesAreSecureStopAndUnknknown) { + const std::vector usage_entry_info_vector = { kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, kUsageEntryInfoSecureStop2, kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; - ToVector(usage_entry_info_vector, usage_entry_info_array, - sizeof(usage_entry_info_array)); Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); - uint32_t usage_entry_number_to_be_deleted = - usage_entry_info_vector.size() - 5; // kUsageEntryInfoSecureStop1 - uint32_t last_valid_usage_entry_number = - usage_entry_info_vector.size() - 3; // kUsageEntryInfoSecureStop3 + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) - .Times(2) - .WillRepeatedly(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - LoadUsageEntry(last_valid_usage_entry_number, kUsageEntry)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - MoveUsageEntry(usage_entry_number_to_be_deleted)) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) - .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), - SetArgPointee<1>(kAnotherUsageEntry), Return(NO_ERROR))); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(last_valid_usage_entry_number, NotNull())) - .WillOnce( - DoAll(SetArgPointee<1>(kAnotherUsageTableHeader), Return(NO_ERROR))); - + // Expect calls for moving streaming license 3 (position 4) to position 1. EXPECT_CALL(*device_files_, RetrieveUsageInfoByKeySetId( kUsageEntryInfoSecureStop3.usage_info_file_name, kUsageEntryInfoSecureStop3.key_set_id, NotNull(), NotNull(), NotNull(), NotNull(), NotNull())) - .WillRepeatedly( - DoAll(SetArgPointee<2>(kProviderSessionToken), - SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), - SetArgPointee<5>(kUsageEntry), - SetArgPointee<6>(last_valid_usage_entry_number), Return(true))); + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(4), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop3.usage_info_file_name, + kUsageEntryInfoSecureStop3.key_set_id, _, 1)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(4, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(1)); + + // Expect calls for moving streaming license 2 (position 3) to position 2. + EXPECT_CALL(*device_files_, + RetrieveUsageInfoByKeySetId( + kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, NotNull(), NotNull(), + NotNull(), NotNull(), NotNull())) + .Times(2) // First to get entry, second to update. + .WillRepeatedly(DoAll( + SetArgPointee<2>(kProviderSessionToken), + SetArgPointee<3>(kKeyRequest), SetArgPointee<4>(kKeyResponse), + SetArgPointee<5>(kUsageEntry), SetArgPointee<6>(3), Return(true))); + EXPECT_CALL( + *device_files_, + DeleteUsageInfo(kUsageEntryInfoSecureStop2.usage_info_file_name, _)) + .WillOnce(Return(true)); + EXPECT_CALL( + *device_files_, + StoreUsageInfo(_, _, _, kUsageEntryInfoSecureStop2.usage_info_file_name, + kUsageEntryInfoSecureStop2.key_set_id, _, 2)) + .WillOnce(Return(true)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(3, kUsageEntry)) + .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(2)); + + // Common to both moves. + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .Times(2) + .WillRepeatedly(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kAnotherUsageEntry), + Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)) + .Times(2) + .WillRepeatedly(Return(true)); + + // Shrink to contain the remaining valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 3, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kYetAnotherUsageEntry), Return(NO_ERROR))); EXPECT_CALL(*device_files_, - DeleteUsageInfo(kUsageEntryInfoSecureStop3.usage_info_file_name, - kProviderSessionToken)) + StoreUsageTableInfo(kYetAnotherUsageEntry, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop3, + kUsageEntryInfoSecureStop2))) .WillOnce(Return(true)); + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 3; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// Initial Test state: +// 1. Usage entry to be deleted is not last (Secure stop 1) +// 2. All other entries are invalid. +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Defrag is skipped (no calls to Move()). +// c. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 Deleted +// Storage Type Unknown 1 Deleted +// Storage Type Unknown 2 Deleted +// Secure stop 1 3 Deleted +// Storage Type Unknown 4 Deleted +// Storage Type Unknown 5 Deleted +// Storage Type Unknown 6 Deleted +// +// # of usage entries 7 0 +TEST_F(UsageTableHeaderTest, InvalidateEntry_NoValidSessionsAfter) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoSecureStop1, + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 3; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // No calls related to defragging, just shrinking the table and save. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 0, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, + kEmptyUsageEntryInfoVector)) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + EXPECT_TRUE(usage_table_header_->usage_entry_info().empty()); +} + +// 1. Usage entry to be deleted is last valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. OEMCrypto is at max sessions, and any attempt to open a new session +// will fail with INSUFFICIENT_CRYPTO_RESOURCES. +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Opening session for move will fail. +// d. Defrag is aborted; but shrink is still made up to last valid. +// e. Usage table will be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 0 +// Offline License 1 1 1 +// Secure Stop 1 2 Deleted +// Storage Type Unknown 3 Deleted +// +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_MaxSessionReached) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + // But will fail when opening a crypto session. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES)); + + // Despite being unable to open session, the table should be resized to + // exclude the trailing invalid entries. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// 1. Usage entry to be deleted is first valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. OEMCrypto is at max sessions, and any attempt to open a new session +// will fail with INSUFFICIENT_CRYPTO_RESOURCES. +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Opening session for move will fail. +// d. Defrag is aborted; but shrink is still made up to last valid. +// e. Usage table will be resized. +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Secure Stop 1 0 0 (Storage type unknown) +// Offline License 1 1 1 (Failed to move) +// Storage Type Unknown 2 Deleted +// Storage Type Unknown 3 Deleted +// +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_FirstEntry_MaxSessionReached) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoSecureStop1, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 0; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + // But will fail when opening a crypto session. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)) + .WillRepeatedly(Return(INSUFFICIENT_CRYPTO_RESOURCES)); + + // Despite being unable to open session, the table should be resized to + // exclude the trailing invalid entries. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) + .WillOnce( + DoAll(SetArgPointee<2>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// 1. Usage entry to be deleted is last valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. Moving entry will result in a system invalidation error. +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Moving entry will result in system invalidation. +// d. Defrag is aborted; no call to Shrink() +// f. Updated table will be saved. +// g. InvalidateEntry() will return SYSTEM_INVALIDATED_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 0 +// Offline License 1 1 1 +// Secure Stop 1 2 2 (Storage Type Unknown) +// Storage Type Unknown 3 3 +// +// # of usage entries 4 4 +TEST_F(UsageTableHeaderTest, InvalidateEntry_SystemInvalidation_OnMove) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + // But will fail when moving. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(1, kUsageEntry)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(0)) + .WillOnce(Return(SYSTEM_INVALIDATED_ERROR)); + + // Defrag is aborted, and table is saved, but no call to shrink(). EXPECT_CALL( *device_files_, - StoreUsageInfo(kProviderSessionToken, kKeyRequest, kKeyResponse, - kUsageEntryInfoSecureStop3.usage_info_file_name, - kUsageEntryInfoSecureStop3.key_set_id, kAnotherUsageEntry, - usage_entry_number_to_be_deleted)) + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown))) .WillOnce(Return(true)); + EXPECT_EQ(SYSTEM_INVALIDATED_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, + true, device_files_, &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 4; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// 1. Usage entry to be deleted is last valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. Moving entry will result in a session invalidation error. +// +// Note: This is very similar to InvalidateEntry_SystemInvalidation_OnMove +// except that the error returned is not of importance to the calling +// session and is ignored (NO_ERROR returned). +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Moving entry will result in session invalidation. +// d. Defrag is aborted; no call to Shrink() +// f. Updated table will be saved. +// g. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 0 +// Offline License 1 1 1 +// Secure Stop 1 2 2 (Storage Type Unknown) +// Storage Type Unknown 3 3 +// +// # of usage entries 4 4 +TEST_F(UsageTableHeaderTest, InvalidateEntry_SessionInvalidation_OnMove) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + // But will fail when moving. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(1, kUsageEntry)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(0)) + .WillOnce(Return(SESSION_LOST_STATE_ERROR)); + + // Defrag is aborted, and table is saved, but no call to shrink(). EXPECT_CALL( *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2))) + StoreUsageTableInfo(kUsageTableHeader, + ElementsAre(kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown))) .WillOnce(Return(true)); + // The underlying error should not be returned to the caller. + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 4; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// 1. Usage entry to be deleted is last valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. Shrinking table will fail due to an unspecified entry being in use. +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Entry will be moved successfully. +// d. Shrinking table fill fail with SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE. +// f. Updated table will be saved, with trailing invalid entries. +// g. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 1 (swapped Offline License 1) +// Offline License 1 1 0 (moved) +// Secure Stop 1 2 2 (Storage Type Unknown) +// Storage Type Unknown 3 3 +// +// # of usage entries 4 4 +TEST_F(UsageTableHeaderTest, InvalidateEntry_ShrinkFails) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .Times(2) // First to get entry, then again to update. + .WillRepeatedly( + DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(1, kUsageEntry)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(0)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), + SetArgPointee<1>(kUsageEntry), Return(NO_ERROR))); EXPECT_CALL( *device_files_, - StoreUsageTableInfo( - kAnotherUsageTableHeader, - UnorderedElementsAre( - kUsageEntryInfoOfflineLicense1, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoSecureStop3, kUsageEntryInfoSecureStop2, - kUsageEntryInfoSecureStop3, kUsageEntryInfoStorageTypeUnknown, - kUsageEntryInfoStorageTypeUnknown))) + StoreUsageTableInfo(kAnotherUsageTableHeader, + ElementsAre(kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoStorageTypeUnknown))) + .WillOnce(Return(true)); + EXPECT_CALL(*device_files_, StoreLicense(_, NotNull())) .WillOnce(Return(true)); - EXPECT_EQ(NO_ERROR, - usage_table_header_->DeleteEntry(usage_entry_number_to_be_deleted, - device_files_, &metrics)); + // Expect a call to shrink table, but will fail due to an unspecified + // entry being in use. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 1, NotNull())) + .WillOnce(Return(SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 4; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); +} + +// 1. Usage entry to be deleted is last valid entry (Secure stop 1) +// 2. There exists an entry to be moved (Offline License 1) +// 3. Moving entry cannot be done as the destination is in use. +// (this is unexpected but possible in normal operation) +// +// Attempting to delete the entry in (1) will result in: +// a. The entry will be marked as kStorageTypeUnknown. +// b. Only remaining entry is selected for move (Offline license 1) +// c. Call to move will fail with MOVE_USAGE_ENTRY_DESTINATION_IN_USE. +// d. Usage table will be resized. +// e. Updated table will be saved. +// f. InvalidateEntry() will return NO_ERROR. +// +// Storage type Usage entries +// at start at end +// ============= ======== ====== +// Storage Type Unknown 0 0 +// Offline License 1 1 1 (unable to move) +// Secure Stop 1 2 Deleted +// Storage Type Unknown 3 Deleted +// +// # of usage entries 4 2 +TEST_F(UsageTableHeaderTest, InvalidateEntry_DestinationInUse_OnMove) { + const std::vector usage_entry_info_vector = { + kUsageEntryInfoStorageTypeUnknown, kUsageEntryInfoOfflineLicense1, + kUsageEntryInfoSecureStop1, kUsageEntryInfoStorageTypeUnknown}; + + Init(kSecurityLevelL1, kUsageTableHeader, usage_entry_info_vector); + const uint32_t usage_entry_number_to_be_deleted = + 2; // kUsageEntryInfoSecureStop1 + metrics::CryptoMetrics metrics; + + // Expected calls for moving offline license 1 (position 1) to position 0. + // But will fail due to the destination being in use. + const DeviceFiles::CdmLicenseData offline_license_1_data{ + kUsageEntryInfoOfflineLicense1.key_set_id, + kActiveLicenseState, + kPsshData, + kKeyRequest, + kKeyResponse, + kKeyRenewalRequest, + kKeyRenewalResponse, + kReleaseServerUrl, + kPlaybackStartTime, + kPlaybackStartTime + kPlaybackDuration, + kGracePeriodEndTime, + kEmptyAppParameters, + kUsageEntry, + static_cast(1)}; + EXPECT_CALL(*device_files_, + RetrieveLicense(kUsageEntryInfoOfflineLicense1.key_set_id, + NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(offline_license_1_data), Return(true))); + EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*crypto_session_, LoadUsageEntry(1, kUsageEntry)); + EXPECT_CALL(*crypto_session_, MoveUsageEntry(0)) + .WillOnce(Return(MOVE_USAGE_ENTRY_DESTINATION_IN_USE)); + // No expectations for updating the entry or the header from the move + // operation. + + // Shrink table down to the last valid entry. + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 2, NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(kAnotherUsageEntry), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, + StoreUsageTableInfo(kAnotherUsageEntry, + ElementsAre(kUsageEntryInfoStorageTypeUnknown, + kUsageEntryInfoOfflineLicense1))) + .WillOnce(Return(true)); + + EXPECT_EQ(NO_ERROR, usage_table_header_->InvalidateEntry( + usage_entry_number_to_be_deleted, true, device_files_, + &metrics)); + // Check the end state of the usage table. + constexpr size_t expected_size = 2; + EXPECT_EQ(expected_size, usage_table_header_->usage_entry_info().size()); } // If the crypto session says the usage table header is stale, init should fail. @@ -2557,11 +3136,13 @@ TEST_F(UsageTableHeaderTest, StaleHeader) { .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), SetArgPointee<1>(usage_entry_info_vector), SetArgPointee<2>(false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kLevelDefault, kUsageTableHeader)) .WillOnce(Return(LOAD_USAGE_HEADER_GENERATION_SKEW)); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(kLevelDefault, NotNull())) .WillOnce( - DoAll(SetArgPointee<0>(kEmptyUsageTableHeader), Return(NO_ERROR))); + DoAll(SetArgPointee<1>(kEmptyUsageTableHeader), Return(NO_ERROR))); EXPECT_CALL(*device_files_, DeleteAllLicenses()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteAllUsageInfo()).WillOnce(Return(true)); EXPECT_CALL(*device_files_, DeleteUsageTableInfo()).WillOnce(Return(true)); @@ -2580,7 +3161,9 @@ TEST_F(UsageTableHeaderTest, Shrink_NoneOfTable) { // These calls are "expensive" and should be avoided if possible. EXPECT_CALL(*crypto_session_, Open(_)).Times(0); - EXPECT_CALL(*crypto_session_, ShrinkUsageTableHeader(_, NotNull())).Times(0); + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, _, NotNull())) + .Times(0); EXPECT_CALL(*device_files_, StoreUsageTableInfo(_, _)).Times(0); EXPECT_EQ(usage_table_header_->Shrink(&metrics, 0), NO_ERROR); @@ -2595,9 +3178,9 @@ TEST_F(UsageTableHeaderTest, Shrink_PartOfTable) { k10UsageEntryInfoVector.cend() - to_shink); metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, - ShrinkUsageTableHeader(shunken_entries.size(), NotNull())) + EXPECT_CALL( + *crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, shunken_entries.size(), NotNull())) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, shunken_entries)) @@ -2611,8 +3194,8 @@ TEST_F(UsageTableHeaderTest, Shrink_AllOfTable) { Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, ShrinkUsageTableHeader(0, NotNull())) + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 0, NotNull())) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, kEmptyUsageEntryInfoVector)) @@ -2628,8 +3211,8 @@ TEST_F(UsageTableHeaderTest, Shrink_AllOfTable) { TEST_F(UsageTableHeaderTest, Shrink_MoreThanTable) { Init(kSecurityLevelL1, kUsageTableHeader, k10UsageEntryInfoVector); metrics::CryptoMetrics metrics; - EXPECT_CALL(*crypto_session_, Open(kLevelDefault)).WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, ShrinkUsageTableHeader(0, NotNull())) + EXPECT_CALL(*crypto_session_, + ShrinkUsageTableHeader(kLevelDefault, 0, NotNull())) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(kUsageTableHeader, kEmptyUsageEntryInfoVector)) @@ -2654,7 +3237,7 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_NoAction) { SetArgPointee<2>(/* lru_upgrade = */ false), Return(true))); EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(kUpgradableUsageTableHeader)) + LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); // These function are called specifically by the LRU upgrading system. @@ -2680,7 +3263,7 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_Succeed) { SetArgPointee<2>(/* lru_upgrade = */ true), Return(true))); EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(kUpgradableUsageTableHeader)) + LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { @@ -2744,7 +3327,7 @@ TEST_F(UsageTableHeaderTest, SetArgPointee<2>(/* lru_upgrade = */ true), Return(true))); EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(kUpgradableUsageTableHeader)) + LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); // Expectations of the one successful license. @@ -2804,7 +3387,7 @@ TEST_F(UsageTableHeaderTest, SetArgPointee<2>(/* lru_upgrade = */ true), Return(true))); EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(kUpgradableUsageTableHeader)) + LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { @@ -2857,7 +3440,7 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_AllFailure) { SetArgPointee<2>(/* lru_upgrade = */ true), Return(true))); EXPECT_CALL(*crypto_session_, - LoadUsageTableHeader(kUpgradableUsageTableHeader)) + LoadUsageTableHeader(kLevelDefault, kUpgradableUsageTableHeader)) .WillOnce(Return(NO_ERROR)); for (size_t i = 0; i < kUpgradableUsageEntryInfoList.size(); ++i) { @@ -2881,7 +3464,8 @@ TEST_F(UsageTableHeaderTest, LruUsageTableUpgrade_AllFailure) { EXPECT_CALL(*device_files_, DeleteAllLicenses()); EXPECT_CALL(*device_files_, DeleteAllUsageInfo()); EXPECT_CALL(*device_files_, DeleteUsageTableInfo()); - EXPECT_CALL(*crypto_session_, CreateUsageTableHeader(NotNull())) + EXPECT_CALL(*crypto_session_, + CreateUsageTableHeader(kLevelDefault, NotNull())) .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*device_files_, StoreUsageTableInfo(_, _)); @@ -2896,7 +3480,8 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateLicenseEntry) { SetArgPointee<1>(kUpgradedUsageEntryInfoList), SetArgPointee<2>(/* lru_upgrade = */ false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kLevelDefault, kUpgradedUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); @@ -2917,8 +3502,10 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateLicenseEntry) { MockClock mock_clock; usage_table_header_->SetClock(&mock_clock); EXPECT_CALL(mock_clock, GetCurrentTime()).WillOnce(Return(kLruBaseTime)); - EXPECT_CALL(*device_files_, - StoreUsageTableInfo(kUpgradedUsageTableHeader, _)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)); // The Call. uint32_t usage_entry_number = 0; @@ -2940,7 +3527,8 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateUsageInfoEntry) { SetArgPointee<1>(kUpgradedUsageEntryInfoList), SetArgPointee<2>(/* lru_upgrade = */ false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kLevelDefault, kUpgradedUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); @@ -2962,8 +3550,10 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_CreateUsageInfoEntry) { MockClock mock_clock; usage_table_header_->SetClock(&mock_clock); EXPECT_CALL(mock_clock, GetCurrentTime()).WillOnce(Return(kLruBaseTime)); - EXPECT_CALL(*device_files_, - StoreUsageTableInfo(kUpgradedUsageTableHeader, _)); + EXPECT_CALL(*crypto_session_, UpdateUsageEntry(NotNull(), NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kAnotherUsageTableHeader), Return(NO_ERROR))); + EXPECT_CALL(*device_files_, StoreUsageTableInfo(kAnotherUsageTableHeader, _)); // The Call. uint32_t usage_entry_number = 0; @@ -2985,7 +3575,8 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_UpdateEntry) { SetArgPointee<1>(kUpgradedUsageEntryInfoList), SetArgPointee<2>(/* lru_upgrade = */ false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kLevelDefault, kUpgradedUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); @@ -3027,7 +3618,8 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_LoadEntry) { SetArgPointee<1>(kUpgradedUsageEntryInfoList), SetArgPointee<2>(/* lru_upgrade = */ false), Return(true))); - EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kUpgradedUsageTableHeader)) + EXPECT_CALL(*crypto_session_, + LoadUsageTableHeader(kLevelDefault, kUpgradedUsageTableHeader)) .WillOnce(Return(NO_ERROR)); EXPECT_TRUE(usage_table_header_->Init(kSecurityLevelL1, crypto_session_)); @@ -3066,38 +3658,16 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_LoadEntry) { // operations. TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) { constexpr size_t kUnexpiredThreshold = 50; // Arbitrary - constexpr size_t kRemovalCount = 3; // Also artbirary std::vector usage_entry_info_list; - std::vector removal_candidates; + uint32_t entry_to_remove = 0; // Empty list. EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - &removal_candidates)); + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, + &entry_to_remove)); // Output is null. usage_entry_info_list = kUpgradedUsageEntryInfoList; EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - nullptr)); - // Zero size requests. - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, 0, - &removal_candidates)); - // Request more than is available. Not invalid, but an unlikely use - // case. - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, - /* removal_count = */ usage_entry_info_list.size() * 2, - &removal_candidates)); - // Only unexpired offline license, but threshold is not met. - usage_entry_info_list.resize(kRemovalCount); - for (size_t i = 0; i < kRemovalCount; ++i) { - usage_entry_info_list[i].storage_type = kStorageLicense; - usage_entry_info_list[i].last_use_time = kLruBaseTime; - usage_entry_info_list[i].offline_license_expiry_time = kLruBaseTime + 1; - } - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount, - &removal_candidates)); + usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, nullptr)); } // Check that the major priority buckets are respected. @@ -3107,6 +3677,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) { TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { constexpr int64_t kOneDay = 24 * 60 * 60; constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; // Unexpired offline license. @@ -3140,56 +3711,98 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { usage_entry_info_list.push_back(unknown_entry_info); constexpr uint32_t unknown_entry_number = 3; - std::vector removal_candidates; + // Case 1: If there is an entry with unknown storage type, it should + // be selected above any other entry. + uint32_t entry_to_remove = kInvalidEntry; // Expect the unknown entry. EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 1, &removal_candidates)); - const std::vector unknown_entry_numbers = {unknown_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(unknown_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(unknown_entry_number, entry_to_remove); usage_entry_info_list.pop_back(); // Removing unknown. - // Expect both expired offline and streaming license. + // Case 2a: Threshold not met, all entries are equally stale. + // The expired entry should be selected over the streaming license. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 3, &removal_candidates)); - const std::vector expired_and_streaming_entry_numbers = { - expired_entry_number, streaming_entry_number}; - EXPECT_THAT(removal_candidates, - ContainerEq(expired_and_streaming_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); - // With threshold met, expect all three. + // Case 2b: Threshold not met, streaming license is most stale. + usage_entry_info_list[streaming_entry_number].last_use_time--; + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, - /* removal_count = */ 3, &removal_candidates)); - const std::vector all_license_entry_numbers = { - expired_entry_number, streaming_entry_number, unexpired_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(all_license_entry_numbers)); + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(streaming_entry_number, entry_to_remove); usage_entry_info_list.pop_back(); // Removing streaming. - usage_entry_info_list.pop_back(); // Removing expired offline. + // |usage_entry_info_list| only contains 1 expired and 1 unexpired offline + // license. - // Sanity check: while below threshold there will not be any possible - // candidates, expect failure. This is an unexpected case in normal - // operation. - EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 3, - /* removal_count = */ 1, &removal_candidates)); - - // Expect the unexpired license. + // Case 2c: Threshold met, equally stale entries. Expect the expired + // entry over the unexpired. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, - /* removal_count = */ 1, &removal_candidates)); - const std::vector unexpired_entry_numbers = { - unexpired_entry_number}; - EXPECT_THAT(removal_candidates, ContainerEq(unexpired_entry_numbers)); + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3a: Threshold not met, expired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time--; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3b: Threshold not met, unexpired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time++; + usage_entry_info_list[unexpired_entry_number].last_use_time--; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + // Case 3c: Threshold met, unexpired entry is the most stale. + entry_to_remove = kInvalidEntry; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); + + // Case 3d: Threshold met, expired entry is the most stale. + entry_to_remove = kInvalidEntry; + usage_entry_info_list[expired_entry_number].last_use_time--; + usage_entry_info_list[unexpired_entry_number].last_use_time++; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(expired_entry_number, entry_to_remove); + + usage_entry_info_list.pop_back(); // Removing expired offline. + + // Case 4a: Threshold met, and only an unexpired offline license + // is available. + entry_to_remove = kInvalidEntry; // Invalidate value. + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); + + // Case 4b (stability check): Threshold not met, and only an + // unexpired offline license is available. This is an unexpected + // condition in normal operation, but the algorithm should still + // return an entry. + entry_to_remove = kInvalidEntry; + EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( + usage_entry_info_list, kCurrentTime, + /* unexpired_threshold = */ 3, &entry_to_remove)); + EXPECT_EQ(unexpired_entry_number, entry_to_remove); } // Testing algorithm with unexpired offline and streaming license. The @@ -3198,6 +3811,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) { TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_NoExpiredAndBelowThreshold) { constexpr int64_t kOneDay = 24 * 60 * 60; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list = kUpgradedUsageEntryInfoList; const size_t offline_threshold = usage_entry_info_list.size() + 1; @@ -3218,17 +3832,13 @@ TEST_F(UsageTableHeaderTest, // Must exist at least one streaming license for test to work. ASSERT_LT(0ull, usage_info_count); - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kLruBaseTime, offline_threshold, - /* removal_count = */ 3, &removal_candidates)); + &entry_to_remove)); - EXPECT_EQ(usage_info_count, removal_candidates.size()); - // Ensure that the proposed removal candidates are streaming. - for (uint32_t usage_entry_number : removal_candidates) { - EXPECT_EQ(kStorageUsageInfo, - usage_entry_info_list[usage_entry_number].storage_type); - } + EXPECT_EQ(kStorageUsageInfo, + usage_entry_info_list[entry_to_remove].storage_type); } // When the number of unexpired offline licenses are below the @@ -3243,6 +3853,7 @@ TEST_F(UsageTableHeaderTest, constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; // A threshold larger than the possible number of offline entries. constexpr size_t kUnexpiredThreshold = kSetSize + 1; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; usage_entry_info_list.resize(kSetSize); @@ -3271,126 +3882,16 @@ TEST_F(UsageTableHeaderTest, expired_license_numbers.push_back(i); } - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, 3, - &removal_candidates)); + usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, + &entry_to_remove)); - // Sorting to ensure equality will work. - std::sort(removal_candidates.begin(), removal_candidates.end()); - std::sort(expired_license_numbers.begin(), expired_license_numbers.end()); - - EXPECT_EQ(expired_license_numbers, removal_candidates); -} - -// Test that if all of the license are unknown. For unknown licenses, -// the last use time has no effect on their selection. -TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_AllUnknown) { - constexpr int64_t kOneDay = 24 * 60 * 60; - constexpr size_t kSetSize = 10; - constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; - constexpr size_t kRemovalCount = 3; - - std::vector usage_entry_info_list; - usage_entry_info_list.resize(kSetSize); - - // Create a set of all unknown licenses. - for (uint32_t i = 0; i < kSetSize; ++i) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.storage_type = kStorageTypeUnknown; - usage_entry_info.key_set_id = "unknown_key_set_id"; - usage_entry_info.last_use_time = - CdmRandom::RandomInRange(kLruBaseTime, kCurrentTime); - } - - std::vector removal_candidates; - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, 0, kRemovalCount, - &removal_candidates)); - // There should always be 3 (assuming kSetSize >= 3). - EXPECT_EQ(kRemovalCount, removal_candidates.size()); -} - -// Should there be two or more license which have the same -// |last_use_time| but only 1 of them are to be selected (due to the -// number being requested), then the algorithm should randomly select -// between the ones available. -TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_RandomnessOfCutoff) { - constexpr int64_t kOneDay = 24 * 60 * 60; - constexpr size_t kSetSize = 10; - constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; - constexpr size_t kTrials = 25; - std::vector usage_entry_info_list; - - // All will be streaming licenses. - usage_entry_info_list.resize(kSetSize); - for (size_t i = 0; i < kSetSize; ++i) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.storage_type = kStorageUsageInfo; - usage_entry_info.last_use_time = kLruBaseTime + kOneDay; - usage_entry_info.key_set_id = "nothing_unusual"; - } - - std::vector expected_removals; - // Select two to be the most stale. - while (expected_removals.size() < 2) { - const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - if (usage_entry_info.key_set_id != "nothing_unusual") continue; - usage_entry_info_list[i].last_use_time = kLruBaseTime; - usage_entry_info.key_set_id = "most_stale"; - expected_removals.push_back(i); - } - - // Select another two to be slightly less stale. - std::vector random_removals; - while (random_removals.size() < 2) { - const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1); - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - if (usage_entry_info.key_set_id != "nothing_unusual") continue; - usage_entry_info_list[i].last_use_time = kLruBaseTime + 1; - usage_entry_info.key_set_id = "somewhat_stale"; - random_removals.push_back(i); - } - - // Create two sets for each possible outcome (one for each random - // removal). - std::vector possible_removal_1 = expected_removals; - possible_removal_1.push_back(random_removals[0]); - std::sort(possible_removal_1.begin(), possible_removal_1.end()); - std::vector possible_removal_2 = expected_removals; - possible_removal_2.push_back(random_removals[1]); - std::sort(possible_removal_2.begin(), possible_removal_2.end()); - std::vector possible_removal_3 = expected_removals; - - // Flags to check that both outcomes have occurred. - bool occurrence_1 = false; - bool occurrence_2 = false; - - // Each set should be equally likely. Possible false-negative every - // 2^kTrials time. - for (size_t i = 0; i < kTrials; ++i) { - // Remove 3 out of the 4 possible candidates. - std::vector removal_candidates; - EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( - usage_entry_info_list, kCurrentTime, 0, 3, &removal_candidates)); - - std::sort(removal_candidates.begin(), removal_candidates.end()); - if (removal_candidates == possible_removal_1) { - occurrence_1 = true; - } else if (removal_candidates == possible_removal_2) { - occurrence_2 = true; - } else { - EXPECT_TRUE(false) << "Unexpected removal set"; - } - } - - EXPECT_TRUE(occurrence_1); - EXPECT_TRUE(occurrence_2); + EXPECT_THAT(expired_license_numbers, Contains(entry_to_remove)); } // This test primarily tests the robustness of the algorithm for a full -// set of entries (200). Creates 3 stale streaming license and 3 +// set of entries (200). Creates 1 stale streaming license and 1 // offline licenses which are more stale than the streaming. // // First, with the stale offline licenses unexpired, checks that the @@ -3403,6 +3904,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { constexpr int64_t kOneDay = 24 * 60 * 60; constexpr size_t kLargeSetSize = 200; constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2; + constexpr uint32_t kInvalidEntry = 9999; std::vector usage_entry_info_list; usage_entry_info_list.resize(kLargeSetSize); @@ -3421,72 +3923,57 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) { } } - // Select 3 streaming license to be more stale than the rest. - std::vector modified_usage_info_numbers; - while (modified_usage_info_numbers.size() < 3) { + // Select a streaming license to be more stale than the rest. + uint32_t modified_usage_info_number = kInvalidEntry; + while (modified_usage_info_number == kInvalidEntry) { const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - // Skip streaming license that have already been modified and offline - // licenses. - if (usage_entry_info.storage_type != kStorageUsageInfo || - usage_entry_info.key_set_id != "nothing_unusual") - continue; - usage_entry_info.last_use_time = - kLruBaseTime + 10 + modified_usage_info_numbers.size(); + // Skip offline licenses. + if (usage_entry_info.storage_type != kStorageUsageInfo) continue; + usage_entry_info.last_use_time = kLruBaseTime + 10; usage_entry_info.key_set_id = "stale_streaming"; - modified_usage_info_numbers.push_back(i); + modified_usage_info_number = i; } - std::sort(modified_usage_info_numbers.begin(), - modified_usage_info_numbers.end()); - // Select 3 offline license to be even more stale, but unexpired. - std::vector modified_offline_license_numbers; - while (modified_offline_license_numbers.size() < 3) { + // Select a offline license to be even more stale, but unexpired. + uint32_t modified_offline_license_number = kInvalidEntry; + while (modified_offline_license_number == kInvalidEntry) { const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1); CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - // Skip offline license that have already been modified and streaming - // licenses. - if (usage_entry_info.storage_type != kStorageLicense || - usage_entry_info.key_set_id != "nothing_unusual") - continue; + // Skip streaming licenses. + if (usage_entry_info.storage_type != kStorageLicense) continue; usage_entry_info.last_use_time = kLruBaseTime; usage_entry_info.key_set_id = "stale_offline"; - modified_offline_license_numbers.push_back(i); + modified_offline_license_number = i; } - std::sort(modified_offline_license_numbers.begin(), - modified_offline_license_numbers.end()); // Test using only streaming and expired offline licenses // (which there are none). - std::vector removal_candidates; + uint32_t entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_usage_info_numbers)); + /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); + EXPECT_EQ(modified_usage_info_number, entry_to_remove); - // Test where the equality threshold is met, now the 3 unexpired - // licenses should be selected. - removal_candidates.clear(); + // Test where the equality threshold is met, now the stale unexpired + // license should be selected. + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ 0, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_offline_license_numbers)); + /* unexpired_threshold = */ 0, &entry_to_remove)); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); - // Make the 3 offline licenses expired. - for (uint32_t i : modified_offline_license_numbers) { - CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i]; - usage_entry_info.offline_license_expiry_time = kLruBaseTime; - } + // Make the stale offline license expired. + CdmUsageEntryInfo& offline_usage_entry_info = + usage_entry_info_list[modified_offline_license_number]; + offline_usage_entry_info.offline_license_expiry_time = kLruBaseTime; // Test again, expecting that the expired license should be considered. - removal_candidates.clear(); + entry_to_remove = kInvalidEntry; EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting( usage_entry_info_list, kCurrentTime, - /* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates)); - EXPECT_THAT(removal_candidates, - UnorderedElementsAreArray(modified_offline_license_numbers)); + /* unexpired_threshold = */ kLargeSetSize, &entry_to_remove)); + EXPECT_EQ(modified_offline_license_number, entry_to_remove); } TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) { @@ -3494,15 +3981,7 @@ TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); EXPECT_EQ(usage_table_header_->potential_table_capacity(), kMinimumUsageTableEntriesSupported); -} - -TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Zero) { - // This will issue a warning about the reported capacity is unexpected, - // and will default to the version's required minimum. - crypto_session_->SetMaximumUsageTableEntries(0u); - Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); - EXPECT_EQ(usage_table_header_->potential_table_capacity(), - kMinimumUsageTableEntriesSupported); + EXPECT_FALSE(usage_table_header_->HasUnlimitedTableCapacity()); } TEST_F(UsageTableHeaderTest, PotentialTableCapacity_TooSmall) { @@ -3513,6 +3992,25 @@ TEST_F(UsageTableHeaderTest, PotentialTableCapacity_TooSmall) { Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); EXPECT_EQ(usage_table_header_->potential_table_capacity(), kMinimumUsageTableEntriesSupported); + EXPECT_FALSE(usage_table_header_->HasUnlimitedTableCapacity()); +} + +TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unlimited) { + MockUsageTableHeader* mock_usage_table_header = SetUpMock(); + // Zero indicates that the table size is unlimited. + crypto_session_->SetMaximumUsageTableEntries(0u); + // Expect calls to make a delete entry. + EXPECT_CALL(*crypto_session_, Open(_)).WillOnce(Return(NO_ERROR)); + const size_t test_index = kUsageEntryInfoVector.size(); + EXPECT_CALL(*mock_usage_table_header, AddEntry(_, _, _, _, _, NotNull())) + .WillOnce(DoAll(SetArgPointee<5>(test_index), Return(NO_ERROR))); + EXPECT_CALL(*mock_usage_table_header, InvalidateEntry(test_index, _, _, _)) + .WillOnce(Return(NO_ERROR)); + + Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); + constexpr size_t kZero = 0u; + EXPECT_EQ(mock_usage_table_header->potential_table_capacity(), kZero); + EXPECT_TRUE(mock_usage_table_header->HasUnlimitedTableCapacity()); } TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Available) { @@ -3520,6 +4018,7 @@ TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Available) { crypto_session_->SetMaximumUsageTableEntries(kTableCapacity); Init(kSecurityLevelL1, kUsageTableHeader, kUsageEntryInfoVector); EXPECT_EQ(usage_table_header_->potential_table_capacity(), kTableCapacity); + EXPECT_FALSE(usage_table_header_->HasUnlimitedTableCapacity()); } } // namespace wvcdm diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index db54f9d8..6e71a894 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -8,7 +8,7 @@ * Reference APIs needed to support Widevine's crypto algorithms. * * See the document "WV Modular DRM Security Integration Guide for Common - * Encryption (CENC) -- version 16.2" for a description of this API. You + * Encryption (CENC) -- version 16.3" for a description of this API. You * can find this document in the widevine repository as * docs/WidevineModularDRMSecurityIntegrationGuideforCENC_v16.pdf * Changes between different versions of this API are documented in the files diff --git a/oemcrypto/include/OEMCryptoCENCCommon.h b/oemcrypto/include/OEMCryptoCENCCommon.h index e8f2ad90..03d7030a 100644 --- a/oemcrypto/include/OEMCryptoCENCCommon.h +++ b/oemcrypto/include/OEMCryptoCENCCommon.h @@ -112,7 +112,8 @@ typedef enum OEMCrypto_Usage_Entry_Status { */ typedef enum OEMCrypto_LicenseType { OEMCrypto_ContentLicense = 0, - OEMCrypto_EntitlementLicense = 1 + OEMCrypto_EntitlementLicense = 1, + OEMCrypto_LicenstType_MaxValue = OEMCrypto_EntitlementLicense, } OEMCrypto_LicenseType; /* Private key type used in the provisioning response. */ diff --git a/oemcrypto/test/fuzz_tests/README.md b/oemcrypto/test/fuzz_tests/README.md new file mode 100644 index 00000000..aafeda15 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/README.md @@ -0,0 +1,187 @@ +# OEMCRYPTO Fuzzing + +## Objective + +* Run fuzzing on OEMCrypto public APIs on linux using google supported + clusterfuzz infrastructure to find security vulnerabilities. + + Design Document - https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit?usp=sharing + + Fuzzing at google - + [go/fuzzing](https://g3doc.corp.google.com/security/fuzzing/g3doc/fuzzing_resources.md?cl=head) +## Monitoring +### Cluster fuzz statistics + +* Performance of OEMCrypto fuzz binaries running continuously using cluster + fuzz infrastructure can be monitored + [here](https://clusterfuzz.corp.google.com/fuzzer-stats). + + The options to select are `Job type: libfuzzer_asan_oemcrypto` and `Fuzzer: + fuzzer name you are looking for` + + Example: [load_license_fuzz](https://clusterfuzz.corp.google.com/fuzzer-stats?group_by=by-day&date_start=2020-07-11&date_end=2020-07-17&fuzzer=libFuzzer_oemcrypto_load_license_fuzz&job=libfuzzer_asan_oemcrypto) + +### Issues filed by clusterfuzz - Fixing those issues + +* Any issues found with the fuzz target under test are reported by clusterfuzz + [here](https://b.corp.google.com/hotlists/2442954). + +* The bug will have a link to the test case that generated the bug. Download + the test case and follow the steps from + [testing fuzzer locally](#testing-fuzzer-locally) section to run the fuzzer + locally using the test case that caused the crash. + +* Once the issue is fixed, consider adding the test case that caused the crash + to the seed corpus zip file. Details about seed corpus and their location + are mentioned in + [this section](#build-oemcrypto-unit-tests-to-generate-corpus). + +## Corpus + +* Once the fuzzer scripts are ready and running continuously using clusterfuzz + or android infrastructure, we can measure the efficiency of fuzzers by + looking at code coverage and number of new features that have been + discovered by fuzzer scripts here Fuzz script statistics. + + A fuzzer which tries to start from random inputs and figure out intelligent + inputs to crash the libraries can be time consuming and not effective. A way + to make fuzzers more effective is by providing a set of valid and invalid + inputs of the library so that fuzzer can use those as a starting point. + These sets of valid and invalid inputs are called corpus. + + The idea is to run OEMCrypto unit tests and read required data into binary + corpus files before calling into respective OEMCrypto APIs under test. + Writing corpus data to binary files is controlled by --generate_corpus flag. + +### Build OEMCrypto unit tests to generate corpus + +* Install Pre-requisites + + ```shell + $ sudo apt-get install gyp ninja-build + ``` + +* download cdm source code (including ODK & OEMCrypto unit tests): + + ```shell + $ git clone sso://widevine-internal/cdm + ``` + +* Build OEMCrypto unit tests and run with --generate_corpus flag to generate + corpus files: + + ```shell + $ cd /path/to/cdm/repo + $ export CDM_DIR=/path/to/cdm/repo + $ export PATH_TO_CDM_DIR=.. + $ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp + $ ninja -C out/Default/ + $ ./out/Default/oemcrypto_unittests --generate_corpus + ``` + +* To avoid uploading huge binary files to git repository, the corpus files + will be saved in fuzzername_seed_corpus.zip format in blockbuster project's + oemcrypto_fuzzing_corpus GCS bucket using gsutil. If you need permissions + for blockbuster project, contact widevine-engprod@google.com. + + ```shell + $ gsutil cp gs://oemcrypto_fuzzing_corpus/ \ + + ``` + +## Testing fuzzer locally + +* Corpus needed to run fuzz tests locally are available in blockbuster + project's oemcrypto_fuzzing_corpus GCS bucket. If you need permissions for + this project, contact widevine-engprod@google.com. Download corpus. + + ```shell + $ gsutil cp gs://oemcrypto_fuzzing_corpus/ \ + + ``` + +* Add flags to generate additional debugging information. Add '-g3' flag to + oemcrypto_fuzztests.gypi cflags_cc in order to generate additional debug + information locally. + +* Build and test fuzz scripts locally using: + + ```shell + $ export CXX=clang++ + $ export CC=clang + $ export GYP_DEFINES="clang=1" + $ cd /path/to/cdm/repo + $ export PATH_TO_CDM_DIR=. + $ gyp --format=ninja --depth=$(pwd) \ + oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp + $ ninja -C out/Default/ + $ mkdir /tmp/new_interesting_corpus + $ ./out/Default/fuzzer_binary /tmp/new_interesting_corpus \ + /path/to/fuzz/seed/corpus/folder + ``` + +* In order to run fuzz script against a crash input, follow the above steps + and run the fuzz binary against crash input rather than seed corpus. + + ```shell + $ ./out/Default/fuzzer_binary crash_input_file + ``` +## Adding a new OEMCrypto fuzz script +* In order to fuzz a new OEMCrypto API in future, a fuzz script can be added to + oemcrypto/test/fuzz_tests folder which ends with _fuzz.cc. + +* In the program, define the function LLVMFuzzerTestOneInput with the following signature: + ``` + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + + return 0; + } + ``` + *Note*: Make sure LLVMFuzzerTestOneInput calls the function you want to fuzz. + +* Add a new target to oemcrypto_fuzztests.gyp file and follow instructions in + [testing fuzzer locally](#testing-fuzzer-locally) to build and test locally. + +## Generate code coverage reports locally + +* Code coverage is a means of measuring fuzzer performance. We want to make + sure that our fuzzer covers all the paths in our code and make any tweeks to + fuzzer logic so we can maximize coverage to get better results. + + Coverage reports for git on borg project is not automated and needs to be + generated manually. Future plan is to build a dashboard for git on borg + coverage reports. + +* In order to generate coverage reports, we need to compile fuzzer binary with + flags to enable coverage. We can remove + `-fsanitize=fuzzer,address,undefined` from oemcrypto_fuzztests.gypi file as + that is needed only while fuzzing. Add following flags to both cflags_cc and + ldflags of oemcrypto_fuzztests.gypi and build fuzz binaries as mentioned in + `Testing fuzzer locally` section. + + ``` + '-fprofile-instr-generate', + '-fcoverage-mapping', + ``` + +* We need to run fuzzer binary against the corpus downloaded from + [clusterfuzz](https://clusterfuzz.corp.google.com/fuzzer-stats). Clock on + download link from corpus_backup column. Use gsutil command to download the + entire corpus for the fuzz binary. + +* Use the following commands to generate raw profile data file with coverage + information and generate a html coverage report for a single fuzzer. More + information about clang source based coverage can be found + [here](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html). Follow + [this](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) for steps + to combine code coverage reports of multiple fuzzers. + + ```shell + # Run fuzz binary against corpus backup to generate default.profraw file. + $ ./out/Default/fuzz_binary path/to/corpus/backup -runs=0 + # Index raw profile files to generate coverage reports. + $ llvm-profdata merge -sparse default.profraw -o default.profdata + # Generate html coverage file. + $ llvm-cov show ./out/Default/fuzz_binary -format=html \ + -instr-profile=default.profdata -o default.html + ``` \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc new file mode 100644 index 00000000..2984ff00 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.cc @@ -0,0 +1,8 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +#include "oemcrypto_fuzz_helper.h" + +namespace wvoec { +void RedirectStdoutToFile() { freopen("log.txt", "a", stdout); } +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h new file mode 100644 index 00000000..d74c1a88 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_helper.h @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +#ifndef OEMCRYPTO_FUZZ_HELPER_H_ +#define OEMCRYPTO_FUZZ_HELPER_H_ + +#include + +#include "FuzzedDataProvider.h" +#include "OEMCryptoCENC.h" +#include "oec_device_features.h" +#include "oec_session_util.h" +#include "oemcrypto_corpus_generator_helper.h" +#include "oemcrypto_session_tests_helper.h" +namespace wvoec { +// Initial setup to create a valid OEMCrypto state such as initializing crypto +// firmware/hardware, installing golden key box etc. in order to fuzz +// OEMCrypto APIs. +class InitializeFuzz : public SessionUtil { + public: + InitializeFuzz() { + wvoec::global_features.Initialize(); + OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); + OEMCrypto_Initialize(); + EnsureTestKeys(); + } + + ~InitializeFuzz() { OEMCrypto_Terminate(); } +}; + +class OEMCryptoLicenseAPIFuzz : public InitializeFuzz { + public: + OEMCryptoLicenseAPIFuzz() : license_messages_(&session_) { + session_.open(); + InstallTestRSAKey(&session_); + session_.GenerateNonce(); + } + + ~OEMCryptoLicenseAPIFuzz() { + session_.close(); + } + + LicenseRoundTrip& license_messages() { return license_messages_; } + + private: + Session session_; + LicenseRoundTrip license_messages_; +}; + +class OEMCryptoProvisioningAPIFuzz : public InitializeFuzz { + public: + OEMCryptoProvisioningAPIFuzz() + : provisioning_messages_(&session_, encoded_rsa_key_) { + // Opens a session and Generates Nonce. + provisioning_messages_.PrepareSession(keybox_); + } + + ~OEMCryptoProvisioningAPIFuzz() { session_.close(); } + + ProvisioningRoundTrip& provisioning_messages() { + return provisioning_messages_; + } + + private: + Session session_; + ProvisioningRoundTrip provisioning_messages_; +}; + +// Initial setup to create a valid state such as creating session, installing +// golden key box etc. in order to fuzz Load Renewal API. +class OEMCryptoRenewalAPIFuzz : public OEMCryptoLicenseAPIFuzz { + public: + OEMCryptoRenewalAPIFuzz() : renewal_messages_(&license_messages()) {} + + RenewalRoundTrip& renewal_messages() { return renewal_messages_; } + + private: + RenewalRoundTrip renewal_messages_; +}; + +// Convert data to valid enum value. +template +void ConvertDataToValidEnum(T max_enum_value, T* t) { + FuzzedDataProvider fuzzed_enum_data(reinterpret_cast(t), sizeof(T)); + *t = static_cast(fuzzed_enum_data.ConsumeIntegralInRange( + 0, static_cast(max_enum_value))); +} + +// Redirect printf and log statements from oemcrypto functions to a file to +// reduce noise +void RedirectStdoutToFile(); +} // namespace wvoec + +#endif // OEMCRYPTO_FUZZ_HELPER_H_ diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h new file mode 100644 index 00000000..a12a1f0a --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzz_structs.h @@ -0,0 +1,31 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. +#ifndef OEMCRYPTO_FUZZ_STRUCTS_H_ +#define OEMCRYPTO_FUZZ_STRUCTS_H_ + +namespace wvoec { +struct OEMCrypto_Renewal_Response_Fuzz { + // Timer limits in core license response needs to be fuzzed as load renewal + // depends on timer limits loaded from license response. + ODK_TimerLimits timer_limits; + // message(core_response + license_renewal_response) which mimics + // response from license renewal server needs to be fuzzed. core_request + // will be used to generate serialized core response. + oemcrypto_core_message::ODK_RenewalRequest core_request; + // Renewal duration seconds needs to be fuzzed which is part of serialized + // core message from license renewal server. + uint64_t renewal_duration_seconds; + // license_renewal_response is of variable length and not included in this + // structure. +}; + +struct OEMCrypto_Request_Fuzz { + // We would like to fuzz computed signature_length, input core_message_length + // that ODK parses and actual message buffer to the request APIs. + size_t signature_length; + size_t core_message_length; +}; +} // namespace wvoec + +#endif // OEMCRYPTO_FUZZ_STRUCTS_H_ \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp index 52d6c67c..a0cd6457 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gyp @@ -5,20 +5,48 @@ # Builds under the CDM ./build.py (target platform) build system # Refer to the distribution package's README for details. { - 'variables': { - 'oemcrypto_lib%': '', + 'target_defaults': { + 'type': 'executable', + 'includes': [ + 'oemcrypto_fuzztests.gypi', + ], }, 'targets': [ { - 'target_name': 'wv_ce_cdm_oemcrypto_generate_signature_fuzz_test', - 'type': 'executable', + 'target_name': 'oemcrypto_load_license_fuzz', 'sources': [ - # The test runner and the testing device certificate. - 'oemcrypto_generate_signature.cc', + 'oemcrypto_load_license_fuzz.cc', ], - 'includes': [ - 'oemcrypto_fuzztests.gypi', + }, + { + 'target_name': 'oemcrypto_load_provisioning_fuzz', + 'sources': [ + 'oemcrypto_load_provisioning_fuzz.cc', ], - }, + }, + { + 'target_name': 'oemcrypto_load_renewal_fuzz', + 'sources': [ + 'oemcrypto_load_renewal_fuzz.cc', + ], + }, + { + 'target_name': 'oemcrypto_license_request_fuzz', + 'sources': [ + 'oemcrypto_license_request_fuzz.cc', + ], + }, + { + 'target_name': 'oemcrypto_provisioning_request_fuzz', + 'sources': [ + 'oemcrypto_provisioning_request_fuzz.cc', + ], + }, + { + 'target_name': 'oemcrypto_renewal_request_fuzz', + 'sources': [ + 'oemcrypto_renewal_request_fuzz.cc', + ], + }, ], } diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi index 03635156..f0d5d163 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi @@ -1,48 +1,82 @@ # Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary #source code may only be used and distributed under the Widevine Master License #Agreement. -# -# Include this in any custom unit test targets. -# Does not include the test runner main. + { + 'variables': { + 'boringssl_libcrypto_path%': '../../../third_party/boringssl/boringssl.gyp:crypto', + 'boringssl_libssl_path%': '../../../third_party/boringssl/boringssl.gyp:ssl', + 'oemcrypto_dir': '../..', + 'platform_specific_dir': '../../../linux/src', + 'privacy_crypto_impl%': 'boringssl', + # Flag used to generate source based code coverage reports. + 'generate_code_coverage_report%': 'false', + 'util_dir': '../../../util', + }, 'sources': [ + '../../odk/src/core_message_deserialize.cpp', + '../../odk/src/core_message_serialize.cpp', '../oec_device_features.cpp', '../oec_key_deriver.cpp', + '../oemcrypto_corpus_generator_helper.cpp', '../oec_session_util.cpp', + '../oemcrypto_corpus_generator_helper.cpp', + 'oemcrypto_fuzz_helper.cc', '../oemcrypto_session_tests_helper.cpp', - '../oemcrypto_session_tests_helper.h', - '../../../cdm/test/device_cert.cpp', - '../../../cdm/test/device_cert.h', + '<(platform_specific_dir)/file_store.cpp', + '<(platform_specific_dir)/log.cpp', + '<(util_dir)/src/platform.cpp', + '<(util_dir)/src/rw_lock.cpp', + '<(util_dir)/src/string_conversions.cpp', + '<(util_dir)/test/test_sleep.cpp', + '<(util_dir)/test/test_clock.cpp', ], 'include_dirs': [ - '../../../core/include', # log.h - '../../include', - '../../ref/src', # oemcrypto_key_ref.h - '../', - '../../../cdm/test', + '../../../third_party/fuzz', + '<(util_dir)/include', + '<(util_dir)/test', + '<(oemcrypto_dir)/include', + '<(oemcrypto_dir)/ref/src', + '<(oemcrypto_dir)/test', + '<(oemcrypto_dir)/test/fuzz_tests', + '<(oemcrypto_dir)/odk/include', + '<(oemcrypto_dir)/odk/src', ], - 'defines': [ - 'OEMCRYPTO_TESTS', - 'OEMCRYPTO_FUZZ_TESTS', - ], - 'libraries': [ - '../../../third_party/fuzz/platforms/x86-64/libFuzzer.a', + 'includes': [ + '../../../util/libssl_dependency.gypi', + '../../ref/oec_ref.gypi', ], 'dependencies': [ - '../../../cdm/cdm.gyp:widevine_ce_cdm_shared', - '../../../third_party/gmock.gyp:gmock', '../../../third_party/gmock.gyp:gtest', + '../../../third_party/gmock.gyp:gmock', + ], + 'defines': [ + 'OEMCRYPTO_FUZZ_TESTS', ], 'conditions': [ - ['oemcrypto_lib==""', { - 'includes': [ - '../../ref/oec_ref.gypi', - ], - }, { - 'libraries': [ - '../../../third_party/fuzz/platforms/x86-64/libFuzzer.a', - '<(oemcrypto_lib)', - ], + ['generate_code_coverage_report=="false"', { + # Include flags to build fuzzer binaries for cluster fuzz. + 'cflags_cc': [ + '-std=c++11', + '-fsanitize=fuzzer,address,undefined', + # Need -g flag to include source line numbers in error stack trace. + '-g', + ], }], + ['generate_code_coverage_report=="true"', { + # Include flags to build fuzzer binaries to generate source based code coverage reports. + 'cflags_cc': [ + '-std=c++11', + '-fprofile-instr-generate', + '-fcoverage-mapping', + ], + }], + ], # conditions + 'ldflags': [ + '-fPIC', + '-fsanitize=fuzzer,address,undefined', + ], + 'libraries': [ + '-lpthread', ], } diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_license_request_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_license_request_fuzz.cc new file mode 100644 index 00000000..d100ddea --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_license_request_fuzz.cc @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +namespace wvoec { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + // Reject the input if it is less than fuzz data structure size. + if (size < sizeof(OEMCrypto_Request_Fuzz)) { + return 0; + } + // Input for license request API will be modified by OEMCrypto, hence it + // cannot be a const. Fuzzer complains if const identifier is removed of data, + // hence copying data into a non const pointer. + uint8_t* input = new uint8_t[size]; + memcpy(input, data, size); + OEMCryptoLicenseAPIFuzz license_api_fuzz; + license_api_fuzz.license_messages().InjectFuzzedRequestData(input, size); + delete[] input; + return 0; +} +} // namespace wvoec \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc new file mode 100644 index 00000000..e125ae04 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_license_fuzz.cc @@ -0,0 +1,30 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" + +namespace wvoec { +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + if (size < sizeof(ODK_ParsedLicense) + sizeof(MessageData)) { + return 0; + } + OEMCryptoLicenseAPIFuzz license_api_fuzz; + license_api_fuzz.license_messages().SignAndVerifyRequest(); + // Interpreting input fuzz data as unencrypted (core_response + license + // message data) from license server. + license_api_fuzz.license_messages().InjectFuzzedResponseData(data, size); + + // Convert OEMCrypto_LicenseType in core_response to a valid enum value. + ConvertDataToValidEnum( + OEMCrypto_LicenstType_MaxValue, + &license_api_fuzz.license_messages().core_response().license_type); + + license_api_fuzz.license_messages().EncryptAndSignResponse(); + license_api_fuzz.license_messages().LoadResponse(); + return 0; +} +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc new file mode 100644 index 00000000..739f79f4 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_provisioning_fuzz.cc @@ -0,0 +1,27 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" + +namespace wvoec { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + if (size < sizeof(ODK_ParsedProvisioning) + sizeof(RSAPrivateKeyMessage)) { + return 0; + } + + OEMCryptoProvisioningAPIFuzz provisioning_api_fuzz; + provisioning_api_fuzz.provisioning_messages().SignAndVerifyRequest(); + // Interpreting input fuzz data as unencrypted(core_response + provisioning + // message data) from provisioning server. + provisioning_api_fuzz.provisioning_messages().InjectFuzzedResponseData(data, + size); + provisioning_api_fuzz.provisioning_messages().EncryptAndSignResponse(); + provisioning_api_fuzz.provisioning_messages().LoadResponse(); + return 0; +} +} // namespace wvoec diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc new file mode 100644 index 00000000..93baee6d --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_load_renewal_fuzz.cc @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +namespace wvoec { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + if (size < sizeof(OEMCrypto_Renewal_Response_Fuzz)) { + return 0; + } + // Copy input data to OEMCrypto_Renewal_Response_Fuzz and rest of message + // into encrypted license_renewal_response. + OEMCrypto_Renewal_Response_Fuzz fuzzed_data; + memcpy(&fuzzed_data, data, sizeof(fuzzed_data)); + const uint8_t* renewal_response = + data + sizeof(OEMCrypto_Renewal_Response_Fuzz); + const size_t renewal_response_size = + size - sizeof(OEMCrypto_Renewal_Response_Fuzz); + + OEMCryptoLicenseAPIFuzz license_api_fuzz; + license_api_fuzz.license_messages().SignAndVerifyRequest(); + license_api_fuzz.license_messages().CreateDefaultResponse(); + // Inject timer limits from fuzzed input to timer_limits field from + // core license response. + license_api_fuzz.license_messages().InjectFuzzedTimerLimits(fuzzed_data); + license_api_fuzz.license_messages().EncryptAndSignResponse(); + license_api_fuzz.license_messages().LoadResponse(); + + OEMCryptoRenewalAPIFuzz renewal_response_fuzz; + renewal_response_fuzz.renewal_messages().SignAndVerifyRequest(); + renewal_response_fuzz.renewal_messages().InjectFuzzedResponseData( + fuzzed_data, renewal_response, renewal_response_size); + renewal_response_fuzz.renewal_messages().LoadResponse(); + return 0; +} +} // namespace wvoec \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_provisioning_request_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_provisioning_request_fuzz.cc new file mode 100644 index 00000000..1cda2abd --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_provisioning_request_fuzz.cc @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +namespace wvoec { +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + // If input size is less than fuzz data structure size, reject the input. + if (size < sizeof(OEMCrypto_Request_Fuzz)) { + return 0; + } + // Input for provisioning request API will be modified by OEMCrypto, hence it + // cannot be a const. Fuzzer complains if const identifier is removed of data, + // hence copying data into a non const pointer. + uint8_t* input = new uint8_t[size]; + memcpy(input, data, size); + OEMCryptoProvisioningAPIFuzz provisioning_api_fuzz; + provisioning_api_fuzz.provisioning_messages().InjectFuzzedRequestData(input, + size); + delete[] input; + return 0; +} +} // namespace wvoec \ No newline at end of file diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc new file mode 100644 index 00000000..67ecb0a9 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/oemcrypto_renewal_request_fuzz.cc @@ -0,0 +1,27 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "oemcrypto_fuzz_helper.h" +#include "oemcrypto_fuzz_structs.h" + +namespace wvoec { +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // Redirect printf and log statements from oemcrypto functions to a file to + // reduce noise + RedirectStdoutToFile(); + // If input size is less than fuzz data structure, reject the input. + if (size < sizeof(OEMCrypto_Request_Fuzz)) { + return 0; + } + // Input for renewal request API will be modified by OEMCrypto, hence it + // cannot be a const. Fuzzer complains if const identifier is removed of data, + // hence copying data into a non const pointer. + uint8_t* input = new uint8_t[size]; + memcpy(input, data, size); + OEMCryptoRenewalAPIFuzz renewal_api_fuzz; + renewal_api_fuzz.renewal_messages().InjectFuzzedRequestData(input, size); + delete[] input; + return 0; +} +} // namespace wvoec \ No newline at end of file diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index 74f4d492..7c4fa267 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -8,58 +8,15 @@ #include -#ifdef _WIN32 -# include -#else -# include -# include -#endif - #include #include "oec_test_data.h" +#include "test_sleep.h" namespace wvoec { DeviceFeatures global_features; -bool CanChangeTime() { -#ifdef _WIN32 - LUID desired_id; - if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id)) - return false; - HANDLE token; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) - return false; - std::unique_ptr safe_token(token, &CloseHandle); - - // This queries all the permissions given to the token to determine if we can - // change the system time. Note this is subtly different from PrivilegeCheck - // as that only checks "enabled" privileges; even with admin rights, the - // privilege is default disabled, even when granted. - - DWORD size = 0; - // Determine how big we need to allocate first. - GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size); - // Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc - std::unique_ptr privileges( - (TOKEN_PRIVILEGES*)malloc(size), &free); - if (privileges && GetTokenInformation(token, TokenPrivileges, - privileges.get(), size, &size)) { - for (int i = 0; i < privileges->PrivilegeCount; i++) { - if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart && - privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) { - return true; - } - } - } - - return false; -#else - return getuid() == 0; -#endif -} - void DeviceFeatures::Initialize() { if (initialized_) return; uses_keybox = false; @@ -188,8 +145,11 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { // clang-format on // Some tests may require root access. If user is not root, filter these tests // out. - if (!CanChangeTime()) { + if (!wvcdm::TestSleep::CanChangeSystemTime()) { + printf("Filtering out TimeRollbackPrevention.\n"); FilterOut(&filter, "*TimeRollbackPrevention*"); + } else { + printf("Can change time. I will run TimeRollbackPrevention.\n"); } // Performance tests take a long time. Filter them out if they are not // specifically requested. diff --git a/oemcrypto/test/oec_key_deriver.cpp b/oemcrypto/test/oec_key_deriver.cpp index 34fb0fe5..d2bff254 100644 --- a/oemcrypto/test/oec_key_deriver.cpp +++ b/oemcrypto/test/oec_key_deriver.cpp @@ -61,6 +61,12 @@ void Encryptor::PadAndEncryptProvisioningMessage( EXPECT_EQ(1, GetRandBytes(data->rsa_key_iv, KEY_IV_SIZE)); ASSERT_EQ(enc_key_.size(), KEY_SIZE); *encrypted = *data; + if (data->rsa_key_length > sizeof(data->rsa_key)) { + // OEMCrypto Fuzzing: fuzzed |rsa_key_length| overflows the allocated + // buffer. Skip encryption in that case. + return; + } + size_t padding = AES_BLOCK_SIZE - (data->rsa_key_length % AES_BLOCK_SIZE); memset(data->rsa_key + data->rsa_key_length, static_cast(padding), padding); diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index 49ab4acf..7cc37f8b 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -31,8 +31,10 @@ #include "core_message_serialize.h" #include "disallow_copy_and_assign.h" #include "log.h" +#include "odk_structs.h" #include "oec_device_features.h" #include "oec_test_data.h" +#include "oemcrypto_corpus_generator_helper.h" #include "oemcrypto_types.h" #include "platform.h" #include "string_conversions.h" @@ -162,6 +164,10 @@ void RoundTrip(gen_signature_length, + core_message_length, data); + } vector gen_signature(gen_signature_length); ASSERT_EQ(PrepAndSignRequest(session()->session_id(), data.data(), @@ -177,6 +183,27 @@ void RoundTrip +void RoundTrip::InjectFuzzedRequestData(uint8_t* data, + size_t size) { + OEMCrypto_Request_Fuzz fuzz_structure; + // Copy data into fuzz structure, cap signature length at 1mb as it will be + // used to initialize signature vector. + memcpy(&fuzz_structure, data, sizeof(fuzz_structure)); + fuzz_structure.signature_length = + std::min(fuzz_structure.signature_length, MB); + vector signature(fuzz_structure.signature_length); + + // Interpret rest of data as actual message buffer to request APIs. + uint8_t* message_ptr = data + sizeof(fuzz_structure); + size_t message_size = size - sizeof(fuzz_structure); + PrepAndSignRequest(session()->session_id(), message_ptr, message_size, + &fuzz_structure.core_message_length, signature.data(), + &fuzz_structure.signature_length); +} + template OEMCrypto_Substring RoundTripnonce(); +} + OEMCryptoResult ProvisioningRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); + // Write corpus for oemcrypto_load_provisioning_fuzz. Fuzz script expects + // unencrypted response from provisioning server as input corpus data. + // Data will be encrypted and signed again explicitly by fuzzer script after + // mutations. + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_load_provisioning_fuzz_seed_corpus"); + // Corpus for license response fuzzer should be in the format: + // unencrypted (core_response + response_data). + AppendToFile(file_name, reinterpret_cast(&core_response_), + sizeof(ODK_ParsedProvisioning)); + AppendToFile(file_name, reinterpret_cast(&response_data_), + sizeof(response_data_)); + } size_t wrapped_key_length = 0; const OEMCryptoResult sts = LoadResponseNoRetry(session, &wrapped_key_length); if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return sts; @@ -472,6 +526,69 @@ void LicenseRoundTrip::CreateDefaultResponse() { FillCoreResponseSubstrings(); } +void LicenseRoundTrip::ConvertDataToValidBools(ODK_ParsedLicense* t) { + t->nonce_required = ConvertByteToValidBoolean(&t->nonce_required); + t->timer_limits.soft_enforce_playback_duration = ConvertByteToValidBoolean( + &t->timer_limits.soft_enforce_playback_duration); + t->timer_limits.soft_enforce_rental_duration = + ConvertByteToValidBoolean(&t->timer_limits.soft_enforce_rental_duration); +} + +void LicenseRoundTrip::InjectFuzzedTimerLimits( + OEMCrypto_Renewal_Response_Fuzz& fuzzed_data) { + // Interpreting fuzz data as timer limits. + // Copy timer limits from data. + memcpy(&core_response_.timer_limits, &fuzzed_data.timer_limits, + sizeof(fuzzed_data.timer_limits)); + ConvertDataToValidBools(&core_response_); +} + +void LicenseRoundTrip::InjectFuzzedResponseData(const uint8_t* data, + size_t size) { + // Interpreting fuzz data as unencrypted core_response + message_data + const size_t core_response_size = sizeof(ODK_ParsedLicense); + // Copy core_response from data. + memcpy(&core_response_, data, core_response_size); + // Maximum number of keys could be kMaxNumKeys(30). key_array_length can be + // any random value as it is read from fuzz data. + // Key data array(MessageKeyData keys[kMaxNumKeys]) will be looped over + // key_array_length number of times during LoadLicense. If key_array_length is + // more than kMaxNumKeys, setting it to max value of kMaxNumKeys as we should + // not go out of bounds of this array length. For corpus, this value is + // already hard coded to 4. + if (core_response_.key_array_length > kMaxNumKeys) { + core_response_.key_array_length = kMaxNumKeys; + } + // For corpus data, this value gets set to 4, but we need to test other + // scenarios too, hence reading key_array_length value. + set_num_keys(core_response_.key_array_length); + ConvertDataToValidBools(&core_response_); + // TODO(b/157520981): Once assertion bug is fixed, for loop can be removed. + // Workaround for the above bug: key_data.length and key_id.length are being + // used in AES decryption process and are expected to be a multiple of 16. An + // assertion in AES decryption fails if this condition is not met which will + // crash fuzzer. + for (uint32_t i = 0; i < num_keys_; ++i) { + size_t key_data_length = core_response_.key_array[i].key_data.length; + size_t key_id_length = core_response_.key_array[i].key_id.length; + if (key_data_length % 16 != 0) { + core_response_.key_array[i].key_data.length = + key_data_length - (key_data_length % 16); + } + if (key_id_length % 16 != 0) { + core_response_.key_array[i].key_id.length = + key_id_length - (key_id_length % 16); + } + } + + // Copy response_data from data and set nonce to match one in request to pass + // nonce validations. + memcpy(&response_data_, data + core_response_size, sizeof(response_data_)); + for (uint32_t i = 0; i < num_keys_; ++i) { + response_data_.keys[i].control.nonce = session()->nonce(); + } +} + void LicenseRoundTrip::CreateResponseWithGenericCryptoKeys() { CreateDefaultResponse(); response_data_.keys[0].control.control_bits |= @@ -540,17 +657,28 @@ void LicenseRoundTrip::EncryptAndSignResponse() { 2 * MAC_KEY_SIZE, response_data_.mac_key_iv); for (unsigned int i = 0; i < num_keys_; i++) { - memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE); - AES_KEY aes_key; - AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key); - AES_cbc_encrypt( - reinterpret_cast(&response_data_.keys[i].control), - reinterpret_cast(&encrypted_response_data_.keys[i].control), - KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); - session_->key_deriver().CBCEncrypt( - &response_data_.keys[i].key_data[0], - &encrypted_response_data_.keys[i].key_data[0], - response_data_.keys[i].key_data_length, response_data_.keys[i].key_iv); + // OEMCrypto Fuzzing skip encryption: key_data_length can be any number when + // called from fuzzer. We want to skip encryption if that happens and let + // LoadLicense be called with unencrypted data for that key. OEMCrypto + // Fuzzing skip encryption: key_data_length being a random value will + // encrypt data which is not expected to, there by leading to inefficient + // fuzzing. + if (response_data_.keys[i].key_data_length <= + sizeof(response_data_.keys[i].key_data) && + response_data_.keys[i].key_data_length % 16 == 0) { + memcpy(iv_buffer, &response_data_.keys[i].control_iv[0], KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&response_data_.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt( + reinterpret_cast(&response_data_.keys[i].control), + reinterpret_cast(&encrypted_response_data_.keys[i].control), + KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + session_->key_deriver().CBCEncrypt( + &response_data_.keys[i].key_data[0], + &encrypted_response_data_.keys[i].key_data[0], + response_data_.keys[i].key_data_length, + response_data_.keys[i].key_iv); + } } if (api_version_ < kCoreMessagesAPI) { serialized_core_message_.resize(0); @@ -588,6 +716,21 @@ void LicenseRoundTrip::EncryptAndSignResponse() { OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { EXPECT_NE(session, nullptr); + // Write corpus for oemcrypto_load_license_fuzz. Fuzz script expects + // unecnrypted response from license server as input corpus data. + // Data will be encrypted and signed again explicitly by fuzzer script + // after mutations. + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_load_license_fuzz_seed_corpus"); + // Corpus for license response fuzzer should be in the format: + // core_response + response_data. + AppendToFile(file_name, reinterpret_cast(&core_response_), + sizeof(ODK_ParsedLicense)); + AppendToFile(file_name, reinterpret_cast(&response_data_), + sizeof(response_data_)); + } + // Some tests adjust the offset to be beyond the length of the message. Here, // we create a duplicate of the main message buffer so that these offsets do // not point to garbage data. The goal is to make sure OEMCrypto is verifying @@ -630,7 +773,7 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session) { // Note: we verify content licenses here. For entitlement license, we verify // the key control blocks after loading entitled content keys. - if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(); + if (license_type_ == OEMCrypto_ContentLicense) VerifyTestKeys(session); } return result; } @@ -644,12 +787,12 @@ OEMCryptoResult LicenseRoundTrip::ReloadResponse(Session* session) { // with the truth key control block. Failures in this function probably // indicate the OEMCrypto_LoadLicense/LoadKeys did not correctly process the key // control block. -void LicenseRoundTrip::VerifyTestKeys() { +void LicenseRoundTrip::VerifyTestKeys(Session* session) { for (unsigned int i = 0; i < num_keys_; i++) { KeyControlBlock block; size_t size = sizeof(block); OEMCryptoResult sts = OEMCrypto_QueryKeyControl( - session_->session_id(), response_data_.keys[i].key_id, + session->session_id(), response_data_.keys[i].key_id, response_data_.keys[i].key_id_length, reinterpret_cast(&block), &size); if (sts != OEMCrypto_ERROR_NOT_IMPLEMENTED) { @@ -906,7 +1049,46 @@ void RenewalRoundTrip::EncryptAndSignResponse() { &response_signature_); } +void RenewalRoundTrip::InjectFuzzedResponseData( + OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, + const uint8_t* renewal_response, const size_t renewal_response_size) { + // Serializing core message. + // This call also sets nonce in core response to match with session nonce. + oemcrypto_core_message::serialize::CreateCoreRenewalResponse( + fuzzed_data.core_request, fuzzed_data.renewal_duration_seconds, + &serialized_core_message_); + + // Copy serialized core message and encrypted response from data and + // calculate signature. Now we will have a valid signature for data generated + // by fuzzer. + encrypted_response_.assign(serialized_core_message_.begin(), + serialized_core_message_.end()); + encrypted_response_.insert(encrypted_response_.end(), renewal_response, + renewal_response + renewal_response_size); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); +} + OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { + // Write corpus for oemcrypto_load_renewal_fuzz. Fuzz script expects + // encrypted response from Renewal server as input corpus data. + // Data will be signed again explicitly by fuzzer script after mutations. + if (ShouldGenerateCorpus()) { + const std::string file_name = + GetFileName("oemcrypto_load_renewal_fuzz_seed_corpus"); + // Corpus for renewal response fuzzer should be in the format: + // OEMCrypto_Renewal_Response_Fuzz + license_renewal_response. + OEMCrypto_Renewal_Response_Fuzz renewal_response_fuzz; + renewal_response_fuzz.core_request = core_request_; + renewal_response_fuzz.renewal_duration_seconds = renewal_duration_seconds_; + AppendToFile(file_name, + reinterpret_cast(&renewal_response_fuzz), + sizeof(renewal_response_fuzz)); + AppendToFile(file_name, + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + } if (license_messages_->api_version() < kCoreMessagesAPI) { return OEMCrypto_RefreshKeys( session->session_id(), encrypted_response_.data(), @@ -1009,7 +1191,7 @@ void Session::GenerateDerivedKeysFromSessionKey() { // Uses test certificate. vector session_key; vector enc_session_key; - if (public_rsa_ == nullptr) PreparePublicKey(); + ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code."; // A failure here probably indicates that there is something wrong with the // test program and its dependency on BoringSSL. ASSERT_TRUE(GenerateRSASessionKey(&session_key, &enc_session_key)); @@ -1297,12 +1479,11 @@ void Session::VerifyRSASignature(const vector& message, const uint8_t* signature, size_t signature_length, RSA_Padding_Scheme padding_scheme) { - EXPECT_TRUE(nullptr != public_rsa_) - << "No public RSA key loaded in test code.\n"; + ASSERT_NE(public_rsa_, nullptr) << "No public RSA key loaded in test code."; - EXPECT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) + ASSERT_EQ(static_cast(RSA_size(public_rsa_)), signature_length) << "Signature size is wrong. " << signature_length << ", should be " - << RSA_size(public_rsa_) << "\n"; + << RSA_size(public_rsa_); if (padding_scheme == kSign_RSASSA_PSS) { boringssl_ptr pkey(EVP_PKEY_new()); @@ -1471,7 +1652,10 @@ void Session::VerifyReport(Test_PST_Report expected, int64_t time_first_decrypt, int64_t time_last_decrypt) { const int64_t now = wvcdm::Clock().GetCurrentTime(); - expected.seconds_since_license_received = now - time_license_received; + expected.seconds_since_license_received = + (time_license_received > 0 && time_license_received < now) + ? now - time_license_received + : 0; expected.seconds_since_first_decrypt = (time_first_decrypt > 0 && time_first_decrypt < now) ? now - time_first_decrypt @@ -1482,4 +1666,41 @@ void Session::VerifyReport(Test_PST_Report expected, : 0; ASSERT_NO_FATAL_FAILURE(VerifyPST(expected)); } + +bool ConvertByteToValidBoolean(const bool* in) { + const char* buf = reinterpret_cast(in); + for (size_t i = 0; i < sizeof(bool); i++) { + if (buf[i]) { + return true; + } + } + return false; +} + +template +void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length, + vector& data) { + std::string file_name; + if (std::is_same::value) { + file_name = GetFileName("oemcrypto_license_request_fuzz_seed_corpus"); + } else if (std::is_same< + CoreRequest, + oemcrypto_core_message::ODK_ProvisioningRequest>::value) { + file_name = GetFileName("oemcrypto_provisioning_request_fuzz_seed_corpus"); + } else if (std::is_same::value) { + file_name = GetFileName("oemcrypto_renewal_request_fuzz_seed_corpus"); + } else { + LOGE("Invalid CoreRequest type while writing request api corups."); + } + // Corpus for request APIs should be signature_length + core_message_length + + // data pointer. + AppendToFile(file_name, reinterpret_cast(&signature_length), + sizeof(signature_length)); + AppendToFile(file_name, reinterpret_cast(&core_message_length), + sizeof(core_message_length)); + AppendToFile(file_name, reinterpret_cast(data.data()), + data.size()); +} } // namespace wvoec diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index 36848d52..ffde2994 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -18,6 +18,7 @@ #include "odk.h" #include "oec_device_features.h" #include "oec_key_deriver.h" +#include "oemcrypto_fuzz_structs.h" #include "oemcrypto_types.h" #include "pst_report.h" @@ -32,6 +33,8 @@ void PrintTo(const vector& value, ostream* os); } // namespace std namespace wvoec { +// OEMCrypto Fuzzing: Set max signture length to 1mb. +const size_t MB = 1024 * 1024; // Make sure this is larger than kMaxKeysPerSession, in oemcrypto_test.cpp constexpr size_t kMaxNumKeys = 30; @@ -158,6 +161,9 @@ class RoundTrip { // Have OEMCrypto sign a request message and then verify the signature and the // core message. virtual void SignAndVerifyRequest(); + // Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid + // License/Provisioning/Renwal request data that can be serialized. + virtual void InjectFuzzedRequestData(uint8_t* data, size_t size); // Create a default |response_data| and |core_response|. virtual void CreateDefaultResponse() = 0; // Copy fields from |response_data| to |padded_response_data|, encrypting @@ -241,6 +247,11 @@ class ProvisioningRoundTrip void set_allowed_schemes(uint32_t allowed_schemes) { allowed_schemes_ = allowed_schemes; } + // Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid + // provisioning response data that can be parsed. Calculates signature for + // data generated by fuzzer, so that signature validation passes when parsing + // provisioning response. + void InjectFuzzedResponseData(const uint8_t* data, size_t size); protected: void VerifyRequestSignature(const vector& data, @@ -286,6 +297,18 @@ class LicenseRoundTrip license_type_(OEMCrypto_ContentLicense), request_hash_() {} void CreateDefaultResponse() override; + // Used for OEMCrypto Fuzzing: Function to inject fuzzed timer limits + // into timer_limits field from core_response. We need to fuzz timer + // limits in order to efficiently fuzz load renewal response API. + void InjectFuzzedTimerLimits(OEMCrypto_Renewal_Response_Fuzz& fuzzed_data); + // Used for OEMCrypto Fuzzing: Function to convert fuzzer data to valid + // License response data that can be parsed. Calculates signature for data + // generated by fuzzer, so that signature validation passes when parsing + // license response. + void InjectFuzzedResponseData(const uint8_t* data, size_t size); + // Used for OEMCrypto Fuzzing: Convert boolean flags in parsed_license to + // valid bytes to avoid errors from msan. + void ConvertDataToValidBools(ODK_ParsedLicense* t); // Create a license with four keys. Each key is responsible for one of generic // encrypt (key 0), decrypt (key 1), sign (key 2) and verify (key 3). Each key // is allowed only one type of operation. @@ -298,7 +321,7 @@ class LicenseRoundTrip // Reload an offline license into a different session. This derives new mac // keys and then calls LoadResponse. OEMCryptoResult ReloadResponse(Session* session); - void VerifyTestKeys(); + void VerifyTestKeys(Session* session); // Set the default key control block for all keys. This is used in // CreateDefaultResponse. The key control block determines the restrictions // that OEMCrypto should place on a key's use. For example, it specifies the @@ -386,6 +409,9 @@ class RenewalRoundTrip is_release_(false) {} void CreateDefaultResponse() override; void EncryptAndSignResponse() override; + void InjectFuzzedResponseData(OEMCrypto_Renewal_Response_Fuzz& fuzzed_data, + const uint8_t* renewal_response, + const size_t renewal_response_size); OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; uint64_t renewal_duration_seconds() const { @@ -599,6 +625,13 @@ class Session { string pst_; }; +// Used for OEMCrypto Fuzzing: Convert byte to a valid boolean to avoid errors +// generated by msan. +bool ConvertByteToValidBoolean(const bool* in); +// Used for OEMCrypto Fuzzing: Generates corpus for request APIs. +template +void WriteRequestApiCorpus(size_t signature_length, size_t core_message_length, + vector& data); } // namespace wvoec #endif // CDM_OEC_SESSION_UTIL_H_ diff --git a/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp b/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp new file mode 100644 index 00000000..33c0f7ac --- /dev/null +++ b/oemcrypto/test/oemcrypto_corpus_generator_helper.cpp @@ -0,0 +1,34 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ +#include "oemcrypto_corpus_generator_helper.h" + +#include +#include + +namespace wvoec { +bool g_generate_corpus; + +void AppendToFile(const std::string& file_name, const char* message, + const size_t message_size) { + std::ofstream filebuf(file_name.c_str(), std::ios::app | std::ios::binary); + if (!filebuf) { + std::cout << "Cannot open file " << file_name.c_str() << std::endl; + } + filebuf.write(message, message_size); + filebuf.close(); +} + +std::string GetFileName(const char* directory) { + std::string file_name(PATH_TO_CORPUS); + file_name += directory; + file_name += "/"; + file_name += std::to_string(rand()); + return file_name; +} + +void SetGenerateCorpus(bool should_generate_corpus) { + g_generate_corpus = should_generate_corpus; +} +bool ShouldGenerateCorpus() { return g_generate_corpus; } +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_corpus_generator_helper.h b/oemcrypto/test/oemcrypto_corpus_generator_helper.h new file mode 100644 index 00000000..e145fe63 --- /dev/null +++ b/oemcrypto/test/oemcrypto_corpus_generator_helper.h @@ -0,0 +1,25 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary */ +/* source code may only be used and distributed under the Widevine Master */ +/* License Agreement. */ +#ifndef CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ +#define CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ + +#define PATH_TO_CORPUS "./oemcrypto/test/fuzz_tests/corpus/" + +#include +#include +#include + +namespace wvoec { +void AppendToFile(const std::string& file_name, const char* message, + const size_t message_size); + +std::string GetFileName(const char* directory); + +void SetGenerateCorpus(bool should_generate_corpus); +// Output of this function decides if binary data needs to be written +// to corpus files or not. Controlled by --generate_corpus flag. +bool ShouldGenerateCorpus(); +} // namespace wvoec + +#endif // CDM_OEMCRYPTO_CORPUS_GENERATOR_HELPER_H_ diff --git a/oemcrypto/test/oemcrypto_session_tests_helper.cpp b/oemcrypto/test/oemcrypto_session_tests_helper.cpp index 9e2bc7e7..da8d5ec2 100644 --- a/oemcrypto/test/oemcrypto_session_tests_helper.cpp +++ b/oemcrypto/test/oemcrypto_session_tests_helper.cpp @@ -81,6 +81,6 @@ void SessionUtil::InstallTestRSAKey(Session* s) { ASSERT_NO_FATAL_FAILURE(s->InstallRSASessionTestKey(wrapped_rsa_key_)); } // Test RSA key should be loaded. - ASSERT_NO_FATAL_FAILURE(s->GenerateDerivedKeysFromSessionKey()); + ASSERT_NO_FATAL_FAILURE(s->PreparePublicKey()); } } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 06144d70..50e1027f 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -14,12 +14,6 @@ #include #include -#ifdef _WIN32 -# include -#else -# include -#endif - #include #include #include @@ -122,37 +116,6 @@ const size_t kLargeMessageSize[] = { 8*KiB, 8*KiB, 16*KiB, 32*KiB}; // const size_t kAV1NumberSubsamples[] = { 72, 144, 288, 576}; // clang-format on -/** @return The Unix time of the given time point. */ -template -uint64_t UnixTime( - const std::chrono::time_point& point) { - return point.time_since_epoch() / std::chrono::seconds(1); -} - -#ifdef _WIN32 -using NativeTime = SYSTEMTIME; -#else -using NativeTime = timeval; -#endif - -void AddNativeTime(int64_t delta_seconds, NativeTime* time) { -#ifdef _WIN32 - // See remarks from this for why this series is used. - // https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b - FILETIME file_time; - ASSERT_TRUE(SystemTimeToFileTime(time, &file_time)); - uint64_t long_time = static_cast(file_time.dwLowDateTime) | - (static_cast(file_time.dwHighDateTime) << 32); - long_time += - delta_seconds * 1e7; // long_time is in 100-nanosecond intervals. - file_time.dwLowDateTime = long_time & ((1ull << 32) - 1); - file_time.dwHighDateTime = long_time >> 32; - ASSERT_TRUE(FileTimeToSystemTime(&file_time, time)); -#else - time->tv_sec += delta_seconds; -#endif -} - } // namespace class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { @@ -191,13 +154,13 @@ class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { // tests are failing when the device has the wrong keybox installed. TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 16.2. Tests last updated 2020-03-27"; + "OEMCrypto unit tests for API 16.3. Tests last updated 2020-06-01"; cout << " " << log_message << "\n"; LOGI("%s", log_message.c_str()); // If any of the following fail, then it is time to update the log message // above. EXPECT_EQ(ODK_MAJOR_VERSION, 16); - EXPECT_EQ(ODK_MINOR_VERSION, 2); + EXPECT_EQ(ODK_MINOR_VERSION, 3); EXPECT_EQ(kCurrentAPI, 16u); const char* level = OEMCrypto_SecurityLevel(); ASSERT_NE(nullptr, level); @@ -725,6 +688,7 @@ TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) { ASSERT_NO_FATAL_FAILURE(s.open()); // Install the DRM Cert's RSA key. ASSERT_NO_FATAL_FAILURE(s.InstallRSASessionTestKey(wrapped_rsa_key_)); + ASSERT_NO_FATAL_FAILURE(s.PreparePublicKey()); // Request the OEM Cert. -- This should NOT load the OEM Private key. vector public_cert; size_t public_cert_length = 0; @@ -890,7 +854,13 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithNoRequest) { license_messages_.core_request().api_minor_version = ODK_MINOR_VERSION; ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); + + // Load license in a different session, which did not create the request. + Session session2; + ASSERT_NO_FATAL_FAILURE(session2.open()); + ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&session2)); + ASSERT_NO_FATAL_FAILURE(session2.GenerateDerivedKeysFromSessionKey()); + ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse(&session2)); } // Verify that a license may be loaded with a nonce. @@ -1865,7 +1835,7 @@ TEST_P(OEMCryptoRefreshTest, RefreshLargeBuffer) { LoadLicense(); RenewalRoundTrip renewal_messages(&license_messages_); const size_t max_size = GetResourceValue(kLargeMessageSize); - license_messages_.set_message_size(max_size); + renewal_messages.set_message_size(max_size); MakeRenewalRequest(&renewal_messages); LoadRenewal(&renewal_messages, OEMCrypto_SUCCESS); } @@ -2233,10 +2203,11 @@ class OEMCryptoSessionTestsDecryptTests void TestDecryptCENC() { OEMCryptoResult sts; + // OEMCrypto only supports providing a decrypt hash for one sample. + if (samples_.size() > 1) verify_crc_ = false; + // If supported, check the decrypt hashes. if (verify_crc_) { - // OEMCrypto only supports providing a decrypt hash for the first sample - // in the sample array. const TestSample& sample = samples_[0]; uint32_t hash = @@ -2291,7 +2262,7 @@ class OEMCryptoSessionTestsDecryptTests } } } - if (global_features.supports_crc) { + if (verify_crc_) { uint32_t frame; ASSERT_EQ(OEMCrypto_GetHashErrorCode(session_.session_id(), &frame), OEMCrypto_SUCCESS); @@ -2611,18 +2582,6 @@ TEST_P(OEMCryptoLicenseTest, DecryptSecureToClear) { session_.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); } -// If analog is forbidden, then decrypt to a clear buffer should be forbidden. -TEST_P(OEMCryptoLicenseTest, DecryptNoAnalogToClearAPI13) { - ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - license_messages_.set_control(wvoec::kControlDisableAnalogOutput); - ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); - ASSERT_NO_FATAL_FAILURE( - session_.TestDecryptCTR(true, OEMCrypto_ERROR_ANALOG_OUTPUT)); -} - // Test that key duration is honored. TEST_P(OEMCryptoLicenseTest, KeyDuration) { ASSERT_NO_FATAL_FAILURE(session_.GenerateNonce()); @@ -4201,6 +4160,11 @@ class OEMCryptoGenericCryptoTest : public OEMCryptoRefreshTest { } } + void ResizeBuffer(size_t new_size) { + buffer_size_ = new_size; + InitializeClearBuffer(); // Re-initialize the clear buffer. + } + void EncryptAndLoadKeys() { ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); @@ -4536,7 +4500,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) { // Test Generic_Encrypt with the maximum buffer size. TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { - buffer_size_ = GetResourceValue(kMaxGenericBuffer); + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); unsigned int key_index = 0; vector expected_encrypted; @@ -4559,7 +4523,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { // Test Generic_Decrypt with the maximum buffer size. TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { // Some applications are known to pass in a block that is almost 400k. - buffer_size_ = GetResourceValue(kMaxGenericBuffer); + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); unsigned int key_index = 1; vector encrypted; @@ -4580,7 +4544,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { // Test Generic_Sign with the maximum buffer size. TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { - buffer_size_ = GetResourceValue(kMaxGenericBuffer); + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); unsigned int key_index = 2; vector expected_signature; @@ -4608,7 +4572,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { // Test Generic_Verify with the maximum buffer size. TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { - buffer_size_ = GetResourceValue(kMaxGenericBuffer); + ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); unsigned int key_index = 3; vector signature; @@ -4917,6 +4881,7 @@ class LicenseWithUsageEntry { ASSERT_NO_FATAL_FAILURE(session_.open()); ASSERT_NO_FATAL_FAILURE(session_.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(util->InstallTestRSAKey(&session_)); + ASSERT_NO_FATAL_FAILURE(session_.GenerateDerivedKeysFromSessionKey()); ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); } @@ -5719,19 +5684,29 @@ class OEMCryptoUsageTableDefragTest : public OEMCryptoUsageTableTest { void ShrinkHeader(uint32_t new_size, OEMCryptoResult expected_result = OEMCrypto_SUCCESS) { + // We call OEMCrypto_ShrinkUsageTableHeader once with a zero length buffer, + // so that OEMCrypto can tell us how big the buffer should be. size_t header_buffer_length = 0; OEMCryptoResult sts = OEMCrypto_ShrinkUsageTableHeader( new_size, nullptr, &header_buffer_length); + // If we are expecting success, then the first call shall return + // SHORT_BUFFER. However, if we are not expecting success, this first call + // may return either SHORT_BUFFER or the expect error. if (expected_result == OEMCrypto_SUCCESS) { ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - } else { - ASSERT_NE(OEMCrypto_SUCCESS, sts); - if (sts != OEMCrypto_ERROR_SHORT_BUFFER) return; + } else if (sts != OEMCrypto_ERROR_SHORT_BUFFER) { + // If we got any thing from the first call, it should be the expected + // error, and we don't need to call a second time. + ASSERT_EQ(expected_result, sts); + return; } + // If the first call resulted in SHORT_BUFFER, we should resize the buffer + // and try again. ASSERT_LT(0u, header_buffer_length); encrypted_usage_header_.resize(header_buffer_length); sts = OEMCrypto_ShrinkUsageTableHeader( new_size, encrypted_usage_header_.data(), &header_buffer_length); + // For the second call, we always demand the expected result. ASSERT_EQ(expected_result, sts); } }; @@ -6030,6 +6005,7 @@ TEST_P(OEMCryptoUsageTableTest, ReloadUsageTableWithSkew) { old_usage_entry_1.data(), old_usage_entry_1.size())); ASSERT_NO_FATAL_FAILURE(InstallTestRSAKey(&s)); + ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); ASSERT_EQ(OEMCrypto_SUCCESS, entry.license_messages().LoadResponse()); } @@ -6200,44 +6176,12 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { public: void SetUp() override { OEMCryptoUsageTableTest::SetUp(); - did_change_system_time_ = false; - test_start_steady_ = steady_clock_.now(); -#ifdef _WIN32 - GetSystemTime(&test_start_wall_); -#else - ASSERT_EQ(0, gettimeofday(&test_start_wall_, nullptr)); -#endif } void TearDown() override { - if (did_change_system_time_) { - const auto delta = steady_clock_.now() - test_start_steady_; - const int64_t delta_sec = delta / std::chrono::seconds(1); - ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta(delta_sec)); - } + wvcdm::TestSleep::ResetRollback(); OEMCryptoUsageTableTest::TearDown(); } - - protected: - /** - * Sets the current wall-clock time to a delta based on the start of the - * test. - */ - void SetWallTimeDelta(int64_t delta_seconds) { - did_change_system_time_ = true; - NativeTime time = test_start_wall_; - ASSERT_NO_FATAL_FAILURE(AddNativeTime(delta_seconds, &time)); -#ifdef _WIN32 - ASSERT_TRUE(SetSystemTime(&time)); -#else - ASSERT_EQ(0, settimeofday(&time, nullptr)); -#endif - } - - std::chrono::steady_clock steady_clock_; - bool did_change_system_time_; - NativeTime test_start_wall_; - std::chrono::time_point test_start_steady_; }; // NOTE: This test needs root access since clock_settime messes with the system @@ -6246,61 +6190,104 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { // We don't test roll-forward protection or instances where the user rolls back // the time to the last decrypt call since this requires hardware-secure clocks // to guarantee. +// +// This test overlaps two tests in parallel because they each have several +// seconds of sleeping, then we roll the system clock back, and then we sleep +// some more. +// For the first test, we use entry1. The playback duration is 6 short +// intervals. We play for 3, roll the clock back 2, and then play for 3 more. +// We then sleep until after the allowed playback duration and try to play. If +// OEMCrypto allows the rollback, then there is only 5 intervals, which is +// legal. But if OEMCrypto forbids the rollback, then there is 8 intervals of +// playback, which is not legal. +// +// For the second test, we use entry2. The rental duration is 6 short +// intervals. The times are the same as for entry1, except we do not start +// playback for entry2 until the end. + +// clang-format off +// [--][--][--][--][--][--][--] -- playback or rental limit. +// +// Here's what the system clock sees with rollback: +// [--][--][--] 3 short intervals of playback or sleep +// <------> Rollback 2 short intervals. +// [--][--][--] 3 short intervals of playback or sleep +// [--] 1 short intervals of sleep. +// +// Here's what the system clock sees without rollback: +// [--][--][--] 3 short intervals of playback or sleep +// [--][--][--] 3 short intervals of playback or sleep +// [--][--]X 2 short intervals of sleep. +// +// |<---------------------------->| 8 short intervals from license received +// until pst reports generated. +// clang-format on + TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { cout << "This test temporarily rolls back the system time in order to verify " - << "that the usage report accounts for the change. It then rolls " - << "the time back forward to the absolute time." << endl; - LicenseWithUsageEntry entry; - entry.MakeOfflineAndClose(this); - Session& s = entry.session(); - std::chrono::system_clock wall_clock; - std::chrono::steady_clock monotonic_clock; - const auto loaded = wall_clock.now(); + << "that the usage report accounts for the change. After the test, it " + << "rolls the clock back forward." << endl; + constexpr int kRollBackTime = kShortSleep * 2; + constexpr int kPlaybackCount = 3; + constexpr int kTotalTime = kShortSleep * 8; - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - const auto first_decrypt = wall_clock.now(); - // Monotonic clock can't be changed. We use this since system clock will be - // unreliable. - const auto first_decrypt_monotonic = monotonic_clock.now(); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); + LicenseWithUsageEntry entry1; + entry1.license_messages() + .core_response() + .timer_limits.total_playback_duration_seconds = 7 * kShortSleep; + entry1.MakeOfflineAndClose(this); + Session& s1 = entry1.session(); + ASSERT_NO_FATAL_FAILURE(entry1.OpenAndReload(this)); - // Imitate playback. - wvcdm::TestSleep::Sleep(kLongDuration * 2); + LicenseWithUsageEntry entry2; + entry2.license_messages() + .core_response() + .timer_limits.rental_duration_seconds = 7 * kShortSleep; + entry2.MakeOfflineAndClose(this); + Session& s2 = entry2.session(); + ASSERT_NO_FATAL_FAILURE(entry2.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); + // Start with three short intervals of playback for entry1. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvcdm::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } - // Rollback the wall clock time. cout << "Rolling the system time back..." << endl; + ASSERT_TRUE(wvcdm::TestSleep::RollbackSystemTime(kRollBackTime)); + + // Three more short intervals of playback after the rollback. + for (int i = 0; i < kPlaybackCount; i++) { + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + wvcdm::TestSleep::Sleep(kShortSleep); + ASSERT_NO_FATAL_FAILURE(entry1.TestDecryptCTR()); + } + + // One short interval of sleep to push us past the 6 interval duration. + wvcdm::TestSleep::Sleep(2 * kShortSleep); + + // Should not be able to continue playback in entry1. ASSERT_NO_FATAL_FAILURE( - SetWallTimeDelta(-static_cast(kLongDuration) * 10)); - - // Try to playback again. - ASSERT_NO_FATAL_FAILURE(entry.OpenAndReload(this)); - const auto third_decrypt_monotonic = monotonic_clock.now(); - ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); - ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); - ASSERT_NO_FATAL_FAILURE(s.close()); - Test_PST_Report expected(entry.pst(), kActive); - - // Restore wall clock to its original position to verify that OEMCrypto does - // not report negative times. - const auto test_duration = third_decrypt_monotonic - first_decrypt_monotonic; - cout << "Rolling the system time forward to the absolute time..." << endl; + entry1.TestDecryptCTR(false, OEMCrypto_ERROR_KEY_EXPIRED)); + // Should not be able to start playback in entry2. ASSERT_NO_FATAL_FAILURE( - SetWallTimeDelta(test_duration / std::chrono::seconds(1))); - // Need to update time created since the verification checks the time of PST - // report creation. - expected.time_created = UnixTime(wall_clock.now()); + entry2.TestDecryptCTR(true, OEMCrypto_ERROR_KEY_EXPIRED)); - const auto end_time = first_decrypt + test_duration; - ASSERT_NO_FATAL_FAILURE(s.VerifyReport( - expected, UnixTime(loaded), UnixTime(first_decrypt), UnixTime(end_time))); - ASSERT_NO_FATAL_FAILURE(s.close()); + // Now we look at the usage reports: + ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE(s2.UpdateUsageEntry(&encrypted_usage_header_)); + + ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(entry1.pst())); + wvcdm::Unpacked_PST_Report report1 = s1.pst_report(); + EXPECT_EQ(report1.status(), kActive); + EXPECT_GE(report1.seconds_since_license_received(), kTotalTime); + EXPECT_GE(report1.seconds_since_first_decrypt(), kTotalTime); + + ASSERT_NO_FATAL_FAILURE(s2.GenerateReport(entry2.pst())); + wvcdm::Unpacked_PST_Report report2 = s2.pst_report(); + EXPECT_EQ(report2.status(), kUnused); + EXPECT_GE(report2.seconds_since_license_received(), kTotalTime); } // Verify that a large PST can be used with usage table entries. diff --git a/oemcrypto/test/oemcrypto_test_main.cpp b/oemcrypto/test/oemcrypto_test_main.cpp index 1cf4a598..5815fd4d 100644 --- a/oemcrypto/test/oemcrypto_test_main.cpp +++ b/oemcrypto/test/oemcrypto_test_main.cpp @@ -4,6 +4,7 @@ #include "OEMCryptoCENC.h" #include "log.h" #include "oec_device_features.h" +#include "oemcrypto_corpus_generator_helper.h" #include "test_sleep.h" static void acknowledge_cast() { @@ -23,6 +24,9 @@ int main(int argc, char** argv) { // Skip the first element, which is the program name. const std::vector args(argv + 1, argv + argc); for (const std::string& arg : args) { + if (arg == "--generate_corpus") { + wvoec::SetGenerateCorpus(true); + } if (arg == "--verbose" || arg == "-v") { ++verbosity; } else if (arg == "--cast") { diff --git a/oemcrypto/test/oemcrypto_unittests.gypi b/oemcrypto/test/oemcrypto_unittests.gypi index 6857fc7e..ac747be6 100644 --- a/oemcrypto/test/oemcrypto_unittests.gypi +++ b/oemcrypto/test/oemcrypto_unittests.gypi @@ -10,6 +10,7 @@ 'oec_decrypt_fallback_chain.cpp', 'oec_key_deriver.cpp', 'oec_session_util.cpp', + 'oemcrypto_corpus_generator_helper.cpp', 'oemcrypto_session_tests_helper.cpp', 'oemcrypto_test.cpp', 'wvcrc.cpp', @@ -20,6 +21,7 @@ '<(oemcrypto_dir)/include', '<(oemcrypto_dir)/ref/src', '<(oemcrypto_dir)/test', + '<(oemcrypto_dir)/test/fuzz_tests', '<(oemcrypto_dir)/odk/include', ], 'defines': [ diff --git a/platforms/x86-64/settings.gypi b/platforms/x86-64/settings.gypi index 610514e7..068888cb 100644 --- a/platforms/x86-64/settings.gypi +++ b/platforms/x86-64/settings.gypi @@ -14,8 +14,9 @@ 'cflags': [ '-fPIC', '-fvisibility=hidden', + '-fno-common', + '-Wno-unknown-warning-option', ], - # These are flags passed to the compiler for plain C only. 'cflags_c': [ # Compile using the C11 standard with POSIX extensions @@ -56,7 +57,6 @@ '-Wno-unused-parameter', # repeated in protobufs triggers this '-Wno-unused-local-typedefs', # metrics requires this #'-Wno-maybe-uninitialized', - '-Wno-unknown-warning-option', '-Wno-dangling-else', # Allowed by Google C++ Style ], diff --git a/third_party/boringssl/boringssl.gyp b/third_party/boringssl/boringssl.gyp index de2e8364..69b455e4 100644 --- a/third_party/boringssl/boringssl.gyp +++ b/third_party/boringssl/boringssl.gyp @@ -13,14 +13,17 @@ '-fvisibility=hidden', # BoringSSL violates these warnings '-Wno-cast-qual', + '-Wno-ignored-qualifiers', ], 'cflags!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], 'cflags_cc!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], 'cflags_c': [ # BoringSSL violates these warnings @@ -29,6 +32,7 @@ 'cflags_c!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], 'msvs_settings': { @@ -41,15 +45,14 @@ 'ldflags': [ '-fPIC', ], - 'link_settings': { - 'ldflags': [ - '-lpthread', - ], - }, 'link_settings': { 'conditions': [ - ['OS=="win"', { + ['OS!="win"', { + 'libraries': [ + '-lpthread', + ], + }, { 'libraries': [ '-ladvapi32', ], @@ -62,10 +65,12 @@ 'cflags': [ '-Wno-unknown-warning-option', '-Wno-cast-qual', + '-Wno-ignored-qualifiers', ], 'cflags!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], 'cflags_c': [ '-Wno-bad-function-cast', @@ -73,10 +78,12 @@ 'cflags_c!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], 'cflags_cc!': [ '-Wbad-function-cast', '-Wcast-qual', + '-Wignored-qualifiers', ], }, diff --git a/third_party/gmock.gyp b/third_party/gmock.gyp index 0cc6cf60..d7c70a8b 100644 --- a/third_party/gmock.gyp +++ b/third_party/gmock.gyp @@ -12,6 +12,11 @@ ], 'cflags_cc': [ '-std=c++11', + # gMock violates these warnings + '-Wno-deprecated-copy', + ], + 'cflags_cc!': [ + '-Wdeprecated-copy', ], 'ldflags': [ '-fPIC', @@ -27,6 +32,13 @@ 'googletest/googlemock/include', 'googletest/googletest/include', ], + 'cflags_cc': [ + # gMock's exported headers violate these warnings + '-Wno-deprecated-copy', + ], + 'cflags_cc!': [ + '-Wdeprecated-copy', + ], 'xcode_settings': { 'OTHER_CFLAGS': [ '-Wno-inconsistent-missing-override', @@ -65,7 +77,7 @@ 'conditions': [ ['OS!="win"', { 'link_settings': { - 'ldflags': [ + 'libraries': [ '-lpthread', ], }, diff --git a/third_party/gyp/xcode_emulation.py b/third_party/gyp/xcode_emulation.py index 0bdf88db..ca76187b 100644 --- a/third_party/gyp/xcode_emulation.py +++ b/third_party/gyp/xcode_emulation.py @@ -726,6 +726,8 @@ class XcodeSettings(object): def _AddObjectiveCARCFlags(self, flags): if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'): flags.append('-fobjc-arc') + if self._Test('CLANG_ENABLE_OBJC_WEAK', 'YES', default='NO'): + flags.append('-fobjc-weak') def _AddObjectiveCMissingPropertySynthesisFlags(self, flags): if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS', diff --git a/util/test/test_sleep.cpp b/util/test/test_sleep.cpp index 2140c9ee..1656381a 100644 --- a/util/test/test_sleep.cpp +++ b/util/test/test_sleep.cpp @@ -4,16 +4,26 @@ #include "test_sleep.h" +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include #include #include #include "clock.h" +#include "log.h" namespace wvcdm { bool TestSleep::real_sleep_ = true; TestSleep::CallBack* TestSleep::callback_ = nullptr; +int TestSleep::total_clock_rollback_ = 0; void TestSleep::Sleep(unsigned int seconds) { int64_t milliseconds = 1000 * seconds; @@ -23,10 +33,10 @@ void TestSleep::Sleep(unsigned int seconds) { // total since the start, and then compare to a running total of sleep // calls. We sleep for approximately x second, and then advance the clock by // the amount of time that has actually passed. - static auto start_real = std::chrono::steady_clock().now(); + static auto start_real = std::chrono::system_clock().now(); static int64_t fake_clock = 0; sleep(seconds); - auto now_real = std::chrono::steady_clock().now(); + auto now_real = std::chrono::system_clock().now(); int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1); // We want to advance the fake clock by the difference between the real // clock, and the previous value on the fake clock. @@ -41,4 +51,98 @@ void TestSleep::SyncFakeClock() { Sleep(0); } +bool TestSleep::RollbackSystemTime(int seconds) { + if (real_sleep_) { +#ifdef _WIN32 + // See remarks from this for why this series is used. + // https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b + SYSTEMTIME time; + GetSystemTime(&time); + FILETIME file_time; + if (!SystemTimeToFileTime(time, &file_time)) return false; + uint64_t long_time = + static_cast(file_time.dwLowDateTime) | + (static_cast(file_time.dwHighDateTime) << 32); + long_time += static_cast(delta_seconds) * + 1e7; // long_time is in 100-nanosecond intervals. + file_time.dwLowDateTime = long_time & ((1ull << 32) - 1); + file_time.dwHighDateTime = long_time >> 32; + if (!FileTimeToSystemTime(&file_time, &time)) return false; + if (!SetSystemTime(&time)) return false; +#else + auto time = std::chrono::system_clock::now(); + auto modified_time = time - std::chrono::seconds(seconds); + ; + timespec time_spec; + time_spec.tv_sec = std::chrono::duration_cast( + modified_time.time_since_epoch()) + .count(); + time_spec.tv_nsec = std::chrono::duration_cast( + modified_time.time_since_epoch()) + .count() % + (1000 * 1000 * 1000); + if (clock_settime(CLOCK_REALTIME, &time_spec)) { + LOGE("Error setting clock: %s", strerror(errno)); + return false; + } +#endif + } // end if(real_sleep_)... + + // For both real and fake sleep we still update the callback and we still keep + // track of the total amount of time slept. + total_clock_rollback_ += seconds; + if (callback_ != nullptr) callback_->ElapseTime(-1000 * seconds); + return true; +} + +bool TestSleep::CanChangeSystemTime() { + // If we are using a fake clock, then we can move the clock backwards by + // just going backwards. + // ElapseTime. + if (!real_sleep_) { + return true; + } +#ifdef _WIN32 + LUID desired_id; + if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id)) { + LOGE("Win32 time rollback: no SYSTEMTIME permission."); + return false; + } + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + LOGE("Win32 time rollback: cannot access process token."); + return false; + } + std::unique_ptr safe_token(token, &CloseHandle); + + // This queries all the permissions given to the token to determine if we can + // change the system time. Note this is subtly different from PrivilegeCheck + // as that only checks "enabled" privileges; even with admin rights, the + // privilege is default disabled, even when granted. + + DWORD size = 0; + // Determine how big we need to allocate first. + GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size); + // Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc + std::unique_ptr privileges( + (TOKEN_PRIVILEGES*)malloc(size), &free); + if (privileges && GetTokenInformation(token, TokenPrivileges, + privileges.get(), size, &size)) { + for (int i = 0; i < privileges->PrivilegeCount; i++) { + if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart && + privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) { + return true; + } + } + } + LOGE("Win32 time rollback: cannot set system time."); + return false; +#else + // Otherwise, the test needs to be run as root. + const uid_t uid = getuid(); + if (uid == 0) return true; + LOGE("Unix time rollback: not running as root (uid=%u.", uid); + return false; +#endif +} } // namespace wvcdm diff --git a/util/test/test_sleep.h b/util/test/test_sleep.h index 832f1ade..34c4b3f6 100644 --- a/util/test/test_sleep.h +++ b/util/test/test_sleep.h @@ -22,8 +22,9 @@ class TestSleep { virtual ~CallBack(){}; }; - // If real_sleep_ is true, then this sleeps for |seconds| of time. - // If the callback exists, this calls the callback. + // If real_sleep_ is true, then this sleeps for |seconds| of time. If + // real_sleep_ is false, then the fake clock is advanced by |seconds|. If the + // callback exists, this calls the callback. static void Sleep(unsigned int seconds); // If we are using a real clock and a fake clock, then the real clock advances @@ -33,8 +34,30 @@ class TestSleep { // failing due to this drift. static void SyncFakeClock(); - static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; } + // Roll the system clock back by |seconds|. Returns true on success. A well + // mannered test will call CanChangeSystemTime before attempting to call this + // function and then assert that this is true. This function should *NOT* roll + // back the clock used by OEMCrypto -- in fact, there are several tests that + // verify this function does not roll back the clock used by OEMCrypto. + static bool RollbackSystemTime(int seconds); + // Roll the system clock forward to undo all previous calls to + // RollBackSystemTime. Returns true on success. + static bool ResetRollback() { + return total_clock_rollback_ == 0 || + RollbackSystemTime(-total_clock_rollback_); + } + + // Returns true if the system time can be rolled back. This is true on some + // devices if the tests are run as root. It is also true when using a fake + // clock with the reference version of OEMCrypto. This function is about the + // system clock, *NOT* the clock used by OEMCrypto. + static bool CanChangeSystemTime(); + + static void set_real_sleep(bool real_sleep) { real_sleep_ = real_sleep; } + static bool real_sleep() { return real_sleep_; } + + // The callback is notified whenever sleep is called. static void set_callback(CallBack* callback) { callback_ = callback; } private: @@ -42,6 +65,9 @@ class TestSleep { static bool real_sleep_; // Called when the clock should advance. static CallBack* callback_; + // The sum of all calls to RollBackSystemTime. Kept so we can undo all changes + // at the end of a test. + static int total_clock_rollback_; }; } // namespace wvcdm