// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. #include "test_host.h" #include #include #include #include "cdm_version.h" #include "device_cert.h" #include "file_store.h" #include "log.h" using namespace widevine; namespace { constexpr char kCertificateFilename[] = "cert.bin"; // Some files are expected to go in global storage. All other files are expected // to go in per-origin storage. To help us enforce this in tests, this set // tracks the filenames that belong in global storage. TestHost::Storage will // reject attempts to access these files via per-origin storage or to access // files not in this list via global storage. const std::unordered_set kGlobalFilenames = { "usgtable.bin", "StoredUsageTime.dat", "GenerationNumber.dat", "persistent.dat", "okp.bin", "keybox.dat", wvutil::kOemCertificateFileName, }; } // namespace TestHost::TestHost() : global_storage_(true), per_origin_storage_(false) { Reset(); } TestHost::~TestHost() { wvutil::TestSleep::set_callback(nullptr); } void TestHost::Reset() { auto now = std::chrono::system_clock().now(); now_ = now.time_since_epoch() / std::chrono::milliseconds(1); wvutil::TestSleep::set_callback(this); // Surprisingly, std::priority_queue has no clear(). while (!timers_.empty()) { timers_.pop(); } global_storage_.Reset(); per_origin_storage_.Reset(); } void TestHost::ElapseTime(int64_t milliseconds) { // Note that, during the time rollback tests, milliseconds will be negative, // so we cannot assume goal_time > now_. int64_t goal_time = now_ + milliseconds; // Walk forward from now_ to goal_time, stepping at each timer along the way // to fire its callback. while (!timers_.empty() && now_ < goal_time) { Timer t = timers_.top(); ASSERT_GE(t.expiry_time(), now_); if (t.expiry_time() <= goal_time) { timers_.pop(); now_ = t.expiry_time(); t.client()->onTimerExpired(t.context()); } else { // The next timer is further in the future than goal_time, so we are done // processing the timers. break; } } // No matter what happened with the timers, update now_ to the goal_time. now_ = goal_time; } size_t TestHost::NumTimers() const { return timers_.size(); } int64_t TestHost::now() { return now_; } void TestHost::setTimeout(int64_t delay_ms, IClient* client, void* context) { int64_t expiry_time = now_ + delay_ms; timers_.push(Timer(expiry_time, client, context)); } void TestHost::cancel(IClient* client) { // Filter out the timers for this client and put the rest into |others|. std::priority_queue others; while (timers_.size()) { Timer t = timers_.top(); timers_.pop(); if (t.client() != client) { others.push(t); } } // Now swap the queues. std::swap(timers_, others); } TestHost::Storage::Storage(bool is_global) : is_global_(is_global) { Reset(); } void TestHost::Storage::Reset() { files_.clear(); if (!is_global_) { files_[kCertificateFilename] = std::string((const char*)kDeviceCert, kDeviceCertSize); } } bool TestHost::Storage::read(const std::string& name, std::string* data) { StorageMap::iterator it = files_.find(name); bool ok = it != files_.end(); LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail"); if (!CheckFilename(name) || !ok) return false; *data = it->second; return true; } bool TestHost::Storage::write(const std::string& name, const std::string& data) { LOGV("write file: %s", name.c_str()); if (!CheckFilename(name)) return false; files_[name] = data; return true; } bool TestHost::Storage::exists(const std::string& name) { StorageMap::iterator it = files_.find(name); bool ok = it != files_.end(); LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false"); if (!CheckFilename(name)) return false; return ok; } bool TestHost::Storage::remove(const std::string& name) { if (name.empty()) { // If no name, delete all files (see DeviceFiles::DeleteAllFiles()) LOGV("remove all files"); files_.clear(); return true; } LOGV("remove: %s", name.c_str()); if (!CheckFilename(name)) return false; return files_.erase(name) > 0; } int32_t TestHost::Storage::size(const std::string& name) { StorageMap::iterator it = files_.find(name); bool ok = (it != files_.end()); LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail"); if (!CheckFilename(name) || !ok) return -1; return static_cast(it->second.size()); } bool TestHost::Storage::list(std::vector* names) { names->clear(); for (StorageMap::iterator it = files_.begin(); it != files_.end(); it++) { names->push_back(it->first); } return true; } bool TestHost::Storage::CheckFilename(const std::string& name) { const bool is_global_filename = (kGlobalFilenames.find(name) != kGlobalFilenames.end()); if (is_global_ != is_global_filename) { LOGE("Attempt to access %s in %s storage rejected.", name.c_str(), is_global_ ? "global" : "per-origin"); return false; } return true; }