Merge changes I442b7919,Ie5b4ff75

* changes:
  Improve android MediaDrm property latency
  Delay OEMCrypto Termination
This commit is contained in:
Rahul Frias
2020-02-19 15:51:14 +00:00
committed by Android (Google) Code Review
11 changed files with 254 additions and 108 deletions

View File

@@ -44,11 +44,11 @@ class CryptoSessionFactory;
class CryptoSession { class CryptoSession {
public: public:
using HdcpCapability = OEMCrypto_HDCP_Capability; using HdcpCapability = OEMCrypto_HDCP_Capability;
typedef enum { enum UsageDurationStatus {
kUsageDurationsInvalid = 0, kUsageDurationsInvalid = 0,
kUsageDurationPlaybackNotBegun = 1, kUsageDurationPlaybackNotBegun = 1,
kUsageDurationsValid = 2, kUsageDurationsValid = 2,
} UsageDurationStatus; };
struct SupportedCertificateTypes { struct SupportedCertificateTypes {
bool rsa_2048_bit; bool rsa_2048_bit;
@@ -64,6 +64,17 @@ class CryptoSession {
virtual ~CryptoSession(); virtual ~CryptoSession();
// This method will try to terminate OEMCrypto if |session_size_| is 0.
// A platform configured property |delay_oem_crypto_termination| will
// determine if termination occurs immediately or after a delay.
// If termination is delayed, a countdown mechanism is employed.
// Call |TryTerminate| periodically until it no longer returns true.
// To immediately terminate call |DisableDelayedTermination| before calling
// |TryTerminate|.
static bool TryTerminate();
static void DisableDelayedTermination();
virtual CdmResponseType GetProvisioningToken(std::string* client_token); virtual CdmResponseType GetProvisioningToken(std::string* client_token);
virtual CdmClientTokenType GetPreProvisionTokenType() { virtual CdmClientTokenType GetPreProvisionTokenType() {
return pre_provision_token_type_; return pre_provision_token_type_;
@@ -282,7 +293,6 @@ class CryptoSession {
} }
void Init(); void Init();
void Terminate();
CdmResponseType GetTokenFromKeybox(std::string* token); CdmResponseType GetTokenFromKeybox(std::string* token);
CdmResponseType GetTokenFromOemCert(std::string* token); CdmResponseType GetTokenFromOemCert(std::string* token);
static bool ExtractSystemIdFromOemCert(const std::string& oem_cert, static bool ExtractSystemIdFromOemCert(const std::string& oem_cert,
@@ -385,6 +395,7 @@ class CryptoSession {
static bool initialized_; static bool initialized_;
static int session_count_; static int session_count_;
static int termination_counter_;
metrics::CryptoMetrics* metrics_; metrics::CryptoMetrics* metrics_;
metrics::TimerMetric life_span_; metrics::TimerMetric life_span_;

View File

@@ -61,6 +61,9 @@ class Properties {
static void set_provisioning_messages_are_binary(bool flag) { static void set_provisioning_messages_are_binary(bool flag) {
provisioning_messages_are_binary_ = flag; provisioning_messages_are_binary_ = flag;
} }
static inline bool delay_oem_crypto_termination() {
return delay_oem_crypto_termination_;
}
static bool GetCompanyName(std::string* company_name); static bool GetCompanyName(std::string* company_name);
static bool GetModelName(std::string* model_name); static bool GetModelName(std::string* model_name);
static bool GetArchitectureName(std::string* arch_name); static bool GetArchitectureName(std::string* arch_name);
@@ -147,6 +150,7 @@ class Properties {
static bool allow_service_certificate_requests_; static bool allow_service_certificate_requests_;
static bool device_files_is_a_real_filesystem_; static bool device_files_is_a_real_filesystem_;
static bool allow_restore_of_offline_licenses_with_release_; static bool allow_restore_of_offline_licenses_with_release_;
static bool delay_oem_crypto_termination_;
static std::unique_ptr<CdmClientPropertySetMap> session_property_set_; static std::unique_ptr<CdmClientPropertySetMap> session_property_set_;
CORE_DISALLOW_COPY_AND_ASSIGN(Properties); CORE_DISALLOW_COPY_AND_ASSIGN(Properties);

View File

@@ -60,6 +60,7 @@ const size_t kOemCryptoApiVersionSupportsSwitchingCipherMode = 14;
// Constants and utility objects relating to OEM Certificates // Constants and utility objects relating to OEM Certificates
const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1"; const char* const kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1";
constexpr int kMaxTerminateCountDown = 5;
const std::string kStringNotAvailable = "NA"; const std::string kStringNotAvailable = "NA";
@@ -96,6 +97,7 @@ shared_mutex CryptoSession::static_field_mutex_;
shared_mutex CryptoSession::oem_crypto_mutex_; shared_mutex CryptoSession::oem_crypto_mutex_;
bool CryptoSession::initialized_ = false; bool CryptoSession::initialized_ = false;
int CryptoSession::session_count_ = 0; int CryptoSession::session_count_ = 0;
int CryptoSession::termination_counter_ = 0;
UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr; UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr;
UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr; UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr;
std::atomic<uint64_t> CryptoSession::request_id_index_source_(0); std::atomic<uint64_t> CryptoSession::request_id_index_source_(0);
@@ -189,7 +191,15 @@ CryptoSession::~CryptoSession() {
if (open_) { if (open_) {
Close(); Close();
} }
Terminate(); WithStaticFieldWriteLock("~CryptoSession", [&] {
if (session_count_ > 0) {
--session_count_;
} else {
LOGE("Invalid crypto session count: session_count_ = %d", session_count_);
}
});
TryTerminate();
M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs()); M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs());
} }
@@ -244,6 +254,9 @@ void CryptoSession::Init() {
LOGE("OEMCrypto_Initialize failed: status = %d", static_cast<int>(sts)); LOGE("OEMCrypto_Initialize failed: status = %d", static_cast<int>(sts));
return; return;
} }
termination_counter_ = Properties::delay_oem_crypto_termination()
? kMaxTerminateCountDown
: 0;
initialized_ = true; initialized_ = true;
initialized = true; initialized = true;
} }
@@ -273,17 +286,20 @@ void CryptoSession::Init() {
} }
} }
void CryptoSession::Terminate() { bool CryptoSession::TryTerminate() {
LOGV("Terminating crypto session"); LOGV("Terminating crypto session");
WithStaticFieldWriteLock("Terminate", [&] { WithStaticFieldWriteLock("TryTerminate", [&] {
LOGV("Terminating crypto session: initialized_ = %s, session_count_ = %d", LOGV(
initialized_ ? "true" : "false", session_count_); "Terminating crypto session: initialized_ = %s, session_count_ = %d, "
if (session_count_ > 0) { "termination_counter_ = %d",
session_count_ -= 1; initialized_ ? "true" : "false", session_count_, termination_counter_);
} else {
LOGE("Invalid crypto session count: session_count_ = %d", session_count_); if (termination_counter_ > 0) {
--termination_counter_;
} }
if (session_count_ > 0 || !initialized_) return; if (session_count_ > 0 || termination_counter_ > 0 || !initialized_)
return false;
OEMCryptoResult sts; OEMCryptoResult sts;
WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); }); WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); });
if (OEMCrypto_SUCCESS != sts) { if (OEMCrypto_SUCCESS != sts) {
@@ -300,7 +316,15 @@ void CryptoSession::Terminate() {
} }
initialized_ = false; initialized_ = false;
return true;
}); });
return true;
}
void CryptoSession::DisableDelayedTermination() {
LOGV("Disable delayed termination");
WithStaticFieldWriteLock("DisableDelayedTermination",
[&] { termination_counter_ = 0; });
} }
CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) { CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) {
@@ -687,6 +711,11 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open"); return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open");
} }
WithStaticFieldWriteLock("Open() termination_counter", [&] {
termination_counter_ =
Properties::delay_oem_crypto_termination() ? kMaxTerminateCountDown : 0;
});
oec_session_id_ = static_cast<CryptoSessionId>(sid); oec_session_id_ = static_cast<CryptoSessionId>(sid);
LOGV("Opened session: id = %u", oec_session_id_); LOGV("Opened session: id = %u", oec_session_id_);
open_ = true; open_ = true;

