diff --git a/README.md b/README.md index ad6c7598..1472ea51 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ *Make sure to check out our counterpart too: [TrueTime](https://github.com/instacart/TrueTime.swift), an SNTP library for Swift.* -SNTP client for Android. Calculate the date and time "now" impervious to manual changes to device clock time. +NTP client for Android. Calculate the date and time "now" impervious to manual changes to device clock time. In certain applications it becomes important to get the real or "true" date and time. On most devices, if the clock has been changed manually, then a `new Date()` instance gives you a time impacted by local settings. @@ -16,7 +16,7 @@ You can read more about the use case in our [blog post](https://tech.instacart.c It's pretty simple actually. We make a request to an NTP server that gives us the actual time. We then establish the delta between device uptime and uptime at the time of the network response. Each time "now" is requested subsequently, we account for that offset and return a corrected `Date` object. -Also, once we have this information it's valid until the next time you boot your device. This means if you enable the disk caching feature, after a signle successfull SNTP request you can use the information on disk directly without ever making another network request. This applies even across application kills which can happen frequently if your users have a memory starved device. +Also, once we have this information it's valid until the next time you boot your device. This means if you enable the disk caching feature, after a single successfull NTP request you can use the information on disk directly without ever making another network request. This applies even across application kills which can happen frequently if your users have a memory starved device. # Installation @@ -62,13 +62,11 @@ Date noReallyThisIsTheTrueDateAndTime = TrueTime.now(); ## Rx-ified Version -If you're down to using [RxJava](https://github.com/ReactiveX/RxJava) then there's a niftier `initialize()` api that takes in the pool of hosts you want to query. +If you're down to using [RxJava](https://github.com/ReactiveX/RxJava) then we go all the way and implement the full NTP. Use the nifty `initializeRx()` api which takes in an NTP pool server host. ```java -List ntpHosts = Arrays.asList("0.north-america.pool.ntp.org", - "1.north-america.pool.ntp.org"); TrueTimeRx.build() - .initialize(ntpHosts) + .initializeRx("0.north-america.pool.ntp.org") .subscribeOn(Schedulers.io()) .subscribe(date -> { Log.v(TAG, "TrueTime was initialized and we have a time: " + date); @@ -85,24 +83,24 @@ TrueTimeRx.now(); // return a Date object with the "true" time. ### What is nifty about the Rx version? -* it can take in multiple SNTP hosts to shoot out the UDP request -* those UDP requests are executed in parallel -* if one of the SNTP requests fail, we retry the failed request (alone) for a specified number of times -* as soon as we hear back from any of the hosts, we immediately take that and terminate the rest of the requests +* as against just SNTP, you get full NTP (read: far more accurate time) +* the NTP pool address you provide is resolved into multiple IP addresses +* we query each IP multiple times, guarding against checks, and taking the best response +* if any one of the requests fail, we retry that failed request (alone) for a specified number of times +* we collect all the responses and again filter for the best result as per the NTP spec ## Notes/tips: * Each `initialize` call makes an SNTP network request. TrueTime needs to be `initialize`d only once ever, per device boot. Use TrueTime's `withSharedPreferences` option to make use of this feature and avoid repeated network request calls. * Preferable use dependency injection (like [Dagger](http://square.github.io/dagger/)) and create a TrueTime @Singleton object -* TrueTime was built to be accurate "enough", hence the use of [SNTP](https://en.wikipedia.org/wiki/Network_Time_Protocol#SNTP). If you need exact millisecond accuracy then you probably want [NTP](https://www.meinbergglobal.com/english/faq/faq_37.htm) (i.e. SNTP + statistical analysis to ensure the reference time is exactly correct). TrueTime provides the building blocks for this. We welcome PRs if you think you can do this with TrueTime(Rx) pretty easily :). +* You can read up on Wikipedia the differences between [SNTP](https://en.wikipedia.org/wiki/Network_Time_Protocol#SNTP) and [NTP](https://www.meinbergglobal.com/english/faq/faq_37.htm). * TrueTime is also [available for iOS/Swift](https://github.com/instacart/truetime.swift) ## Exception handling: -* an `InvalidNtpServerResponseException` is thrown every time the server gets an invalid response (this can happen with the SNTP calls). +* an `InvalidNtpServerResponseException` is thrown every time the server gets an invalid response (this can happen with the individual SNTP calls). * If TrueTime fails to initialize (because of the above exception being throw), then an `IllegalStateException` is thrown if you try to call `TrueTime.now()` at a later point. - # License ``` diff --git a/app/src/main/java/com/instacart/library/sample/Sample2Activity.java b/app/src/main/java/com/instacart/library/sample/Sample2Activity.java index 229b9498..1836e1e0 100644 --- a/app/src/main/java/com/instacart/library/sample/Sample2Activity.java +++ b/app/src/main/java/com/instacart/library/sample/Sample2Activity.java @@ -10,12 +10,10 @@ import butterknife.ButterKnife; import butterknife.OnClick; import com.instacart.library.truetime.TrueTime; -import com.instacart.library.truetime.extensionrx.TrueTimeRx; +import com.instacart.library.truetime.TrueTimeRx; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; -import java.util.List; import java.util.Locale; import java.util.TimeZone; import rx.android.schedulers.AndroidSchedulers; @@ -41,13 +39,6 @@ protected void onCreate(Bundle savedInstanceState) { ButterKnife.bind(this); refreshBtn.setEnabled(false); - List ntpHosts = Arrays.asList("time.apple.com", - "0.north-america.pool.ntp.org", - "1.north-america.pool.ntp.org", - "2.north-america.pool.ntp.org", - "3.north-america.pool.ntp.org", - "0.us.pool.ntp.org", - "1.us.pool.ntp.org"); //TrueTimeRx.clearCachedInfo(this); TrueTimeRx.build() @@ -55,7 +46,7 @@ protected void onCreate(Bundle savedInstanceState) { .withRetryCount(100) .withSharedPreferences(this) .withLoggingEnabled(true) - .initialize(ntpHosts) + .initializeRx("time.apple.com") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1() { @@ -66,7 +57,7 @@ public void call(Date date) { }, new Action1() { @Override public void call(Throwable throwable) { - Log.e(TAG, "something went wrong when trying to initialize TrueTime", throwable); + Log.e(TAG, "something went wrong when trying to initializeRx TrueTime", throwable); } }, new Action0() { @Override diff --git a/build.gradle b/build.gradle index c8f3e08d..b048ef72 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4' + classpath 'com.android.tools.build:gradle:2.2.1' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/library-extension-rx/build.gradle b/library-extension-rx/build.gradle index c5e9e0a7..4003dd65 100644 --- a/library-extension-rx/build.gradle +++ b/library-extension-rx/build.gradle @@ -8,8 +8,8 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 23 - versionCode 5 - versionName "1.5" + versionCode 6 + versionName "2.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/library-extension-rx/src/main/java/com/instacart/library/truetime/TrueTimeRx.java b/library-extension-rx/src/main/java/com/instacart/library/truetime/TrueTimeRx.java new file mode 100644 index 00000000..dff1ce47 --- /dev/null +++ b/library-extension-rx/src/main/java/com/instacart/library/truetime/TrueTimeRx.java @@ -0,0 +1,197 @@ +package com.instacart.library.truetime; + +import android.content.Context; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import rx.Observable; +import rx.Observable.Transformer; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; + +public class TrueTimeRx + extends TrueTime { + + private static final TrueTimeRx RX_INSTANCE = new TrueTimeRx(); + private static final String TAG = TrueTimeRx.class.getSimpleName(); + + private int _retryCount = 50; + + public static TrueTimeRx build() { + return RX_INSTANCE; + } + + public TrueTimeRx withSharedPreferences(Context context) { + super.withSharedPreferences(context); + return this; + } + + public TrueTimeRx withConnectionTimeout(int timeout) { + super.withConnectionTimeout(timeout); + return this; + } + + public TrueTimeRx withLoggingEnabled(boolean isLoggingEnabled) { + super.withLoggingEnabled(isLoggingEnabled); + return this; + } + + public TrueTimeRx withRetryCount(int retryCount) { + _retryCount = retryCount; + return this; + } + + /** + * Initialize TrueTime + * See {@link #initializeNtp(String)} for details on working + * + * @return accurate NTP Date + */ + public Observable initializeRx(String ntpPool) { + return initializeNtp(ntpPool)// + .map(new Func1() { + @Override + public Date call(long[] longs) { + return now(); + } + }); + } + + /** + * Initialize TrueTime + * A single NTP pool server is provided. + * Using DNS we resolve that to multiple IP hosts + * Against each IP host we issue a UDP call and retrieve the best response using the NTP algorithm + * + * Use this instead of {@link #initializeRx(String)} if you wish to also get additional info for + * instrumentation/tracking actual NTP response data + * + * @param ntpPool NTP pool server e.g. time.apple.com, 0.us.pool.ntp.org + * @return Observable of detailed long[] containing most important parts of the actual NTP response + * See RESPONSE_INDEX_ prefixes in {@link SntpClient} for details + */ + public Observable initializeNtp(String ntpPool) { + return Observable// + .just(ntpPool)// + .compose(resolveNtpPoolToIpAddresses())// + .flatMap(bestResponseAgainstSingleIp(5)) // get best response from querying the ip 5 times + .take(5) // take 5 of the best results + .toList()// + .map(filterMedianResponse())// + .doOnNext(new Action1() { + @Override + public void call(long[] ntpResponse) { + cacheTrueTimeInfo(ntpResponse); + saveTrueTimeInfoToDisk(); + } + }); + } + + private Transformer resolveNtpPoolToIpAddresses() { + return new Transformer() { + @Override + public Observable call(Observable ntpPoolObservable) { + return ntpPoolObservable// + .observeOn(Schedulers.io())// + .flatMap(new Func1>() { + @Override + public Observable call(String ntpPoolAddress) { + try { + TrueLog.d(TAG, "---- resolving ntpHost : " + ntpPoolAddress); + return Observable.from(InetAddress.getAllByName(ntpPoolAddress)); + } catch (UnknownHostException e) { + return Observable.error(e); + } + } + })// + .map(new Func1() { + @Override + public String call(InetAddress inetAddress) { + TrueLog.d(TAG, "---- resolved address [" + inetAddress + "]"); + return inetAddress.getHostAddress(); + } + }); + } + }; + } + + private Func1> bestResponseAgainstSingleIp(final int repeatCount) { + return new Func1>() { + @Override + public Observable call(String singleIp) { + return Observable.just(singleIp)// + .repeat(repeatCount)// + .flatMap(new Func1>() { + @Override + public Observable call(final String singleIpHostAddress) { + return Observable// + .fromCallable(new Callable() { + @Override + public long[] call() throws Exception { + TrueLog.d(TAG, "---- requestTime from: " + singleIpHostAddress); + return requestTime(singleIpHostAddress); + } + })// + .subscribeOn(Schedulers.io())// + .doOnError(new Action1() { + @Override + public void call(Throwable throwable) { + TrueLog.e(TAG, "---- Error requesting time", throwable); + } + })// + .retry(_retryCount); + } + })// + .toList()// + .onErrorResumeNext(Observable.>empty()) + .map(filterLeastRoundTripDelay()); // pick best response for each ip + } + }; + } + + private Func1, long[]> filterLeastRoundTripDelay() { + return new Func1, long[]>() { + @Override + public long[] call(List responseTimeList) { + Collections.sort(responseTimeList, new Comparator() { + @Override + public int compare(long[] lhsParam, long[] rhsLongParam) { + long lhs = SntpClient.getRoundTripDelay(lhsParam); + long rhs = SntpClient.getRoundTripDelay(rhsLongParam); + return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); + } + }); + + TrueLog.d(TAG, "---- filterLeastRoundTrip: " + responseTimeList); + + return responseTimeList.get(0); + } + }; + } + + private Func1, long[]> filterMedianResponse() { + return new Func1, long[]>() { + @Override + public long[] call(List bestResponses) { + Collections.sort(bestResponses, new Comparator() { + @Override + public int compare(long[] lhsParam, long[] rhsParam) { + long lhs = SntpClient.getClockOffset(lhsParam); + long rhs = SntpClient.getClockOffset(rhsParam); + return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1); + } + }); + + TrueLog.d(TAG, "---- bestResponse: " + Arrays.toString(bestResponses.get(bestResponses.size() / 2))); + + return bestResponses.get(bestResponses.size() / 2); + } + }; + } +} diff --git a/library-extension-rx/src/main/java/com/instacart/library/truetime/extensionrx/TrueTimeRx.java b/library-extension-rx/src/main/java/com/instacart/library/truetime/extensionrx/TrueTimeRx.java deleted file mode 100644 index abddf77f..00000000 --- a/library-extension-rx/src/main/java/com/instacart/library/truetime/extensionrx/TrueTimeRx.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.instacart.library.truetime.extensionrx; - -import android.content.Context; -import com.instacart.library.truetime.TrueTime; -import java.io.IOException; -import java.util.Date; -import java.util.List; -import rx.Observable; -import rx.functions.Action1; -import rx.functions.Func1; -import rx.schedulers.Schedulers; - -public class TrueTimeRx - extends TrueTime { - - private static final TrueTimeRx RX_INSTANCE = new TrueTimeRx(); - - private int _retryCount = 50; - - public static TrueTimeRx build() { - return RX_INSTANCE; - } - - public TrueTimeRx withSharedPreferences(Context context) { - super.withSharedPreferences(context); - return this; - } - - public TrueTimeRx withConnectionTimeout(int timeout) { - super.withConnectionTimeout(timeout); - return this; - } - - public TrueTimeRx withLoggingEnabled(boolean isLoggingEnabled) { - super.withLoggingEnabled(isLoggingEnabled); - return this; - } - - public TrueTimeRx withRetryCount(int retryCount) { - _retryCount = retryCount; - return this; - } - - /** - * Initialize the SntpClient - * Issue SNTP call via UDP to list of provided hosts - * Pick the first successful call and return - * Retry failed calls individually - */ - public Observable initialize(final List ntpHosts) { - return Observable// - .from(ntpHosts)// - .flatMap(new Func1>() { - @Override - public Observable call(String ntpHost) { - return Observable// - .just(ntpHost)// - .subscribeOn(Schedulers.io())// - .flatMap(new Func1>() { - @Override - public Observable call(String ntpHost) { - try { - initialize(ntpHost); - } catch (IOException e) { - return Observable.error(e); - } - return Observable.just(now()); - } - })// - .retry(_retryCount)// - .onErrorReturn(new Func1() { - @Override - public Date call(Throwable throwable) { - throwable.printStackTrace(); - return null; - } - }).take(1).doOnNext(new Action1() { - @Override - public void call(Date date) { - cacheTrueTimeInfo(); - } - }); - } - })// - .filter(new Func1() { - @Override - public Boolean call(Date date) { - return date != null; - } - })// - .take(1); - } -} diff --git a/library/build.gradle b/library/build.gradle index 333e7644..861ad0a6 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -8,8 +8,8 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 23 - versionCode 5 - versionName "1.5" + versionCode 6 + versionName "2.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/library/src/main/java/com/instacart/library/truetime/SntpClient.java b/library/src/main/java/com/instacart/library/truetime/SntpClient.java index 28e472fa..7c2f71f3 100644 --- a/library/src/main/java/com/instacart/library/truetime/SntpClient.java +++ b/library/src/main/java/com/instacart/library/truetime/SntpClient.java @@ -28,6 +28,16 @@ */ public class SntpClient { + public static final int RESPONSE_INDEX_ORIGINATE_TIME = 0; + public static final int RESPONSE_INDEX_RECEIVE_TIME = 1; + public static final int RESPONSE_INDEX_TRANSMIT_TIME = 2; + public static final int RESPONSE_INDEX_RESPONSE_TIME = 3; + public static final int RESPONSE_INDEX_ROOT_DELAY = 4; + public static final int RESPONSE_INDEX_DISPERSION = 5; + public static final int RESPONSE_INDEX_STRATUM = 6; + public static final int RESPONSE_INDEX_RESPONSE_TICKS = 7; + public static final int RESPONSE_INDEX_SIZE = 8; + private static final String TAG = SntpClient.class.getSimpleName(); private static final int NTP_PORT = 123; @@ -49,13 +59,31 @@ public class SntpClient { private long _cachedSntpTime; private boolean _sntpInitialized = false; + /** + * See δ : + * https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm + */ + public static long getRoundTripDelay(long[] response) { + return (response[RESPONSE_INDEX_RESPONSE_TIME] - response[RESPONSE_INDEX_ORIGINATE_TIME]) - + (response[RESPONSE_INDEX_TRANSMIT_TIME] - response[RESPONSE_INDEX_RECEIVE_TIME]); + } + + /** + * See θ : + * https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm + */ + public static long getClockOffset(long[] response) { + return ((response[RESPONSE_INDEX_RECEIVE_TIME] - response[RESPONSE_INDEX_ORIGINATE_TIME]) + + (response[RESPONSE_INDEX_TRANSMIT_TIME] - response[RESPONSE_INDEX_RESPONSE_TIME])) / 2; + } + /** * Sends an NTP request to the given host and processes the response. * * @param ntpHost host name of the server. * @param timeoutInMillis network timeout in milliseconds. */ - void requestTime(String ntpHost, int timeoutInMillis) throws IOException { + long[] requestTime(String ntpHost, int timeoutInMillis) throws IOException { DatagramSocket socket = null; @@ -83,32 +111,40 @@ void requestTime(String ntpHost, int timeoutInMillis) throws IOException { // ----------------------------------------------------------------------------------- // read the response + long t[] = new long[RESPONSE_INDEX_SIZE]; DatagramPacket response = new DatagramPacket(buffer, buffer.length); socket.receive(response); long responseTicks = SystemClock.elapsedRealtime(); + t[RESPONSE_INDEX_RESPONSE_TICKS] = responseTicks; // ----------------------------------------------------------------------------------- // extract the results + // See here for the algorithm used: + // https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm long originateTime = _readTimeStamp(buffer, INDEX_ORIGINATE_TIME); // T0 long receiveTime = _readTimeStamp(buffer, INDEX_RECEIVE_TIME); // T1 long transmitTime = _readTimeStamp(buffer, INDEX_TRANSMIT_TIME); // T2 - - // See here for the algorithm used: - // https://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm long responseTime = requestTime + (responseTicks - requestTicks); // T3 + t[RESPONSE_INDEX_ORIGINATE_TIME] = originateTime; + t[RESPONSE_INDEX_RECEIVE_TIME] = receiveTime; + t[RESPONSE_INDEX_TRANSMIT_TIME] = transmitTime; + t[RESPONSE_INDEX_RESPONSE_TIME] = responseTime; + // ----------------------------------------------------------------------------------- // check validity of response long rootDelay = _read(buffer, INDEX_ROOT_DELAY); + t[RESPONSE_INDEX_ROOT_DELAY] = rootDelay; if (rootDelay > 100) { throw new InvalidNtpServerResponseException("Invalid response from NTP server. Root delay violation " + rootDelay); } long rootDispersion = _read(buffer, INDEX_ROOT_DISPERSION); + t[RESPONSE_INDEX_DISPERSION] = rootDispersion; if (rootDispersion > 100) { throw new InvalidNtpServerResponseException( "Invalid response from NTP server. Root dispersion violation " + rootDispersion); @@ -120,6 +156,7 @@ void requestTime(String ntpHost, int timeoutInMillis) throws IOException { } final int stratum = buffer[1] & 0xff; + t[RESPONSE_INDEX_STRATUM] = stratum; if (stratum < 1 || stratum > 15) { throw new InvalidNtpServerResponseException("untrusted stratum value for TrueTime: " + stratum); } @@ -134,15 +171,19 @@ void requestTime(String ntpHost, int timeoutInMillis) throws IOException { throw new InvalidNtpServerResponseException("Server response delay too large for comfort " + delay); } + long timeElapsedSinceRequest = Math.abs(originateTime - System.currentTimeMillis()); + if (timeElapsedSinceRequest >= 10_000) { + throw new InvalidNtpServerResponseException("Request was sent more than 10 seconds back " + + timeElapsedSinceRequest); + } + _sntpInitialized = true; TrueLog.i(TAG, "---- SNTP successful response from " + ntpHost); // ----------------------------------------------------------------------------------- - // θ - long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; - - _cachedSntpTime = responseTime + clockOffset; - _cachedDeviceUptime = responseTicks; + // TODO: + cacheTrueTimeInfo(t); + return t; } catch (Exception e) { TrueLog.d(TAG, "---- SNTP request failed for " + ntpHost); @@ -154,6 +195,21 @@ void requestTime(String ntpHost, int timeoutInMillis) throws IOException { } } + void cacheTrueTimeInfo(long[] response) { + _cachedSntpTime = sntpTime(response); + _cachedDeviceUptime = response[RESPONSE_INDEX_RESPONSE_TICKS]; + } + + long sntpTime(long[] response) { + long clockOffset = getClockOffset(response); + long responseTime = response[RESPONSE_INDEX_RESPONSE_TIME]; + return responseTime + clockOffset; + } + + boolean wasInitialized() { + return _sntpInitialized; + } + /** * @return time value computed from NTP server response */ @@ -168,10 +224,6 @@ long getCachedDeviceUptime() { return _cachedDeviceUptime; } - boolean wasInitialized() { - return _sntpInitialized; - } - // ----------------------------------------------------------------------------------- // private helpers diff --git a/library/src/main/java/com/instacart/library/truetime/TrueLog.java b/library/src/main/java/com/instacart/library/truetime/TrueLog.java index f9062e52..55555a96 100644 --- a/library/src/main/java/com/instacart/library/truetime/TrueLog.java +++ b/library/src/main/java/com/instacart/library/truetime/TrueLog.java @@ -6,55 +6,55 @@ class TrueLog { private static boolean LOGGING_ENABLED = true; - public static void v(String tag, String msg) { + static void v(String tag, String msg) { if (LOGGING_ENABLED) { Log.v(tag, msg); } } - public static void d(String tag, String msg) { + static void d(String tag, String msg) { if (LOGGING_ENABLED) { Log.d(tag, msg); } } - public static void i(String tag, String msg) { + static void i(String tag, String msg) { if (LOGGING_ENABLED) { Log.i(tag, msg); } } - public static void w(String tag, String msg) { + static void w(String tag, String msg) { if (LOGGING_ENABLED) { Log.w(tag, msg); } } - public static void w(String tag, String msg, Throwable t) { + static void w(String tag, String msg, Throwable t) { if (LOGGING_ENABLED) { Log.w(tag, msg, t); } } - public static void e(String tag, String msg) { + static void e(String tag, String msg) { if (LOGGING_ENABLED) { Log.e(tag, msg); } } - public static void e(String tag, String msg, Throwable t) { + static void e(String tag, String msg, Throwable t) { if (LOGGING_ENABLED) { Log.e(tag, msg, t); } } - public static void wtf(String tag, String msg) { + static void wtf(String tag, String msg) { if (LOGGING_ENABLED) { Log.wtf(tag, msg); } } - public static void wtf(String tag, String msg, Throwable tr) { + static void wtf(String tag, String msg, Throwable tr) { if (LOGGING_ENABLED) { Log.wtf(tag, msg, tr); } diff --git a/library/src/main/java/com/instacart/library/truetime/TrueTime.java b/library/src/main/java/com/instacart/library/truetime/TrueTime.java index 09674aa2..2fc7897d 100644 --- a/library/src/main/java/com/instacart/library/truetime/TrueTime.java +++ b/library/src/main/java/com/instacart/library/truetime/TrueTime.java @@ -10,8 +10,8 @@ public class TrueTime { private static final String TAG = TrueTime.class.getSimpleName(); private static final TrueTime INSTANCE = new TrueTime(); - private static final SntpClient SNTP_CLIENT = new SntpClient(); private static final DiskCacheClient DISK_CACHE_CLIENT = new DiskCacheClient(); + private static final SntpClient SNTP_CLIENT = new SntpClient(); private static int _udpSocketTimeoutInMillis = 30_000; @@ -47,7 +47,7 @@ public static void clearCachedInfo(Context context) { public void initialize() throws IOException { initialize(_ntpHost); - cacheTrueTimeInfo(); + saveTrueTimeInfoToDisk(); } /** @@ -81,10 +81,15 @@ protected void initialize(String ntpHost) throws IOException { TrueLog.i(TAG, "---- TrueTime already initialized from previous boot/init"); return; } - SNTP_CLIENT.requestTime(ntpHost, _udpSocketTimeoutInMillis); + + requestTime(ntpHost); } - protected synchronized static void cacheTrueTimeInfo() { + long[] requestTime(String ntpHost) throws IOException { + return SNTP_CLIENT.requestTime(ntpHost, _udpSocketTimeoutInMillis); + } + + synchronized static void saveTrueTimeInfoToDisk() { if (!SNTP_CLIENT.wasInitialized()) { TrueLog.i(TAG, "---- SNTP client not available. not caching TrueTime info in disk"); return; @@ -92,6 +97,10 @@ protected synchronized static void cacheTrueTimeInfo() { DISK_CACHE_CLIENT.cacheTrueTimeInfo(SNTP_CLIENT); } + void cacheTrueTimeInfo(long[] response) { + SNTP_CLIENT.cacheTrueTimeInfo(response); + } + private static long _getCachedDeviceUptime() { long cachedDeviceUptime = SNTP_CLIENT.wasInitialized() ? SNTP_CLIENT.getCachedDeviceUptime()