There are three changes here that should help reduce the amount of duration test failures caused by clock skew. First, we reported some skew when the test expected playback to start immediately after loading the license. However, with round-off, this could easily be more than 1 second. So this does not warrent even a warning. Second, the fake and real clocks were only synced after computing how long to sleep. This is fixed by moving SleepUntil to the TestSleep class and having it sync before computing the delta and after doing the sleep. Third, I am guessing that some failures due to unexpected lenience were caused by the rental or playback clock being started at the end of signing the license or the end of the first decrypt instead of the beginning. We work around this by recording how long these operations take, and then adding this extra time at the end of the check for FailDecrypt. Bug: 275003529 Bug: 279249646 Bug: 207500749 Merged from https://widevine-internal-review.googlesource.com/176070 Change-Id: I6a973565edfbebca53ee7f239b4b93f8f73d1e0a
189 lines
6.6 KiB
C++
189 lines
6.6 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 <inttypes.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::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<unsigned int>(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();
|
|
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
|