View File

@@ -21,6 +21,7 @@ bool Properties::provisioning_messages_are_binary_;
bool Properties::allow_service_certificate_requests_; bool Properties::allow_service_certificate_requests_;
bool Properties::device_files_is_a_real_filesystem_; bool Properties::device_files_is_a_real_filesystem_;
bool Properties::allow_restore_of_offline_licenses_with_release_; bool Properties::allow_restore_of_offline_licenses_with_release_;
bool Properties::delay_oem_crypto_termination_;
std::unique_ptr<CdmClientPropertySetMap> Properties::session_property_set_; std::unique_ptr<CdmClientPropertySetMap> Properties::session_property_set_;
bool Properties::AddSessionPropertySet(const CdmSessionId& session_id, bool Properties::AddSessionPropertySet(const CdmSessionId& session_id,

View File

@@ -39,6 +39,11 @@ const bool kDeviceFilesIsARealFileSystem = true;
// will be treated as a release request. If false, a restoration will fail. // will be treated as a release request. If false, a restoration will fail.
const bool kAllowRestoreOfflineLicenseWithRelease = false; const bool kAllowRestoreOfflineLicenseWithRelease = false;
// This controls whether an OEMCrypto_Terminate call is issued immediately
// after crypto session count declines to 0 or if a termination is
// delayed by a countdown timer. If false, termination is immediate.
const bool kPropertyDelayOemCryptoTermination = true;
} // namespace wvcdm } // namespace wvcdm
#endif // CDM_BASE_WV_PROPERTIES_CONFIGURATION_H_ #endif // CDM_BASE_WV_PROPERTIES_CONFIGURATION_H_

View File

@@ -43,7 +43,10 @@ class Timer {
~Timer(); ~Timer();
bool Start(TimerHandler* handler, uint32_t time_in_secs); bool Start(TimerHandler* handler, uint32_t time_in_secs);
void Stop(); // Enable |wait_for_exit| only if the method is being invoked from a thread
// other than the timer thread. Deadlock might occur if enabled and called
// from the timer thread.
void Stop(bool wait_for_exit);
bool IsRunning(); bool IsRunning();
private: private:

View File

@@ -184,14 +184,17 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
uint32_t GenerateSessionSharingId(); uint32_t GenerateSessionSharingId();
// timer related methods to drive policy decisions // Timer related methods to drive policy decisions
void EnablePolicyTimer(); void EnableTimer();
void DisablePolicyTimer(); void DisableTimer();
// Call this method only if invoked from a thread other than the timer thread.
// Otherwise this might result in deadlock.
void DisableTimerAndWaitForExit();
void OnTimerEvent(); void OnTimerEvent();
static std::mutex session_sharing_id_generation_lock_; static std::mutex session_sharing_id_generation_lock_;
std::mutex policy_timer_lock_; std::mutex timer_lock_;
Timer policy_timer_; Timer timer_;
// instance variables // instance variables
// This manages the lifetime of the CDM instances. // This manages the lifetime of the CDM instances.

View File

@@ -52,6 +52,7 @@ void Properties::InitOnce() {
device_files_is_a_real_filesystem_ = kDeviceFilesIsARealFileSystem; device_files_is_a_real_filesystem_ = kDeviceFilesIsARealFileSystem;
allow_restore_of_offline_licenses_with_release_ = allow_restore_of_offline_licenses_with_release_ =
kAllowRestoreOfflineLicenseWithRelease; kAllowRestoreOfflineLicenseWithRelease;
delay_oem_crypto_termination_ = kPropertyDelayOemCryptoTermination;
session_property_set_.reset(new CdmClientPropertySetMap()); session_property_set_.reset(new CdmClientPropertySetMap());
} }

View File

@@ -28,12 +28,13 @@ class Timer::Impl : virtual public android::RefBase {
return run("wvcdm::Timer::Impl") == android::NO_ERROR; return run("wvcdm::Timer::Impl") == android::NO_ERROR;
} }
void Stop() { void Stop(bool wait_for_exit) {
{ stop_condition_.signal();
android::Mutex::Autolock autoLock(lock_); if (wait_for_exit) {
stop_condition_.signal(); requestExitAndWait();
} else {
requestExit();
} }
requestExitAndWait();
} }
private: private:
@@ -63,8 +64,8 @@ class Timer::Impl : virtual public android::RefBase {
return impl_thread_->Start(handler, time_in_secs); return impl_thread_->Start(handler, time_in_secs);
} }
void Stop() { void Stop(bool wait_for_exit) {
impl_thread_->Stop(); impl_thread_->Stop(wait_for_exit);
impl_thread_.clear(); impl_thread_.clear();
} }
@@ -78,7 +79,7 @@ class Timer::Impl : virtual public android::RefBase {
Timer::Timer() : impl_(new Timer::Impl()) {} Timer::Timer() : impl_(new Timer::Impl()) {}
Timer::~Timer() { Timer::~Timer() {
if (IsRunning()) Stop(); if (IsRunning()) Stop(false);
} }
bool Timer::Start(TimerHandler* handler, uint32_t time_in_secs) { bool Timer::Start(TimerHandler* handler, uint32_t time_in_secs) {
@@ -87,7 +88,7 @@ bool Timer::Start(TimerHandler* handler, uint32_t time_in_secs) {
return impl_->Start(handler, time_in_secs); return impl_->Start(handler, time_in_secs);
} }
void Timer::Stop() { impl_->Stop(); } void Timer::Stop(bool wait_for_exit) { impl_->Stop(wait_for_exit); }
bool Timer::IsRunning() { return impl_->IsRunning(); } bool Timer::IsRunning() { return impl_->IsRunning(); }

View File

@@ -18,7 +18,7 @@
#include "wv_cdm_event_listener.h" #include "wv_cdm_event_listener.h"
namespace { namespace {
const int kCdmPolicyTimerDurationSeconds = 1; const int kCdmTimerDurationSeconds = 1;
} }
namespace wvcdm { namespace wvcdm {
@@ -28,8 +28,10 @@ std::mutex WvContentDecryptionModule::session_sharing_id_generation_lock_;
WvContentDecryptionModule::WvContentDecryptionModule() {} WvContentDecryptionModule::WvContentDecryptionModule() {}
WvContentDecryptionModule::~WvContentDecryptionModule() { WvContentDecryptionModule::~WvContentDecryptionModule() {
CryptoSession::DisableDelayedTermination();
CloseAllCdms(); CloseAllCdms();
DisablePolicyTimer(); CryptoSession::TryTerminate();
DisableTimerAndWaitForExit();
} }
bool WvContentDecryptionModule::IsSupported(const std::string& init_data_type) { bool WvContentDecryptionModule::IsSupported(const std::string& init_data_type) {
@@ -123,16 +125,9 @@ CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
sts = cdm_engine->GenerateKeyRequest(session_id, key_set_id, sts = cdm_engine->GenerateKeyRequest(session_id, key_set_id,
initialization_data, license_type, initialization_data, license_type,
app_parameters, key_request); app_parameters, key_request);
switch (license_type) { if (license_type == kLicenseTypeRelease && sts != KEY_MESSAGE) {
case kLicenseTypeRelease: cdm_engine->CloseKeySetSession(key_set_id);
if (sts != KEY_MESSAGE) { cdm_by_session_id_.erase(key_set_id);
cdm_engine->CloseKeySetSession(key_set_id);
cdm_by_session_id_.erase(key_set_id);
}
break;
default:
if (sts == KEY_MESSAGE) EnablePolicyTimer();
break;
} }
return sts; return sts;
} }
@@ -165,7 +160,6 @@ CdmResponseType WvContentDecryptionModule::RestoreKey(
if (!cdm_engine) return SESSION_NOT_FOUND_4; if (!cdm_engine) return SESSION_NOT_FOUND_4;
CdmResponseType sts; CdmResponseType sts;
sts = cdm_engine->RestoreKey(session_id, key_set_id); sts = cdm_engine->RestoreKey(session_id, key_set_id);
if (sts == KEY_ADDED) EnablePolicyTimer();
return sts; return sts;
} }
@@ -355,21 +349,28 @@ WvContentDecryptionModule::CdmInfo::CdmInfo()
CdmEngine* WvContentDecryptionModule::EnsureCdmForIdentifier( CdmEngine* WvContentDecryptionModule::EnsureCdmForIdentifier(
const CdmIdentifier& identifier) { const CdmIdentifier& identifier) {
std::unique_lock<std::mutex> auto_lock(cdms_lock_); CdmEngine* cdm_engine;
if (cdms_.find(identifier) == cdms_.end()) { bool enable_timer = false;
// Accessing the map entry will create a new instance using the default {
// constructor. We then need to provide it with two pieces of info: The std::unique_lock<std::mutex> auto_lock(cdms_lock_);
// origin provided by the app and an identifier that uniquely identifies if (cdms_.size() == 0) enable_timer = true;
// this CDM. We concatenate all pieces of the CdmIdentifier in order to if (cdms_.find(identifier) == cdms_.end()) {
// create an ID that is unique to that identifier. // Accessing the map entry will create a new instance using the default
cdms_[identifier].file_system.set_origin(identifier.origin); // constructor. We then need to provide it with two pieces of info: The
cdms_[identifier].file_system.set_identifier(identifier.spoid + // origin provided by the app and an identifier that uniquely identifies
identifier.origin); // this CDM. We concatenate all pieces of the CdmIdentifier in order to
cdms_[identifier].cdm_engine->SetAppPackageName( // create an ID that is unique to that identifier.
identifier.app_package_name); cdms_[identifier].file_system.set_origin(identifier.origin);
cdms_[identifier].cdm_engine->SetSpoid(identifier.spoid); cdms_[identifier].file_system.set_identifier(identifier.spoid +
identifier.origin);
cdms_[identifier].cdm_engine->SetAppPackageName(
identifier.app_package_name);
cdms_[identifier].cdm_engine->SetSpoid(identifier.spoid);
}
cdm_engine = cdms_[identifier].cdm_engine.get();
} }
CdmEngine* cdm_engine = cdms_[identifier].cdm_engine.get(); // Do not enable timer while holding on to the |cdms_lock_|
if (enable_timer) EnableTimer();
return cdm_engine; return cdm_engine;
} }
@@ -392,35 +393,23 @@ void WvContentDecryptionModule::CloseAllCdms() {
CdmResponseType WvContentDecryptionModule::CloseCdm( CdmResponseType WvContentDecryptionModule::CloseCdm(
const CdmIdentifier& cdm_identifier) { const CdmIdentifier& cdm_identifier) {
// The policy timer ultimately calls OnTimerEvent (which wants to std::unique_lock<std::mutex> auto_lock(cdms_lock_);
// acquire cdms_lock_). Therefore, we cannot acquire cdms_lock_ and then the auto it = cdms_.find(cdm_identifier);
// policy_timer_lock_ (via DisablePolicyTimer) at the same time. if (it == cdms_.end()) {
// Acquire the cdms_lock_ first, in its own scope. LOGE("WVContentDecryptionModule::Close. cdm_identifier not found.");
bool cdms_empty = false; // TODO(blueeyes): Create a better error.
{ return UNKNOWN_ERROR;
std::unique_lock<std::mutex> auto_lock(cdms_lock_);
auto it = cdms_.find(cdm_identifier);
if (it == cdms_.end()) {
LOGE("WVContentDecryptionModule::Close. cdm_identifier not found.");
// TODO(blueeyes): Create a better error.
return UNKNOWN_ERROR;
}
// Remove any sessions that point to this engine.
for (auto session_it = cdm_by_session_id_.begin();
session_it != cdm_by_session_id_.end();) {
if (session_it->second == it->second.cdm_engine.get()) {
session_it = cdm_by_session_id_.erase(session_it);
} else {
++session_it;
}
}
cdms_.erase(it);
cdms_empty = cdms_.empty();
} }
// Remove any sessions that point to this engine.
if (cdms_empty) { for (auto session_it = cdm_by_session_id_.begin();
DisablePolicyTimer(); session_it != cdm_by_session_id_.end();) {
if (session_it->second == it->second.cdm_engine.get()) {
session_it = cdm_by_session_id_.erase(session_it);
} else {
++session_it;
}
} }
cdms_.erase(it);
return NO_ERROR; return NO_ERROR;
} }
@@ -462,23 +451,60 @@ CdmResponseType WvContentDecryptionModule::GetDecryptHashError(
return cdm_engine->GetDecryptHashError(session_id, hash_error_string); return cdm_engine->GetDecryptHashError(session_id, hash_error_string);
} }
void WvContentDecryptionModule::EnablePolicyTimer() { void WvContentDecryptionModule::EnableTimer() {
std::unique_lock<std::mutex> auto_lock(policy_timer_lock_); std::unique_lock<std::mutex> auto_lock(timer_lock_);
if (!policy_timer_.IsRunning()) if (!timer_.IsRunning()) timer_.Start(this, kCdmTimerDurationSeconds);
policy_timer_.Start(this, kCdmPolicyTimerDurationSeconds);
} }
void WvContentDecryptionModule::DisablePolicyTimer() { void WvContentDecryptionModule::DisableTimer() {
std::unique_lock<std::mutex> auto_lock(policy_timer_lock_); std::unique_lock<std::mutex> auto_lock(timer_lock_);
if (policy_timer_.IsRunning()) { if (timer_.IsRunning()) {
policy_timer_.Stop(); timer_.Stop(false);
}
}
void WvContentDecryptionModule::DisableTimerAndWaitForExit() {
std::unique_lock<std::mutex> auto_lock(timer_lock_);
if (timer_.IsRunning()) {
timer_.Stop(true);
} }
} }
void WvContentDecryptionModule::OnTimerEvent() { void WvContentDecryptionModule::OnTimerEvent() {
std::unique_lock<std::mutex> auto_lock(cdms_lock_); bool disable_timer = false;
for (auto it = cdms_.begin(); it != cdms_.end(); ++it) { {
it->second.cdm_engine->OnTimerEvent(); std::unique_lock<std::mutex> auto_lock(cdms_lock_);
for (auto it = cdms_.begin(); it != cdms_.end(); ++it) {
it->second.cdm_engine->OnTimerEvent();
}
if (cdms_.empty()) {
if (CryptoSession::TryTerminate()) {
// If CryptoSession is in a state to be terminated, try acquiring the
// |timer_lock_| before deciding whether to disable the timer. If the
// lock cannot be acquired, there is no need to disable the timer.
// The |timer_lock_| is either being held by
// * EnableTimer - This does not appear possible, but if it did
// happen |OnTimerEvent| will be called again. The timer can be
// disabled during a future call.
// * DisableTimer - If so, allow that call to disable the timer. No
// need to call to disable it here.
// * DisableTimerAndWaitForExit - If so, allow that call to disable
// the timer. No need to call to disable it here. Note that
// |DisableTimerAndWaitForExit| will try to stop the timer but
// wait for it to exit. This might kick off the timer thread one
// last time, which will call into OnTimerEvent. Calling
// |DisableTimer| here will result in deadlock.
if (timer_lock_.try_lock()) {
disable_timer = true;
timer_lock_.unlock();
}
}
}
}
// Release |cdms_lock_| before attempting to disable the timer
if (disable_timer) {
DisableTimer();
} }
} }

