Skip to content

Commit

Permalink
Retry alternative addresses on timeout in SNTP client
Browse files Browse the repository at this point in the history
Changed the default timeout for SNTP requests to 1 second.

Issue: #1540
PiperOrigin-RevId: 652897579
  • Loading branch information
rohitjoins authored and copybara-github committed Jul 16, 2024
1 parent 7f30409 commit ded66de
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 51 deletions.
4 changes: 2 additions & 2 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
* `MediaCodecVideoRenderer` avoids decoding samples that are neither
rendered nor used as reference by other samples.
* Add `BasePreloadManager.Listener` to propagate preload events to apps.
* Allow changing SNTP client timeout
([#1540](https://github.com/androidx/media/issues/1540)).
* Allow changing SNTP client timeout and retry alternative addresses on
timeout ([#1540](https://github.com/androidx/media/issues/1540)).
* Remove `MediaCodecAdapter.Configuration.flags` as the field was always
zero.
* Transformer:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package androidx.media3.exoplayer.util;

import static androidx.media3.common.util.Assertions.checkNotNull;

import android.os.SystemClock;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
Expand All @@ -27,6 +29,7 @@
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.ConcurrentModificationException;

Expand All @@ -44,7 +47,7 @@ public final class SntpClient {
public static final String DEFAULT_NTP_HOST = "time.android.com";

/** The default maximum time, in milliseconds, to wait for the SNTP request to complete. */
public static final int DEFAULT_TIMEOUT_MS = 5_000;
public static final int DEFAULT_TIMEOUT_MS = 1_000;

/** Callback for calls to {@link #initialize(Loader, InitializationCallback)}. */
public interface InitializationCallback {
Expand All @@ -60,6 +63,7 @@ public interface InitializationCallback {
void onInitializationFailed(IOException error);
}

private static final int MAX_RETRY_COUNT = 10;
private static final int ORIGINATE_TIME_OFFSET = 24;
private static final int RECEIVE_TIME_OFFSET = 32;
private static final int TRANSMIT_TIME_OFFSET = 40;
Expand Down Expand Up @@ -191,56 +195,78 @@ public static void initialize(
}

private static long loadNtpTimeOffsetMs() throws IOException {
InetAddress address = InetAddress.getByName(getNtpHost());
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(getTimeoutMs());
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);

// Set mode = 3 (client) and version = 3. Mode is in low 3 bits of the first byte and Version
// is in bits 3-5 of the first byte.
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

// Get current time and write it to the request packet.
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimestamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

socket.send(request);

// Read the response.
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);

// Extract the results.
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET);

// Check server reply validity according to RFC.
checkValidServerReply(leap, mode, stratum, transmitTime);

// receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;

// Save our results using the times on this side of the network latency (i.e. response rather
// than request time)
long ntpTime = responseTime + clockOffset;
long ntpTimeReference = responseTicks;

return ntpTime - ntpTimeReference;

int retryCount = 0;
SocketTimeoutException timeoutException = null;
InetAddress[] addresses = InetAddress.getAllByName(getNtpHost());
for (InetAddress address : addresses) {
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);

// Set mode = 3 (client) and version = 3. Mode is in low 3 bits of the first byte and
// Version is in bits 3-5 of the first byte.
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

// Get current time and write it to the request packet.
long requestTime = System.currentTimeMillis();
long requestTicks = SystemClock.elapsedRealtime();
writeTimestamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

socket.send(request);

// Read the response.
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(response);
} catch (SocketTimeoutException e) {
// Store the timeout exception and try the next address if we have not reached retry limit
if (timeoutException == null) {
timeoutException = e;
} else {
timeoutException.addSuppressed(e);
}
if (retryCount++ < MAX_RETRY_COUNT) {
continue;
} else {
break;
}
}

final long responseTicks = SystemClock.elapsedRealtime();
final long responseTime = requestTime + (responseTicks - requestTicks);

// Extract the results.
final byte leap = (byte) ((buffer[0] >> 6) & 0x3);
final byte mode = (byte) (buffer[0] & 0x7);
final int stratum = (int) (buffer[1] & 0xff);
final long originateTime = readTimestamp(buffer, ORIGINATE_TIME_OFFSET);
final long receiveTime = readTimestamp(buffer, RECEIVE_TIME_OFFSET);
final long transmitTime = readTimestamp(buffer, TRANSMIT_TIME_OFFSET);

// Check server reply validity according to RFC.
checkValidServerReply(leap, mode, stratum, transmitTime);

// receiveTime = originateTime + transit + skew
// responseTime = transmitTime + transit - skew
// clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
// = ((originateTime + transit + skew - originateTime) +
// (transmitTime - (transmitTime + transit - skew)))/2
// = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;

// Save our results using the times on this side of the network latency (i.e. response
// rather than request time)
long ntpTime = responseTime + clockOffset;
long ntpTimeReference = responseTicks;

return ntpTime - ntpTimeReference;
}
// If no response is received from any of the addresses, throw an exception.
throw checkNotNull(timeoutException);
}
}

Expand Down

0 comments on commit ded66de

Please sign in to comment.