// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/backoff_entry.h" #include "base/containers/span.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/time/tick_clock.h" #include "base/time/time.h" #include "base/values.h" #include "net/base/backoff_entry_serializer.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { namespace { using base::Time; using base::TimeTicks; const Time kParseTime = Time::FromJsTime(1430907555111); // May 2015 for realism BackoffEntry::Policy base_policy = { 0 /* num_errors_to_ignore */, 1000 /* initial_delay_ms */, 2.0 /* multiply_factor */, 0.0 /* jitter_factor */, 20000 /* maximum_backoff_ms */, 2000 /* entry_lifetime_ms */, false /* always_use_initial_delay */ }; class TestTickClock : public base::TickClock { public: TestTickClock() = default; TestTickClock(const TestTickClock&) = delete; TestTickClock& operator=(const TestTickClock&) = delete; ~TestTickClock() override = default; TimeTicks NowTicks() const override { return now_ticks_; } void set_now(TimeTicks now) { now_ticks_ = now; } private: TimeTicks now_ticks_; }; // This test exercises the code that computes the "backoff duration" and tests // BackoffEntrySerializer::SerializeToList computes the backoff duration of a // BackoffEntry by subtracting two base::TimeTicks values. Note that // base::TimeTicks::operator- does not protect against overflow. Because // SerializeToList never returns null, its resolution strategy is to default to // a zero base::TimeDelta when the subtraction would overflow. TEST(BackoffEntrySerializerTest, SpecialCasesOfBackoffDuration) { const base::TimeTicks kZeroTicks; struct TestCase { base::TimeTicks release_time; base::TimeTicks timeticks_now; base::TimeDelta expected_backoff_duration; }; TestCase test_cases[] = { // Non-overflowing subtraction works as expected. { .release_time = kZeroTicks + base::Microseconds(100), .timeticks_now = kZeroTicks + base::Microseconds(75), .expected_backoff_duration = base::Microseconds(25), }, { .release_time = kZeroTicks + base::Microseconds(25), .timeticks_now = kZeroTicks + base::Microseconds(100), .expected_backoff_duration = base::Microseconds(-75), }, // Defaults to zero when one of the operands is +/- infinity. { .release_time = base::TimeTicks::Min(), .timeticks_now = kZeroTicks, .expected_backoff_duration = base::TimeDelta(), }, { .release_time = base::TimeTicks::Max(), .timeticks_now = kZeroTicks, .expected_backoff_duration = base::TimeDelta(), }, { .release_time = kZeroTicks, .timeticks_now = base::TimeTicks::Min(), .expected_backoff_duration = base::TimeDelta(), }, { .release_time = kZeroTicks, .timeticks_now = base::TimeTicks::Max(), .expected_backoff_duration = base::TimeDelta(), }, // Defaults to zero when both of the operands are +/- infinity. { .release_time = base::TimeTicks::Min(), .timeticks_now = base::TimeTicks::Min(), .expected_backoff_duration = base::TimeDelta(), }, { .release_time = base::TimeTicks::Min(), .timeticks_now = base::TimeTicks::Max(), .expected_backoff_duration = base::TimeDelta(), }, { .release_time = base::TimeTicks::Max(), .timeticks_now = base::TimeTicks::Min(), .expected_backoff_duration = base::TimeDelta(), }, { .release_time = base::TimeTicks::Max(), .timeticks_now = base::TimeTicks::Max(), .expected_backoff_duration = base::TimeDelta(), }, // Defaults to zero when the subtraction overflows, even when neither // operand is infinity. { .release_time = base::TimeTicks::Max() - base::Microseconds(1), .timeticks_now = kZeroTicks + base::Microseconds(-1), .expected_backoff_duration = base::TimeDelta(), }, }; size_t test_index = 0; for (const TestCase& test_case : test_cases) { SCOPED_TRACE(base::StringPrintf("Running test case #%zu", test_index)); ++test_index; Time original_time = base::Time::Now(); TestTickClock original_ticks; original_ticks.set_now(test_case.timeticks_now); BackoffEntry original(&base_policy, &original_ticks); // Set the custom release time. original.SetCustomReleaseTime(test_case.release_time); base::Value::List serialized = BackoffEntrySerializer::SerializeToList(original, original_time); // Check that the serialized backoff duration matches our expectation. const std::string& serialized_backoff_duration_string = serialized[2].GetString(); int64_t serialized_backoff_duration_us; EXPECT_TRUE(base::StringToInt64(serialized_backoff_duration_string, &serialized_backoff_duration_us)); base::TimeDelta serialized_backoff_duration = base::Microseconds(serialized_backoff_duration_us); EXPECT_EQ(serialized_backoff_duration, test_case.expected_backoff_duration); } } // This test verifies that BackoffEntrySerializer::SerializeToList will not // serialize an infinite release time. // // In pseudocode, this is how absolute_release_time is computed: // backoff_duration = release_time - now; // absolute_release_time = backoff_duration + original_time; // // This test induces backoff_duration to be a nonzero duration and directly sets // original_time as a large value, such that their addition will overflow. TEST(BackoffEntrySerializerTest, SerializeFiniteReleaseTime) { const TimeTicks release_time = TimeTicks() + base::Microseconds(5); const Time original_time = Time::Max() - base::Microseconds(4); TestTickClock original_ticks; original_ticks.set_now(TimeTicks()); BackoffEntry original(&base_policy, &original_ticks); original.SetCustomReleaseTime(release_time); base::Value::List serialized = BackoffEntrySerializer::SerializeToList(original, original_time); // Reach into the serialization and check the string-formatted release time. const std::string& serialized_release_time = serialized[3].GetString(); EXPECT_EQ(serialized_release_time, "0"); // Test that |DeserializeFromList| notices this zero-valued release time and // does not take it at face value. std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList(serialized, &base_policy, &original_ticks, kParseTime); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.GetReleaseTime(), deserialized->GetReleaseTime()); } TEST(BackoffEntrySerializerTest, SerializeNoFailures) { Time original_time = Time::Now(); TestTickClock original_ticks; original_ticks.set_now(TimeTicks::Now()); BackoffEntry original(&base_policy, &original_ticks); base::Value::List serialized = BackoffEntrySerializer::SerializeToList(original, original_time); std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, &original_ticks, original_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); EXPECT_EQ(original.GetReleaseTime(), deserialized->GetReleaseTime()); } // Test that deserialization fails instead of producing an entry with an // infinite release time. (Regression test for https://crbug.com/1293904) TEST(BackoffEntrySerializerTest, DeserializeNeverInfiniteReleaseTime) { base::Value::List serialized; serialized.Append(2); serialized.Append(2); serialized.Append("-9223372036854775807"); serialized.Append("2"); TestTickClock original_ticks; original_ticks.set_now(base::TimeTicks() + base::Microseconds(-1)); base::Time time_now = base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(-1)); std::unique_ptr entry = BackoffEntrySerializer::DeserializeFromList(serialized, &base_policy, &original_ticks, time_now); ASSERT_FALSE(entry); } TEST(BackoffEntrySerializerTest, SerializeTimeOffsets) { Time original_time = Time::FromJsTime(1430907555111); // May 2015 for realism TestTickClock original_ticks; BackoffEntry original(&base_policy, &original_ticks); // 2 errors. original.InformOfRequest(false); original.InformOfRequest(false); base::Value::List serialized = BackoffEntrySerializer::SerializeToList(original, original_time); { // Test that immediate deserialization round-trips. std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, &original_ticks, original_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); EXPECT_EQ(original.GetReleaseTime(), deserialized->GetReleaseTime()); } { // Test deserialization when wall clock has advanced but TimeTicks::Now() // hasn't (e.g. device was rebooted). Time later_time = original_time + base::Days(1); std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, &original_ticks, later_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); // Remaining backoff duration continues decreasing while device is off. // Since TimeTicks::Now() has not advanced, the absolute release time ticks // will decrease accordingly. EXPECT_GT(original.GetTimeUntilRelease(), deserialized->GetTimeUntilRelease()); EXPECT_EQ(original.GetReleaseTime() - base::Days(1), deserialized->GetReleaseTime()); } { // Test deserialization when TimeTicks::Now() has advanced but wall clock // hasn't (e.g. it's an hour later, but a DST change cancelled that out). TestTickClock later_ticks; later_ticks.set_now(TimeTicks() + base::Days(1)); std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, &later_ticks, original_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); // According to the wall clock, no time has passed. So remaining backoff // duration is preserved, hence the absolute release time ticks increases. // This isn't ideal - by also serializing the current time and time ticks, // it would be possible to detect that time has passed but the wall clock // went backwards, and reduce the remaining backoff duration accordingly, // however the current implementation does not do this as the benefit would // be somewhat marginal. EXPECT_EQ(original.GetTimeUntilRelease(), deserialized->GetTimeUntilRelease()); EXPECT_EQ(original.GetReleaseTime() + base::Days(1), deserialized->GetReleaseTime()); } { // Test deserialization when both wall clock and TimeTicks::Now() have // advanced (e.g. it's just later than it used to be). TestTickClock later_ticks; later_ticks.set_now(TimeTicks() + base::Days(1)); Time later_time = original_time + base::Days(1); std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList(serialized, &base_policy, &later_ticks, later_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); // Since both have advanced by the same amount, the absolute release time // ticks should be preserved; the remaining backoff duration will have // decreased of course, since time has passed. EXPECT_GT(original.GetTimeUntilRelease(), deserialized->GetTimeUntilRelease()); EXPECT_EQ(original.GetReleaseTime(), deserialized->GetReleaseTime()); } { // Test deserialization when wall clock has gone backwards but TimeTicks // haven't (e.g. the system clock was fast but they fixed it). EXPECT_LT(base::Seconds(1), original.GetTimeUntilRelease()); Time earlier_time = original_time - base::Seconds(1); std::unique_ptr deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, &original_ticks, earlier_time); ASSERT_TRUE(deserialized.get()); EXPECT_EQ(original.failure_count(), deserialized->failure_count()); // If only the absolute wall clock time was serialized, subtracting the // (decreased) current wall clock time from the serialized wall clock time // could give very large (incorrect) values for remaining backoff duration. // But instead the implementation also serializes the remaining backoff // duration, and doesn't allow the duration to increase beyond it's previous // value during deserialization. Hence when the wall clock goes backwards // the remaining backoff duration will be preserved. EXPECT_EQ(original.GetTimeUntilRelease(), deserialized->GetTimeUntilRelease()); // Since TimeTicks::Now() hasn't changed, the absolute release time ticks // will be equal too in this particular case. EXPECT_EQ(original.GetReleaseTime(), deserialized->GetReleaseTime()); } } TEST(BackoffEntrySerializerTest, DeserializeUnknownVersion) { base::Value::List serialized; serialized.Append(0); // Format version that never existed serialized.Append(0); // Failure count serialized.Append(2.0); // Backoff duration serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_FALSE(deserialized); } TEST(BackoffEntrySerializerTest, DeserializeVersion1) { base::Value::List serialized; serialized.Append(SerializationFormatVersion::kVersion1); serialized.Append(0); // Failure count serialized.Append(2.0); // Backoff duration in seconds as double serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_TRUE(deserialized); } TEST(BackoffEntrySerializerTest, DeserializeVersion2) { base::Value::List serialized; serialized.Append(SerializationFormatVersion::kVersion2); serialized.Append(0); // Failure count serialized.Append("2000"); // Backoff duration serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_TRUE(deserialized); } TEST(BackoffEntrySerializerTest, DeserializeVersion2NegativeDuration) { base::Value::List serialized; serialized.Append(SerializationFormatVersion::kVersion2); serialized.Append(0); // Failure count serialized.Append("-2000"); // Backoff duration serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_TRUE(deserialized); } TEST(BackoffEntrySerializerTest, DeserializeVersion1WrongDurationType) { base::Value::List serialized; serialized.Append(SerializationFormatVersion::kVersion1); serialized.Append(0); // Failure count serialized.Append("2000"); // Backoff duration in seconds as double serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_FALSE(deserialized); } TEST(BackoffEntrySerializerTest, DeserializeVersion2WrongDurationType) { base::Value::List serialized; serialized.Append(SerializationFormatVersion::kVersion2); serialized.Append(0); // Failure count serialized.Append(2.0); // Backoff duration serialized.Append("1234"); // Absolute release time auto deserialized = BackoffEntrySerializer::DeserializeFromList( serialized, &base_policy, nullptr, kParseTime); ASSERT_FALSE(deserialized); } } // namespace } // namespace net