View File

@@ -8,17 +8,46 @@
namespace wvcdm { namespace wvcdm {
namespace {
// duration in seconds after starting the timer
constexpr uint32_t kTestDuration = 5;
// duration in seconds after test completes, before evaluating the result
constexpr uint32_t kPostTestDuration = 3;
} // namespace
class TestTimerHandler : public TimerHandler { class TestTimerHandler : public TimerHandler {
public: public:
TestTimerHandler() : timer_events_(0){}; TestTimerHandler() : timer_events_count_(0){};
virtual ~TestTimerHandler(){}; virtual ~TestTimerHandler(){};
virtual void OnTimerEvent() { timer_events_++; } virtual void OnTimerEvent() { timer_events_count_++; }
uint32_t timer_events() { return timer_events_; } uint32_t timer_events_count() { return timer_events_count_; }
private: protected:
uint32_t timer_events_; uint32_t timer_events_count_;
};
class TestStopTimerHandler : public TestTimerHandler {
public:
TestStopTimerHandler(uint32_t stop_at_timer_events_count, Timer* timer)
: stop_at_timer_events_count_(stop_at_timer_events_count),
timer_(timer) {}
virtual ~TestStopTimerHandler() {}
virtual void OnTimerEvent() {
if (++timer_events_count_ >= stop_at_timer_events_count_) {
if (timer_ != nullptr) {
timer_->Stop(false);
}
}
}
protected:
uint32_t stop_at_timer_events_count_;
Timer* timer_;
}; };
TEST(TimerTest, ParametersCheck) { TEST(TimerTest, ParametersCheck) {
@@ -29,25 +58,58 @@ TEST(TimerTest, ParametersCheck) {
EXPECT_FALSE(timer.Start(&handler, 0)); EXPECT_FALSE(timer.Start(&handler, 0));
} }
TEST(TimerTest, TimerCheck) { TEST(TimerTest, TimerCheck_StopAndWaitForExit) {
TestTimerHandler handler; TestTimerHandler handler;
Timer timer; Timer timer;
uint32_t duration = 10;
EXPECT_EQ(0u, handler.timer_events()); EXPECT_EQ(0u, handler.timer_events_count());
EXPECT_FALSE(timer.IsRunning()); EXPECT_FALSE(timer.IsRunning());
EXPECT_TRUE(timer.Start(&handler, 1)); EXPECT_TRUE(timer.Start(&handler, 1));
EXPECT_TRUE(timer.IsRunning()); EXPECT_TRUE(timer.IsRunning());
sleep(duration); sleep(kTestDuration);
EXPECT_LE(duration - 1, handler.timer_events()); EXPECT_LE(kTestDuration - 1, handler.timer_events_count());
EXPECT_LE(handler.timer_events(), duration + 1); EXPECT_LE(handler.timer_events_count(), kTestDuration + 1);
timer.Stop(); timer.Stop(true);
EXPECT_FALSE(timer.IsRunning()); EXPECT_FALSE(timer.IsRunning());
sleep(duration); sleep(kPostTestDuration);
EXPECT_LE(duration - 1, handler.timer_events()); EXPECT_LE(kTestDuration - 1, handler.timer_events_count());
EXPECT_LE(handler.timer_events(), duration + 1); EXPECT_LE(handler.timer_events_count(), kTestDuration + 1);
} }
TEST(TimerTest, TimerCheck_Stop) {
TestTimerHandler handler;
Timer timer;
EXPECT_EQ(0u, handler.timer_events_count());
EXPECT_FALSE(timer.IsRunning());
EXPECT_TRUE(timer.Start(&handler, 1));
EXPECT_TRUE(timer.IsRunning());
sleep(kTestDuration);
EXPECT_LE(kTestDuration - 1, handler.timer_events_count());
EXPECT_LE(handler.timer_events_count(), kTestDuration + 1);
timer.Stop(false);
sleep(kPostTestDuration);
EXPECT_FALSE(timer.IsRunning());
}
TEST(TimerTest, StopOnTimerThread) {
Timer timer;
TestStopTimerHandler handler(kTestDuration - 2, &timer);
EXPECT_EQ(0u, handler.timer_events_count());
EXPECT_FALSE(timer.IsRunning());
EXPECT_TRUE(timer.Start(&handler, 1));
EXPECT_TRUE(timer.IsRunning());
sleep(kTestDuration + kPostTestDuration);
EXPECT_FALSE(timer.IsRunning());
}
} // namespace wvcdm } // namespace wvcdm