Further refactoring for multiple NTP servers
Further refactoring to support adding multiple NTP servers in a later commit. The logic for multiple servers will be added to forceRefresh() later. This commit also contains some refactoring to support improved testing of NtpTrustedTime.forceRefresh(). Tests for the existing behavior have been added here. As part of this change, SntpClient / NtpTrustedTime / NetworkTimeUpdateService have also been extended to pipe information about the IP address of the NTP server used to obtain the time back to the NetworkTimeUpdateService for improved logging / debugging support. Bug: 223365217 Test: atest frameworks/base/core/tests/coretests/src/android/util/NtpTrustedTimeTest.java Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/timedetector/ Test: atest cts/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java Test: Inspection: adb shell dumpsys network_time_update_service Test: Inspection: adb shell dumpsys time_detector Change-Id: Ia0f13e2c8cbd102d677797b745414d8ab5fa1eba
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.net.sntp.Duration64;
|
||||
import android.net.sntp.Timestamp64;
|
||||
@@ -29,6 +30,7 @@ import com.android.internal.util.TrafficStatsConstants;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -39,9 +41,7 @@ import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*
|
||||
* Simple SNTP client class for retrieving network time.
|
||||
* Simple, single-use SNTP client class for retrieving network time.
|
||||
*
|
||||
* Sample usage:
|
||||
* <pre>SntpClient client = new SntpClient();
|
||||
@@ -49,6 +49,10 @@ import java.util.function.Supplier;
|
||||
* long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>This class is not thread-safe.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SntpClient {
|
||||
private static final String TAG = "SntpClient";
|
||||
@@ -87,6 +91,9 @@ public class SntpClient {
|
||||
// The round trip (network) time in milliseconds
|
||||
private long mRoundTripTime;
|
||||
|
||||
// Details of the NTP server used to obtain the time last.
|
||||
@Nullable private InetSocketAddress mServerSocketAddress;
|
||||
|
||||
private static class InvalidServerReplyException extends Exception {
|
||||
public InvalidServerReplyException(String message) {
|
||||
super(message);
|
||||
@@ -202,6 +209,7 @@ public class SntpClient {
|
||||
mNtpTime = responseTime.plus(clockOffsetDuration).toEpochMilli();
|
||||
mNtpTimeReference = responseTicks;
|
||||
mRoundTripTime = roundTripTimeMillis;
|
||||
mServerSocketAddress = new InetSocketAddress(address, port);
|
||||
} catch (Exception e) {
|
||||
EventLogTags.writeNtpFailure(address.toString(), e.toString());
|
||||
if (DBG) Log.d(TAG, "request time failed: " + e);
|
||||
@@ -284,6 +292,14 @@ public class SntpClient {
|
||||
return mRoundTripTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the NTP server used in the NTP transaction
|
||||
*/
|
||||
@Nullable
|
||||
public InetSocketAddress getServerSocketAddress() {
|
||||
return mServerSocketAddress;
|
||||
}
|
||||
|
||||
private static void checkValidServerReply(
|
||||
byte leap, byte mode, int stratum, Timestamp64 transmitTimestamp,
|
||||
Timestamp64 referenceTimestamp, Timestamp64 randomizedRequestTimestamp,
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Duration;
|
||||
@@ -50,7 +51,7 @@ import java.util.function.Supplier;
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NtpTrustedTime implements TrustedTime {
|
||||
public abstract class NtpTrustedTime implements TrustedTime {
|
||||
|
||||
private static final String URI_SCHEME_NTP = "ntp";
|
||||
|
||||
@@ -107,16 +108,19 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static class TimeResult {
|
||||
public static final class TimeResult {
|
||||
private final long mUnixEpochTimeMillis;
|
||||
private final long mElapsedRealtimeMillis;
|
||||
private final int mUncertaintyMillis;
|
||||
@NonNull private final InetSocketAddress mNtpServerSocketAddress;
|
||||
|
||||
public TimeResult(
|
||||
long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis) {
|
||||
long unixEpochTimeMillis, long elapsedRealtimeMillis, int uncertaintyMillis,
|
||||
@NonNull InetSocketAddress ntpServerSocketAddress) {
|
||||
mUnixEpochTimeMillis = unixEpochTimeMillis;
|
||||
mElapsedRealtimeMillis = elapsedRealtimeMillis;
|
||||
mUncertaintyMillis = uncertaintyMillis;
|
||||
mNtpServerSocketAddress = Objects.requireNonNull(ntpServerSocketAddress);
|
||||
}
|
||||
|
||||
public long getTimeMillis() {
|
||||
@@ -152,12 +156,35 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
return currentElapsedRealtimeMillis - mElapsedRealtimeMillis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof TimeResult)) {
|
||||
return false;
|
||||
}
|
||||
TimeResult that = (TimeResult) o;
|
||||
return mUnixEpochTimeMillis == that.mUnixEpochTimeMillis
|
||||
&& mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
|
||||
&& mUncertaintyMillis == that.mUncertaintyMillis
|
||||
&& mNtpServerSocketAddress.equals(
|
||||
that.mNtpServerSocketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUnixEpochTimeMillis, mElapsedRealtimeMillis, mUncertaintyMillis,
|
||||
mNtpServerSocketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimeResult{"
|
||||
+ "unixEpochTime=" + Instant.ofEpochMilli(mUnixEpochTimeMillis)
|
||||
+ ", elapsedRealtime=" + Duration.ofMillis(mElapsedRealtimeMillis)
|
||||
+ ", mUncertaintyMillis=" + mUncertaintyMillis
|
||||
+ ", mNtpServerSocketAddress=" + mNtpServerSocketAddress
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -167,46 +194,23 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
|
||||
private static NtpTrustedTime sSingleton;
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
|
||||
/**
|
||||
* A supplier that returns the ConnectivityManager. The Supplier can return null if
|
||||
* ConnectivityService isn't running yet.
|
||||
*/
|
||||
private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
|
||||
new Supplier<ConnectivityManager>() {
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public synchronized ConnectivityManager get() {
|
||||
// We can't do this at initialization time: ConnectivityService might not be running
|
||||
// yet.
|
||||
if (mConnectivityManager == null) {
|
||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||
}
|
||||
return mConnectivityManager;
|
||||
}
|
||||
};
|
||||
|
||||
/** An in-memory config override for use during tests. */
|
||||
@GuardedBy("this")
|
||||
@Nullable
|
||||
private NtpConfig mNtpConfigForTests;
|
||||
|
||||
// Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
|
||||
// Declared volatile and accessed outside synchronized blocks to avoid blocking reads during
|
||||
// forceRefresh().
|
||||
private volatile TimeResult mTimeResult;
|
||||
|
||||
private NtpTrustedTime(Context context) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
protected NtpTrustedTime() {
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public static synchronized NtpTrustedTime getInstance(Context context) {
|
||||
if (sSingleton == null) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
sSingleton = new NtpTrustedTime(appContext);
|
||||
sSingleton = new NtpTrustedTimeImpl(appContext);
|
||||
}
|
||||
return sSingleton;
|
||||
}
|
||||
@@ -224,65 +228,71 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public boolean forceRefresh() {
|
||||
synchronized (this) {
|
||||
NtpConfig connectionInfo = getNtpConfig();
|
||||
if (connectionInfo == null) {
|
||||
NtpConfig ntpConfig = getNtpConfig();
|
||||
if (ntpConfig == null) {
|
||||
// missing server config, so no NTP time available
|
||||
if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
|
||||
if (connectivityManager == null) {
|
||||
if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
|
||||
return false;
|
||||
}
|
||||
final Network network = connectivityManager.getActiveNetwork();
|
||||
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
|
||||
|
||||
// This connectivity check is to avoid performing a DNS lookup for the time server on a
|
||||
// unconnected network. There are races to obtain time in Android when connectivity
|
||||
// changes, which means that forceRefresh() can be called by various components before
|
||||
// the network is actually available. This led in the past to DNS lookup failures being
|
||||
// cached (~2 seconds) thereby preventing the device successfully making an NTP request
|
||||
// when connectivity had actually been established.
|
||||
// A side effect of check is that tests that run a fake NTP server on the device itself
|
||||
// will only be able to use it if the active network is connected, even though loopback
|
||||
// addresses are actually reachable.
|
||||
if (ni == null || !ni.isConnected()) {
|
||||
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
|
||||
Network network = getNetwork();
|
||||
if (network == null) {
|
||||
if (LOGD) Log.d(TAG, "forceRefresh: no network available");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
|
||||
final SntpClient client = new SntpClient();
|
||||
final URI ntpServerUri = connectionInfo.getServerUri();
|
||||
final String serverName = ntpServerUri.getHost();
|
||||
final int port = ntpServerUri.getPort() == -1
|
||||
? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort();
|
||||
final int timeoutMillis = saturatedCast(connectionInfo.getTimeout().toMillis());
|
||||
if (client.requestTime(serverName, port, timeoutMillis, network)) {
|
||||
int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2);
|
||||
mTimeResult = new TimeResult(
|
||||
client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
if (LOGD) {
|
||||
Log.d(TAG, "forceRefresh: NTP request network=" + network
|
||||
+ " ntpConfig=" + ntpConfig);
|
||||
}
|
||||
TimeResult timeResult =
|
||||
queryNtpServer(network, ntpConfig.getServerUri(), ntpConfig.getTimeout());
|
||||
if (timeResult != null) {
|
||||
// Keep any previous time result.
|
||||
mTimeResult = timeResult;
|
||||
}
|
||||
return timeResult != null;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private NtpConfig getNtpConfig() {
|
||||
if (mNtpConfigForTests != null) {
|
||||
return mNtpConfigForTests;
|
||||
}
|
||||
return getNtpConfigInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a {@code long} to an {@code int}, clamping the value within the int range.
|
||||
* Returns the {@link NtpConfig} to use during an NTP query. This method can return {@code null}
|
||||
* if there is no config, or the config found is invalid.
|
||||
*
|
||||
* <p>This method has been made public for easy replacement during tests.
|
||||
*/
|
||||
private static int saturatedCast(long longValue) {
|
||||
if (longValue > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
if (longValue < Integer.MIN_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
return (int) longValue;
|
||||
}
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public abstract NtpConfig getNtpConfigInternal();
|
||||
|
||||
/**
|
||||
* Returns the {@link Network} to use during an NTP query. This method can return {@code null}
|
||||
* if there is no connectivity
|
||||
*
|
||||
* <p>This method has been made public for easy replacement during tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public abstract Network getNetwork();
|
||||
|
||||
/**
|
||||
* Queries the specified NTP server. This is a blocking call. Returns {@code null} if the query
|
||||
* fails.
|
||||
*
|
||||
* <p>This method has been made public for easy replacement during tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public abstract TimeResult queryNtpServer(
|
||||
@NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout);
|
||||
|
||||
/**
|
||||
* Only kept for UnsupportedAppUsage.
|
||||
@@ -371,39 +381,6 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private NtpConfig getNtpConfig() {
|
||||
if (mNtpConfigForTests != null) {
|
||||
return mNtpConfigForTests;
|
||||
}
|
||||
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final Resources res = mContext.getResources();
|
||||
|
||||
// The Settings value has priority over static config. Check settings first.
|
||||
final String serverGlobalSetting =
|
||||
Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);
|
||||
final URI settingsServerInfo = parseNtpServerSetting(serverGlobalSetting);
|
||||
|
||||
URI ntpServerUri;
|
||||
if (settingsServerInfo != null) {
|
||||
ntpServerUri = settingsServerInfo;
|
||||
} else {
|
||||
String configValue = res.getString(com.android.internal.R.string.config_ntpServer);
|
||||
try {
|
||||
ntpServerUri = parseNtpUriStrict(configValue);
|
||||
} catch (URISyntaxException e) {
|
||||
ntpServerUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
final int defaultTimeoutMillis =
|
||||
res.getInteger(com.android.internal.R.integer.config_ntpTimeout);
|
||||
final Duration timeout = Duration.ofMillis(Settings.Global.getInt(
|
||||
resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis));
|
||||
return ntpServerUri == null ? null : new NtpConfig(ntpServerUri, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and returns an NTP server config URI, or throws an exception if the URI doesn't
|
||||
* conform to expectations.
|
||||
@@ -487,4 +464,129 @@ public class NtpTrustedTime implements TrustedTime {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The real implementation of {@link NtpTrustedTime}. Contains the parts that are more difficult
|
||||
* to test.
|
||||
*/
|
||||
private static final class NtpTrustedTimeImpl extends NtpTrustedTime {
|
||||
|
||||
/**
|
||||
* A supplier that returns the ConnectivityManager. The Supplier can return null if
|
||||
* ConnectivityService isn't running yet.
|
||||
*/
|
||||
private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
|
||||
new Supplier<>() {
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public synchronized ConnectivityManager get() {
|
||||
// We can't do this at initialization time: ConnectivityService might not be running
|
||||
// yet.
|
||||
if (mConnectivityManager == null) {
|
||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||
}
|
||||
return mConnectivityManager;
|
||||
}
|
||||
};
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
|
||||
private NtpTrustedTimeImpl(@NonNull Context context) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
public NtpConfig getNtpConfigInternal() {
|
||||
final ContentResolver resolver = mContext.getContentResolver();
|
||||
final Resources res = mContext.getResources();
|
||||
|
||||
// The Settings value has priority over static config. Check settings first.
|
||||
final String serverGlobalSetting =
|
||||
Settings.Global.getString(resolver, Settings.Global.NTP_SERVER);
|
||||
final URI settingsServerInfo = parseNtpServerSetting(serverGlobalSetting);
|
||||
|
||||
URI ntpServerUri;
|
||||
if (settingsServerInfo != null) {
|
||||
ntpServerUri = settingsServerInfo;
|
||||
} else {
|
||||
String configValue = res.getString(com.android.internal.R.string.config_ntpServer);
|
||||
try {
|
||||
ntpServerUri = parseNtpUriStrict(configValue);
|
||||
} catch (URISyntaxException e) {
|
||||
ntpServerUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
final int defaultTimeoutMillis =
|
||||
res.getInteger(com.android.internal.R.integer.config_ntpTimeout);
|
||||
final Duration timeout = Duration.ofMillis(Settings.Global.getInt(
|
||||
resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis));
|
||||
return ntpServerUri == null ? null : new NtpConfig(ntpServerUri, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Network getNetwork() {
|
||||
ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
|
||||
if (connectivityManager == null) {
|
||||
if (LOGD) Log.d(TAG, "getNetwork: no ConnectivityManager");
|
||||
return null;
|
||||
}
|
||||
final Network network = connectivityManager.getActiveNetwork();
|
||||
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
|
||||
|
||||
// This connectivity check is to avoid performing a DNS lookup for the time server on a
|
||||
// unconnected network. There are races to obtain time in Android when connectivity
|
||||
// changes, which means that forceRefresh() can be called by various components before
|
||||
// the network is actually available. This led in the past to DNS lookup failures being
|
||||
// cached (~2 seconds) thereby preventing the device successfully making an NTP request
|
||||
// when connectivity had actually been established.
|
||||
// A side effect of check is that tests that run a fake NTP server on the device itself
|
||||
// will only be able to use it if the active network is connected, even though loopback
|
||||
// addresses are actually reachable.
|
||||
if (ni == null || !ni.isConnected()) {
|
||||
if (LOGD) Log.d(TAG, "getNetwork: no connectivity");
|
||||
return null;
|
||||
}
|
||||
return network;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TimeResult queryNtpServer(
|
||||
@NonNull Network network, @NonNull URI ntpServerUri, @NonNull Duration timeout) {
|
||||
|
||||
final SntpClient client = new SntpClient();
|
||||
final String serverName = ntpServerUri.getHost();
|
||||
final int port = ntpServerUri.getPort() == -1
|
||||
? SntpClient.STANDARD_NTP_PORT : ntpServerUri.getPort();
|
||||
final int timeoutMillis = saturatedCast(timeout.toMillis());
|
||||
if (client.requestTime(serverName, port, timeoutMillis, network)) {
|
||||
int ntpUncertaintyMillis = saturatedCast(client.getRoundTripTime() / 2);
|
||||
InetSocketAddress ntpServerSocketAddress = client.getServerSocketAddress();
|
||||
return new TimeResult(
|
||||
client.getNtpTime(), client.getNtpTimeReference(), ntpUncertaintyMillis,
|
||||
ntpServerSocketAddress);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a {@code long} to an {@code int}, clamping the value within the int range.
|
||||
*/
|
||||
private static int saturatedCast(long longValue) {
|
||||
if (longValue > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
if (longValue < Integer.MIN_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
return (int) longValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,20 @@
|
||||
package android.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.Network;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -26,6 +37,7 @@ import androidx.test.filters.SmallTest;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Duration;
|
||||
@@ -138,4 +150,158 @@ public class NtpTrustedTimeTest {
|
||||
public void testNtpConfig_negativeTimeout() {
|
||||
new NtpTrustedTime.NtpConfig(VALID_SERVER_URI, Duration.ofMillis(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_nullConfig() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(null);
|
||||
|
||||
assertFalse(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertFalse(ntpTrustedTime.hasCache());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
|
||||
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
|
||||
assertNull(ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
|
||||
verify(ntpTrustedTime, never()).getNetwork();
|
||||
verify(ntpTrustedTime, never()).queryNtpServer(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_noConnectivity() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
URI serverUri = URI.create("ntp://ntpserver.name");
|
||||
Duration timeout = Duration.ofSeconds(5);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
|
||||
new NtpTrustedTime.NtpConfig(serverUri, timeout));
|
||||
|
||||
when(ntpTrustedTime.getNetwork()).thenReturn(null);
|
||||
|
||||
assertFalse(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertFalse(ntpTrustedTime.hasCache());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
|
||||
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
|
||||
assertNull(ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
|
||||
verify(ntpTrustedTime, times(1)).getNetwork();
|
||||
verify(ntpTrustedTime, never()).queryNtpServer(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_queryFailed() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
URI serverUri = URI.create("ntp://ntpserver.name");
|
||||
Duration timeout = Duration.ofSeconds(5);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
|
||||
new NtpTrustedTime.NtpConfig(serverUri, timeout));
|
||||
|
||||
Network network = mock(Network.class);
|
||||
when(ntpTrustedTime.getNetwork()).thenReturn(network);
|
||||
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(null);
|
||||
|
||||
assertFalse(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertFalse(ntpTrustedTime.hasCache());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTime());
|
||||
assertEquals(0, ntpTrustedTime.getCachedNtpTimeReference());
|
||||
assertEquals(Long.MAX_VALUE, ntpTrustedTime.getCacheAge());
|
||||
assertNull(ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
|
||||
verify(ntpTrustedTime, times(1)).getNetwork();
|
||||
verify(ntpTrustedTime, times(1)).queryNtpServer(network, serverUri, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_querySucceeded() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
URI serverUri = URI.create("ntp://ntpserver.name");
|
||||
Duration timeout = Duration.ofSeconds(5);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
|
||||
new NtpTrustedTime.NtpConfig(serverUri, timeout));
|
||||
|
||||
Network network = mock(Network.class);
|
||||
when(ntpTrustedTime.getNetwork()).thenReturn(network);
|
||||
|
||||
NtpTrustedTime.TimeResult successResult = new NtpTrustedTime.TimeResult(123L, 456L, 789,
|
||||
InetSocketAddress.createUnresolved("placeholder", 123));
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult);
|
||||
|
||||
assertTrue(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertTrue(ntpTrustedTime.hasCache());
|
||||
assertEquals(successResult.getTimeMillis(), ntpTrustedTime.getCachedNtpTime());
|
||||
assertEquals(successResult.getElapsedRealtimeMillis(),
|
||||
ntpTrustedTime.getCachedNtpTimeReference());
|
||||
assertTrue(ntpTrustedTime.getCacheAge() != Long.MAX_VALUE);
|
||||
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
verify(ntpTrustedTime, times(1)).getNtpConfigInternal();
|
||||
verify(ntpTrustedTime, times(1)).getNetwork();
|
||||
verify(ntpTrustedTime, times(1)).queryNtpServer(network, serverUri, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_keepsOldValueOnFailure() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
URI serverUri = URI.create("ntp://ntpserver.name");
|
||||
Duration timeout = Duration.ofSeconds(5);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
|
||||
new NtpTrustedTime.NtpConfig(serverUri, timeout));
|
||||
|
||||
Network network = mock(Network.class);
|
||||
when(ntpTrustedTime.getNetwork()).thenReturn(network);
|
||||
|
||||
NtpTrustedTime.TimeResult successResult = new NtpTrustedTime.TimeResult(123L, 456L, 789,
|
||||
InetSocketAddress.createUnresolved("placeholder", 123));
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult);
|
||||
|
||||
assertTrue(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertTrue(ntpTrustedTime.hasCache());
|
||||
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(null);
|
||||
|
||||
assertFalse(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertTrue(ntpTrustedTime.hasCache());
|
||||
assertEquals(successResult, ntpTrustedTime.getCachedTimeResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceRefresh_keepsNewValueOnSuccess() {
|
||||
NtpTrustedTime ntpTrustedTime = spy(NtpTrustedTime.class);
|
||||
URI serverUri = URI.create("ntp://ntpserver.name");
|
||||
Duration timeout = Duration.ofSeconds(5);
|
||||
when(ntpTrustedTime.getNtpConfigInternal()).thenReturn(
|
||||
new NtpTrustedTime.NtpConfig(serverUri, timeout));
|
||||
|
||||
Network network = mock(Network.class);
|
||||
when(ntpTrustedTime.getNetwork()).thenReturn(network);
|
||||
|
||||
NtpTrustedTime.TimeResult successResult1 = new NtpTrustedTime.TimeResult(123L, 456L, 789,
|
||||
InetSocketAddress.createUnresolved("placeholder", 123));
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult1);
|
||||
|
||||
assertTrue(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertTrue(ntpTrustedTime.hasCache());
|
||||
assertEquals(successResult1, ntpTrustedTime.getCachedTimeResult());
|
||||
|
||||
NtpTrustedTime.TimeResult successResult2 = new NtpTrustedTime.TimeResult(123L, 456L, 789,
|
||||
InetSocketAddress.createUnresolved("placeholder", 123));
|
||||
when(ntpTrustedTime.queryNtpServer(network, serverUri, timeout)).thenReturn(successResult2);
|
||||
|
||||
assertTrue(ntpTrustedTime.forceRefresh());
|
||||
|
||||
assertTrue(ntpTrustedTime.hasCache());
|
||||
assertEquals(successResult2, ntpTrustedTime.getCachedTimeResult());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,7 @@ public class NetworkTimeUpdateService extends Binder {
|
||||
NetworkTimeSuggestion timeSuggestion =
|
||||
new NetworkTimeSuggestion(timeSignal, ntpResult.getUncertaintyMillis());
|
||||
timeSuggestion.addDebugInfo(debugInfo);
|
||||
timeSuggestion.addDebugInfo(ntpResult.toString());
|
||||
mTimeDetectorInternal.suggestNetworkTime(timeSuggestion);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Instant;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -405,8 +406,8 @@ public class TimeDetectorServiceTest {
|
||||
|
||||
@Test
|
||||
public void testLatestNetworkTime() {
|
||||
NtpTrustedTime.TimeResult latestNetworkTime =
|
||||
new NtpTrustedTime.TimeResult(1234L, 54321L, 999);
|
||||
NtpTrustedTime.TimeResult latestNetworkTime = new NtpTrustedTime.TimeResult(
|
||||
1234L, 54321L, 999, InetSocketAddress.createUnresolved("test.timeserver", 123));
|
||||
when(mMockNtpTrustedTime.getCachedTimeResult())
|
||||
.thenReturn(latestNetworkTime);
|
||||
TimePoint expected = new TimePoint(latestNetworkTime.getTimeMillis(),
|
||||
|
||||
Reference in New Issue
Block a user