Merge "Thread: fill in ticks in active timestamp" into main am: 4355dae9d8
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/3132040
Change-Id: I7f00fec509a484a0aa382ffe4c0a8c8ff694c44d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
index 520acbd..cecb4e9 100644
--- a/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
+++ b/thread/framework/java/android/net/thread/OperationalDatasetTimestamp.java
@@ -65,11 +65,32 @@
*/
@NonNull
public static OperationalDatasetTimestamp fromInstant(@NonNull Instant instant) {
+ return OperationalDatasetTimestamp.fromInstant(instant, true /* isAuthoritativeSource */);
+ }
+
+ /**
+ * Creates a new {@link OperationalDatasetTimestamp} object from an {@link Instant}.
+ *
+ * <p>The {@code seconds} is set to {@code instant.getEpochSecond()}, {@code ticks} is set to
+ * {@link instant#getNano()} based on frequency of 32768 Hz, and {@code isAuthoritativeSource}
+ * is set to {@code isAuthoritativeSource}.
+ *
+ * <p>Note that this conversion can lose precision and a value returned by {@link #toInstant}
+ * may not equal exactly the {@code instant}.
+ *
+ * @throws IllegalArgumentException if {@code instant.getEpochSecond()} is larger than {@code
+ * 0xffffffffffffL}
+ * @see toInstant
+ * @hide
+ */
+ @NonNull
+ public static OperationalDatasetTimestamp fromInstant(
+ @NonNull Instant instant, boolean isAuthoritativeSource) {
int ticks = getRoundedTicks(instant.getNano());
long seconds = instant.getEpochSecond() + ticks / TICKS_UPPER_BOUND;
// the rounded ticks can be 0x8000 if instant.getNano() >= 999984742
ticks = ticks % TICKS_UPPER_BOUND;
- return new OperationalDatasetTimestamp(seconds, ticks, true /* isAuthoritativeSource */);
+ return new OperationalDatasetTimestamp(seconds, ticks, isAuthoritativeSource);
}
/**
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 1d823e0..2f60d9a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -955,9 +955,7 @@
final byte[] securityFlags = new byte[] {(byte) 0xff, (byte) 0xf8};
return new ActiveOperationalDataset.Builder()
- .setActiveTimestamp(
- new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, authoritative))
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(now, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
diff --git a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
index 2244a89..11c78e3 100644
--- a/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
+++ b/thread/tests/unit/src/android/net/thread/OperationalDatasetTimestampTest.java
@@ -41,6 +41,19 @@
}
@Test
+ public void fromInstant_authoritativeIsSetAsSpecified() {
+ Instant instant = Instant.now();
+
+ OperationalDatasetTimestamp timestampAuthoritativeFalse =
+ OperationalDatasetTimestamp.fromInstant(instant, false);
+ OperationalDatasetTimestamp timestampAuthoritativeTrue =
+ OperationalDatasetTimestamp.fromInstant(instant, true);
+
+ assertThat(timestampAuthoritativeFalse.isAuthoritativeSource()).isEqualTo(false);
+ assertThat(timestampAuthoritativeTrue.isAuthoritativeSource()).isEqualTo(true);
+ }
+
+ @Test
public void fromTlvValue_goodValue_success() {
OperationalDatasetTimestamp timestamp =
OperationalDatasetTimestamp.fromTlvValue(base16().decode("FFEEDDCCBBAA9989"));
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index de6d1e1..6e2369f 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -99,6 +99,8 @@
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -584,6 +586,55 @@
}
@Test
+ public void createRandomizedDataset_zeroNanoseconds_returnsZeroTicks() throws Exception {
+ Instant now = Instant.ofEpochSecond(0, 0);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(0);
+ }
+
+ @Test
+ public void createRandomizedDataset_maxNanoseconds_returnsMaxTicks() throws Exception {
+ // The nanoseconds to ticks conversion is rounded in the current implementation.
+ // 32767.5 / 32768 * 1000000000 = 999984741.2109375, using 999984741 to
+ // produce the maximum ticks.
+ Instant now = Instant.ofEpochSecond(0, 999984741);
+ Clock clock = Clock.fixed(now, ZoneId.systemDefault());
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock()).thenReturn(clock);
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().getTicks()).isEqualTo(32767);
+ }
+
+ @Test
public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
throws Exception {
MockitoSession session =