blob: 25f67788a7ad88e27a84fe113bc3938cfa1384f9 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/sys/time/network_time_service/service.h"
#include <fcntl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/sys/cpp/file_descriptor.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/zx/time.h>
#include <stdio.h>
#include <time.h>
#include <thread>
#include "src/lib/files/scoped_temp_dir.h"
#include "src/sys/time/lib/network_time/test/common.h"
#include "src/sys/time/lib/network_time/test/local_roughtime_server.h"
#include "src/sys/time/lib/network_time/time_server_config.h"
#include "third_party/roughtime/protocol.h"
namespace time_external = fuchsia::time::external;
namespace network_time_service {
// Although our test Roughtime server doesn't advance time, there is some error introduced
// in the processing that results in the final reported sample being slightly off.
const int64_t kTimeSpreadNanos = 100'000'000;
const int64_t kExpectedTimeNanos = 7'000'000'000'000;
const uint64_t kTestNanosAfterPoll = 100;
class PushSourceTest : public gtest::TestLoopFixture {
protected:
void TearDown() override {
TestLoopFixture::TearDown();
if (local_roughtime_server_) {
local_roughtime_server_->Stop();
}
time_service_.reset();
}
std::shared_ptr<time_server::LocalRoughtimeServer> local_roughtime_server_ = nullptr;
// Launch a local Roughtime server in a new thread. Returns the port it is running on.
uint16_t LaunchLocalRoughtimeServer(const uint8_t* private_key) {
std::unique_ptr<time_server::LocalRoughtimeServer> local_roughtime_raw =
time_server::LocalRoughtimeServer::MakeInstance(private_key, 0, 1537485257118'000);
// The roughtime server thread may outlive the test, so wrap the server in a shared pointer
// so both test and server threads may safely access it.
local_roughtime_server_.reset(local_roughtime_raw.release());
std::shared_ptr<time_server::LocalRoughtimeServer> local_roughtime_server(
local_roughtime_server_);
auto roughtime_thread = std::make_unique<std::thread>(
std::thread([local_roughtime_server]() { local_roughtime_server->Start(); }));
roughtime_thread->detach();
uint16_t port_number = local_roughtime_server_->GetPortNumber();
EXPECT_GT(port_number, 0);
return port_number;
}
// Connect to the PushSource protocol of a TimeServiceImpl. Launches it if not already launched.
time_external::PushSourcePtr ConnectToService(uint16_t roughtime_port) {
if (!time_service_) {
time_server::TimeServerConfig config = ConfigForLocalServer(roughtime_port);
time_server::RoughTimeServer server = config.ServerList()[0];
network_time_service::RetryConfig retry_config(kTestNanosAfterPoll, 0, 1,
kTestNanosAfterPoll);
time_service_.reset(
new TimeServiceImpl(provider_.TakeContext(), server, dispatcher(), retry_config));
}
time_external::PushSourcePtr push_source;
provider_.ConnectToPublicService(push_source.NewRequest());
return push_source;
}
private:
time_server::TimeServerConfig ConfigForLocalServer(uint16_t port_number) {
std::string config_json = time_server::local_client_config(port_number);
std::string client_config_path;
files::ScopedTempDir temp_dir;
temp_dir.NewTempFileWithData(config_json, &client_config_path);
time_server::TimeServerConfig config;
config.Parse(client_config_path);
return config;
}
std::unique_ptr<TimeServiceImpl> time_service_;
sys::testing::ComponentContextProvider provider_;
};
TEST_F(PushSourceTest, PushSourceRejectsMultipleClients) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kTestPrivateKey);
local_roughtime_server_->SetTime(kExpectedTimeNanos / 1000);
auto first_client = ConnectToService(roughtime_port);
// Currently the PushSource implementation accepts only one client at a time - see fxbug.dev/58068
auto second_client = ConnectToService(roughtime_port);
bool error_handler_called = false;
second_client.set_error_handler([&](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_ALREADY_BOUND);
error_handler_called = true;
});
second_client->WatchSample([&](time_external::TimeSample sample) { FAIL(); });
RunLoopUntilIdle();
EXPECT_TRUE(error_handler_called);
// original client should still be usable.
bool call_completed = false;
first_client->WatchSample([&](time_external::TimeSample sample) { call_completed = true; });
RunLoopUntilIdle();
EXPECT_TRUE(call_completed);
}
TEST_F(PushSourceTest, PushSourceStateResetOnDisconnect) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kTestPrivateKey);
local_roughtime_server_->SetTime(kExpectedTimeNanos / 1000);
time_external::TimeSample original_sample;
auto first_client = ConnectToService(roughtime_port);
bool first_call_complete = false;
first_client->WatchSample([&](time_external::TimeSample sample) {
original_sample = std::move(sample);
first_call_complete = true;
});
RunLoopUntilIdle();
EXPECT_TRUE(first_call_complete);
first_client.Unbind();
RunLoopUntilIdle();
// last sent state for previous client should not be retained, so call should
// return immediately with the same result.
bool second_call_complete = false;
auto second_client = ConnectToService(roughtime_port);
second_client->WatchSample([&](time_external::TimeSample sample) {
EXPECT_EQ(original_sample.monotonic(), sample.monotonic());
EXPECT_EQ(original_sample.utc(), sample.utc());
second_call_complete = true;
});
RunLoopUntilIdle();
EXPECT_TRUE(second_call_complete);
}
TEST_F(PushSourceTest, WatchSample) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kTestPrivateKey);
auto proxy = ConnectToService(roughtime_port);
local_roughtime_server_->SetTime(kExpectedTimeNanos / 1000);
bool first_call_complete = false;
zx_time_t mono_before = zx_clock_get_monotonic();
proxy->WatchSample([&](time_external::TimeSample sample) {
zx_time_t mono_after = zx_clock_get_monotonic();
EXPECT_GT(sample.utc(), kExpectedTimeNanos - kTimeSpreadNanos);
EXPECT_LT(sample.utc(), kExpectedTimeNanos + kTimeSpreadNanos);
EXPECT_GT(sample.monotonic(), mono_before);
EXPECT_LT(sample.monotonic(), mono_after);
EXPECT_GT(sample.standard_deviation(), 0);
EXPECT_LT(sample.standard_deviation(), EstimateStandardDeviation(mono_before, mono_after));
first_call_complete = true;
});
RunLoopUntilIdle();
EXPECT_TRUE(first_call_complete);
bool second_call_complete = false;
mono_before = zx_clock_get_monotonic();
proxy->WatchSample([&](time_external::TimeSample sample) {
zx_time_t mono_after = zx_clock_get_monotonic();
EXPECT_GT(sample.utc(), kExpectedTimeNanos - kTimeSpreadNanos);
EXPECT_LT(sample.utc(), kExpectedTimeNanos + kTimeSpreadNanos);
EXPECT_GT(sample.monotonic(), mono_before);
EXPECT_LT(sample.monotonic(), mono_after);
EXPECT_GT(sample.standard_deviation(), 0);
EXPECT_LT(sample.standard_deviation(), EstimateStandardDeviation(mono_before, mono_after));
second_call_complete = true;
});
// Next poll should only complete after the configured wait period.
RunLoopFor(zx::nsec(kTestNanosAfterPoll / 2));
EXPECT_FALSE(second_call_complete);
RunLoopFor(zx::nsec(kTestNanosAfterPoll));
EXPECT_TRUE(second_call_complete);
}
TEST_F(PushSourceTest, WatchStatus) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kTestPrivateKey);
auto proxy = ConnectToService(roughtime_port);
bool call_complete = false;
proxy->WatchStatus([&](time_external::Status status) {
EXPECT_EQ(status, time_external::Status::OK);
call_complete = true;
});
RunLoopUntilIdle();
// Second call should not complete since status should not change.
// A call to WatchSample made afterwards should complete while the status watch is active.
proxy->WatchStatus([&](time_external::Status status) { FAIL(); });
bool sample_call_complete = false;
proxy->WatchSample([&](time_external::TimeSample sample) { sample_call_complete = true; });
RunLoopUntilIdle();
EXPECT_TRUE(sample_call_complete);
}
TEST_F(PushSourceTest, WatchStatusUnhealthy) {
// Connect to a roughtime server signing with a bad key to simulate unhealthy.
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kWrongPrivateKey);
auto proxy = ConnectToService(roughtime_port);
// First call always indicates OK
proxy->WatchStatus(
[&](time_external::Status status) { EXPECT_EQ(status, time_external::Status::OK); });
RunLoopUntilIdle();
// Obtaining a sample should fail and result in reporting an unhealthy status.
proxy->WatchSample([&](time_external::TimeSample sample) { FAIL(); });
bool second_call_complete = false;
proxy->WatchStatus([&](time_external::Status status) {
EXPECT_EQ(status, time_external::Status::PROTOCOL);
second_call_complete = true;
});
RunLoopUntilIdle();
EXPECT_TRUE(second_call_complete);
}
TEST_F(PushSourceTest, ChannelClosedOnConcurrentWatchStatus) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kWrongPrivateKey);
bool error_handler_called = false;
auto proxy = ConnectToService(roughtime_port);
proxy.set_error_handler([&](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_BAD_STATE);
error_handler_called = true;
});
// first call always completes immediately
proxy->WatchStatus(
[&](time_external::Status status) { EXPECT_EQ(status, time_external::Status::OK); });
RunLoopUntilIdle();
proxy->WatchStatus([&](time_external::Status sample) { FAIL(); });
proxy->WatchStatus([&](time_external::Status sample) { FAIL(); });
RunLoopUntilIdle();
EXPECT_TRUE(error_handler_called);
}
TEST_F(PushSourceTest, ChannelClosedOnConcurrentWatchSample) {
uint16_t roughtime_port = LaunchLocalRoughtimeServer(time_server::kWrongPrivateKey);
bool error_handler_called = false;
auto proxy = ConnectToService(roughtime_port);
proxy.set_error_handler([&](zx_status_t status) {
EXPECT_EQ(status, ZX_ERR_BAD_STATE);
error_handler_called = true;
});
// First call should not complete as server is returning bad responses.
proxy->WatchSample([&](time_external::TimeSample sample) { FAIL(); });
proxy->WatchSample([&](time_external::TimeSample sample) { FAIL(); });
RunLoopUntilIdle();
EXPECT_TRUE(error_handler_called);
}
class RetryConfigTest : public gtest::TestLoopFixture {};
TEST_F(RetryConfigTest, DefaultConfigTest) {
RetryConfig config;
std::vector<uint32_t> expected_durations_sec{1, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 8};
for (uint32_t failure_num = 0; failure_num < expected_durations_sec.size(); failure_num++) {
EXPECT_EQ(zx::sec(expected_durations_sec[failure_num]), config.WaitAfterFailure(failure_num));
}
}
} // namespace network_time_service