Ejemplo n.º 1
0
        public void AddTimeData_WithLargeInboundSampleSet_ForgetsOldSamples()
        {
            var dateTimeProvider        = new DateTimeProvider();
            TimeSyncBehaviorState state = this.CreateTimeSyncBehaviorState(dateTimeProvider);

            // Add max inbound samples.
            for (int i = 0; i < TimeSyncBehaviorState.MaxInboundSamples; i++)
            {
                bool used = state.AddTimeData(new IPAddress(i), TimeSpan.FromSeconds(400), true);
                Assert.True(used);
            }

            // Add enough outbound samples to get an adjustment (and across an even range).
            Assert.True(state.AddTimeData(new IPAddress(1), TimeSpan.FromSeconds(-500), false));
            Assert.True(state.AddTimeData(new IPAddress(2), TimeSpan.FromSeconds(-250), false));
            Assert.True(state.AddTimeData(new IPAddress(3), TimeSpan.FromSeconds(250), false));
            Assert.True(state.AddTimeData(new IPAddress(4), TimeSpan.FromSeconds(500), false));

            TimeSpan diff = dateTimeProvider.GetAdjustedTime() - dateTimeProvider.GetUtcNow();

            Assert.True(Math.Abs(diff.TotalMilliseconds - 250 * 1000) < TimeEpsilonMs, $"Should be 250ms because leaning towards the inbound value of 400ms, but still in outbound. Actual: {diff.TotalMilliseconds}ms");

            // Add another batch of inbound samples with a greater offset than highest outbound sample.
            for (int i = TimeSyncBehaviorState.MaxInboundSamples; i < TimeSyncBehaviorState.MaxInboundSamples * 2; i++)
            {
                Assert.True(state.AddTimeData(new IPAddress(i * 2), TimeSpan.FromSeconds(-1000), true), $"index: {i}");
            }

            diff = dateTimeProvider.GetAdjustedTime() - dateTimeProvider.GetUtcNow();
            Assert.True(Math.Abs(diff.TotalMilliseconds - (-250 * 1000)) < TimeEpsilonMs, $"Should be -250ms because the new inbound samples replace the old and swing the median closer to the new -1000ms values, but still in outbound. Actual: {diff.TotalMilliseconds}ms");
        }
Ejemplo n.º 2
0
        public void AddTimeData_WithLargeOutboundSampleSet_ForgetsOldSamples()
        {
            var dateTimeProvider        = new DateTimeProvider();
            TimeSyncBehaviorState state = this.CreateTimeSyncBehaviorState(dateTimeProvider);

            // Add max outbound.
            for (int i = 0; i < TimeSyncBehaviorState.MaxOutboundSamples; i++)
            {
                var  peerAddress = new IPAddress(i);
                bool used        = state.AddTimeData(peerAddress, TimeSpan.FromSeconds(400), false);
                Assert.True(used);
            }

            TimeSpan diff = dateTimeProvider.GetAdjustedTime() - dateTimeProvider.GetUtcNow();

            Assert.True(Math.Abs(diff.TotalMilliseconds - 400 * 1000) < TimeEpsilonMs, $"should be 400ms because of outbound preference. Actual: {diff.TotalMilliseconds}ms");

            // Add another batch of outbounds with a different offset.
            for (int i = TimeSyncBehaviorState.MaxOutboundSamples; i < TimeSyncBehaviorState.MaxOutboundSamples * 2; i++)
            {
                var  peerAddress = new IPAddress(i * 2);
                bool used        = state.AddTimeData(peerAddress, TimeSpan.FromSeconds(800), false);
                Assert.True(used, $"index: {i}");
            }

            diff = dateTimeProvider.GetAdjustedTime() - dateTimeProvider.GetUtcNow();
            Assert.True(Math.Abs(diff.TotalMilliseconds - 800 * 1000) < TimeEpsilonMs, $"should be 800ms because of outbound preference. Actual: {diff.TotalMilliseconds}ms");
        }
