// Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "test_sleep.h" #ifdef _WIN32 # include #else # include #endif #ifdef __APPLE__ # include #endif #include #include #include #include #include #include "clock.h" #include "log.h" namespace wvutil { bool TestSleep::real_sleep_ = true; std::unordered_set TestSleep::callbacks_; int TestSleep::total_clock_rollback_seconds_ = 0; void TestSleep::Sleep(unsigned int seconds) { int64_t milliseconds = 1000 * static_cast(seconds); if (real_sleep_) { // This next bit of logic is to avoid slow drift apart of the real clock and // the fake clock. We compute how far from the real clock has advanced in // 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 const auto start_real = std::chrono::system_clock().now(); sleep(seconds); const auto now_real = std::chrono::system_clock().now(); const int64_t rollback_adjustment = 1000 * static_cast(total_clock_rollback_seconds_); const int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1) + rollback_adjustment; static int64_t fake_clock = 0; // We want to advance the fake clock by the difference between the real // clock, and the previous value on the fake clock. milliseconds = total_real - fake_clock; fake_clock += milliseconds; } for (auto* cb : callbacks_) cb->ElapseTime(milliseconds); } void TestSleep::SleepUntil(int64_t desired_time) { SyncFakeClock(); const int64_t now = Clock().GetCurrentTime(); if (desired_time < now) { LOGE("Test Clock skew sleeping from time %" PRId64 " to %" PRId64, now, desired_time); return; } const unsigned int sleep_time = static_cast(desired_time - now); TestSleep::Sleep(sleep_time); } void TestSleep::SyncFakeClock() { // Syncing can be done by sleeping 0 seconds. Sleep(0); } void TestSleep::SetFakeClock(int64_t time_seconds) { if (real_sleep_) { LOGE("SetFakeClock when using a real clock. Expect other failures."); } // Delta could be positive or negative. If the fake clock had been initialized // by the current time on a real clock, and then the command line // re-initializes it to 0, then delta is negative. int64_t delta = time_seconds - Clock().GetCurrentTime(); for (auto* cb : callbacks_) cb->ElapseTime(delta * 1000); if (callbacks_.empty()) { LOGE("Setting fake clock with no callback. This won't work."); } } 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; #elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE LOGE("iOS time rollback: cannot set system 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_ += seconds; for (auto* cb : callbacks_) { cb->ElapseTime(-1000 * static_cast(seconds)); } return true; } bool TestSleep::CanChangeSystemTime() { // If we are using a fake clock, then we can move the clock backwards by // just going backwards. 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; #elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE LOGE("iOS 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 wvutil