176 lines
6.3 KiB
C++
176 lines
6.3 KiB
C++
// 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 <windows.h>
|
|
#else
|
|
# include <sys/types.h>
|
|
#endif
|
|
#ifdef __APPLE__
|
|
# include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <chrono>
|
|
|
|
#include "clock.h"
|
|
#include "log.h"
|
|
|
|
namespace wvutil {
|
|
|
|
bool TestSleep::real_sleep_ = true;
|
|
TestSleep::CallBack* TestSleep::callback_ = nullptr;
|
|
int TestSleep::total_clock_rollback_seconds_ = 0;
|
|
|
|
void TestSleep::Sleep(unsigned int seconds) {
|
|
int64_t milliseconds = 1000 * 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 * 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;
|
|
}
|
|
if (callback_ != nullptr) callback_->ElapseTime(milliseconds);
|
|
}
|
|
|
|
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();
|
|
if (callback_ != nullptr) {
|
|
callback_->ElapseTime(delta * 1000);
|
|
} else {
|
|
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<uint64_t>(file_time.dwLowDateTime) |
|
|
(static_cast<uint64_t>(file_time.dwHighDateTime) << 32);
|
|
long_time += static_cast<uint64_t>(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<std::chrono::seconds>(
|
|
modified_time.time_since_epoch())
|
|
.count();
|
|
time_spec.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
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;
|
|
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.
|
|
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<void, decltype(&CloseHandle)> 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<TOKEN_PRIVILEGES, decltype(&free)> 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
|