Ejemplo n.º 3
0
        public void AddTimeData_WithSmallSampleSet_TurnsWarningOnAndSwitchesSyncOff()
        {
            int offsetAboveWarningLevelSeconds = TimeSyncBehaviorState.TimeOffsetWarningThresholdSeconds + 1;
            int offsetAboveWarningLevelMs      = offsetAboveWarningLevelSeconds * 1000;

            int offsetAbovSwitchOffLevel   = BitcoinMain.BitcoinMaxTimeOffsetSeconds + 1;
            int offsetAbovSwitchOffLevelMs = offsetAbovSwitchOffLevel * 1000;

            // Samples to be inserted to the state.
            // Columns meanings:  isUsed, isWarningOn, isSyncOff, timeOffsetSample, peerAddress
            var samples = new List <TestSample>
            {
                // First group of samples does not affect adjusted time, so difference should be ~0 ms.
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(offsetAboveWarningLevelSeconds), IPAddress.Parse("1.2.3.41")),
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(offsetAboveWarningLevelSeconds), IPAddress.Parse("1.2.3.42")),
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(offsetAboveWarningLevelSeconds), IPAddress.Parse("1.2.3.43")),

                // The next sample turns on the warning.
                TestSample.Outbound(true, true, false, offsetAboveWarningLevelMs, TimeSpan.FromSeconds(offsetAboveWarningLevelSeconds), IPAddress.Parse("1.2.3.44")),

                // It can't be turned off.
                TestSample.Outbound(true, true, false, offsetAboveWarningLevelMs, TimeSpan.FromSeconds(0), IPAddress.Parse("1.2.3.45")),
                TestSample.Outbound(true, true, false, offsetAboveWarningLevelMs, TimeSpan.FromSeconds(0), IPAddress.Parse("1.2.3.46")),

                // Add more samples (above switch off level, trying to switch it off).
                TestSample.Outbound(true, true, false, offsetAboveWarningLevelMs, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.3.47")),
                TestSample.Outbound(true, true, false, offsetAboveWarningLevelMs / 2, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.3.48")),
                TestSample.Outbound(true, true, false, 0, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.3.49")),
                TestSample.Outbound(true, true, false, 0, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.31.4")),
                TestSample.Outbound(true, true, false, 0, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.32.4")),
                TestSample.Outbound(true, true, false, -offsetAbovSwitchOffLevelMs / 2, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.33.4")),

                // Now the feature should be turned off.
                TestSample.Outbound(true, true, true, 0, TimeSpan.FromSeconds(-offsetAbovSwitchOffLevel), IPAddress.Parse("1.2.33.5")),

                // No more samples should be accepted now.
                TestSample.Outbound(false, true, true, 0, TimeSpan.FromSeconds(2), IPAddress.Parse("1.2.34.4")),
                TestSample.Outbound(false, true, true, 0, TimeSpan.FromSeconds(1), IPAddress.Parse("1.2.35.4")),
            };

            var dateTimeProvider        = new DateTimeProvider();
            TimeSyncBehaviorState state = this.CreateTimeSyncBehaviorState(dateTimeProvider);


            for (int i = 0; i < samples.Count; i++)
            {
                bool used = state.AddTimeData(samples[i].PeerIpAddress, samples[i].InputTimeOffset, samples[i].IsInbound);

                Assert.Equal(samples[i].ExpectedIsUsed, used);
                Assert.Equal(samples[i].ExpectedIsWarningOn, state.IsSystemTimeOutOfSync);
                Assert.Equal(samples[i].ExpectedIsSyncOff, state.SwitchedOffLimitReached);
                Assert.Equal(samples[i].ExpectedIsSyncOff, state.SwitchedOff);

                TimeSpan diff = dateTimeProvider.GetAdjustedTime() - dateTimeProvider.GetUtcNow();
                Assert.True(Math.Abs(diff.TotalMilliseconds - samples[i].ExpectedTimeOffsetMs) < TimeEpsilonMs, $"Failed in sample at index: {i}. Actual offset milliseconds: {diff.TotalMilliseconds}. Expected offset milliseconds: {samples[i].ExpectedTimeOffsetMs}");
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Sets up the system under test.
        /// </summary>
        /// <param name="dateTimeProvider">The datetime provider injected to the constructor of the system under test.</param>
        /// <returns>The system under test.</returns>
        private TimeSyncBehaviorState CreateTimeSyncBehaviorState(IDateTimeProvider dateTimeProvider)
        {
            var lifetime         = new NodeLifetime();
            var loggerFactory    = new LoggerFactory();
            var asyncLoopFactory = new AsyncLoopFactory(loggerFactory);
            var state            = new TimeSyncBehaviorState(dateTimeProvider, lifetime, asyncLoopFactory, loggerFactory, KnownNetworks.Main);

            return(state);
        }
Ejemplo n.º 5
0
        public void AddTimeData_WithLargeSampleSet_ForgetsOldSamples()
        {
            int inboundSamples  = 300;
            int outboundSamples = 300;

            var dateTimeProvider = DateTimeProvider.Default;
            var lifetime         = new NodeLifetime();
            var loggerFactory    = new LoggerFactory();
            var asyncLoopFactory = new AsyncLoopFactory(loggerFactory);
            var state            = new TimeSyncBehaviorState(dateTimeProvider, lifetime, asyncLoopFactory, loggerFactory);

            var inSamples = new List <int>();

            for (int i = 0; i < inboundSamples; i++)
            {
                IPAddress peerAddress = new IPAddress(i);
                bool      used        = state.AddTimeData(peerAddress, TimeSpan.FromSeconds(i), true);
                Assert.True(used);
                if (i >= inboundSamples - TimeSyncBehaviorState.MaxInboundSamples)
                {
                    inSamples.Add(i * 1000);
                }
            }

            DateTime adjustedTime   = dateTimeProvider.GetAdjustedTime();
            DateTime normalTime     = dateTimeProvider.GetUtcNow();
            TimeSpan diff           = adjustedTime - normalTime;
            int      expectedDiffMs = inSamples.Median();

            Assert.True(Math.Abs(diff.TotalMilliseconds) < Math.Abs(expectedDiffMs) + TimeEpsilonMs);

            var outSamples = new List <int>();

            for (int i = 0; i < outboundSamples; i++)
            {
                IPAddress peerAddress = new IPAddress(i);
                bool      used        = state.AddTimeData(peerAddress, TimeSpan.FromSeconds(-i), false);
                Assert.True(used);
                if (i >= outboundSamples - TimeSyncBehaviorState.MaxOutboundSamples)
                {
                    outSamples.Add(-i * 1000);
                }
            }

            var allSamples = new List <int>(inSamples);

            for (int i = 0; i < TimeSyncBehaviorState.OutboundToInboundWeightRatio; i++)
            {
                allSamples.AddRange(outSamples);
            }

            adjustedTime   = dateTimeProvider.GetAdjustedTime();
            normalTime     = dateTimeProvider.GetUtcNow();
            diff           = adjustedTime - normalTime;
            expectedDiffMs = allSamples.Median();
            Assert.True(Math.Abs(diff.TotalMilliseconds) < Math.Abs(expectedDiffMs) + TimeEpsilonMs);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Sets up the system under test.
        /// </summary>
        /// <param name="dateTimeProvider">The datetime provider injected to the constructor of the system under test.</param>
        /// <returns>The system under test.</returns>
        private TimeSyncBehaviorState CreateTimeSyncBehaviorState(IDateTimeProvider dateTimeProvider)
        {
            var lifetime      = new NodeLifetime();
            var loggerFactory = new LoggerFactory();
            var signals       = new Signals.Signals(loggerFactory, null);

            var asyncProvider = new AsyncProvider(loggerFactory, signals, lifetime);
            var state         = new TimeSyncBehaviorState(dateTimeProvider, lifetime, asyncProvider, loggerFactory, KnownNetworks.Main);

            return(state);
        }
Ejemplo n.º 7
0
        public void AddTimeData_WithSmallSampleSet_CalculatedCorrectly()
        {
            // Samples to be inserted to the state.
            // Columns meanings: isInbound, isUsed, expectedTimeOffsetLessThanMs, timeOffsetSample, peerAddress
            var samples = new List <object[]>
            {
                // First group of samples does not affect adjusted time, so difference should be ~0 ms.
                new object[] { false, true, 0, TimeSpan.FromSeconds(3.56), IPAddress.Parse("1.2.3.4"), },
                new object[] { true, true, 0, TimeSpan.FromSeconds(13.123), IPAddress.Parse("1.2.3.4"), },
                new object[] { false, false, 0, TimeSpan.FromSeconds(7.123), IPAddress.Parse("1.2.3.4"), },
                new object[] { false, true, 0, TimeSpan.FromSeconds(26.0), IPAddress.Parse("2001:0db8:85a3:1232:0000:8a2e:0370:7334"), },
                new object[] { false, false, 0, TimeSpan.FromSeconds(260), IPAddress.Parse("2001:0db8:85a3:1232:0000:8a2e:0370:7334"), },
                new object[] { false, true, 0, TimeSpan.FromSeconds(-2126.0), IPAddress.Parse("1.2.3.5"), },
                new object[] { true, true, 0, TimeSpan.FromSeconds(-391), IPAddress.Parse("1.2.3.45"), },

                // These samples will change adjusted time.
                new object[] { false, true, 1280, TimeSpan.FromSeconds(-1), IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), },
                new object[] { true, true, 3560, TimeSpan.FromSeconds(23.6), IPAddress.Parse("::1"), },
                new object[] { true, true, 3560, TimeSpan.FromSeconds(236), IPAddress.Parse("12.2.3.5"), },
                new object[] { false, true, 1236, TimeSpan.FromSeconds(1.236), IPAddress.Parse("13.2.3.5"), },
                new object[] { true, true, 1236, TimeSpan.FromSeconds(-1000), IPAddress.Parse("14.2.3.5"), },
                new object[] { true, true, 1236, TimeSpan.FromSeconds(-4.9236), IPAddress.Parse("15.2.3.5"), },
                new object[] { false, true, 118, TimeSpan.FromSeconds(-4444.444), IPAddress.Parse("16.2.3.5"), },
            };

            var dateTimeProvider = DateTimeProvider.Default;
            var lifetime         = new NodeLifetime();
            var loggerFactory    = new LoggerFactory();
            var asyncLoopFactory = new AsyncLoopFactory(loggerFactory);
            var state            = new TimeSyncBehaviorState(dateTimeProvider, lifetime, asyncLoopFactory, loggerFactory);

            for (int i = 0; i < samples.Count; i++)
            {
                bool      isInbound = (bool)samples[i][0];
                bool      isUsed    = (bool)samples[i][1];
                int       expectedTimeOffsetLessThanMs = (int)samples[i][2];
                TimeSpan  timeOffsetSample             = (TimeSpan)samples[i][3];
                IPAddress peerAddress = (IPAddress)samples[i][4];

                bool used = state.AddTimeData(peerAddress, timeOffsetSample, isInbound);
                Assert.Equal(isUsed, used);

                DateTime adjustedTime = dateTimeProvider.GetAdjustedTime();
                DateTime normalTime   = dateTimeProvider.GetUtcNow();
                TimeSpan diff         = adjustedTime - normalTime;

                Assert.True(Math.Abs(diff.TotalMilliseconds) < expectedTimeOffsetLessThanMs + TimeEpsilonMs);
            }
        }
Ejemplo n.º 8
0
        public void AddTimeData_WithSmallSampleSet_CalculatedCorrectly()
        {
            // Samples to be inserted to the state.
            var samples = new List <TestSample>
            {
                // Columns meanings: expectedIsUsed, expectedIsWarningOn, expectedIsTimeSyncOff, expectedTimeOffsetMs, timeOffsetSample, peerAddress

                // First group of samples does not affect adjusted time, so difference should be ~0 ms.
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(3.56), new IPAddress(1)),
                TestSample.Inbound(true, false, false, 0, TimeSpan.FromSeconds(13.123), new IPAddress(1)),
                TestSample.Outbound(false, false, false, 0, TimeSpan.FromSeconds(7.123), new IPAddress(1)),                                         // IP address already used for outbound.
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(26.0), IPAddress.Parse("2000:0db8:85a3:1232:0000:8a2e:0370:7334")),
                TestSample.Outbound(false, false, false, 0, TimeSpan.FromSeconds(260), IPAddress.Parse("2000:0db8:85a3:1232:0000:8a2e:0370:7334")), // IP address already used for outbound.
                TestSample.Outbound(true, false, false, 0, TimeSpan.FromSeconds(-2126.0), new IPAddress(2)),
                TestSample.Inbound(true, false, false, 0, TimeSpan.FromSeconds(-391), new IPAddress(3)),

                // These samples will change adjusted time because next outbound is the 4th outbound and we are under the limits.
                TestSample.Outbound(true, false, false, 1280, TimeSpan.FromSeconds(-1), new IPAddress(4)),          // 2 inbound, 4 outbound. 2/4 * 3 = 1.5 -> ceil -> 2 of each outbound   { -2126000, -2126000, -391000, -1000, -1000, 3560, 3560, 13123, 26000, 26000 } -> median is 1280ms.
                TestSample.Inbound(true, false, false, 3560, TimeSpan.FromSeconds(23.6), new IPAddress(5)),         // 3 inbound, 4 outbound. 3/4 * 3 = 2.25 -> ceil -> 3 of each outbound  { -2126000, -2126000, -2126000, -391000, -1000, -1000, -1000, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000 } -> median is 3560ms.
                TestSample.Inbound(true, false, false, 3560, TimeSpan.FromSeconds(236), new IPAddress(6)),          // 4 inbound, 4 outbound. 4/4 * 3 = 3 -> ceil -> 3 of each outbound     { -2126000, -2126000, -2126000, -391000, -1000, -1000, -1000, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000, 236000 } -> median is 3560ms.
                TestSample.Outbound(true, false, false, 1236, TimeSpan.FromSeconds(1.236), new IPAddress(7)),       // 4 inbound, 5 outbound. 4/5 * 3 = 2.4 -> ceil -> 3 of each outbound   { -2126000, -2126000, -2126000, -391000, -1000, -1000, -1000, 1236, 1236, 1236, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000, 236000 }  -> median is 1236ms.
                TestSample.Inbound(true, false, false, 1236, TimeSpan.FromSeconds(-1001), new IPAddress(8)),        // 5 inbound, 5 outbound. 5/5 * 3 = 3 -> ceil -> 3 of each outbound     { -2126000, -2126000, -2126000, -391000, -1001, -1000, -1000, -1000, 1236, 1236, 1236, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000, 236000 }  -> median is 1236ms.
                TestSample.Inbound(true, false, false, 1236, TimeSpan.FromSeconds(-4.9236), new IPAddress(9)),      // 6 inbound, 5 outbound. 6/5 * 3 = 3.6 -> ceil -> 4 of each outbound   { -2126000, -2126000, -2126000, -2126000, -391000, -4923.6, -1001, -1000, -1000, -1000, -1000, 1236, 1236, 1236, 1236, 3560, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000, 26000, 236000 }  -> median is 1236ms.
                TestSample.Outbound(true, false, false, 118, TimeSpan.FromSeconds(-4444.444), new IPAddress(10)),   // 6 inbound, 6 outbound. 6/6 * 3 = 3 -> ceil -> 3 of each outbound    { -4444444, -4444444, -4444444, -2126000, -2126000, -2126000, -391000, -4923.6, -1001, -1000, -1000, -1000, 1236, 1236, 1236, 3560, 3560, 3560, 13123, 23600, 26000, 26000, 26000, 236000 }  -> median is 118ms.
            };

            int maliciousOffset = TimeSyncBehaviorState.MaxTimeOffsetSeconds - 1;

            // Introduce 100 malicious inbound and show we are protected from malicious inbounds.
            for (int i = 11; i < 111; i++)
            {
                // Median always lands on one of the outbounds no matter how many malicious inbounds.
                samples.Add(TestSample.Inbound(true, false, false, 1236, TimeSpan.FromSeconds(maliciousOffset), new IPAddress(i)));
            }

            // Add 3 malicious outbound which is 3/9 (33.3%) which is equal to the 33.3% of outbounds and show protected.
            // Median always lands on one of the non malicious outbound.
            samples.Add(TestSample.Outbound(true, false, false, 3560, TimeSpan.FromSeconds(maliciousOffset), new IPAddress(11)));
            samples.Add(TestSample.Outbound(true, false, false, 26000, TimeSpan.FromSeconds(maliciousOffset), new IPAddress(12)));
            samples.Add(TestSample.Outbound(true, false, false, 26000, TimeSpan.FromSeconds(maliciousOffset), new IPAddress(13)));

            // Add a 4th malicious outbound which is 4/10 (40%) which is greater that 33.3% of outbounds. Show we are not protected -> the offset gets set to the malicious value.
            samples.Add(TestSample.Outbound(true, false, false, maliciousOffset * 1000, TimeSpan.FromSeconds(maliciousOffset), new IPAddress(14)));

            var dateTimeProvider        = new DateTimeProvider();
            TimeSyncBehaviorState state = this.CreateTimeSyncBehaviorState(dateTimeProvider);

            for (int i = 0; i < samples.Count; i++)
            {
                bool used = state.AddTimeData(samples[i].PeerIpAddress, samples[i].InputTimeOffset, samples[i].IsInbound);
                Assert.Equal(samples[i].ExpectedIsUsed, used);

                Assert.Equal(samples[i].ExpectedIsSyncOff, state.SwitchedOffLimitReached);
                Assert.Equal(samples[i].ExpectedIsSyncOff, state.SwitchedOff);

                DateTime adjustedTime = dateTimeProvider.GetAdjustedTime();
                DateTime normalTime   = dateTimeProvider.GetUtcNow();
                TimeSpan diff         = adjustedTime - normalTime;

                Assert.True(Math.Abs(diff.TotalMilliseconds - samples[i].ExpectedTimeOffsetMs) < TimeEpsilonMs, $"Failed in sample at index: {i}. Actual offset milliseconds: {diff.TotalMilliseconds}. Expected offset milliseconds: {samples[i].ExpectedTimeOffsetMs}");
            }
        }
Ejemplo n.º 9
0
        public void AddTimeData_WithSmallSampleSet_TurnsWarningOnAndSwitchesSyncOff()
        {
            // Samples to be inserted to the state.
            // Columns meanings: isInbound, isUsed, isWarningOn, isSyncOff, timeOffsetSample, peerAddress
            var samples = new List <object[]>
            {
                // First group of samples does not affect adjusted time, so difference should be ~0 ms.
                new object[] { false, true, false, false, TimeSpan.FromSeconds(TimeSyncBehaviorState.TimeOffsetWarningThresholdSeconds + 1), IPAddress.Parse("1.2.3.41"), },
                new object[] { false, true, false, false, TimeSpan.FromSeconds(TimeSyncBehaviorState.TimeOffsetWarningThresholdSeconds + 2), IPAddress.Parse("1.2.3.42"), },
                new object[] { false, true, false, false, TimeSpan.FromSeconds(TimeSyncBehaviorState.TimeOffsetWarningThresholdSeconds + 3), IPAddress.Parse("1.2.3.43"), },

                // The next sample turns on the warning.
                new object[] { false, true, true, false, TimeSpan.FromSeconds(TimeSyncBehaviorState.TimeOffsetWarningThresholdSeconds + 4), IPAddress.Parse("1.2.3.44"), },

                // It can't be turned off.
                new object[] { false, true, true, false, TimeSpan.FromSeconds(3), IPAddress.Parse("1.2.3.45"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(4), IPAddress.Parse("1.2.3.46"), },

                // Add more samples.
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 10), IPAddress.Parse("1.2.3.47"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 11), IPAddress.Parse("1.2.3.48"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 12), IPAddress.Parse("1.2.3.49"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 13), IPAddress.Parse("1.2.31.4"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 14), IPAddress.Parse("1.2.32.4"), },
                new object[] { false, true, true, false, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 15), IPAddress.Parse("1.2.33.4"), },

                // Now the feature should be turned off.
                new object[] { true, true, true, true, TimeSpan.FromSeconds(-TimeSyncBehaviorState.MaxTimeOffsetSeconds - 16), IPAddress.Parse("1.2.33.4"), },

                // No more samples should be accepted now.
                new object[] { false, false, true, true, TimeSpan.FromSeconds(2), IPAddress.Parse("1.2.34.4"), },
                new object[] { false, false, true, true, TimeSpan.FromSeconds(1), IPAddress.Parse("1.2.35.4"), },
            };

            var dateTimeProvider = DateTimeProvider.Default;
            var lifetime         = new NodeLifetime();
            var loggerFactory    = new LoggerFactory();
            var asyncLoopFactory = new AsyncLoopFactory(loggerFactory);
            var state            = new TimeSyncBehaviorState(dateTimeProvider, lifetime, asyncLoopFactory, loggerFactory);

            for (int i = 0; i < samples.Count; i++)
            {
                bool      isInbound        = (bool)samples[i][0];
                bool      isUsed           = (bool)samples[i][1];
                bool      isWarningOn      = (bool)samples[i][2];
                bool      isSyncOff        = (bool)samples[i][3];
                TimeSpan  timeOffsetSample = (TimeSpan)samples[i][4];
                IPAddress peerAddress      = (IPAddress)samples[i][5];

                bool used = state.AddTimeData(peerAddress, timeOffsetSample, isInbound);
                Assert.Equal(isUsed, used);

                Assert.Equal(isWarningOn, state.IsSystemTimeOutOfSync);
                Assert.Equal(isSyncOff, state.SwitchedOffLimitReached);
                Assert.Equal(isSyncOff, state.SwitchedOff);

                if (state.SwitchedOff)
                {
                    DateTime adjustedTime = dateTimeProvider.GetAdjustedTime();
                    DateTime normalTime   = dateTimeProvider.GetUtcNow();
                    TimeSpan diff         = adjustedTime - normalTime;
                    Assert.True(Math.Abs(diff.TotalMilliseconds) < TimeEpsilonMs);
                }
            }
        }